From 5f8de423f190bbb79a62f804151bc24824fa32d8 Mon Sep 17 00:00:00 2001 From: "Matt A. Tobin" Date: Fri, 2 Feb 2018 04:16:08 -0500 Subject: Add m-esr52 at 52.6.0 --- xpcom/base/AvailableMemoryTracker.cpp | 447 +++ xpcom/base/AvailableMemoryTracker.h | 30 + xpcom/base/ClearOnShutdown.cpp | 45 + xpcom/base/ClearOnShutdown.h | 124 + xpcom/base/CodeAddressService.h | 198 + xpcom/base/CountingAllocatorBase.h | 140 + xpcom/base/CycleCollectedJSContext.cpp | 1717 ++++++++ xpcom/base/CycleCollectedJSContext.h | 494 +++ xpcom/base/Debug.cpp | 23 + xpcom/base/Debug.h | 21 + xpcom/base/DebuggerOnGCRunnable.cpp | 47 + xpcom/base/DebuggerOnGCRunnable.h | 35 + xpcom/base/DeferredFinalize.cpp | 28 + xpcom/base/DeferredFinalize.h | 33 + xpcom/base/ErrorList.h | 1026 +++++ xpcom/base/ErrorNames.cpp | 84 + xpcom/base/ErrorNames.h | 25 + xpcom/base/HoldDropJSObjects.cpp | 68 + xpcom/base/HoldDropJSObjects.h | 77 + xpcom/base/JSObjectHolder.cpp | 9 + xpcom/base/JSObjectHolder.h | 42 + xpcom/base/LinuxUtils.cpp | 50 + xpcom/base/LinuxUtils.h | 34 + xpcom/base/LogModulePrefWatcher.cpp | 170 + xpcom/base/LogModulePrefWatcher.h | 42 + xpcom/base/Logging.cpp | 568 +++ xpcom/base/Logging.h | 255 ++ xpcom/base/MacHelpers.h | 18 + xpcom/base/MacHelpers.mm | 41 + xpcom/base/NSPRLogModulesParser.cpp | 54 + xpcom/base/NSPRLogModulesParser.h | 22 + xpcom/base/OwningNonNull.h | 198 + xpcom/base/StaticMutex.h | 96 + xpcom/base/StaticPtr.h | 270 ++ xpcom/base/SystemMemoryReporter.cpp | 989 +++++ xpcom/base/SystemMemoryReporter.h | 29 + xpcom/base/moz.build | 159 + xpcom/base/nsAgg.h | 336 ++ xpcom/base/nsAlgorithm.h | 75 + xpcom/base/nsAllocator.h | 17 + xpcom/base/nsAutoPtr.h | 454 +++ xpcom/base/nsAutoRef.h | 672 ++++ xpcom/base/nsCom.h | 12 + xpcom/base/nsConsoleMessage.cpp | 56 + xpcom/base/nsConsoleMessage.h | 33 + xpcom/base/nsConsoleService.cpp | 473 +++ xpcom/base/nsConsoleService.h | 118 + xpcom/base/nsCrashOnException.cpp | 44 + xpcom/base/nsCrashOnException.h | 23 + xpcom/base/nsCycleCollector.cpp | 4213 ++++++++++++++++++++ xpcom/base/nsCycleCollector.h | 72 + xpcom/base/nsCycleCollectorTraceJSHelpers.cpp | 105 + xpcom/base/nsDebugImpl.cpp | 607 +++ xpcom/base/nsDebugImpl.h | 41 + xpcom/base/nsDumpUtils.cpp | 513 +++ xpcom/base/nsDumpUtils.h | 203 + xpcom/base/nsError.h | 219 + xpcom/base/nsErrorService.cpp | 50 + xpcom/base/nsErrorService.h | 37 + xpcom/base/nsGZFileWriter.cpp | 111 + xpcom/base/nsGZFileWriter.h | 55 + xpcom/base/nsIConsoleListener.idl | 18 + xpcom/base/nsIConsoleMessage.idl | 43 + xpcom/base/nsIConsoleService.idl | 56 + xpcom/base/nsICycleCollectorListener.idl | 164 + xpcom/base/nsIDebug2.idl | 82 + xpcom/base/nsIErrorService.idl | 49 + xpcom/base/nsIException.idl | 86 + xpcom/base/nsIGZFileWriter.idl | 82 + xpcom/base/nsIID.h | 10 + xpcom/base/nsIInterfaceRequestor.idl | 36 + xpcom/base/nsIMacUtils.idl | 32 + xpcom/base/nsIMemory.idl | 78 + xpcom/base/nsIMemoryInfoDumper.idl | 162 + xpcom/base/nsIMemoryReporter.idl | 581 +++ xpcom/base/nsIMessageLoop.idl | 36 + xpcom/base/nsIMutable.idl | 22 + xpcom/base/nsIProgrammingLanguage.idl | 25 + xpcom/base/nsISecurityConsoleMessage.idl | 20 + xpcom/base/nsISizeOf.h | 35 + xpcom/base/nsIStatusReporter.idl | 90 + xpcom/base/nsISupports.idl | 44 + xpcom/base/nsISupportsBase.h | 85 + xpcom/base/nsIUUIDGenerator.idl | 39 + xpcom/base/nsIVersionComparator.idl | 49 + xpcom/base/nsIWeakReference.idl | 74 + xpcom/base/nsInterfaceRequestorAgg.cpp | 86 + xpcom/base/nsInterfaceRequestorAgg.h | 38 + xpcom/base/nsMacUtilsImpl.cpp | 147 + xpcom/base/nsMacUtilsImpl.h | 42 + xpcom/base/nsMemoryImpl.cpp | 184 + xpcom/base/nsMemoryImpl.h | 55 + xpcom/base/nsMemoryInfoDumper.cpp | 830 ++++ xpcom/base/nsMemoryInfoDumper.h | 46 + xpcom/base/nsMemoryReporterManager.cpp | 2717 +++++++++++++ xpcom/base/nsMemoryReporterManager.h | 288 ++ xpcom/base/nsMessageLoop.cpp | 172 + xpcom/base/nsMessageLoop.h | 31 + xpcom/base/nsObjCExceptions.h | 230 ++ xpcom/base/nsQueryObject.h | 109 + xpcom/base/nsSecurityConsoleMessage.cpp | 45 + xpcom/base/nsSecurityConsoleMessage.h | 30 + xpcom/base/nsSetDllDirectory.h | 45 + xpcom/base/nsStatusReporterManager.cpp | 320 ++ xpcom/base/nsStatusReporterManager.h | 41 + xpcom/base/nsSystemInfo.cpp | 985 +++++ xpcom/base/nsSystemInfo.h | 66 + xpcom/base/nsTraceRefcnt.cpp | 1319 ++++++ xpcom/base/nsTraceRefcnt.h | 40 + xpcom/base/nsUUIDGenerator.cpp | 177 + xpcom/base/nsUUIDGenerator.h | 44 + xpcom/base/nsVersionComparatorImpl.cpp | 22 + xpcom/base/nsVersionComparatorImpl.h | 25 + xpcom/base/nsWeakPtr.h | 15 + xpcom/base/nsWindowsHelpers.h | 371 ++ xpcom/base/nscore.h | 288 ++ xpcom/base/nsrootidl.idl | 97 + xpcom/build/BinaryPath.h | 188 + xpcom/build/FileLocation.cpp | 224 ++ xpcom/build/FileLocation.h | 134 + xpcom/build/FrozenFunctions.cpp | 138 + xpcom/build/IOInterposer.cpp | 582 +++ xpcom/build/IOInterposer.h | 295 ++ xpcom/build/IOInterposerPrivate.h | 167 + xpcom/build/LateWriteChecks.cpp | 258 ++ xpcom/build/LateWriteChecks.h | 60 + xpcom/build/MainThreadIOLogger.cpp | 225 ++ xpcom/build/MainThreadIOLogger.h | 19 + xpcom/build/NSPRInterposer.cpp | 185 + xpcom/build/NSPRInterposer.h | 28 + xpcom/build/Omnijar.cpp | 188 + xpcom/build/Omnijar.h | 160 + xpcom/build/PoisonIOInterposer.h | 91 + xpcom/build/PoisonIOInterposerBase.cpp | 291 ++ xpcom/build/PoisonIOInterposerMac.cpp | 386 ++ xpcom/build/PoisonIOInterposerStub.cpp | 16 + xpcom/build/PoisonIOInterposerWin.cpp | 501 +++ xpcom/build/ServiceList.h | 46 + xpcom/build/Services.cpp | 71 + xpcom/build/Services.h | 45 + xpcom/build/XPCOM.h | 178 + xpcom/build/XPCOMInit.cpp | 1126 ++++++ xpcom/build/XPCOMModule.inc | 81 + xpcom/build/XREChildData.h | 51 + xpcom/build/XREShellData.h | 29 + xpcom/build/mach_override.c | 789 ++++ xpcom/build/mach_override.h | 121 + xpcom/build/moz.build | 102 + xpcom/build/nsWindowsDllInterceptor.h | 1127 ++++++ xpcom/build/nsXPCOM.h | 433 ++ xpcom/build/nsXPCOMCID.h | 185 + xpcom/build/nsXPCOMCIDInternal.h | 54 + xpcom/build/nsXPCOMPrivate.h | 317 ++ xpcom/build/nsXPCOMStrings.cpp | 366 ++ xpcom/build/nsXREAppData.h | 164 + xpcom/build/nsXULAppAPI.h | 538 +++ xpcom/build/perfprobe.cpp | 242 ++ xpcom/build/perfprobe.h | 204 + xpcom/build/xpcom_alpha.def | 256 ++ xpcom/build/xrecore.h | 25 + xpcom/components/ManifestParser.cpp | 792 ++++ xpcom/components/ManifestParser.h | 22 + xpcom/components/Module.h | 157 + xpcom/components/ModuleLoader.h | 44 + xpcom/components/ModuleUtils.h | 98 + xpcom/components/moz.build | 55 + xpcom/components/nsCategoryManager.cpp | 832 ++++ xpcom/components/nsCategoryManager.h | 146 + xpcom/components/nsCategoryManagerUtils.h | 18 + xpcom/components/nsComponentManager.cpp | 2083 ++++++++++ xpcom/components/nsComponentManager.h | 363 ++ xpcom/components/nsICategoryManager.idl | 69 + xpcom/components/nsIClassInfo.idl | 87 + xpcom/components/nsIComponentManager.idl | 106 + xpcom/components/nsIComponentRegistrar.idl | 163 + xpcom/components/nsIFactory.idl | 42 + xpcom/components/nsIModule.idl | 82 + xpcom/components/nsIServiceManager.idl | 72 + xpcom/components/nsNativeModuleLoader.cpp | 220 + xpcom/components/nsNativeModuleLoader.h | 39 + xpcom/doc/README | 11 + xpcom/ds/IncrementalTokenizer.cpp | 195 + xpcom/ds/IncrementalTokenizer.h | 122 + xpcom/ds/StickyTimeDuration.h | 267 ++ xpcom/ds/Tokenizer.cpp | 738 ++++ xpcom/ds/Tokenizer.h | 446 +++ xpcom/ds/moz.build | 105 + xpcom/ds/nsArray.cpp | 242 ++ xpcom/ds/nsArray.h | 76 + xpcom/ds/nsAtomService.cpp | 25 + xpcom/ds/nsAtomService.h | 25 + xpcom/ds/nsAtomTable.cpp | 736 ++++ xpcom/ds/nsAtomTable.h | 19 + xpcom/ds/nsCRT.cpp | 187 + xpcom/ds/nsCRT.h | 186 + xpcom/ds/nsCharSeparatedTokenizer.h | 200 + xpcom/ds/nsCheapSets.h | 171 + xpcom/ds/nsExpirationTracker.h | 570 +++ xpcom/ds/nsHashPropertyBag.cpp | 284 ++ xpcom/ds/nsHashPropertyBag.h | 57 + xpcom/ds/nsIArray.idl | 91 + xpcom/ds/nsIArrayExtensions.idl | 51 + xpcom/ds/nsIAtom.idl | 166 + xpcom/ds/nsIAtomService.idl | 35 + xpcom/ds/nsICollection.idl | 67 + xpcom/ds/nsIEnumerator.idl | 50 + xpcom/ds/nsIHashable.idl | 24 + xpcom/ds/nsIINIParser.idl | 58 + xpcom/ds/nsIMutableArray.idl | 110 + xpcom/ds/nsINIParserImpl.cpp | 142 + xpcom/ds/nsINIParserImpl.h | 33 + xpcom/ds/nsINIProcessor.js | 192 + xpcom/ds/nsINIProcessor.manifest | 2 + xpcom/ds/nsIObserver.idl | 38 + xpcom/ds/nsIObserverService.idl | 77 + xpcom/ds/nsIPersistentProperties.h | 14 + xpcom/ds/nsIPersistentProperties2.idl | 63 + xpcom/ds/nsIProperties.idl | 46 + xpcom/ds/nsIProperty.idl | 25 + xpcom/ds/nsIPropertyBag.idl | 30 + xpcom/ds/nsIPropertyBag2.idl | 42 + xpcom/ds/nsISerializable.idl | 32 + xpcom/ds/nsISimpleEnumerator.idl | 46 + xpcom/ds/nsIStringEnumerator.idl | 25 + xpcom/ds/nsISupportsArray.idl | 60 + xpcom/ds/nsISupportsIterators.idl | 292 ++ xpcom/ds/nsISupportsPrimitives.idl | 235 ++ xpcom/ds/nsIVariant.idl | 155 + xpcom/ds/nsIWindowsRegKey.idl | 332 ++ xpcom/ds/nsIWritablePropertyBag.idl | 27 + xpcom/ds/nsIWritablePropertyBag2.idl | 22 + xpcom/ds/nsMathUtils.h | 127 + xpcom/ds/nsObserverList.cpp | 142 + xpcom/ds/nsObserverList.h | 93 + xpcom/ds/nsObserverService.cpp | 312 ++ xpcom/ds/nsObserverService.h | 55 + xpcom/ds/nsPersistentProperties.cpp | 666 ++++ xpcom/ds/nsPersistentProperties.h | 67 + xpcom/ds/nsProperties.cpp | 99 + xpcom/ds/nsProperties.h | 39 + xpcom/ds/nsStaticAtom.h | 53 + xpcom/ds/nsStaticNameTable.cpp | 201 + xpcom/ds/nsStaticNameTable.h | 49 + xpcom/ds/nsStringEnumerator.cpp | 262 ++ xpcom/ds/nsStringEnumerator.h | 91 + xpcom/ds/nsSupportsArray.cpp | 264 ++ xpcom/ds/nsSupportsArray.h | 107 + xpcom/ds/nsSupportsArrayEnumerator.cpp | 131 + xpcom/ds/nsSupportsArrayEnumerator.h | 56 + xpcom/ds/nsSupportsPrimitives.cpp | 849 ++++ xpcom/ds/nsSupportsPrimitives.h | 327 ++ xpcom/ds/nsVariant.cpp | 2220 +++++++++++ xpcom/ds/nsVariant.h | 229 ++ xpcom/ds/nsWhitespaceTokenizer.h | 110 + xpcom/ds/nsWindowsRegKey.cpp | 579 +++ xpcom/ds/nsWindowsRegKey.h | 43 + xpcom/glue/AppData.cpp | 95 + xpcom/glue/AppData.h | 63 + xpcom/glue/AutoRestore.h | 55 + xpcom/glue/BlockingResourceBase.cpp | 511 +++ xpcom/glue/BlockingResourceBase.h | 344 ++ xpcom/glue/CondVar.h | 144 + xpcom/glue/DeadlockDetector.h | 382 ++ xpcom/glue/EnumeratedArrayCycleCollection.h | 43 + xpcom/glue/FileUtils.cpp | 568 +++ xpcom/glue/FileUtils.h | 220 + xpcom/glue/GenericFactory.cpp | 27 + xpcom/glue/GenericFactory.h | 43 + xpcom/glue/GenericModule.cpp | 98 + xpcom/glue/IntentionalCrash.h | 58 + xpcom/glue/MainThreadUtils.h | 42 + xpcom/glue/Monitor.h | 135 + xpcom/glue/Mutex.h | 229 ++ xpcom/glue/Observer.h | 83 + xpcom/glue/PLDHashTable.cpp | 801 ++++ xpcom/glue/PLDHashTable.h | 621 +++ xpcom/glue/ReentrantMonitor.h | 249 ++ xpcom/glue/moz.build | 123 + xpcom/glue/nsArrayEnumerator.cpp | 213 + xpcom/glue/nsArrayEnumerator.h | 32 + xpcom/glue/nsArrayUtils.cpp | 23 + xpcom/glue/nsArrayUtils.h | 40 + xpcom/glue/nsBaseHashtable.h | 270 ++ xpcom/glue/nsCOMArray.cpp | 323 ++ xpcom/glue/nsCOMArray.h | 473 +++ xpcom/glue/nsCOMPtr.cpp | 128 + xpcom/glue/nsCOMPtr.h | 1472 +++++++ xpcom/glue/nsCRTGlue.cpp | 441 ++ xpcom/glue/nsCRTGlue.h | 147 + xpcom/glue/nsCategoryCache.cpp | 149 + xpcom/glue/nsCategoryCache.h | 95 + xpcom/glue/nsClassHashtable.h | 140 + xpcom/glue/nsClassInfoImpl.cpp | 73 + xpcom/glue/nsComponentManagerUtils.cpp | 301 ++ xpcom/glue/nsComponentManagerUtils.h | 247 ++ xpcom/glue/nsCycleCollectionNoteChild.h | 101 + xpcom/glue/nsCycleCollectionNoteRootCallback.h | 31 + xpcom/glue/nsCycleCollectionParticipant.cpp | 39 + xpcom/glue/nsCycleCollectionParticipant.h | 852 ++++ xpcom/glue/nsCycleCollectionTraversalCallback.h | 62 + xpcom/glue/nsDataHashtable.h | 58 + xpcom/glue/nsDebug.h | 460 +++ xpcom/glue/nsDeque.cpp | 361 ++ xpcom/glue/nsDeque.h | 195 + xpcom/glue/nsEnumeratorUtils.cpp | 291 ++ xpcom/glue/nsEnumeratorUtils.h | 24 + xpcom/glue/nsHashKeys.h | 660 +++ xpcom/glue/nsIClassInfoImpl.h | 179 + xpcom/glue/nsID.cpp | 133 + xpcom/glue/nsID.h | 179 + xpcom/glue/nsIInterfaceRequestorUtils.cpp | 33 + xpcom/glue/nsIInterfaceRequestorUtils.h | 49 + xpcom/glue/nsINIParser.cpp | 331 ++ xpcom/glue/nsINIParser.h | 118 + xpcom/glue/nsISupportsImpl.cpp | 27 + xpcom/glue/nsISupportsImpl.h | 1090 +++++ xpcom/glue/nsISupportsUtils.h | 145 + xpcom/glue/nsIWeakReferenceUtils.h | 102 + xpcom/glue/nsInterfaceHashtable.h | 142 + xpcom/glue/nsJSThingHashtable.h | 61 + xpcom/glue/nsMemory.cpp | 53 + xpcom/glue/nsMemory.h | 136 + xpcom/glue/nsPointerHashKeys.h | 48 + xpcom/glue/nsProxyRelease.cpp | 21 + xpcom/glue/nsProxyRelease.h | 353 ++ xpcom/glue/nsQuickSort.cpp | 187 + xpcom/glue/nsQuickSort.h | 41 + xpcom/glue/nsRefPtrHashtable.h | 191 + xpcom/glue/nsServiceManagerUtils.h | 94 + xpcom/glue/nsStringAPI.cpp | 1304 ++++++ xpcom/glue/nsStringAPI.h | 1596 ++++++++ xpcom/glue/nsStringGlue.h | 24 + xpcom/glue/nsTArray-inl.h | 463 +++ xpcom/glue/nsTArray.cpp | 29 + xpcom/glue/nsTArray.h | 2371 +++++++++++ xpcom/glue/nsTArrayForwardDeclare.h | 36 + xpcom/glue/nsTHashtable.h | 577 +++ xpcom/glue/nsTObserverArray.cpp | 31 + xpcom/glue/nsTObserverArray.h | 520 +++ xpcom/glue/nsTPriorityQueue.h | 161 + xpcom/glue/nsTWeakRef.h | 176 + xpcom/glue/nsTextFormatter.cpp | 1394 +++++++ xpcom/glue/nsTextFormatter.h | 80 + xpcom/glue/nsThreadUtils.cpp | 472 +++ xpcom/glue/nsThreadUtils.h | 1049 +++++ xpcom/glue/nsVersionComparator.cpp | 379 ++ xpcom/glue/nsVersionComparator.h | 174 + xpcom/glue/nsWeakReference.cpp | 164 + xpcom/glue/nsWeakReference.h | 49 + xpcom/glue/nsXPTCUtils.h | 45 + xpcom/glue/objs.mozbuild | 48 + xpcom/glue/standalone/moz.build | 58 + xpcom/glue/standalone/nsXPCOMGlue.cpp | 927 +++++ xpcom/glue/standalone/nsXPCOMGlue.h | 49 + xpcom/glue/standalone/staticruntime/moz.build | 50 + xpcom/glue/staticruntime/moz.build | 48 + xpcom/glue/tests/gtest/TestArray.cpp | 169 + xpcom/glue/tests/gtest/TestFileUtils.cpp | 283 ++ xpcom/glue/tests/gtest/TestGCPostBarriers.cpp | 140 + xpcom/glue/tests/gtest/TestNsDeque.cpp | 342 ++ xpcom/glue/tests/gtest/TestThreadUtils.cpp | 937 +++++ xpcom/glue/tests/gtest/moz.build | 22 + xpcom/idl-parser/setup.py | 15 + xpcom/idl-parser/xpidl/__init__.py | 0 xpcom/idl-parser/xpidl/header.py | 566 +++ xpcom/idl-parser/xpidl/moz.build | 29 + xpcom/idl-parser/xpidl/runtests.py | 114 + xpcom/idl-parser/xpidl/typelib.py | 307 ++ xpcom/idl-parser/xpidl/xpidl.py | 1465 +++++++ xpcom/io/Base64.cpp | 645 +++ xpcom/io/Base64.h | 73 + xpcom/io/CocoaFileUtils.h | 35 + xpcom/io/CocoaFileUtils.mm | 267 ++ xpcom/io/FileUtilsWin.cpp | 75 + xpcom/io/FileUtilsWin.h | 144 + xpcom/io/SlicedInputStream.cpp | 209 + xpcom/io/SlicedInputStream.h | 50 + xpcom/io/SnappyCompressOutputStream.cpp | 256 ++ xpcom/io/SnappyCompressOutputStream.h | 69 + xpcom/io/SnappyFrameUtils.cpp | 258 ++ xpcom/io/SnappyFrameUtils.h | 85 + xpcom/io/SnappyUncompressInputStream.cpp | 362 ++ xpcom/io/SnappyUncompressInputStream.h | 90 + xpcom/io/SpecialSystemDirectory.cpp | 830 ++++ xpcom/io/SpecialSystemDirectory.h | 107 + xpcom/io/crc32c.c | 154 + xpcom/io/crc32c.h | 23 + xpcom/io/moz.build | 140 + xpcom/io/nsAnonymousTemporaryFile.cpp | 314 ++ xpcom/io/nsAnonymousTemporaryFile.h | 31 + xpcom/io/nsAppDirectoryServiceDefs.h | 118 + xpcom/io/nsAppFileLocationProvider.cpp | 609 +++ xpcom/io/nsAppFileLocationProvider.h | 55 + xpcom/io/nsBinaryStream.cpp | 1016 +++++ xpcom/io/nsBinaryStream.h | 101 + xpcom/io/nsDirectoryService.cpp | 766 ++++ xpcom/io/nsDirectoryService.h | 66 + xpcom/io/nsDirectoryServiceAtomList.h | 98 + xpcom/io/nsDirectoryServiceDefs.h | 168 + xpcom/io/nsDirectoryServiceUtils.h | 31 + xpcom/io/nsEscape.cpp | 633 +++ xpcom/io/nsEscape.h | 224 ++ xpcom/io/nsIAsyncInputStream.idl | 104 + xpcom/io/nsIAsyncOutputStream.idl | 104 + xpcom/io/nsIBinaryInputStream.idl | 119 + xpcom/io/nsIBinaryOutputStream.idl | 90 + xpcom/io/nsICloneableInputStream.idl | 22 + xpcom/io/nsIConverterInputStream.idl | 40 + xpcom/io/nsIConverterOutputStream.idl | 44 + xpcom/io/nsIDirectoryEnumerator.idl | 34 + xpcom/io/nsIDirectoryService.idl | 103 + xpcom/io/nsIFile.idl | 521 +++ xpcom/io/nsIIOUtil.idl | 34 + xpcom/io/nsIInputStream.idl | 147 + xpcom/io/nsIInputStreamTee.idl | 42 + xpcom/io/nsILineInputStream.idl | 26 + xpcom/io/nsILocalFile.idl | 17 + xpcom/io/nsILocalFileMac.idl | 179 + xpcom/io/nsILocalFileWin.idl | 121 + xpcom/io/nsIMultiplexInputStream.idl | 55 + xpcom/io/nsIOUtil.cpp | 32 + xpcom/io/nsIOUtil.h | 27 + xpcom/io/nsIObjectInputStream.idl | 53 + xpcom/io/nsIObjectOutputStream.idl | 97 + xpcom/io/nsIOutputStream.idl | 145 + xpcom/io/nsIPipe.idl | 171 + xpcom/io/nsISafeOutputStream.idl | 39 + xpcom/io/nsIScriptableBase64Encoder.idl | 32 + xpcom/io/nsIScriptableInputStream.idl | 67 + xpcom/io/nsISeekableStream.idl | 74 + xpcom/io/nsIStorageStream.idl | 69 + xpcom/io/nsIStreamBufferAccess.idl | 88 + xpcom/io/nsIStringStream.idl | 66 + xpcom/io/nsIUnicharInputStream.idl | 95 + xpcom/io/nsIUnicharLineInputStream.idl | 26 + xpcom/io/nsIUnicharOutputStream.idl | 47 + xpcom/io/nsInputStreamTee.cpp | 366 ++ xpcom/io/nsLinebreakConverter.cpp | 488 +++ xpcom/io/nsLinebreakConverter.h | 131 + xpcom/io/nsLocalFile.h | 124 + xpcom/io/nsLocalFileCommon.cpp | 328 ++ xpcom/io/nsLocalFileUnix.cpp | 2715 +++++++++++++ xpcom/io/nsLocalFileUnix.h | 138 + xpcom/io/nsLocalFileWin.cpp | 3787 ++++++++++++++++++ xpcom/io/nsLocalFileWin.h | 123 + xpcom/io/nsMultiplexInputStream.cpp | 835 ++++ xpcom/io/nsMultiplexInputStream.h | 30 + xpcom/io/nsNativeCharsetUtils.cpp | 1044 +++++ xpcom/io/nsNativeCharsetUtils.h | 63 + xpcom/io/nsPipe.h | 24 + xpcom/io/nsPipe3.cpp | 2007 ++++++++++ xpcom/io/nsScriptableBase64Encoder.cpp | 28 + xpcom/io/nsScriptableBase64Encoder.h | 30 + xpcom/io/nsScriptableInputStream.cpp | 134 + xpcom/io/nsScriptableInputStream.h | 47 + xpcom/io/nsSegmentedBuffer.cpp | 169 + xpcom/io/nsSegmentedBuffer.h | 109 + xpcom/io/nsStorageStream.cpp | 648 +++ xpcom/io/nsStorageStream.h | 73 + xpcom/io/nsStreamUtils.cpp | 957 +++++ xpcom/io/nsStreamUtils.h | 295 ++ xpcom/io/nsStringStream.cpp | 452 +++ xpcom/io/nsStringStream.h | 63 + xpcom/io/nsUnicharInputStream.cpp | 398 ++ xpcom/io/nsUnicharInputStream.h | 15 + xpcom/io/nsWildCard.cpp | 481 +++ xpcom/io/nsWildCard.h | 64 + xpcom/libxpt/xptcall/porting.html | 17 + xpcom/libxpt/xptcall/status.html | 17 + xpcom/moz.build | 50 + xpcom/reflect/moz.build | 8 + xpcom/reflect/xptcall/README | 6 + xpcom/reflect/xptcall/genstubs.pl | 88 + 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 | 207 + 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 | 9 + xpcom/reflect/xptcall/md/test/stub_test.cpp | 213 + xpcom/reflect/xptcall/md/unix/Makefile.in | 79 + xpcom/reflect/xptcall/md/unix/moz.build | 327 ++ .../reflect/xptcall/md/unix/vtable_layout_x86.cpp | 66 + xpcom/reflect/xptcall/md/unix/xptc_gcc_x86_unix.h | 17 + .../reflect/xptcall/md/unix/xptcinvoke_aarch64.cpp | 140 + .../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 | 67 + .../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 | 122 + .../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_netbsd.s | 95 + .../xptcall/md/unix/xptcinvoke_asm_ppc_openbsd.S | 94 + .../xptcall/md/unix/xptcinvoke_asm_ppc_rhapsody.s | 142 + .../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 + .../md/unix/xptcinvoke_asm_sparc_solaris_GCC3.s | 52 + .../md/unix/xptcinvoke_asm_sparc_solaris_SUNW.s | 56 + .../md/unix/xptcinvoke_asm_sparcv9_solaris_SUNW.s | 85 + .../md/unix/xptcinvoke_asm_x86_solaris_SUNW.s | 55 + .../reflect/xptcall/md/unix/xptcinvoke_darwin.cpp | 16 + .../xptcall/md/unix/xptcinvoke_gcc_x86_unix.cpp | 97 + xpcom/reflect/xptcall/md/unix/xptcinvoke_ipf32.cpp | 132 + xpcom/reflect/xptcall/md/unix/xptcinvoke_ipf64.cpp | 100 + .../xptcall/md/unix/xptcinvoke_linux_alpha.cpp | 144 + .../xptcall/md/unix/xptcinvoke_linux_m68k.cpp | 130 + .../xptcall/md/unix/xptcinvoke_linux_s390.cpp | 195 + .../xptcall/md/unix/xptcinvoke_linux_s390x.cpp | 190 + xpcom/reflect/xptcall/md/unix/xptcinvoke_mips.cpp | 99 + .../reflect/xptcall/md/unix/xptcinvoke_mips64.cpp | 142 + .../xptcall/md/unix/xptcinvoke_netbsd_m68k.cpp | 143 + xpcom/reflect/xptcall/md/unix/xptcinvoke_pa32.cpp | 149 + .../xptcall/md/unix/xptcinvoke_ppc64_linux.cpp | 97 + .../reflect/xptcall/md/unix/xptcinvoke_ppc_aix.cpp | 74 + .../xptcall/md/unix/xptcinvoke_ppc_aix64.cpp | 63 + .../xptcall/md/unix/xptcinvoke_ppc_linux.cpp | 128 + .../xptcall/md/unix/xptcinvoke_ppc_netbsd.cpp | 115 + .../xptcall/md/unix/xptcinvoke_ppc_openbsd.cpp | 109 + .../xptcall/md/unix/xptcinvoke_ppc_rhapsody.cpp | 113 + .../xptcall/md/unix/xptcinvoke_sparc64_openbsd.cpp | 69 + .../xptcall/md/unix/xptcinvoke_sparc_netbsd.cpp | 131 + .../xptcall/md/unix/xptcinvoke_sparc_openbsd.cpp | 128 + .../xptcall/md/unix/xptcinvoke_sparc_solaris.cpp | 131 + .../xptcall/md/unix/xptcinvoke_sparcv9_solaris.cpp | 73 + .../xptcall/md/unix/xptcinvoke_x86_64_solaris.cpp | 149 + .../xptcall/md/unix/xptcinvoke_x86_64_unix.cpp | 188 + .../xptcall/md/unix/xptcinvoke_x86_solaris.cpp | 67 + .../reflect/xptcall/md/unix/xptcstubs_aarch64.cpp | 219 + .../xptcall/md/unix/xptcstubs_alpha_openbsd.cpp | 189 + xpcom/reflect/xptcall/md/unix/xptcstubs_arm.cpp | 238 ++ .../xptcall/md/unix/xptcstubs_arm_netbsd.cpp | 113 + .../xptcall/md/unix/xptcstubs_arm_openbsd.cpp | 205 + .../xptcall/md/unix/xptcstubs_asm_aarch64.s | 39 + .../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_netbsd.s | 70 + .../xptcall/md/unix/xptcstubs_asm_ppc_openbsd.S | 72 + .../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 + .../md/unix/xptcstubs_asm_sparcv9_solaris.s | 50 + .../md/unix/xptcstubs_asm_x86_64_solaris_SUNW.s | 63 + .../md/unix/xptcstubs_asm_x86_solaris_SUNW.s | 78 + xpcom/reflect/xptcall/md/unix/xptcstubs_darwin.cpp | 16 + .../xptcall/md/unix/xptcstubs_gcc_x86_unix.cpp | 139 + xpcom/reflect/xptcall/md/unix/xptcstubs_ipf32.cpp | 151 + xpcom/reflect/xptcall/md/unix/xptcstubs_ipf64.cpp | 154 + .../xptcall/md/unix/xptcstubs_linux_alpha.cpp | 187 + .../xptcall/md/unix/xptcstubs_linux_m68k.cpp | 98 + .../xptcall/md/unix/xptcstubs_linux_s390.cpp | 183 + .../xptcall/md/unix/xptcstubs_linux_s390x.cpp | 187 + xpcom/reflect/xptcall/md/unix/xptcstubs_mips.cpp | 112 + xpcom/reflect/xptcall/md/unix/xptcstubs_mips64.cpp | 185 + .../xptcall/md/unix/xptcstubs_netbsd_m68k.cpp | 115 + xpcom/reflect/xptcall/md/unix/xptcstubs_pa32.cpp | 143 + .../xptcall/md/unix/xptcstubs_ppc64_linux.cpp | 246 ++ .../reflect/xptcall/md/unix/xptcstubs_ppc_aix.cpp | 185 + .../xptcall/md/unix/xptcstubs_ppc_aix64.cpp | 172 + .../xptcall/md/unix/xptcstubs_ppc_linux.cpp | 220 + .../xptcall/md/unix/xptcstubs_ppc_netbsd.cpp | 185 + .../xptcall/md/unix/xptcstubs_ppc_openbsd.cpp | 202 + .../xptcall/md/unix/xptcstubs_ppc_rhapsody.cpp | 165 + .../xptcall/md/unix/xptcstubs_sparc64_openbsd.cpp | 104 + .../xptcall/md/unix/xptcstubs_sparc_netbsd.cpp | 117 + .../xptcall/md/unix/xptcstubs_sparc_openbsd.cpp | 120 + .../xptcall/md/unix/xptcstubs_sparc_solaris.cpp | 112 + .../xptcall/md/unix/xptcstubs_sparcv9_solaris.cpp | 101 + .../xptcall/md/unix/xptcstubs_x86_64_darwin.cpp | 190 + .../xptcall/md/unix/xptcstubs_x86_64_linux.cpp | 204 + .../xptcall/md/unix/xptcstubs_x86_64_solaris.cpp | 139 + .../xptcall/md/unix/xptcstubs_x86_solaris.cpp | 77 + xpcom/reflect/xptcall/md/win32/moz.build | 45 + xpcom/reflect/xptcall/md/win32/xptcinvoke.cpp | 45 + .../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 | 59 + .../xptcall/md/win32/xptcinvoke_x86_gnu.cpp | 106 + xpcom/reflect/xptcall/md/win32/xptcstubs.cpp | 227 ++ .../xptcall/md/win32/xptcstubs_asm_x86_64.asm | 335 ++ .../reflect/xptcall/md/win32/xptcstubs_x86_64.cpp | 197 + .../xptcall/md/win32/xptcstubs_x86_64_gnu.cpp | 297 ++ xpcom/reflect/xptcall/moz.build | 23 + xpcom/reflect/xptcall/porting.html | 216 + xpcom/reflect/xptcall/status.html | 412 ++ xpcom/reflect/xptcall/xptcall.cpp | 82 + xpcom/reflect/xptcall/xptcall.h | 193 + xpcom/reflect/xptcall/xptcprivate.h | 67 + xpcom/reflect/xptcall/xptcstubsdecl.inc | 761 ++++ xpcom/reflect/xptcall/xptcstubsdef.inc | 252 ++ xpcom/reflect/xptinfo/ShimInterfaceInfo.cpp | 700 ++++ xpcom/reflect/xptinfo/ShimInterfaceInfo.h | 50 + xpcom/reflect/xptinfo/TODO | 20 + xpcom/reflect/xptinfo/XPTInterfaceInfoManager.h | 119 + xpcom/reflect/xptinfo/moz.build | 37 + xpcom/reflect/xptinfo/nsIInterfaceInfo.idl | 101 + xpcom/reflect/xptinfo/nsIInterfaceInfoManager.idl | 28 + xpcom/reflect/xptinfo/xptiInterfaceInfo.cpp | 742 ++++ xpcom/reflect/xptinfo/xptiInterfaceInfoManager.cpp | 242 ++ xpcom/reflect/xptinfo/xptiTypelibGuts.cpp | 74 + xpcom/reflect/xptinfo/xptiWorkingSet.cpp | 53 + xpcom/reflect/xptinfo/xptinfo.h | 236 ++ xpcom/reflect/xptinfo/xptiprivate.h | 394 ++ xpcom/rust/nsstring/Cargo.toml | 8 + xpcom/rust/nsstring/gtest/Cargo.toml | 12 + xpcom/rust/nsstring/gtest/Test.cpp | 131 + xpcom/rust/nsstring/gtest/moz.build | 12 + xpcom/rust/nsstring/gtest/test.rs | 112 + xpcom/rust/nsstring/src/lib.rs | 853 ++++ xpcom/string/README.html | 11 + 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 | 31 + xpcom/string/crashtests/crashtests.list | 3 + xpcom/string/moz.build | 61 + xpcom/string/nsAString.h | 62 + xpcom/string/nsCharTraits.h | 587 +++ xpcom/string/nsDependentString.cpp | 18 + xpcom/string/nsDependentString.h | 23 + xpcom/string/nsDependentSubstring.cpp | 18 + xpcom/string/nsDependentSubstring.h | 22 + xpcom/string/nsEmbedString.h | 18 + xpcom/string/nsLiteralString.h | 37 + xpcom/string/nsPrintfCString.h | 42 + xpcom/string/nsPromiseFlatString.cpp | 17 + xpcom/string/nsPromiseFlatString.h | 22 + xpcom/string/nsReadableUtils.cpp | 1383 +++++++ xpcom/string/nsReadableUtils.h | 428 ++ xpcom/string/nsReadableUtilsImpl.h | 54 + xpcom/string/nsReadableUtilsSSE2.cpp | 70 + xpcom/string/nsString.cpp | 17 + xpcom/string/nsString.h | 209 + xpcom/string/nsStringBuffer.h | 160 + xpcom/string/nsStringComparator.cpp | 39 + xpcom/string/nsStringFwd.h | 64 + xpcom/string/nsStringIterator.h | 268 ++ xpcom/string/nsStringObsolete.cpp | 1053 +++++ xpcom/string/nsSubstring.cpp | 388 ++ xpcom/string/nsSubstring.h | 12 + xpcom/string/nsSubstringTuple.cpp | 20 + xpcom/string/nsSubstringTuple.h | 22 + xpcom/string/nsTDependentString.cpp | 25 + xpcom/string/nsTDependentString.h | 106 + xpcom/string/nsTDependentSubstring.cpp | 37 + xpcom/string/nsTDependentSubstring.h | 124 + xpcom/string/nsTLiteralString.h | 41 + xpcom/string/nsTPromiseFlatString.cpp | 18 + xpcom/string/nsTPromiseFlatString.h | 112 + xpcom/string/nsTString.cpp | 47 + xpcom/string/nsTString.h | 883 ++++ xpcom/string/nsTStringComparator.cpp | 50 + xpcom/string/nsTStringObsolete.cpp | 700 ++++ xpcom/string/nsTSubstring.cpp | 1089 +++++ xpcom/string/nsTSubstring.h | 1186 ++++++ xpcom/string/nsTSubstringTuple.cpp | 96 + xpcom/string/nsTSubstringTuple.h | 84 + xpcom/string/nsUTF8Utils.h | 742 ++++ xpcom/string/nsUTF8UtilsSSE2.cpp | 105 + xpcom/string/nsXPCOMStrings.h | 748 ++++ xpcom/string/nsXPIDLString.h | 12 + xpcom/string/string-template-def-char.h | 25 + xpcom/string/string-template-def-unichar.h | 25 + xpcom/string/string-template-undef.h | 26 + xpcom/system/moz.build | 26 + xpcom/system/nsIBlocklistService.idl | 140 + xpcom/system/nsICrashReporter.idl | 135 + xpcom/system/nsIDeviceSensors.idl | 60 + xpcom/system/nsIGConfService.idl | 50 + xpcom/system/nsIGIOService.idl | 82 + xpcom/system/nsIGSettingsService.idl | 30 + xpcom/system/nsIGeolocationProvider.idl | 83 + xpcom/system/nsIHapticFeedback.idl | 22 + xpcom/system/nsIPackageKitService.idl | 46 + xpcom/system/nsIPlatformInfo.idl | 19 + xpcom/system/nsIXULAppInfo.idl | 53 + xpcom/system/nsIXULRuntime.idl | 176 + xpcom/tests/Makefile.in | 13 + xpcom/tests/NotXPCOMTest.idl | 23 + xpcom/tests/RegFactory.cpp | 130 + xpcom/tests/SizeTest01.cpp | 107 + xpcom/tests/SizeTest02.cpp | 89 + xpcom/tests/SizeTest03.cpp | 97 + xpcom/tests/SizeTest04.cpp | 68 + xpcom/tests/SizeTest05.cpp | 74 + xpcom/tests/SizeTest06.cpp | 150 + xpcom/tests/TestArguments.cpp | 25 + xpcom/tests/TestBlockingProcess.cpp | 8 + xpcom/tests/TestHarness.h | 292 ++ xpcom/tests/TestPRIntN.cpp | 33 + xpcom/tests/TestQuickReturn.cpp | 8 + xpcom/tests/TestShutdown.cpp | 41 + xpcom/tests/TestStackCrawl.cpp | 11 + xpcom/tests/TestStreamUtils.cpp | 74 + xpcom/tests/TestUnicodeArguments.cpp | 77 + xpcom/tests/TestWinReg.js | 57 + xpcom/tests/TestingAtomList.h | 6 + xpcom/tests/bug656331_component/TestComponent.cpp | 32 + xpcom/tests/bug656331_component/bug656331.manifest | 2 + xpcom/tests/bug656331_component/moz.build | 26 + xpcom/tests/component/TestComponent.cpp | 44 + xpcom/tests/component/moz.build | 26 + xpcom/tests/component/testcomponent.manifest | 4 + xpcom/tests/component_no_aslr/Makefile.in | 8 + xpcom/tests/component_no_aslr/TestComponent.cpp | 33 + xpcom/tests/component_no_aslr/moz.build | 26 + .../component_no_aslr/testcompnoaslr.manifest | 2 + xpcom/tests/external/TestMinStringAPI.cpp | 1009 +++++ xpcom/tests/external/moz.build | 9 + xpcom/tests/gtest/Helpers.cpp | 133 + xpcom/tests/gtest/Helpers.h | 73 + xpcom/tests/gtest/TestAllocReplacement.cpp | 175 + xpcom/tests/gtest/TestAtoms.cpp | 153 + xpcom/tests/gtest/TestAutoPtr.cpp | 220 + xpcom/tests/gtest/TestAutoRef.cpp | 56 + xpcom/tests/gtest/TestBase64.cpp | 291 ++ xpcom/tests/gtest/TestCOMArray.cpp | 286 ++ xpcom/tests/gtest/TestCOMPtr.cpp | 466 +++ xpcom/tests/gtest/TestCOMPtrEq.cpp | 79 + xpcom/tests/gtest/TestCRT.cpp | 86 + xpcom/tests/gtest/TestCallTemplates.cpp | 104 + xpcom/tests/gtest/TestCloneInputStream.cpp | 200 + xpcom/tests/gtest/TestDeadlockDetector.cpp | 322 ++ .../gtest/TestDeadlockDetectorScalability.cpp | 170 + xpcom/tests/gtest/TestEncoding.cpp | 109 + xpcom/tests/gtest/TestEscapeURL.cpp | 69 + xpcom/tests/gtest/TestExpirationTracker.cpp | 185 + xpcom/tests/gtest/TestFile.cpp | 477 +++ xpcom/tests/gtest/TestHashtables.cpp | 435 ++ xpcom/tests/gtest/TestID.cpp | 36 + xpcom/tests/gtest/TestNSPRLogModulesParser.cpp | 111 + xpcom/tests/gtest/TestNsRefPtr.cpp | 479 +++ xpcom/tests/gtest/TestObserverArray.cpp | 167 + xpcom/tests/gtest/TestObserverService.cpp | 288 ++ xpcom/tests/gtest/TestPLDHash.cpp | 368 ++ xpcom/tests/gtest/TestPipes.cpp | 1097 +++++ xpcom/tests/gtest/TestPriorityQueue.cpp | 76 + xpcom/tests/gtest/TestRacingServiceManager.cpp | 300 ++ xpcom/tests/gtest/TestSTLWrappers.cpp | 78 + xpcom/tests/gtest/TestSlicedInputStream.cpp | 266 ++ xpcom/tests/gtest/TestSnappyStreams.cpp | 191 + xpcom/tests/gtest/TestStateWatching.cpp | 46 + xpcom/tests/gtest/TestStorageStream.cpp | 131 + xpcom/tests/gtest/TestStringStream.cpp | 65 + xpcom/tests/gtest/TestStrings.cpp | 982 +++++ xpcom/tests/gtest/TestSynchronization.cpp | 313 ++ xpcom/tests/gtest/TestTArray.cpp | 206 + xpcom/tests/gtest/TestTArray2.cpp | 1033 +++++ xpcom/tests/gtest/TestTextFormatter.cpp | 34 + xpcom/tests/gtest/TestThreadPool.cpp | 124 + xpcom/tests/gtest/TestThreadPoolListener.cpp | 209 + xpcom/tests/gtest/TestThreadUtils.cpp | 378 ++ xpcom/tests/gtest/TestThreads.cpp | 275 ++ xpcom/tests/gtest/TestTimeStamp.cpp | 70 + xpcom/tests/gtest/TestTimers.cpp | 437 ++ xpcom/tests/gtest/TestTokenizer.cpp | 1134 ++++++ xpcom/tests/gtest/TestUTF.cpp | 191 + xpcom/tests/gtest/TestXPIDLString.cpp | 24 + xpcom/tests/gtest/UTFStrings.h | 112 + xpcom/tests/gtest/moz.build | 77 + xpcom/tests/moz.build | 51 + xpcom/tests/resources.h | 19 + xpcom/tests/test.properties | 14 + xpcom/tests/unit/bug725015.manifest | 3 + xpcom/tests/unit/compmgr_warnings.manifest | 9 + .../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 + .../unit/data/child_process_directive_service.js | 21 + 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 + .../unit/data/main_process_directive_service.js | 21 + .../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 | 5 + xpcom/tests/unit/head_xpcom.js | 21 + xpcom/tests/unit/test_bug121341.js | 71 + xpcom/tests/unit/test_bug325418.js | 63 + xpcom/tests/unit/test_bug332389.js | 19 + xpcom/tests/unit/test_bug333505.js | 10 + xpcom/tests/unit/test_bug364285-1.js | 51 + xpcom/tests/unit/test_bug374754.js | 59 + xpcom/tests/unit/test_bug476919.js | 27 + xpcom/tests/unit/test_bug478086.js | 24 + xpcom/tests/unit/test_bug656331.js | 39 + xpcom/tests/unit/test_bug725015.js | 39 + xpcom/tests/unit/test_bug745466.js | 6 + xpcom/tests/unit/test_comp_no_aslr.js | 18 + xpcom/tests/unit/test_compmgr_warnings.js | 71 + xpcom/tests/unit/test_debugger_malloc_size_of.js | 34 + xpcom/tests/unit/test_file_createUnique.js | 29 + xpcom/tests/unit/test_file_equality.js | 43 + xpcom/tests/unit/test_file_renameTo.js | 61 + xpcom/tests/unit/test_hidden_files.js | 28 + xpcom/tests/unit/test_home.js | 24 + xpcom/tests/unit/test_iniProcessor.js | 288 ++ xpcom/tests/unit/test_ioutil.js | 33 + xpcom/tests/unit/test_localfile.js | 151 + xpcom/tests/unit/test_mac_bundle.js | 18 + xpcom/tests/unit/test_notxpcom_scriptable.js | 86 + xpcom/tests/unit/test_nsIMutableArray.js | 184 + xpcom/tests/unit/test_nsIProcess.js | 185 + xpcom/tests/unit/test_nsIProcess_stress.js | 27 + xpcom/tests/unit/test_pipe.js | 63 + xpcom/tests/unit/test_process_directives.js | 25 + xpcom/tests/unit/test_process_directives_child.js | 3 + xpcom/tests/unit/test_seek_multiplex.js | 173 + xpcom/tests/unit/test_storagestream.js | 162 + xpcom/tests/unit/test_streams.js | 157 + xpcom/tests/unit/test_stringstream.js | 23 + xpcom/tests/unit/test_symlinks.js | 144 + xpcom/tests/unit/test_systemInfo.js | 20 + xpcom/tests/unit/test_versioncomparator.js | 59 + xpcom/tests/unit/test_windows_cmdline_file.js | 21 + xpcom/tests/unit/test_windows_registry.js | 205 + xpcom/tests/unit/test_windows_shortcut.js | 279 ++ xpcom/tests/unit/xpcomtest.manifest | 1 + xpcom/tests/unit/xpcshell.ini | 79 + xpcom/tests/windows/TestCOM.cpp | 158 + xpcom/tests/windows/TestHelloXPLoop.cpp | 144 + xpcom/tests/windows/TestNTFSPermissions.cpp | 286 ++ xpcom/tests/windows/TestNtPathToDosPath.cpp | 193 + xpcom/tests/windows/TestWinFileAttribs.cpp | 173 + xpcom/tests/windows/moz.build | 16 + xpcom/threads/AbstractThread.cpp | 192 + xpcom/threads/AbstractThread.h | 111 + xpcom/threads/BackgroundHangMonitor.cpp | 734 ++++ xpcom/threads/BackgroundHangMonitor.h | 246 ++ xpcom/threads/HangAnnotations.cpp | 262 ++ xpcom/threads/HangAnnotations.h | 104 + xpcom/threads/HangMonitor.cpp | 434 ++ xpcom/threads/HangMonitor.h | 58 + xpcom/threads/LazyIdleThread.cpp | 624 +++ xpcom/threads/LazyIdleThread.h | 226 ++ xpcom/threads/LeakRefPtr.h | 52 + xpcom/threads/MainThreadIdlePeriod.cpp | 76 + xpcom/threads/MainThreadIdlePeriod.h | 28 + xpcom/threads/MozPromise.h | 1067 +++++ xpcom/threads/SharedThreadPool.cpp | 224 ++ xpcom/threads/SharedThreadPool.h | 129 + xpcom/threads/StateMirroring.h | 378 ++ xpcom/threads/StateWatching.h | 317 ++ xpcom/threads/SyncRunnable.h | 129 + xpcom/threads/TaskDispatcher.h | 276 ++ xpcom/threads/TaskQueue.cpp | 271 ++ xpcom/threads/TaskQueue.h | 203 + xpcom/threads/ThreadStackHelper.cpp | 726 ++++ xpcom/threads/ThreadStackHelper.h | 147 + xpcom/threads/ThrottledEventQueue.cpp | 446 +++ xpcom/threads/ThrottledEventQueue.h | 94 + xpcom/threads/TimerThread.cpp | 752 ++++ xpcom/threads/TimerThread.h | 115 + xpcom/threads/moz.build | 89 + xpcom/threads/nsEnvironment.cpp | 163 + xpcom/threads/nsEnvironment.h | 36 + xpcom/threads/nsEventQueue.cpp | 155 + xpcom/threads/nsEventQueue.h | 123 + xpcom/threads/nsICancelableRunnable.h | 38 + xpcom/threads/nsIEnvironment.idl | 55 + xpcom/threads/nsIEventTarget.idl | 127 + xpcom/threads/nsIIdlePeriod.idl | 32 + xpcom/threads/nsIIncrementalRunnable.h | 41 + xpcom/threads/nsIProcess.idl | 99 + xpcom/threads/nsIRunnable.idl | 27 + xpcom/threads/nsISupportsPriority.idl | 45 + xpcom/threads/nsIThread.idl | 149 + xpcom/threads/nsIThreadInternal.idl | 135 + xpcom/threads/nsIThreadManager.idl | 68 + xpcom/threads/nsIThreadPool.idl | 88 + xpcom/threads/nsITimer.idl | 244 ++ xpcom/threads/nsMemoryPressure.cpp | 54 + xpcom/threads/nsMemoryPressure.h | 77 + xpcom/threads/nsProcess.h | 82 + xpcom/threads/nsProcessCommon.cpp | 663 +++ xpcom/threads/nsThread.cpp | 1500 +++++++ xpcom/threads/nsThread.h | 284 ++ xpcom/threads/nsThreadManager.cpp | 342 ++ xpcom/threads/nsThreadManager.h | 89 + xpcom/threads/nsThreadPool.cpp | 449 +++ xpcom/threads/nsThreadPool.h | 65 + xpcom/threads/nsThreadSyncDispatch.h | 50 + xpcom/threads/nsTimerImpl.cpp | 658 +++ xpcom/threads/nsTimerImpl.h | 207 + xpcom/typelib/moz.build | 8 + xpcom/typelib/xpt/moz.build | 40 + xpcom/typelib/xpt/tools/moz.build | 13 + xpcom/typelib/xpt/tools/runtests.py | 770 ++++ xpcom/typelib/xpt/tools/xpt.py | 1540 +++++++ xpcom/typelib/xpt/xpt_arena.cpp | 196 + xpcom/typelib/xpt/xpt_arena.h | 70 + xpcom/typelib/xpt/xpt_struct.cpp | 432 ++ xpcom/typelib/xpt/xpt_struct.h | 366 ++ xpcom/typelib/xpt/xpt_xdr.cpp | 227 ++ xpcom/typelib/xpt/xpt_xdr.h | 86 + xpcom/windbgdlg/Makefile.in | 6 + xpcom/windbgdlg/moz.build | 9 + xpcom/windbgdlg/windbgdlg.cpp | 121 + xpcom/xpcom-config.h.in | 24 + xpcom/xpcom-private.h.in | 50 + xpcom/xpidl/Makefile.in | 10 + xpcom/xpidl/moz.build | 5 + 978 files changed, 191912 insertions(+) create mode 100644 xpcom/base/AvailableMemoryTracker.cpp create mode 100644 xpcom/base/AvailableMemoryTracker.h 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/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/ErrorList.h create mode 100644 xpcom/base/ErrorNames.cpp create mode 100644 xpcom/base/ErrorNames.h create mode 100644 xpcom/base/HoldDropJSObjects.cpp create mode 100644 xpcom/base/HoldDropJSObjects.h create mode 100644 xpcom/base/JSObjectHolder.cpp create mode 100644 xpcom/base/JSObjectHolder.h create mode 100644 xpcom/base/LinuxUtils.cpp create mode 100644 xpcom/base/LinuxUtils.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/NSPRLogModulesParser.cpp create mode 100644 xpcom/base/NSPRLogModulesParser.h create mode 100644 xpcom/base/OwningNonNull.h create mode 100644 xpcom/base/StaticMutex.h create mode 100644 xpcom/base/StaticPtr.h create mode 100644 xpcom/base/SystemMemoryReporter.cpp create mode 100644 xpcom/base/SystemMemoryReporter.h create mode 100644 xpcom/base/moz.build create mode 100644 xpcom/base/nsAgg.h create mode 100644 xpcom/base/nsAlgorithm.h create mode 100644 xpcom/base/nsAllocator.h create mode 100644 xpcom/base/nsAutoPtr.h create mode 100644 xpcom/base/nsAutoRef.h 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/nsCycleCollector.cpp create mode 100644 xpcom/base/nsCycleCollector.h create mode 100644 xpcom/base/nsCycleCollectorTraceJSHelpers.cpp 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/nsErrorService.cpp create mode 100644 xpcom/base/nsErrorService.h create mode 100644 xpcom/base/nsGZFileWriter.cpp create mode 100644 xpcom/base/nsGZFileWriter.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/nsIDebug2.idl create mode 100644 xpcom/base/nsIErrorService.idl create mode 100644 xpcom/base/nsIException.idl create mode 100644 xpcom/base/nsIGZFileWriter.idl create mode 100644 xpcom/base/nsIID.h create mode 100644 xpcom/base/nsIInterfaceRequestor.idl create mode 100644 xpcom/base/nsIMacUtils.idl create mode 100644 xpcom/base/nsIMemory.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/nsIMutable.idl create mode 100644 xpcom/base/nsIProgrammingLanguage.idl create mode 100644 xpcom/base/nsISecurityConsoleMessage.idl create mode 100644 xpcom/base/nsISizeOf.h create mode 100644 xpcom/base/nsIStatusReporter.idl create mode 100644 xpcom/base/nsISupports.idl create mode 100644 xpcom/base/nsISupportsBase.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/nsInterfaceRequestorAgg.cpp create mode 100644 xpcom/base/nsInterfaceRequestorAgg.h create mode 100644 xpcom/base/nsMacUtilsImpl.cpp create mode 100644 xpcom/base/nsMacUtilsImpl.h create mode 100644 xpcom/base/nsMemoryImpl.cpp create mode 100644 xpcom/base/nsMemoryImpl.h 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/nsQueryObject.h create mode 100644 xpcom/base/nsSecurityConsoleMessage.cpp create mode 100644 xpcom/base/nsSecurityConsoleMessage.h create mode 100644 xpcom/base/nsSetDllDirectory.h create mode 100644 xpcom/base/nsStatusReporterManager.cpp create mode 100644 xpcom/base/nsStatusReporterManager.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/nsVersionComparatorImpl.cpp create mode 100644 xpcom/base/nsVersionComparatorImpl.h create mode 100644 xpcom/base/nsWeakPtr.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/FrozenFunctions.cpp 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/ServiceList.h create mode 100644 xpcom/build/Services.cpp create mode 100644 xpcom/build/Services.h create mode 100644 xpcom/build/XPCOM.h create mode 100644 xpcom/build/XPCOMInit.cpp create mode 100644 xpcom/build/XPCOMModule.inc create mode 100644 xpcom/build/XREChildData.h create mode 100644 xpcom/build/XREShellData.h 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/nsWindowsDllInterceptor.h 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/nsXPCOMStrings.cpp create mode 100644 xpcom/build/nsXREAppData.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/build/xrecore.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/ModuleLoader.h create mode 100644 xpcom/components/ModuleUtils.h create mode 100644 xpcom/components/moz.build 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/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/nsIModule.idl create mode 100644 xpcom/components/nsIServiceManager.idl create mode 100644 xpcom/components/nsNativeModuleLoader.cpp create mode 100644 xpcom/components/nsNativeModuleLoader.h create mode 100644 xpcom/doc/README create mode 100644 xpcom/ds/IncrementalTokenizer.cpp create mode 100644 xpcom/ds/IncrementalTokenizer.h 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/moz.build create mode 100644 xpcom/ds/nsArray.cpp create mode 100644 xpcom/ds/nsArray.h create mode 100644 xpcom/ds/nsAtomService.cpp create mode 100644 xpcom/ds/nsAtomService.h create mode 100644 xpcom/ds/nsAtomTable.cpp create mode 100644 xpcom/ds/nsAtomTable.h create mode 100644 xpcom/ds/nsCRT.cpp create mode 100644 xpcom/ds/nsCRT.h create mode 100644 xpcom/ds/nsCharSeparatedTokenizer.h create mode 100644 xpcom/ds/nsCheapSets.h create mode 100644 xpcom/ds/nsExpirationTracker.h create mode 100644 xpcom/ds/nsHashPropertyBag.cpp create mode 100644 xpcom/ds/nsHashPropertyBag.h create mode 100644 xpcom/ds/nsIArray.idl create mode 100644 xpcom/ds/nsIArrayExtensions.idl create mode 100644 xpcom/ds/nsIAtom.idl create mode 100644 xpcom/ds/nsIAtomService.idl create mode 100644 xpcom/ds/nsICollection.idl create mode 100644 xpcom/ds/nsIEnumerator.idl create mode 100644 xpcom/ds/nsIHashable.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/nsINIProcessor.js create mode 100644 xpcom/ds/nsINIProcessor.manifest 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/nsISupportsArray.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/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/nsProperties.cpp create mode 100644 xpcom/ds/nsProperties.h create mode 100644 xpcom/ds/nsStaticAtom.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/nsSupportsArray.cpp create mode 100644 xpcom/ds/nsSupportsArray.h create mode 100644 xpcom/ds/nsSupportsArrayEnumerator.cpp create mode 100644 xpcom/ds/nsSupportsArrayEnumerator.h create mode 100644 xpcom/ds/nsSupportsPrimitives.cpp create mode 100644 xpcom/ds/nsSupportsPrimitives.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/glue/AppData.cpp create mode 100644 xpcom/glue/AppData.h create mode 100644 xpcom/glue/AutoRestore.h create mode 100644 xpcom/glue/BlockingResourceBase.cpp create mode 100644 xpcom/glue/BlockingResourceBase.h create mode 100644 xpcom/glue/CondVar.h create mode 100644 xpcom/glue/DeadlockDetector.h create mode 100644 xpcom/glue/EnumeratedArrayCycleCollection.h create mode 100644 xpcom/glue/FileUtils.cpp create mode 100644 xpcom/glue/FileUtils.h create mode 100644 xpcom/glue/GenericFactory.cpp create mode 100644 xpcom/glue/GenericFactory.h create mode 100644 xpcom/glue/GenericModule.cpp create mode 100644 xpcom/glue/IntentionalCrash.h create mode 100644 xpcom/glue/MainThreadUtils.h create mode 100644 xpcom/glue/Monitor.h create mode 100644 xpcom/glue/Mutex.h create mode 100644 xpcom/glue/Observer.h create mode 100644 xpcom/glue/PLDHashTable.cpp create mode 100644 xpcom/glue/PLDHashTable.h create mode 100644 xpcom/glue/ReentrantMonitor.h create mode 100644 xpcom/glue/moz.build create mode 100644 xpcom/glue/nsArrayEnumerator.cpp create mode 100644 xpcom/glue/nsArrayEnumerator.h create mode 100644 xpcom/glue/nsArrayUtils.cpp create mode 100644 xpcom/glue/nsArrayUtils.h create mode 100644 xpcom/glue/nsBaseHashtable.h create mode 100644 xpcom/glue/nsCOMArray.cpp create mode 100644 xpcom/glue/nsCOMArray.h create mode 100644 xpcom/glue/nsCOMPtr.cpp create mode 100644 xpcom/glue/nsCOMPtr.h create mode 100644 xpcom/glue/nsCRTGlue.cpp create mode 100644 xpcom/glue/nsCRTGlue.h create mode 100644 xpcom/glue/nsCategoryCache.cpp create mode 100644 xpcom/glue/nsCategoryCache.h create mode 100644 xpcom/glue/nsClassHashtable.h create mode 100644 xpcom/glue/nsClassInfoImpl.cpp create mode 100644 xpcom/glue/nsComponentManagerUtils.cpp create mode 100644 xpcom/glue/nsComponentManagerUtils.h create mode 100644 xpcom/glue/nsCycleCollectionNoteChild.h create mode 100644 xpcom/glue/nsCycleCollectionNoteRootCallback.h create mode 100644 xpcom/glue/nsCycleCollectionParticipant.cpp create mode 100644 xpcom/glue/nsCycleCollectionParticipant.h create mode 100644 xpcom/glue/nsCycleCollectionTraversalCallback.h create mode 100644 xpcom/glue/nsDataHashtable.h create mode 100644 xpcom/glue/nsDebug.h create mode 100644 xpcom/glue/nsDeque.cpp create mode 100644 xpcom/glue/nsDeque.h create mode 100644 xpcom/glue/nsEnumeratorUtils.cpp create mode 100644 xpcom/glue/nsEnumeratorUtils.h create mode 100644 xpcom/glue/nsHashKeys.h create mode 100644 xpcom/glue/nsIClassInfoImpl.h create mode 100644 xpcom/glue/nsID.cpp create mode 100644 xpcom/glue/nsID.h create mode 100644 xpcom/glue/nsIInterfaceRequestorUtils.cpp create mode 100644 xpcom/glue/nsIInterfaceRequestorUtils.h create mode 100644 xpcom/glue/nsINIParser.cpp create mode 100644 xpcom/glue/nsINIParser.h create mode 100644 xpcom/glue/nsISupportsImpl.cpp create mode 100644 xpcom/glue/nsISupportsImpl.h create mode 100644 xpcom/glue/nsISupportsUtils.h create mode 100644 xpcom/glue/nsIWeakReferenceUtils.h create mode 100644 xpcom/glue/nsInterfaceHashtable.h create mode 100644 xpcom/glue/nsJSThingHashtable.h create mode 100644 xpcom/glue/nsMemory.cpp create mode 100644 xpcom/glue/nsMemory.h create mode 100644 xpcom/glue/nsPointerHashKeys.h create mode 100644 xpcom/glue/nsProxyRelease.cpp create mode 100644 xpcom/glue/nsProxyRelease.h create mode 100644 xpcom/glue/nsQuickSort.cpp create mode 100644 xpcom/glue/nsQuickSort.h create mode 100644 xpcom/glue/nsRefPtrHashtable.h create mode 100644 xpcom/glue/nsServiceManagerUtils.h create mode 100644 xpcom/glue/nsStringAPI.cpp create mode 100644 xpcom/glue/nsStringAPI.h create mode 100644 xpcom/glue/nsStringGlue.h create mode 100644 xpcom/glue/nsTArray-inl.h create mode 100644 xpcom/glue/nsTArray.cpp create mode 100644 xpcom/glue/nsTArray.h create mode 100644 xpcom/glue/nsTArrayForwardDeclare.h create mode 100644 xpcom/glue/nsTHashtable.h create mode 100644 xpcom/glue/nsTObserverArray.cpp create mode 100644 xpcom/glue/nsTObserverArray.h create mode 100644 xpcom/glue/nsTPriorityQueue.h create mode 100644 xpcom/glue/nsTWeakRef.h create mode 100644 xpcom/glue/nsTextFormatter.cpp create mode 100644 xpcom/glue/nsTextFormatter.h create mode 100644 xpcom/glue/nsThreadUtils.cpp create mode 100644 xpcom/glue/nsThreadUtils.h create mode 100644 xpcom/glue/nsVersionComparator.cpp create mode 100644 xpcom/glue/nsVersionComparator.h create mode 100644 xpcom/glue/nsWeakReference.cpp create mode 100644 xpcom/glue/nsWeakReference.h create mode 100644 xpcom/glue/nsXPTCUtils.h 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/glue/standalone/nsXPCOMGlue.h create mode 100644 xpcom/glue/standalone/staticruntime/moz.build create mode 100644 xpcom/glue/staticruntime/moz.build create mode 100644 xpcom/glue/tests/gtest/TestArray.cpp create mode 100644 xpcom/glue/tests/gtest/TestFileUtils.cpp create mode 100644 xpcom/glue/tests/gtest/TestGCPostBarriers.cpp create mode 100644 xpcom/glue/tests/gtest/TestNsDeque.cpp create mode 100644 xpcom/glue/tests/gtest/TestThreadUtils.cpp create mode 100644 xpcom/glue/tests/gtest/moz.build 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/moz.build create mode 100644 xpcom/idl-parser/xpidl/runtests.py create mode 100644 xpcom/idl-parser/xpidl/typelib.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/FileUtilsWin.cpp create mode 100644 xpcom/io/FileUtilsWin.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/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/nsDirectoryServiceAtomList.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/nsIInputStreamTee.idl create mode 100644 xpcom/io/nsILineInputStream.idl create mode 100644 xpcom/io/nsILocalFile.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/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/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/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/libxpt/xptcall/porting.html create mode 100644 xpcom/libxpt/xptcall/status.html 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/Makefile.in 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_netbsd.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_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_sparc_solaris_GCC3.s create mode 100644 xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_sparc_solaris_SUNW.s create mode 100644 xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_sparcv9_solaris_SUNW.s create mode 100644 xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_x86_solaris_SUNW.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_m68k.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_netbsd_m68k.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_netbsd.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_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_sparcv9_solaris.cpp create mode 100644 xpcom/reflect/xptcall/md/unix/xptcinvoke_x86_64_solaris.cpp create mode 100644 xpcom/reflect/xptcall/md/unix/xptcinvoke_x86_64_unix.cpp create mode 100644 xpcom/reflect/xptcall/md/unix/xptcinvoke_x86_solaris.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_netbsd.s create mode 100644 xpcom/reflect/xptcall/md/unix/xptcstubs_asm_ppc_openbsd.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_asm_sparcv9_solaris.s create mode 100644 xpcom/reflect/xptcall/md/unix/xptcstubs_asm_x86_64_solaris_SUNW.s create mode 100644 xpcom/reflect/xptcall/md/unix/xptcstubs_asm_x86_solaris_SUNW.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_m68k.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_netbsd_m68k.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_netbsd.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_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_sparcv9_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/unix/xptcstubs_x86_64_solaris.cpp create mode 100644 xpcom/reflect/xptcall/md/unix/xptcstubs_x86_solaris.cpp create mode 100644 xpcom/reflect/xptcall/md/win32/moz.build create mode 100644 xpcom/reflect/xptcall/md/win32/xptcinvoke.cpp 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_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/porting.html create mode 100644 xpcom/reflect/xptcall/status.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/ShimInterfaceInfo.cpp create mode 100644 xpcom/reflect/xptinfo/ShimInterfaceInfo.h create mode 100644 xpcom/reflect/xptinfo/TODO create mode 100644 xpcom/reflect/xptinfo/XPTInterfaceInfoManager.h create mode 100644 xpcom/reflect/xptinfo/moz.build create mode 100644 xpcom/reflect/xptinfo/nsIInterfaceInfo.idl create mode 100644 xpcom/reflect/xptinfo/nsIInterfaceInfoManager.idl create mode 100644 xpcom/reflect/xptinfo/xptiInterfaceInfo.cpp create mode 100644 xpcom/reflect/xptinfo/xptiInterfaceInfoManager.cpp create mode 100644 xpcom/reflect/xptinfo/xptiTypelibGuts.cpp create mode 100644 xpcom/reflect/xptinfo/xptiWorkingSet.cpp create mode 100644 xpcom/reflect/xptinfo/xptinfo.h create mode 100644 xpcom/reflect/xptinfo/xptiprivate.h create mode 100644 xpcom/rust/nsstring/Cargo.toml create mode 100644 xpcom/rust/nsstring/gtest/Cargo.toml create mode 100644 xpcom/rust/nsstring/gtest/Test.cpp create mode 100644 xpcom/rust/nsstring/gtest/moz.build create mode 100644 xpcom/rust/nsstring/gtest/test.rs create mode 100644 xpcom/rust/nsstring/src/lib.rs create mode 100644 xpcom/string/README.html 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/nsAString.h create mode 100644 xpcom/string/nsCharTraits.h create mode 100644 xpcom/string/nsDependentString.cpp create mode 100644 xpcom/string/nsDependentString.h create mode 100644 xpcom/string/nsDependentSubstring.cpp create mode 100644 xpcom/string/nsDependentSubstring.h create mode 100644 xpcom/string/nsEmbedString.h create mode 100644 xpcom/string/nsLiteralString.h create mode 100644 xpcom/string/nsPrintfCString.h create mode 100644 xpcom/string/nsPromiseFlatString.cpp 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/nsReadableUtilsImpl.h create mode 100644 xpcom/string/nsReadableUtilsSSE2.cpp create mode 100644 xpcom/string/nsString.cpp create mode 100644 xpcom/string/nsString.h create mode 100644 xpcom/string/nsStringBuffer.h create mode 100644 xpcom/string/nsStringComparator.cpp create mode 100644 xpcom/string/nsStringFwd.h create mode 100644 xpcom/string/nsStringIterator.h create mode 100644 xpcom/string/nsStringObsolete.cpp create mode 100644 xpcom/string/nsSubstring.cpp create mode 100644 xpcom/string/nsSubstring.h create mode 100644 xpcom/string/nsSubstringTuple.cpp create mode 100644 xpcom/string/nsSubstringTuple.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.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/nsTStringObsolete.cpp 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/nsUTF8Utils.h create mode 100644 xpcom/string/nsUTF8UtilsSSE2.cpp create mode 100644 xpcom/string/nsXPCOMStrings.h create mode 100644 xpcom/string/nsXPIDLString.h create mode 100644 xpcom/string/string-template-def-char.h create mode 100644 xpcom/string/string-template-def-unichar.h create mode 100644 xpcom/string/string-template-undef.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/nsIGConfService.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/nsIPackageKitService.idl create mode 100644 xpcom/system/nsIPlatformInfo.idl create mode 100644 xpcom/system/nsIXULAppInfo.idl create mode 100644 xpcom/system/nsIXULRuntime.idl create mode 100644 xpcom/tests/Makefile.in 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/TestPRIntN.cpp create mode 100644 xpcom/tests/TestQuickReturn.cpp create mode 100644 xpcom/tests/TestShutdown.cpp create mode 100644 xpcom/tests/TestStackCrawl.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/bug656331_component/TestComponent.cpp create mode 100644 xpcom/tests/bug656331_component/bug656331.manifest create mode 100644 xpcom/tests/bug656331_component/moz.build create mode 100644 xpcom/tests/component/TestComponent.cpp create mode 100644 xpcom/tests/component/moz.build create mode 100644 xpcom/tests/component/testcomponent.manifest create mode 100644 xpcom/tests/component_no_aslr/Makefile.in create mode 100644 xpcom/tests/component_no_aslr/TestComponent.cpp create mode 100644 xpcom/tests/component_no_aslr/moz.build create mode 100644 xpcom/tests/component_no_aslr/testcompnoaslr.manifest create mode 100644 xpcom/tests/external/TestMinStringAPI.cpp create mode 100644 xpcom/tests/external/moz.build 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/TestAtoms.cpp create mode 100644 xpcom/tests/gtest/TestAutoPtr.cpp create mode 100644 xpcom/tests/gtest/TestAutoRef.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/TestDeadlockDetector.cpp create mode 100644 xpcom/tests/gtest/TestDeadlockDetectorScalability.cpp create mode 100644 xpcom/tests/gtest/TestEncoding.cpp create mode 100644 xpcom/tests/gtest/TestEscapeURL.cpp create mode 100644 xpcom/tests/gtest/TestExpirationTracker.cpp create mode 100644 xpcom/tests/gtest/TestFile.cpp create mode 100644 xpcom/tests/gtest/TestHashtables.cpp create mode 100644 xpcom/tests/gtest/TestID.cpp create mode 100644 xpcom/tests/gtest/TestNSPRLogModulesParser.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/TestPLDHash.cpp create mode 100644 xpcom/tests/gtest/TestPipes.cpp create mode 100644 xpcom/tests/gtest/TestPriorityQueue.cpp create mode 100644 xpcom/tests/gtest/TestRacingServiceManager.cpp create mode 100644 xpcom/tests/gtest/TestSTLWrappers.cpp create mode 100644 xpcom/tests/gtest/TestSlicedInputStream.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/TestSynchronization.cpp create mode 100644 xpcom/tests/gtest/TestTArray.cpp create mode 100644 xpcom/tests/gtest/TestTArray2.cpp create mode 100644 xpcom/tests/gtest/TestTextFormatter.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/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/TestXPIDLString.cpp create mode 100644 xpcom/tests/gtest/UTFStrings.h create mode 100644 xpcom/tests/gtest/moz.build 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/bug725015.manifest create mode 100644 xpcom/tests/unit/compmgr_warnings.manifest 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/child_process_directive_service.js 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/main_process_directive_service.js 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_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_bug656331.js create mode 100644 xpcom/tests/unit/test_bug725015.js create mode 100644 xpcom/tests/unit/test_bug745466.js create mode 100644 xpcom/tests/unit/test_comp_no_aslr.js create mode 100644 xpcom/tests/unit/test_compmgr_warnings.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_hidden_files.js create mode 100644 xpcom/tests/unit/test_home.js create mode 100644 xpcom/tests/unit/test_iniProcessor.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_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/test_windows_shortcut.js create mode 100644 xpcom/tests/unit/xpcomtest.manifest create mode 100644 xpcom/tests/unit/xpcshell.ini create mode 100644 xpcom/tests/windows/TestCOM.cpp create mode 100644 xpcom/tests/windows/TestHelloXPLoop.cpp create mode 100644 xpcom/tests/windows/TestNTFSPermissions.cpp create mode 100644 xpcom/tests/windows/TestNtPathToDosPath.cpp create mode 100644 xpcom/tests/windows/TestWinFileAttribs.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/BackgroundHangMonitor.cpp create mode 100644 xpcom/threads/BackgroundHangMonitor.h create mode 100644 xpcom/threads/HangAnnotations.cpp create mode 100644 xpcom/threads/HangAnnotations.h create mode 100644 xpcom/threads/HangMonitor.cpp create mode 100644 xpcom/threads/HangMonitor.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/MozPromise.h create mode 100644 xpcom/threads/SharedThreadPool.cpp create mode 100644 xpcom/threads/SharedThreadPool.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/TaskDispatcher.h create mode 100644 xpcom/threads/TaskQueue.cpp create mode 100644 xpcom/threads/TaskQueue.h create mode 100644 xpcom/threads/ThreadStackHelper.cpp create mode 100644 xpcom/threads/ThreadStackHelper.h 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/moz.build create mode 100644 xpcom/threads/nsEnvironment.cpp create mode 100644 xpcom/threads/nsEnvironment.h create mode 100644 xpcom/threads/nsEventQueue.cpp create mode 100644 xpcom/threads/nsEventQueue.h create mode 100644 xpcom/threads/nsICancelableRunnable.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/nsIIncrementalRunnable.h create mode 100644 xpcom/threads/nsIProcess.idl create mode 100644 xpcom/threads/nsIRunnable.idl create mode 100644 xpcom/threads/nsISupportsPriority.idl 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/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/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/nsTimerImpl.cpp create mode 100644 xpcom/threads/nsTimerImpl.h create mode 100644 xpcom/typelib/moz.build create mode 100644 xpcom/typelib/xpt/moz.build create mode 100644 xpcom/typelib/xpt/tools/moz.build create mode 100644 xpcom/typelib/xpt/tools/runtests.py create mode 100755 xpcom/typelib/xpt/tools/xpt.py create mode 100644 xpcom/typelib/xpt/xpt_arena.cpp create mode 100644 xpcom/typelib/xpt/xpt_arena.h create mode 100644 xpcom/typelib/xpt/xpt_struct.cpp create mode 100644 xpcom/typelib/xpt/xpt_struct.h create mode 100644 xpcom/typelib/xpt/xpt_xdr.cpp create mode 100644 xpcom/typelib/xpt/xpt_xdr.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/xpcom-private.h.in create mode 100644 xpcom/xpidl/Makefile.in create mode 100644 xpcom/xpidl/moz.build (limited to 'xpcom') diff --git a/xpcom/base/AvailableMemoryTracker.cpp b/xpcom/base/AvailableMemoryTracker.cpp new file mode 100644 index 000000000..6272d89cf --- /dev/null +++ b/xpcom/base/AvailableMemoryTracker.cpp @@ -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/. */ + +#include "mozilla/AvailableMemoryTracker.h" + +#if defined(XP_WIN) +#include "prinrval.h" +#include "prenv.h" +#include "nsIMemoryReporter.h" +#include "nsMemoryPressure.h" +#endif + +#include "nsIObserver.h" +#include "nsIObserverService.h" +#include "nsIRunnable.h" +#include "nsISupports.h" +#include "nsThreadUtils.h" + +#include "mozilla/Preferences.h" +#include "mozilla/Services.h" + +#if defined(XP_WIN) +# include "nsWindowsDllInterceptor.h" +# include +#endif + +#if defined(MOZ_MEMORY) +# include "mozmemory.h" +#endif // MOZ_MEMORY + +using namespace mozilla; + +namespace { + +#if defined(_M_IX86) && defined(XP_WIN) + + +uint32_t sLowVirtualMemoryThreshold = 0; +uint32_t sLowCommitSpaceThreshold = 0; +uint32_t sLowPhysicalMemoryThreshold = 0; +uint32_t sLowMemoryNotificationIntervalMS = 0; + +Atomic sNumLowVirtualMemEvents; +Atomic sNumLowCommitSpaceEvents; +Atomic sNumLowPhysicalMemEvents; + +WindowsDllInterceptor sKernel32Intercept; +WindowsDllInterceptor sGdi32Intercept; + +// Has Init() been called? +bool sInitialized = false; + +// Has Activate() been called? The hooks don't do anything until this happens. +bool sHooksActive = false; + +// Alas, we'd like to use mozilla::TimeStamp, but we can't, because it acquires +// a lock! +volatile bool sHasScheduledOneLowMemoryNotification = false; +volatile PRIntervalTime sLastLowMemoryNotificationTime; + +// These are function pointers to the functions we wrap in Init(). + +void* (WINAPI* sVirtualAllocOrig)(LPVOID aAddress, SIZE_T aSize, + DWORD aAllocationType, DWORD aProtect); + +void* (WINAPI* sMapViewOfFileOrig)(HANDLE aFileMappingObject, + DWORD aDesiredAccess, DWORD aFileOffsetHigh, + DWORD aFileOffsetLow, SIZE_T aNumBytesToMap); + +HBITMAP(WINAPI* sCreateDIBSectionOrig)(HDC aDC, const BITMAPINFO* aBitmapInfo, + UINT aUsage, VOID** aBits, + HANDLE aSection, DWORD aOffset); + +/** + * Fire a memory pressure event if it's been long enough since the last one we + * fired. + */ +bool +MaybeScheduleMemoryPressureEvent() +{ + // If this interval rolls over, we may fire an extra memory pressure + // event, but that's not a big deal. + PRIntervalTime interval = PR_IntervalNow() - sLastLowMemoryNotificationTime; + if (sHasScheduledOneLowMemoryNotification && + PR_IntervalToMilliseconds(interval) < sLowMemoryNotificationIntervalMS) { + + return false; + } + + // There's a bit of a race condition here, since an interval may be a + // 64-bit number, and 64-bit writes aren't atomic on x86-32. But let's + // not worry about it -- the races only happen when we're already + // experiencing memory pressure and firing notifications, so the worst + // thing that can happen is that we fire two notifications when we + // should have fired only one. + sHasScheduledOneLowMemoryNotification = true; + sLastLowMemoryNotificationTime = PR_IntervalNow(); + + NS_DispatchEventualMemoryPressure(MemPressure_New); + return true; +} + +void +CheckMemAvailable() +{ + if (!sHooksActive) { + return; + } + + MEMORYSTATUSEX stat; + stat.dwLength = sizeof(stat); + bool success = GlobalMemoryStatusEx(&stat); + + if (success) { + // sLowVirtualMemoryThreshold is in MB, but ullAvailVirtual is in bytes. + if (stat.ullAvailVirtual < sLowVirtualMemoryThreshold * 1024 * 1024) { + // If we're running low on virtual memory, unconditionally schedule the + // notification. We'll probably crash if we run out of virtual memory, + // so don't worry about firing this notification too often. + ++sNumLowVirtualMemEvents; + NS_DispatchEventualMemoryPressure(MemPressure_New); + } else if (stat.ullAvailPageFile < sLowCommitSpaceThreshold * 1024 * 1024) { + if (MaybeScheduleMemoryPressureEvent()) { + ++sNumLowCommitSpaceEvents; + } + } else if (stat.ullAvailPhys < sLowPhysicalMemoryThreshold * 1024 * 1024) { + if (MaybeScheduleMemoryPressureEvent()) { + ++sNumLowPhysicalMemEvents; + } + } + } +} + +LPVOID WINAPI +VirtualAllocHook(LPVOID aAddress, SIZE_T aSize, + DWORD aAllocationType, + DWORD aProtect) +{ + // It's tempting to see whether we have enough free virtual address space for + // this allocation and, if we don't, synchronously fire a low-memory + // notification to free some before we allocate. + // + // Unfortunately that doesn't work, principally because code doesn't expect a + // call to malloc could trigger a GC (or call into the other routines which + // are triggered by a low-memory notification). + // + // I think the best we can do here is try to allocate the memory and check + // afterwards how much free virtual address space we have. If we're running + // low, we schedule a low-memory notification to run as soon as possible. + + LPVOID result = sVirtualAllocOrig(aAddress, aSize, aAllocationType, aProtect); + + // Don't call CheckMemAvailable for MEM_RESERVE if we're not tracking low + // virtual memory. Similarly, don't call CheckMemAvailable for MEM_COMMIT if + // we're not tracking low physical memory. + if ((sLowVirtualMemoryThreshold != 0 && aAllocationType & MEM_RESERVE) || + (sLowPhysicalMemoryThreshold != 0 && aAllocationType & MEM_COMMIT)) { + CheckMemAvailable(); + } + + return result; +} + +LPVOID WINAPI +MapViewOfFileHook(HANDLE aFileMappingObject, + DWORD aDesiredAccess, + DWORD aFileOffsetHigh, + DWORD aFileOffsetLow, + SIZE_T aNumBytesToMap) +{ + LPVOID result = sMapViewOfFileOrig(aFileMappingObject, aDesiredAccess, + aFileOffsetHigh, aFileOffsetLow, + aNumBytesToMap); + CheckMemAvailable(); + return result; +} + +HBITMAP WINAPI +CreateDIBSectionHook(HDC aDC, + const BITMAPINFO* aBitmapInfo, + UINT aUsage, + VOID** aBits, + HANDLE aSection, + DWORD aOffset) +{ + // There are a lot of calls to CreateDIBSection, so we make some effort not + // to CheckMemAvailable() for calls to CreateDIBSection which allocate only + // a small amount of memory. + + // If aSection is non-null, CreateDIBSection won't allocate any new memory. + bool doCheck = false; + if (sHooksActive && !aSection && aBitmapInfo) { + uint16_t bitCount = aBitmapInfo->bmiHeader.biBitCount; + if (bitCount == 0) { + // MSDN says bitCount == 0 means that it figures out how many bits each + // pixel gets by examining the corresponding JPEG or PNG data. We'll just + // assume the worst. + bitCount = 32; + } + + // |size| contains the expected allocation size in *bits*. Height may be + // negative (indicating the direction the DIB is drawn in), so we take the + // absolute value. + int64_t size = bitCount * aBitmapInfo->bmiHeader.biWidth * + aBitmapInfo->bmiHeader.biHeight; + if (size < 0) { + size *= -1; + } + + // If we're allocating more than 1MB, check how much memory is left after + // the allocation. + if (size > 1024 * 1024 * 8) { + doCheck = true; + } + } + + HBITMAP result = sCreateDIBSectionOrig(aDC, aBitmapInfo, aUsage, aBits, + aSection, aOffset); + + if (doCheck) { + CheckMemAvailable(); + } + + return result; +} + +static int64_t +LowMemoryEventsVirtualDistinguishedAmount() +{ + return sNumLowVirtualMemEvents; +} + +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 + { + MOZ_COLLECT_REPORT( + "low-memory-events/virtual", KIND_OTHER, UNITS_COUNT_CUMULATIVE, + LowMemoryEventsVirtualDistinguishedAmount(), +"Number of low-virtual-memory events fired since startup. We fire such an " +"event if we notice there is less than memory.low_virtual_mem_threshold_mb of " +"virtual address space available (if zero, this behavior is disabled). The " +"process will probably crash if it runs out of virtual address space, so " +"this event is dire."); + + MOZ_COLLECT_REPORT( + "low-memory-events/commit-space", KIND_OTHER, UNITS_COUNT_CUMULATIVE, + sNumLowCommitSpaceEvents, +"Number of low-commit-space events fired since startup. We fire such an " +"event if we notice there is less than memory.low_commit_space_threshold_mb of " +"commit space available (if zero, this behavior is disabled). Windows will " +"likely kill the process if it runs out of commit space, so this event is " +"dire."); + + 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 if we notice there is less than memory.low_physical_memory_threshold_mb " +"of physical memory available (if zero, this behavior is disabled). 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."); + + return NS_OK; + } +}; +NS_IMPL_ISUPPORTS(LowEventsReporter, nsIMemoryReporter) + +#endif // defined(_M_IX86) && 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 nsIRunnable +{ + ~nsJemallocFreeDirtyPagesRunnable() {} + +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIRUNNABLE +}; + +NS_IMPL_ISUPPORTS(nsJemallocFreeDirtyPagesRunnable, nsIRunnable) + +NS_IMETHODIMP +nsJemallocFreeDirtyPagesRunnable::Run() +{ + MOZ_ASSERT(NS_IsMainThread()); + +#if defined(MOZ_MEMORY) + jemalloc_free_dirty_pages(); +#endif + + return NS_OK; +} + +/** + * 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() {} + +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + + void Init(); + +private: + static bool sFreeDirtyPages; +}; + +NS_IMPL_ISUPPORTS(nsMemoryPressureWatcher, nsIObserver) + +bool nsMemoryPressureWatcher::sFreeDirtyPages = false; + +/** + * 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); + } + + Preferences::AddBoolVarCache(&sFreeDirtyPages, "memory.free_dirty_pages", + 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"); + + if (sFreeDirtyPages) { + nsCOMPtr runnable = new nsJemallocFreeDirtyPagesRunnable(); + + NS_DispatchToMainThread(runnable); + } + + return NS_OK; +} + +} // namespace + +namespace mozilla { +namespace AvailableMemoryTracker { + +void +Activate() +{ +#if defined(_M_IX86) && defined(XP_WIN) + MOZ_ASSERT(sInitialized); + MOZ_ASSERT(!sHooksActive); + + Preferences::AddUintVarCache(&sLowVirtualMemoryThreshold, + "memory.low_virtual_mem_threshold_mb", 256); + Preferences::AddUintVarCache(&sLowPhysicalMemoryThreshold, + "memory.low_physical_memory_threshold_mb", 0); + Preferences::AddUintVarCache(&sLowCommitSpaceThreshold, + "memory.low_commit_space_threshold_mb", 256); + Preferences::AddUintVarCache(&sLowMemoryNotificationIntervalMS, + "memory.low_memory_notification_interval_ms", + 10000); + + RegisterStrongMemoryReporter(new LowEventsReporter()); + RegisterLowMemoryEventsVirtualDistinguishedAmount( + LowMemoryEventsVirtualDistinguishedAmount); + RegisterLowMemoryEventsPhysicalDistinguishedAmount( + LowMemoryEventsPhysicalDistinguishedAmount); + sHooksActive = true; +#endif + + // This object is held alive by the observer service. + RefPtr watcher = new nsMemoryPressureWatcher(); + watcher->Init(); +} + +void +Init() +{ + // Do nothing on x86-64, because nsWindowsDllInterceptor is not thread-safe + // on 64-bit. (On 32-bit, it's probably thread-safe.) Even if we run Init() + // before any other of our threads are running, another process may have + // started a remote thread which could call VirtualAlloc! + // + // Moreover, the benefit of this code is less clear when we're a 64-bit + // process, because we aren't going to run out of virtual memory, and the + // system is likely to have a fair bit of physical memory. + +#if defined(_M_IX86) && defined(XP_WIN) + // Don't register the hooks if we're a build instrumented for PGO: If we're + // an instrumented build, the compiler adds function calls all over the place + // which may call VirtualAlloc; this makes it hard to prevent + // VirtualAllocHook from reentering itself. + if (!PR_GetEnv("MOZ_PGO_INSTRUMENTED")) { + sKernel32Intercept.Init("Kernel32.dll"); + sKernel32Intercept.AddHook("VirtualAlloc", + reinterpret_cast(VirtualAllocHook), + reinterpret_cast(&sVirtualAllocOrig)); + sKernel32Intercept.AddHook("MapViewOfFile", + reinterpret_cast(MapViewOfFileHook), + reinterpret_cast(&sMapViewOfFileOrig)); + + sGdi32Intercept.Init("Gdi32.dll"); + sGdi32Intercept.AddHook("CreateDIBSection", + reinterpret_cast(CreateDIBSectionHook), + reinterpret_cast(&sCreateDIBSectionOrig)); + } + + sInitialized = true; +#endif +} + +} // namespace AvailableMemoryTracker +} // namespace mozilla diff --git a/xpcom/base/AvailableMemoryTracker.h b/xpcom/base/AvailableMemoryTracker.h new file mode 100644 index 000000000..33572f9c7 --- /dev/null +++ b/xpcom/base/AvailableMemoryTracker.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 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() must be called before any other threads have started, because it +// modifies the in-memory implementations of some DLL functions in +// non-thread-safe ways. +// +// The hooks don't do anything until Activate() is called. It's an error to +// call Activate() without first calling Init(). + +void Init(); +void Activate(); + +} // namespace AvailableMemoryTracker +} // namespace mozilla + +#endif // ifndef mozilla_AvailableMemoryTracker_h diff --git a/xpcom/base/ClearOnShutdown.cpp b/xpcom/base/ClearOnShutdown.cpp new file mode 100644 index 000000000..bfb3142bc --- /dev/null +++ b/xpcom/base/ClearOnShutdown.cpp @@ -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/. */ + +#include "mozilla/ClearOnShutdown.h" + +namespace mozilla { +namespace ClearOnShutdown_Internal { + +Array, + static_cast(ShutdownPhase::ShutdownPhase_Length)> sShutdownObservers; +ShutdownPhase sCurrentShutdownPhase = ShutdownPhase::NotInShutdown; + +} // namespace ClearOnShutdown_Internal + +// Called when XPCOM is shutting down, after all shutdown notifications have +// been sent and after all threads' event loops have been purged. +void +KillClearOnShutdown(ShutdownPhase aPhase) +{ + using namespace ClearOnShutdown_Internal; + + MOZ_ASSERT(NS_IsMainThread()); + // Shutdown only goes one direction... + MOZ_ASSERT(static_cast(sCurrentShutdownPhase) < static_cast(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)]->popFirst()) { + 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 000000000..5c39c281c --- /dev/null +++ b/xpcom/base/ClearOnShutdown.h @@ -0,0 +1,124 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 "MainThreadUtils.h" + +/* + * This header exports one public method in the mozilla namespace: + * + * template + * void ClearOnShutdown(SmartPtr *aPtr, aPhase=ShutdownPhase::ShutdownFinal) + * + * 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, + * nsRefPtr, nsAutoPtr, 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. + * + * ClearOnShutdown is 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 { + +// Must be contiguous starting at 0 +enum class ShutdownPhase { + NotInShutdown = 0, + WillShutdown, + Shutdown, + ShutdownThreads, + ShutdownLoaders, + ShutdownFinal, + ShutdownPhase_Length, // never pass this value + First = WillShutdown, // for iteration + Last = ShutdownFinal +}; + +namespace ClearOnShutdown_Internal { + +class ShutdownObserver : public LinkedListElement +{ +public: + virtual void Shutdown() = 0; + virtual ~ShutdownObserver() + { + } +}; + +template +class PointerClearer : public ShutdownObserver +{ +public: + explicit PointerClearer(SmartPtr* aPtr) + : mPtr(aPtr) + { + } + + virtual void Shutdown() override + { + if (mPtr) { + *mPtr = nullptr; + } + } + +private: + SmartPtr* mPtr; +}; + +typedef LinkedList ShutdownList; +extern Array, + static_cast(ShutdownPhase::ShutdownPhase_Length)> sShutdownObservers; +extern ShutdownPhase sCurrentShutdownPhase; + +} // namespace ClearOnShutdown_Internal + +template +inline void +ClearOnShutdown(SmartPtr* aPtr, ShutdownPhase aPhase = ShutdownPhase::ShutdownFinal) +{ + using namespace ClearOnShutdown_Internal; + + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aPhase != ShutdownPhase::ShutdownPhase_Length); + + // Adding a ClearOnShutdown for a "past" phase is an error. + if (!(static_cast(sCurrentShutdownPhase) < static_cast(aPhase))) { + MOZ_ASSERT(false, "ClearOnShutdown for phase that already was cleared"); + *aPtr = nullptr; + return; + } + + if (!(sShutdownObservers[static_cast(aPhase)])) { + sShutdownObservers[static_cast(aPhase)] = new ShutdownList(); + } + sShutdownObservers[static_cast(aPhase)]->insertBack(new PointerClearer(aPtr)); +} + +// Called when XPCOM is shutting down, after all shutdown notifications have +// been sent and after all threads' event loops have been purged. +void KillClearOnShutdown(ShutdownPhase aPhase); + +} // namespace mozilla + +#endif diff --git a/xpcom/base/CodeAddressService.h b/xpcom/base/CodeAddressService.h new file mode 100644 index 000000000..7f91f93a6 --- /dev/null +++ b/xpcom/base/CodeAddressService.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/. */ + +#ifndef CodeAddressService_h__ +#define CodeAddressService_h__ + +#include "mozilla/Assertions.h" +#include "mozilla/HashFunctions.h" +#include "mozilla/IntegerPrintfMacros.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/Types.h" + +#include "mozilla/StackWalk.h" + +namespace mozilla { + +// This class is used to print details about code locations. +// +// |StringTable| must implement an Intern() method that returns an interned +// copy of the string that was passed in, as well as a standard +// SizeOfExcludingThis() method. +// +// |StringAlloc| must implement |copy| and |free|. |copy| copies a string, +// while |free| is used to free strings created by |copy|. +// +// |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 +{ + // 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. + + StringTable mLibraryStrings; + + struct Entry + { + 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. + StringAlloc::free(mFunction); + StringAlloc::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. + StringAlloc::free(mFunction); + mFunction = !aFunction[0] ? nullptr : StringAlloc::copy(aFunction); + StringAlloc::free(mFileName); + mFileName = !aFileName[0] ? nullptr : StringAlloc::copy(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; + } + }; + + // 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() + : mEntries(), mNumCacheHits(0), mNumCacheMisses(0) + { + } + + void GetLocation(uint32_t aFrameNumber, const void* aPc, char* aBuf, + size_t aBufLen) + { + 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 = mLibraryStrings.Intern(details.library); + entry.Replace(aPc, details.function, library, details.loffset, + details.filename, details.lineno); + + } else { + mNumCacheHits++; + } + + MOZ_ASSERT(entry.mPc == aPc); + + 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.SizeOfExcludingThis(aMallocSizeOf); + + 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 000000000..fb4d2ffe8 --- /dev/null +++ b/xpcom/base/CountingAllocatorBase.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 CountingAllocatorBase_h +#define CountingAllocatorBase_h + +#include "mozilla/Assertions.h" +#include "mozilla/Atomics.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); + } + +private: + // |sAmount| can be (implicitly) accessed by multiple threads, so it + // must be thread-safe. + static Atomic sAmount; + + 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 000000000..87e123078 --- /dev/null +++ b/xpcom/base/CycleCollectedJSContext.cpp @@ -0,0 +1,1717 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 TraceNativeGrayRoots 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/CycleCollectedJSContext.h" +#include +#include "mozilla/ArrayUtils.h" +#include "mozilla/AutoRestore.h" +#include "mozilla/Move.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/Sprintf.h" +#include "mozilla/Telemetry.h" +#include "mozilla/TimelineConsumers.h" +#include "mozilla/TimelineMarker.h" +#include "mozilla/Unused.h" +#include "mozilla/DebuggerOnGCRunnable.h" +#include "mozilla/dom/DOMJSClass.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 "jsprf.h" +#include "js/Debug.h" +#include "nsContentUtils.h" +#include "nsCycleCollectionNoteRootCallback.h" +#include "nsCycleCollectionParticipant.h" +#include "nsCycleCollector.h" +#include "nsDOMJSUtils.h" +#include "nsJSUtils.h" +#include "nsWrapperCache.h" + +#ifdef MOZ_CRASHREPORTER +#include "nsExceptionHandler.h" +#endif + +#include "nsIException.h" +#include "nsIPlatformInfo.h" +#include "nsThread.h" +#include "nsThreadUtils.h" +#include "xpcpublic.h" + +using namespace mozilla; +using namespace mozilla::dom; + +namespace mozilla { + +struct DeferredFinalizeFunctionHolder +{ + DeferredFinalizeFunction run; + void* data; +}; + +class IncrementalFinalizeRunnable : public Runnable +{ + typedef AutoTArray DeferredFinalizeArray; + typedef CycleCollectedJSContext::DeferredFinalizerTable DeferredFinalizerTable; + + CycleCollectedJSContext* mContext; + DeferredFinalizeArray mDeferredFinalizeFunctions; + uint32_t mFinalizeFunctionToRun; + bool mReleasing; + + static const PRTime SliceMillis = 5; /* ms */ + +public: + IncrementalFinalizeRunnable(CycleCollectedJSContext* aCx, + DeferredFinalizerTable& aFinalizerTable); + virtual ~IncrementalFinalizeRunnable(); + + void ReleaseNow(bool aLimited); + + NS_DECL_NSIRUNNABLE +}; + +} // namespace mozilla + +struct NoteWeakMapChildrenTracer : public JS::CallbackTracer +{ + NoteWeakMapChildrenTracer(JSContext* aCx, + nsCycleCollectionNoteRootCallback& aCb) + : JS::CallbackTracer(aCx), mCb(aCb), mTracedAny(false), mMap(nullptr), + mKey(nullptr), mKeyDelegate(nullptr) + { + } + void onChild(const JS::GCCellPtr& aThing) override; + nsCycleCollectionNoteRootCallback& mCb; + bool mTracedAny; + JSObject* mMap; + JS::GCCellPtr mKey; + JSObject* mKeyDelegate; +}; + +void +NoteWeakMapChildrenTracer::onChild(const JS::GCCellPtr& aThing) +{ + if (aThing.is()) { + return; + } + + if (!JS::GCThingIsMarkedGray(aThing) && !mCb.WantAllTraces()) { + return; + } + + if (AddToCCKind(aThing.kind())) { + mCb.NoteWeakMapping(mMap, mKey, mKeyDelegate, aThing); + mTracedAny = true; + } else { + JS::TraceChildren(this, aThing); + } +} + +struct NoteWeakMapsTracer : public js::WeakMapTracer +{ + NoteWeakMapsTracer(JSContext* aCx, nsCycleCollectionNoteRootCallback& aCccb) + : js::WeakMapTracer(aCx), mCb(aCccb), mChildTracer(aCx, 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::GCThingIsMarkedGray(aKey)) && + MOZ_LIKELY(!mCb.WantAllTraces())) { + if (!aValue || !JS::GCThingIsMarkedGray(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(AddToCCKind(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 (!AddToCCKind(aKey.kind())) { + aKey = nullptr; + } + + JSObject* kdelegate = nullptr; + if (aKey.is()) { + kdelegate = js::GetWeakmapKeyDelegate(&aKey.as()); + } + + if (AddToCCKind(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::GCThingIsMarkedGray(aKey) && kdelegate) { + mCb.NoteWeakMapping(aMap, aKey, kdelegate, nullptr); + } + } +} + +// This is based on the logic in FixWeakMappingGrayBitsTracer::trace. +struct FixWeakMappingGrayBitsTracer : public js::WeakMapTracer +{ + explicit FixWeakMappingGrayBitsTracer(JSContext* aCx) + : js::WeakMapTracer(aCx) + { + } + + void + FixAll() + { + do { + mAnyMarked = false; + js::TraceWeakMaps(this); + } while (mAnyMarked); + } + + void trace(JSObject* aMap, JS::GCCellPtr aKey, JS::GCCellPtr aValue) override + { + // If nothing that could be held alive by this entry is marked gray, return. + bool keyMightNeedMarking = aKey && JS::GCThingIsMarkedGray(aKey); + bool valueMightNeedMarking = aValue && JS::GCThingIsMarkedGray(aValue) && + aValue.kind() != JS::TraceKind::String; + if (!keyMightNeedMarking && !valueMightNeedMarking) { + return; + } + + if (!AddToCCKind(aKey.kind())) { + aKey = nullptr; + } + + if (keyMightNeedMarking && aKey.is()) { + JSObject* kdelegate = js::GetWeakmapKeyDelegate(&aKey.as()); + if (kdelegate && !JS::ObjectIsMarkedGray(kdelegate) && + (!aMap || !JS::ObjectIsMarkedGray(aMap))) + { + if (JS::UnmarkGrayGCThingRecursively(aKey)) { + mAnyMarked = true; + } + } + } + + if (aValue && JS::GCThingIsMarkedGray(aValue) && + (!aKey || !JS::GCThingIsMarkedGray(aKey)) && + (!aMap || !JS::ObjectIsMarkedGray(aMap)) && + aValue.kind() != JS::TraceKind::Shape) { + if (JS::UnmarkGrayGCThingRecursively(aValue)) { + mAnyMarked = true; + } + } + } + + MOZ_INIT_OUTSIDE_CTOR bool mAnyMarked; +}; + +static void +CheckParticipatesInCycleCollection(JS::GCCellPtr aThing, const char* aName, + void* aClosure) +{ + bool* cycleCollectionEnabled = static_cast(aClosure); + + if (*cycleCollectionEnabled) { + return; + } + + if (AddToCCKind(aThing.kind()) && JS::GCThingIsMarkedGray(aThing)) { + *cycleCollectionEnabled = true; + } +} + +NS_IMETHODIMP +JSGCThingParticipant::Traverse(void* aPtr, + nsCycleCollectionTraversalCallback& aCb) +{ + auto runtime = reinterpret_cast( + reinterpret_cast(this) - offsetof(CycleCollectedJSContext, + mGCThingCycleCollectorGlobal)); + + JS::GCCellPtr cellPtr(aPtr, JS::GCThingTraceKind(aPtr)); + runtime->TraverseGCThing(CycleCollectedJSContext::TRAVERSE_FULL, cellPtr, aCb); + return NS_OK; +} + +// NB: This is only used to initialize the participant in +// CycleCollectedJSContext. It should never be used directly. +static JSGCThingParticipant sGCThingCycleCollectorGlobal; + +NS_IMETHODIMP +JSZoneParticipant::Traverse(void* aPtr, nsCycleCollectionTraversalCallback& aCb) +{ + auto runtime = reinterpret_cast( + reinterpret_cast(this) - offsetof(CycleCollectedJSContext, + mJSZoneCycleCollectorGlobal)); + + MOZ_ASSERT(!aCb.WantAllTraces()); + JS::Zone* zone = static_cast(aPtr); + + runtime->TraverseZone(zone, aCb); + return NS_OK; +} + +struct TraversalTracer : public JS::CallbackTracer +{ + TraversalTracer(JSContext* aCx, nsCycleCollectionTraversalCallback& aCb) + : JS::CallbackTracer(aCx, DoNotTraceWeakMaps), mCb(aCb) + { + } + void onChild(const JS::GCCellPtr& aThing) override; + nsCycleCollectionTraversalCallback& mCb; +}; + +void +TraversalTracer::onChild(const JS::GCCellPtr& aThing) +{ + // Don't traverse non-gray objects, unless we want all traces. + if (!JS::GCThingIsMarkedGray(aThing) && !mCb.WantAllTraces()) { + return; + } + + /* + * This function needs to be careful to avoid stack overflow. Normally, when + * AddToCCKind 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-AddToCCKind GC things. Places where this can occur + * use special APIs to handle such chains iteratively. + */ + if (AddToCCKind(aThing.kind())) { + if (MOZ_UNLIKELY(mCb.WantDebugInfo())) { + char buffer[200]; + getTracingEdgeName(buffer, sizeof(buffer)); + mCb.NoteNextEdgeName(buffer); + } + mCb.NoteJSChild(aThing); + } else 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 if (aThing.is()) { + // The maximum depth of traversal when tracing an ObjectGroup is unbounded, + // due to information attached to the groups which can lead other groups to + // be traced. + JS_TraceObjectGroupCycleCollectorChildren(this, aThing); + } else if (!aThing.is()) { + JS::TraceChildren(this, aThing); + } +} + +static void +NoteJSChildGrayWrapperShim(void* aData, JS::GCCellPtr aThing) +{ + TraversalTracer* trc = static_cast(aData); + trc->onChild(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 +// CycleCollectedJSContext. It should never be used directly. +static const JSZoneParticipant sJSZoneCycleCollectorGlobal; + +static +void JSObjectsTenuredCb(JSContext* aContext, void* aData) +{ + static_cast(aData)->JSObjectsTenured(); +} + +bool +mozilla::GetBuildId(JS::BuildIdCharVector* aBuildID) +{ + nsCOMPtr info = do_GetService("@mozilla.org/xre/app-info;1"); + if (!info) { + return false; + } + + nsCString buildID; + nsresult rv = info->GetPlatformBuildID(buildID); + NS_ENSURE_SUCCESS(rv, false); + + if (!aBuildID->resize(buildID.Length())) { + return false; + } + + for (size_t i = 0; i < buildID.Length(); i++) { + (*aBuildID)[i] = buildID[i]; + } + + return true; +} + +CycleCollectedJSContext::CycleCollectedJSContext() + : mGCThingCycleCollectorGlobal(sGCThingCycleCollectorGlobal) + , mJSZoneCycleCollectorGlobal(sJSZoneCycleCollectorGlobal) + , mJSContext(nullptr) + , mPrevGCSliceCallback(nullptr) + , mPrevGCNurseryCollectionCallback(nullptr) + , mJSHolders(256) + , mDoingStableStates(false) + , mDisableMicroTaskCheckpoint(false) + , mOutOfMemoryState(OOMState::OK) + , mLargeAllocationFailureState(OOMState::OK) +{ + nsCOMPtr thread = do_GetCurrentThread(); + mOwningThread = thread.forget().downcast().take(); + MOZ_RELEASE_ASSERT(mOwningThread); +} + +CycleCollectedJSContext::~CycleCollectedJSContext() +{ + // If the allocation failed, here we are. + if (!mJSContext) { + return; + } + + MOZ_ASSERT(!mDeferredFinalizerTable.Count()); + + // Last chance to process any events. + ProcessMetastableStateQueue(mBaseRecursionDepth); + MOZ_ASSERT(mMetastableStateEvents.IsEmpty()); + + ProcessStableStateQueue(); + MOZ_ASSERT(mStableStateEvents.IsEmpty()); + + // Clear mPendingException first, since it might be cycle collected. + mPendingException = nullptr; + + MOZ_ASSERT(mDebuggerPromiseMicroTaskQueue.empty()); + MOZ_ASSERT(mPromiseMicroTaskQueue.empty()); + +#ifdef SPIDERMONKEY_PROMISE + mUncaughtRejections.reset(); + mConsumedRejections.reset(); +#endif // SPIDERMONKEY_PROMISE + + JS_DestroyContext(mJSContext); + mJSContext = nullptr; + nsCycleCollector_forgetJSContext(); + + mozilla::dom::DestroyScriptSettings(); + + mOwningThread->SetScriptObserver(nullptr); + NS_RELEASE(mOwningThread); +} + +static void +MozCrashWarningReporter(JSContext*, JSErrorReport*) +{ + MOZ_CRASH("Why is someone touching JSAPI without an AutoJSAPI?"); +} + +nsresult +CycleCollectedJSContext::Initialize(JSContext* aParentContext, + uint32_t aMaxBytes, + uint32_t aMaxNurseryBytes) +{ + MOZ_ASSERT(!mJSContext); + + mOwningThread->SetScriptObserver(this); + // The main thread has a base recursion depth of 0, workers of 1. + mBaseRecursionDepth = RecursionDepth(); + + mozilla::dom::InitScriptSettings(); + mJSContext = JS_NewContext(aMaxBytes, aMaxNurseryBytes, aParentContext); + if (!mJSContext) { + return NS_ERROR_OUT_OF_MEMORY; + } + + NS_GetCurrentThread()->SetCanInvokeJS(true); + + if (!JS_AddExtraGCRootsTracer(mJSContext, TraceBlackJS, this)) { + MOZ_CRASH("JS_AddExtraGCRootsTracer failed"); + } + JS_SetGrayGCRootsTracer(mJSContext, TraceGrayJS, this); + JS_SetGCCallback(mJSContext, GCCallback, this); + mPrevGCSliceCallback = JS::SetGCSliceCallback(mJSContext, 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( + mJSContext, GCNurseryCollectionCallback); + } + + JS_SetObjectsTenuredCallback(mJSContext, JSObjectsTenuredCb, this); + JS::SetOutOfMemoryCallback(mJSContext, OutOfMemoryCallback, this); + JS::SetLargeAllocationFailureCallback(mJSContext, + LargeAllocationFailureCallback, this); + JS_SetDestroyZoneCallback(mJSContext, XPCStringConvert::FreeZoneCache); + JS_SetSweepZoneCallback(mJSContext, XPCStringConvert::ClearZoneCache); + JS::SetBuildIdOp(mJSContext, GetBuildId); + JS::SetWarningReporter(mJSContext, MozCrashWarningReporter); +#ifdef MOZ_CRASHREPORTER + js::AutoEnterOOMUnsafeRegion::setAnnotateOOMAllocationSizeCallback( + CrashReporter::AnnotateOOMAllocationSize); +#endif + + static js::DOMCallbacks DOMcallbacks = { + InstanceClassHasProtoAtDepth + }; + SetDOMCallbacks(mJSContext, &DOMcallbacks); + js::SetScriptEnvironmentPreparer(mJSContext, &mEnvironmentPreparer); + + JS::SetGetIncumbentGlobalCallback(mJSContext, GetIncumbentGlobalCallback); + +#ifdef SPIDERMONKEY_PROMISE + JS::SetEnqueuePromiseJobCallback(mJSContext, EnqueuePromiseJobCallback, this); + JS::SetPromiseRejectionTrackerCallback(mJSContext, PromiseRejectionTrackerCallback, this); + mUncaughtRejections.init(mJSContext, JS::GCVector(js::SystemAllocPolicy())); + mConsumedRejections.init(mJSContext, JS::GCVector(js::SystemAllocPolicy())); +#endif // SPIDERMONKEY_PROMISE + + JS::dbg::SetDebuggerMallocSizeOf(mJSContext, moz_malloc_size_of); + + nsCycleCollector_registerJSContext(this); + + return NS_OK; +} + +size_t +CycleCollectedJSContext::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const +{ + size_t n = 0; + + // We're deliberately not measuring anything hanging off the entries in + // mJSHolders. + n += mJSHolders.ShallowSizeOfExcludingThis(aMallocSizeOf); + + return n; +} + +void +CycleCollectedJSContext::UnmarkSkippableJSHolders() +{ + for (auto iter = mJSHolders.Iter(); !iter.Done(); iter.Next()) { + void* holder = iter.Key(); + nsScriptObjectTracer*& tracer = iter.Data(); + tracer->CanSkip(holder, true); + } +} + +void +CycleCollectedJSContext::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::GetObjectCompartment(obj); + const js::Class* clasp = js::GetObjectClass(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) { + JSFlatString* flat = JS_ASSERT_STRING_IS_FLAT(str); + nsAutoString chars; + AssignJSFlatString(chars, flat); + 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, "JS %s", JS::GCTraceKindToAscii(aThing.kind())); + } + + // Disable printing global for objects while we figure out ObjShrink fallout. + aCb.DescribeGCedNode(aIsMarked, name, compartmentAddress); +} + +void +CycleCollectedJSContext::NoteGCThingJSChildren(JS::GCCellPtr aThing, + nsCycleCollectionTraversalCallback& aCb) const +{ + MOZ_ASSERT(mJSContext); + TraversalTracer trc(mJSContext, aCb); + JS::TraceChildren(&trc, aThing); +} + +void +CycleCollectedJSContext::NoteGCThingXPCOMChildren(const js::Class* aClasp, + JSObject* aObj, + nsCycleCollectionTraversalCallback& aCb) const +{ + MOZ_ASSERT(aClasp); + MOZ_ASSERT(aClasp == js::GetObjectClass(aObj)); + + if (NoteCustomGCThingXPCOMChildren(aClasp, aObj, aCb)) { + // Nothing else to do! + return; + } + // XXX This test does seem fragile, we should probably whitelist classes + // that do hold a strong reference, but that might not be possible. + else if (aClasp->flags & JSCLASS_HAS_PRIVATE && + aClasp->flags & JSCLASS_PRIVATE_IS_NSISUPPORTS) { + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCb, "js::GetObjectPrivate(obj)"); + aCb.NoteXPCOMChild(static_cast(js::GetObjectPrivate(aObj))); + } else { + const DOMJSClass* domClass = GetDOMClass(aObj); + 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(aObj)); + } else if (domClass->mParticipant) { + aCb.NoteNativeChild(UnwrapPossiblyNotInitializedDOMObject(aObj), + domClass->mParticipant); + } + } + } +} + +void +CycleCollectedJSContext::TraverseGCThing(TraverseSelect aTs, JS::GCCellPtr aThing, + nsCycleCollectionTraversalCallback& aCb) +{ + bool isMarkedGray = JS::GCThingIsMarkedGray(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::GetObjectClass(obj), obj, aCb); + } +} + +struct TraverseObjectShimClosure +{ + nsCycleCollectionTraversalCallback& cb; + CycleCollectedJSContext* self; +}; + +void +CycleCollectedJSContext::TraverseZone(JS::Zone* aZone, + nsCycleCollectionTraversalCallback& aCb) +{ + MOZ_ASSERT(mJSContext); + + /* + * 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(mJSContext, aCb); + js::VisitGrayWrapperTargets(aZone, NoteJSChildGrayWrapperShim, &trc); + + /* + * 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 +CycleCollectedJSContext::TraverseObjectShim(void* aData, JS::GCCellPtr aThing) +{ + TraverseObjectShimClosure* closure = + static_cast(aData); + + MOZ_ASSERT(aThing.is()); + closure->self->TraverseGCThing(CycleCollectedJSContext::TRAVERSE_CPP, + aThing, closure->cb); +} + +void +CycleCollectedJSContext::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 (auto iter = mJSHolders.Iter(); !iter.Done(); iter.Next()) { + void* holder = iter.Key(); + nsScriptObjectTracer*& tracer = iter.Data(); + + 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 +CycleCollectedJSContext::TraceBlackJS(JSTracer* aTracer, void* aData) +{ + CycleCollectedJSContext* self = static_cast(aData); + + self->TraceNativeBlackRoots(aTracer); +} + +/* static */ void +CycleCollectedJSContext::TraceGrayJS(JSTracer* aTracer, void* aData) +{ + CycleCollectedJSContext* self = static_cast(aData); + + // Mark these roots as gray so the CC can walk them later. + self->TraceNativeGrayRoots(aTracer); +} + +/* static */ void +CycleCollectedJSContext::GCCallback(JSContext* aContext, + JSGCStatus aStatus, + void* aData) +{ + CycleCollectedJSContext* self = static_cast(aData); + + MOZ_ASSERT(aContext == self->Context()); + + self->OnGC(aStatus); +} + +/* static */ void +CycleCollectedJSContext::GCSliceCallback(JSContext* aContext, + JS::GCProgress aProgress, + const JS::GCDescription& aDesc) +{ + CycleCollectedJSContext* self = CycleCollectedJSContext::Get(); + MOZ_ASSERT(self->Context() == aContext); + + if (aProgress == JS::GC_CYCLE_END) { + JS::gcreason::Reason 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::Reason mReason; + +public: + MinorGCMarker(MarkerTracingType aTracingType, + JS::gcreason::Reason aReason) + : TimelineMarker("MinorGC", + aTracingType, + MarkerStackRequest::NO_STACK) + , mReason(aReason) + { + MOZ_ASSERT(aTracingType == MarkerTracingType::START || + aTracingType == MarkerTracingType::END); + } + + MinorGCMarker(JS::GCNurseryProgress aProgress, + JS::gcreason::Reason 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::gcreason::ExplainReason(mReason); + aMarker.mCauseName.Construct(NS_ConvertUTF8toUTF16(reason)); + } + } + + virtual UniquePtr + Clone() override + { + auto clone = MakeUnique(GetTracingType(), mReason); + clone->SetCustomTime(GetTime()); + return UniquePtr(Move(clone)); + } +}; + +/* static */ void +CycleCollectedJSContext::GCNurseryCollectionCallback(JSContext* aContext, + JS::GCNurseryProgress aProgress, + JS::gcreason::Reason aReason) +{ + CycleCollectedJSContext* self = CycleCollectedJSContext::Get(); + MOZ_ASSERT(self->Context() == aContext); + MOZ_ASSERT(NS_IsMainThread()); + + RefPtr timelines = TimelineConsumers::Get(); + if (timelines && !timelines->IsEmpty()) { + UniquePtr abstractMarker( + MakeUnique(aProgress, aReason)); + timelines->AddMarkerForAllObservedDocShells(abstractMarker); + } + + if (self->mPrevGCNurseryCollectionCallback) { + self->mPrevGCNurseryCollectionCallback(aContext, aProgress, aReason); + } +} + + +/* static */ void +CycleCollectedJSContext::OutOfMemoryCallback(JSContext* aContext, + void* aData) +{ + CycleCollectedJSContext* self = static_cast(aData); + + MOZ_ASSERT(aContext == self->Context()); + + self->OnOutOfMemory(); +} + +/* static */ void +CycleCollectedJSContext::LargeAllocationFailureCallback(void* aData) +{ + CycleCollectedJSContext* self = static_cast(aData); + + self->OnLargeAllocationFailure(); +} + +class PromiseJobRunnable final : public Runnable +{ +public: + PromiseJobRunnable(JS::HandleObject aCallback, JS::HandleObject aAllocationSite, + nsIGlobalObject* aIncumbentGlobal) + : mCallback(new PromiseJobCallback(aCallback, aAllocationSite, aIncumbentGlobal)) + { + } + + virtual ~PromiseJobRunnable() + { + } + +protected: + NS_IMETHOD + Run() override + { + nsIGlobalObject* global = xpc::NativeGlobal(mCallback->CallbackPreserveColor()); + if (global && !global->IsDying()) { + mCallback->Call("promise callback"); + } + return NS_OK; + } + +private: + RefPtr mCallback; +}; + +/* static */ +JSObject* +CycleCollectedJSContext::GetIncumbentGlobalCallback(JSContext* aCx) +{ + nsIGlobalObject* global = mozilla::dom::GetIncumbentGlobal(); + if (global) { + return global->GetGlobalJSObject(); + } + return nullptr; +} + +/* static */ +bool +CycleCollectedJSContext::EnqueuePromiseJobCallback(JSContext* aCx, + JS::HandleObject aJob, + JS::HandleObject aAllocationSite, + JS::HandleObject aIncumbentGlobal, + void* aData) +{ + CycleCollectedJSContext* self = static_cast(aData); + MOZ_ASSERT(aCx == self->Context()); + MOZ_ASSERT(Get() == self); + + nsIGlobalObject* global = nullptr; + if (aIncumbentGlobal) { + global = xpc::NativeGlobal(aIncumbentGlobal); + } + nsCOMPtr runnable = new PromiseJobRunnable(aJob, aAllocationSite, global); + self->DispatchToMicroTask(runnable.forget()); + return true; +} + +#ifdef SPIDERMONKEY_PROMISE +/* static */ +void +CycleCollectedJSContext::PromiseRejectionTrackerCallback(JSContext* aCx, + JS::HandleObject aPromise, + PromiseRejectionHandlingState state, + void* aData) +{ +#ifdef DEBUG + CycleCollectedJSContext* self = static_cast(aData); +#endif // DEBUG + MOZ_ASSERT(aCx == self->Context()); + MOZ_ASSERT(Get() == self); + + if (state == PromiseRejectionHandlingState::Unhandled) { + PromiseDebugging::AddUncaughtRejection(aPromise); + } else { + PromiseDebugging::AddConsumedRejection(aPromise); + } +} +#endif // SPIDERMONKEY_PROMISE + +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(JSObject** aPtr, const char* aName, + void* aClosure) const override + { + js::UnsafeTraceManuallyBarrieredEdge(static_cast(aClosure), aPtr, 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); +} + +void +CycleCollectedJSContext::TraceNativeGrayRoots(JSTracer* aTracer) +{ + MOZ_ASSERT(mJSContext); + + // 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); + + for (auto iter = mJSHolders.Iter(); !iter.Done(); iter.Next()) { + void* holder = iter.Key(); + nsScriptObjectTracer*& tracer = iter.Data(); + tracer->Trace(holder, JsGcTracer(), aTracer); + } +} + +void +CycleCollectedJSContext::AddJSHolder(void* aHolder, nsScriptObjectTracer* aTracer) +{ + MOZ_ASSERT(mJSContext); + mJSHolders.Put(aHolder, aTracer); +} + +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 = JSID_VOID; + } + + virtual void Trace(JS::Heap* aPtr, const char*, void*) const override + { + *aPtr = nullptr; + } + + virtual void Trace(JSObject** aPtr, const char* aName, + void* aClosure) const override + { + *aPtr = nullptr; + } + + 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 +CycleCollectedJSContext::RemoveJSHolder(void* aHolder) +{ + MOZ_ASSERT(mJSContext); + + nsScriptObjectTracer* tracer = mJSHolders.Get(aHolder); + if (!tracer) { + return; + } + tracer->Trace(aHolder, ClearJSHolder(), nullptr); + mJSHolders.Remove(aHolder); +} + +#ifdef DEBUG +bool +CycleCollectedJSContext::IsJSHolder(void* aHolder) +{ + MOZ_ASSERT(mJSContext); + return mJSHolders.Get(aHolder, nullptr); +} + +static void +AssertNoGcThing(JS::GCCellPtr aGCThing, const char* aName, void* aClosure) +{ + MOZ_ASSERT(!aGCThing); +} + +void +CycleCollectedJSContext::AssertNoObjectsToTrace(void* aPossibleJSHolder) +{ + MOZ_ASSERT(mJSContext); + + nsScriptObjectTracer* tracer = mJSHolders.Get(aPossibleJSHolder); + if (tracer) { + tracer->Trace(aPossibleJSHolder, TraceCallbackFunc(AssertNoGcThing), nullptr); + } +} +#endif + +already_AddRefed +CycleCollectedJSContext::GetPendingException() const +{ + MOZ_ASSERT(mJSContext); + + nsCOMPtr out = mPendingException; + return out.forget(); +} + +void +CycleCollectedJSContext::SetPendingException(nsIException* aException) +{ + MOZ_ASSERT(mJSContext); + mPendingException = aException; +} + +std::queue>& +CycleCollectedJSContext::GetPromiseMicroTaskQueue() +{ + MOZ_ASSERT(mJSContext); + return mPromiseMicroTaskQueue; +} + +std::queue>& +CycleCollectedJSContext::GetDebuggerPromiseMicroTaskQueue() +{ + MOZ_ASSERT(mJSContext); + return mDebuggerPromiseMicroTaskQueue; +} + +nsCycleCollectionParticipant* +CycleCollectedJSContext::GCThingParticipant() +{ + MOZ_ASSERT(mJSContext); + return &mGCThingCycleCollectorGlobal; +} + +nsCycleCollectionParticipant* +CycleCollectedJSContext::ZoneParticipant() +{ + MOZ_ASSERT(mJSContext); + return &mJSZoneCycleCollectorGlobal; +} + +nsresult +CycleCollectedJSContext::TraverseRoots(nsCycleCollectionNoteRootCallback& aCb) +{ + MOZ_ASSERT(mJSContext); + + TraverseNativeRoots(aCb); + + NoteWeakMapsTracer trc(mJSContext, aCb); + js::TraceWeakMaps(&trc); + + return NS_OK; +} + +bool +CycleCollectedJSContext::UsefulToMergeZones() const +{ + return false; +} + +void +CycleCollectedJSContext::FixWeakMappingGrayBits() const +{ + MOZ_ASSERT(mJSContext); + MOZ_ASSERT(!JS::IsIncrementalGCInProgress(mJSContext), + "Don't call FixWeakMappingGrayBits during a GC."); + FixWeakMappingGrayBitsTracer fixer(mJSContext); + fixer.FixAll(); +} + +bool +CycleCollectedJSContext::AreGCGrayBitsValid() const +{ + MOZ_ASSERT(mJSContext); + return js::AreGCGrayBitsValid(mJSContext); +} + +void +CycleCollectedJSContext::GarbageCollect(uint32_t aReason) const +{ + MOZ_ASSERT(mJSContext); + + MOZ_ASSERT(aReason < JS::gcreason::NUM_REASONS); + JS::gcreason::Reason gcreason = static_cast(aReason); + + JS::PrepareForFullGC(mJSContext); + JS::GCForReason(mJSContext, GC_NORMAL, gcreason); +} + +void +CycleCollectedJSContext::JSObjectsTenured() +{ + MOZ_ASSERT(mJSContext); + + for (auto iter = mNurseryObjects.Iter(); !iter.Done(); iter.Next()) { + nsWrapperCache* cache = iter.Get(); + JSObject* wrapper = cache->GetWrapperPreserveColor(); + MOZ_ASSERT(wrapper); + if (!JS::ObjectIsTenured(wrapper)) { + MOZ_ASSERT(!cache->PreservingWrapper()); + const JSClass* jsClass = js::GetObjectJSClass(wrapper); + jsClass->doFinalize(nullptr, wrapper); + } + } + +#ifdef DEBUG +for (auto iter = mPreservedNurseryObjects.Iter(); !iter.Done(); iter.Next()) { + MOZ_ASSERT(JS::ObjectIsTenured(iter.Get().get())); +} +#endif + + mNurseryObjects.Clear(); + mPreservedNurseryObjects.Clear(); +} + +void +CycleCollectedJSContext::NurseryWrapperAdded(nsWrapperCache* aCache) +{ + MOZ_ASSERT(mJSContext); + MOZ_ASSERT(aCache); + MOZ_ASSERT(aCache->GetWrapperPreserveColor()); + MOZ_ASSERT(!JS::ObjectIsTenured(aCache->GetWrapperPreserveColor())); + mNurseryObjects.InfallibleAppend(aCache); +} + +void +CycleCollectedJSContext::NurseryWrapperPreserved(JSObject* aWrapper) +{ + MOZ_ASSERT(mJSContext); + + mPreservedNurseryObjects.InfallibleAppend( + JS::PersistentRooted(mJSContext, aWrapper)); +} + +void +CycleCollectedJSContext::DeferredFinalize(DeferredFinalizeAppendFunction aAppendFunc, + DeferredFinalizeFunction aFunc, + void* aThing) +{ + MOZ_ASSERT(mJSContext); + + void* thingArray = nullptr; + bool hadThingArray = mDeferredFinalizerTable.Get(aFunc, &thingArray); + + thingArray = aAppendFunc(thingArray, aThing); + if (!hadThingArray) { + mDeferredFinalizerTable.Put(aFunc, thingArray); + } +} + +void +CycleCollectedJSContext::DeferredFinalize(nsISupports* aSupports) +{ + MOZ_ASSERT(mJSContext); + + typedef DeferredFinalizerImpl Impl; + DeferredFinalize(Impl::AppendDeferredFinalizePointer, Impl::DeferredFinalize, + aSupports); +} + +void +CycleCollectedJSContext::DumpJSHeap(FILE* aFile) +{ + js::DumpHeap(Context(), aFile, js::CollectNurseryBeforeDump); +} + +void +CycleCollectedJSContext::ProcessStableStateQueue() +{ + MOZ_ASSERT(mJSContext); + MOZ_RELEASE_ASSERT(!mDoingStableStates); + mDoingStableStates = true; + + for (uint32_t i = 0; i < mStableStateEvents.Length(); ++i) { + nsCOMPtr event = mStableStateEvents[i].forget(); + event->Run(); + } + + mStableStateEvents.Clear(); + mDoingStableStates = false; +} + +void +CycleCollectedJSContext::ProcessMetastableStateQueue(uint32_t aRecursionDepth) +{ + MOZ_ASSERT(mJSContext); + MOZ_RELEASE_ASSERT(!mDoingStableStates); + mDoingStableStates = true; + + nsTArray localQueue = Move(mMetastableStateEvents); + + for (uint32_t i = 0; i < localQueue.Length(); ++i) + { + RunInMetastableStateData& data = localQueue[i]; + if (data.mRecursionDepth != aRecursionDepth) { + continue; + } + + { + nsCOMPtr runnable = data.mRunnable.forget(); + runnable->Run(); + } + + localQueue.RemoveElementAt(i--); + } + + // If the queue has events in it now, they were added from something we called, + // so they belong at the end of the queue. + localQueue.AppendElements(mMetastableStateEvents); + localQueue.SwapElements(mMetastableStateEvents); + mDoingStableStates = false; +} + +void +CycleCollectedJSContext::AfterProcessTask(uint32_t aRecursionDepth) +{ + MOZ_ASSERT(mJSContext); + + // See HTML 6.1.4.2 Processing model + + // Execute any events that were waiting for a microtask to complete. + // This is not (yet) in the spec. + ProcessMetastableStateQueue(aRecursionDepth); + + // Step 4.1: Execute microtasks. + if (!mDisableMicroTaskCheckpoint) { + if (NS_IsMainThread()) { + nsContentUtils::PerformMainThreadMicroTaskCheckpoint(); + Promise::PerformMicroTaskCheckpoint(); + } else { + Promise::PerformWorkerMicroTaskCheckpoint(); + } + } + + // Step 4.2 Execute any events that were waiting for a stable state. + ProcessStableStateQueue(); +} + +void +CycleCollectedJSContext::AfterProcessMicrotask() +{ + MOZ_ASSERT(mJSContext); + AfterProcessMicrotask(RecursionDepth()); +} + +void +CycleCollectedJSContext::AfterProcessMicrotask(uint32_t aRecursionDepth) +{ + MOZ_ASSERT(mJSContext); + + // Between microtasks, execute any events that were waiting for a microtask + // to complete. + ProcessMetastableStateQueue(aRecursionDepth); +} + +uint32_t +CycleCollectedJSContext::RecursionDepth() +{ + return mOwningThread->RecursionDepth(); +} + +void +CycleCollectedJSContext::RunInStableState(already_AddRefed&& aRunnable) +{ + MOZ_ASSERT(mJSContext); + mStableStateEvents.AppendElement(Move(aRunnable)); +} + +void +CycleCollectedJSContext::RunInMetastableState(already_AddRefed&& aRunnable) +{ + MOZ_ASSERT(mJSContext); + + RunInMetastableStateData data; + data.mRunnable = aRunnable; + + 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 + + mMetastableStateEvents.AppendElement(Move(data)); +} + +IncrementalFinalizeRunnable::IncrementalFinalizeRunnable(CycleCollectedJSContext* aCx, + DeferredFinalizerTable& aFinalizers) + : mContext(aCx) + , 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(); + } +} + +IncrementalFinalizeRunnable::~IncrementalFinalizeRunnable() +{ + MOZ_ASSERT(this != mContext->mFinalizeRunnable); +} + +void +IncrementalFinalizeRunnable::ReleaseNow(bool aLimited) +{ + if (mReleasing) { + NS_WARNING("Re-entering ReleaseNow"); + return; + } + { + 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 = TimeStamp::Now(); + 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(mContext->mFinalizeRunnable == this); + mDeferredFinalizeFunctions.Clear(); + // NB: This may delete this! + mContext->mFinalizeRunnable = nullptr; + } +} + +NS_IMETHODIMP +IncrementalFinalizeRunnable::Run() +{ + if (mContext->mFinalizeRunnable != this) { + /* These items were already processed synchronously in JSGC_END. */ + MOZ_ASSERT(!mDeferredFinalizeFunctions.Length()); + return NS_OK; + } + + TimeStamp start = TimeStamp::Now(); + ReleaseNow(true); + + if (mDeferredFinalizeFunctions.Length()) { + nsresult rv = NS_DispatchToCurrentThread(this); + if (NS_FAILED(rv)) { + ReleaseNow(false); + } + } + + uint32_t duration = (uint32_t)((TimeStamp::Now() - start).ToMilliseconds()); + Telemetry::Accumulate(Telemetry::DEFERRED_FINALIZE_ASYNC, duration); + + return NS_OK; +} + +void +CycleCollectedJSContext::FinalizeDeferredThings(DeferredFinalizeType aType) +{ + MOZ_ASSERT(mJSContext); + + /* + * If the previous GC created a runnable to finalize objects + * incrementally, and if it hasn't finished yet, finish it now. We + * don't want these to build up. We also don't want to allow any + * existing incremental finalize runnables to run after a + * non-incremental GC, since they are often used to detect leaks. + */ + if (mFinalizeRunnable) { + 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 (mDeferredFinalizerTable.Count() == 0) { + return; + } + + mFinalizeRunnable = new IncrementalFinalizeRunnable(this, + mDeferredFinalizerTable); + + // Everything should be gone now. + MOZ_ASSERT(mDeferredFinalizerTable.Count() == 0); + + if (aType == FinalizeIncrementally) { + NS_DispatchToCurrentThread(mFinalizeRunnable); + } else { + mFinalizeRunnable->ReleaseNow(false); + MOZ_ASSERT(!mFinalizeRunnable); + } +} + +void +CycleCollectedJSContext::AnnotateAndSetOutOfMemory(OOMState* aStatePtr, + OOMState aNewState) +{ + MOZ_ASSERT(mJSContext); + + *aStatePtr = aNewState; +#ifdef MOZ_CRASHREPORTER + CrashReporter::AnnotateCrashReport(aStatePtr == &mOutOfMemoryState + ? NS_LITERAL_CSTRING("JSOutOfMemory") + : NS_LITERAL_CSTRING("JSLargeAllocationFailure"), + aNewState == OOMState::Reporting + ? NS_LITERAL_CSTRING("Reporting") + : aNewState == OOMState::Reported + ? NS_LITERAL_CSTRING("Reported") + : NS_LITERAL_CSTRING("Recovered")); +#endif +} + +void +CycleCollectedJSContext::OnGC(JSGCStatus aStatus) +{ + MOZ_ASSERT(mJSContext); + + switch (aStatus) { + case JSGC_BEGIN: + nsCycleCollector_prepareForGarbageCollection(); + mZonesWaitingForGC.Clear(); + break; + case JSGC_END: { +#ifdef MOZ_CRASHREPORTER + if (mOutOfMemoryState == OOMState::Reported) { + AnnotateAndSetOutOfMemory(&mOutOfMemoryState, OOMState::Recovered); + } + if (mLargeAllocationFailureState == OOMState::Reported) { + AnnotateAndSetOutOfMemory(&mLargeAllocationFailureState, OOMState::Recovered); + } +#endif + + // Do any deferred finalization of native objects. + FinalizeDeferredThings(JS::WasIncrementalGC(mJSContext) ? FinalizeIncrementally : + FinalizeNow); + break; + } + default: + MOZ_CRASH(); + } + + CustomGCCallback(aStatus); +} + +void +CycleCollectedJSContext::OnOutOfMemory() +{ + MOZ_ASSERT(mJSContext); + + AnnotateAndSetOutOfMemory(&mOutOfMemoryState, OOMState::Reporting); + CustomOutOfMemoryCallback(); + AnnotateAndSetOutOfMemory(&mOutOfMemoryState, OOMState::Reported); +} + +void +CycleCollectedJSContext::OnLargeAllocationFailure() +{ + MOZ_ASSERT(mJSContext); + + AnnotateAndSetOutOfMemory(&mLargeAllocationFailureState, OOMState::Reporting); + CustomLargeAllocationFailureCallback(); + AnnotateAndSetOutOfMemory(&mLargeAllocationFailureState, OOMState::Reported); +} + +void +CycleCollectedJSContext::PrepareWaitingZonesForGC() +{ + if (mZonesWaitingForGC.Count() == 0) { + JS::PrepareForFullGC(Context()); + } else { + for (auto iter = mZonesWaitingForGC.Iter(); !iter.Done(); iter.Next()) { + JS::PrepareZoneForGC(iter.Get()->GetKey()); + } + mZonesWaitingForGC.Clear(); + } +} + +void +CycleCollectedJSContext::DispatchToMicroTask(already_AddRefed aRunnable) +{ + RefPtr runnable(aRunnable); + + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(runnable); + + mPromiseMicroTaskQueue.push(runnable.forget()); +} + +void +CycleCollectedJSContext::EnvironmentPreparer::invoke(JS::HandleObject scope, + js::ScriptEnvironmentPreparer::Closure& closure) +{ + nsIGlobalObject* global = xpc::NativeGlobal(scope); + + // Not much we can do if we simply don't have a usable global here... + NS_ENSURE_TRUE_VOID(global && global->GetGlobalJSObject()); + + AutoEntryScript aes(global, "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. +} diff --git a/xpcom/base/CycleCollectedJSContext.h b/xpcom/base/CycleCollectedJSContext.h new file mode 100644 index 000000000..9415634b8 --- /dev/null +++ b/xpcom/base/CycleCollectedJSContext.h @@ -0,0 +1,494 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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/DeferredFinalize.h" +#include "mozilla/mozalloc.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/SegmentedVector.h" +#include "jsapi.h" +#include "jsfriendapi.h" + +#include "nsCycleCollectionParticipant.h" +#include "nsDataHashtable.h" +#include "nsHashKeys.h" +#include "nsTArray.h" +#include "nsTHashtable.h" + +class nsCycleCollectionNoteRootCallback; +class nsIException; +class nsIRunnable; +class nsThread; +class nsWrapperCache; + +namespace js { +struct Class; +} // namespace js + +namespace mozilla { + +class JSGCThingParticipant: public nsCycleCollectionParticipant +{ +public: + 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 Traverse(void* aPtr, nsCycleCollectionTraversalCallback& aCb) + override; + + NS_DECL_CYCLE_COLLECTION_CLASS_NAME_METHOD(JSGCThingParticipant) +}; + +class JSZoneParticipant : public nsCycleCollectionParticipant +{ +public: + constexpr JSZoneParticipant(): nsCycleCollectionParticipant() + { + } + + 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 Traverse(void* aPtr, nsCycleCollectionTraversalCallback& aCb) + override; + + NS_DECL_CYCLE_COLLECTION_CLASS_NAME_METHOD(JSZoneParticipant) +}; + +class IncrementalFinalizeRunnable; + +// 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; + 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; + bool mAnyManual; // true if any slice of the CC was manually triggered, or at shutdown. + uint32_t mVisitedRefCounted; + uint32_t mVisitedGCed; + uint32_t mFreedRefCounted; + uint32_t mFreedGCed; + uint32_t mFreedJSZones; + uint32_t mNumSlices; +}; + +class CycleCollectedJSContext +{ + friend class JSGCThingParticipant; + friend class JSZoneParticipant; + friend class IncrementalFinalizeRunnable; +protected: + CycleCollectedJSContext(); + virtual ~CycleCollectedJSContext(); + + MOZ_IS_CLASS_INIT + nsresult Initialize(JSContext* aParentContext, + uint32_t aMaxBytes, + uint32_t aMaxNurseryBytes); + + 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() {} + virtual void CustomLargeAllocationFailureCallback() {} + + std::queue> mPromiseMicroTaskQueue; + std::queue> mDebuggerPromiseMicroTaskQueue; + +private: + void + DescribeGCThing(bool aIsMarked, JS::GCCellPtr aThing, + nsCycleCollectionTraversalCallback& aCb) const; + + virtual bool + DescribeCustomObjects(JSObject* aObject, const js::Class* aClasp, + char (&aName)[72]) const + { + return false; // We did nothing. + } + + void + NoteGCThingJSChildren(JS::GCCellPtr aThing, + nsCycleCollectionTraversalCallback& aCb) const; + + void + NoteGCThingXPCOMChildren(const js::Class* aClasp, JSObject* aObj, + nsCycleCollectionTraversalCallback& aCb) const; + + virtual bool + NoteCustomGCThingXPCOMChildren(const js::Class* 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); + + void TraverseNativeRoots(nsCycleCollectionNoteRootCallback& aCb); + + static void TraceBlackJS(JSTracer* aTracer, void* aData); + static void TraceGrayJS(JSTracer* aTracer, void* aData); + static void GCCallback(JSContext* aContext, JSGCStatus aStatus, void* aData); + static void GCSliceCallback(JSContext* aContext, JS::GCProgress aProgress, + const JS::GCDescription& aDesc); + static void GCNurseryCollectionCallback(JSContext* aContext, + JS::GCNurseryProgress aProgress, + JS::gcreason::Reason aReason); + static void OutOfMemoryCallback(JSContext* aContext, void* aData); + static void LargeAllocationFailureCallback(void* aData); + static bool ContextCallback(JSContext* aCx, unsigned aOperation, + void* aData); + static JSObject* GetIncumbentGlobalCallback(JSContext* aCx); + static bool EnqueuePromiseJobCallback(JSContext* aCx, + JS::HandleObject aJob, + JS::HandleObject aAllocationSite, + JS::HandleObject aIncumbentGlobal, + void* aData); +#ifdef SPIDERMONKEY_PROMISE + static void PromiseRejectionTrackerCallback(JSContext* aCx, + JS::HandleObject aPromise, + PromiseRejectionHandlingState state, + void* aData); +#endif // SPIDERMONKEY_PROMISE + + virtual void TraceNativeBlackRoots(JSTracer* aTracer) { }; + void TraceNativeGrayRoots(JSTracer* aTracer); + + void AfterProcessMicrotask(uint32_t aRecursionDepth); +public: + void ProcessStableStateQueue(); +private: + void ProcessMetastableStateQueue(uint32_t aRecursionDepth); + +public: + enum DeferredFinalizeType { + FinalizeIncrementally, + FinalizeNow, + }; + + void FinalizeDeferredThings(DeferredFinalizeType aType); + + // 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 + }; + +private: + void AnnotateAndSetOutOfMemory(OOMState* aStatePtr, OOMState aNewState); + void OnGC(JSGCStatus aStatus); + void OnOutOfMemory(); + void OnLargeAllocationFailure(); + +public: + void AddJSHolder(void* aHolder, nsScriptObjectTracer* aTracer); + void RemoveJSHolder(void* aHolder); +#ifdef DEBUG + bool IsJSHolder(void* aHolder); + void AssertNoObjectsToTrace(void* aPossibleJSHolder); +#endif + + already_AddRefed GetPendingException() const; + void SetPendingException(nsIException* aException); + + std::queue>& GetPromiseMicroTaskQueue(); + std::queue>& GetDebuggerPromiseMicroTaskQueue(); + + nsCycleCollectionParticipant* GCThingParticipant(); + nsCycleCollectionParticipant* ZoneParticipant(); + + nsresult TraverseRoots(nsCycleCollectionNoteRootCallback& aCb); + virtual bool UsefulToMergeZones() const; + void FixWeakMappingGrayBits() const; + bool AreGCGrayBitsValid() const; + void GarbageCollect(uint32_t aReason) const; + + void NurseryWrapperAdded(nsWrapperCache* aCache); + void NurseryWrapperPreserved(JSObject* aWrapper); + void JSObjectsTenured(); + + void DeferredFinalize(DeferredFinalizeAppendFunction aAppendFunc, + DeferredFinalizeFunction aFunc, + void* aThing); + void DeferredFinalize(nsISupports* aSupports); + + void DumpJSHeap(FILE* aFile); + + virtual void PrepareForForgetSkippable() = 0; + virtual void BeginCycleCollectionCallback() = 0; + virtual void EndCycleCollectionCallback(CycleCollectorResults& aResults) = 0; + virtual void DispatchDeferredDeletion(bool aContinuation, bool aPurge = false) = 0; + + JSContext* Context() const + { + MOZ_ASSERT(mJSContext); + return mJSContext; + } + + JS::RootingContext* RootingCx() const + { + MOZ_ASSERT(mJSContext); + return JS::RootingContext::get(mJSContext); + } + + bool MicroTaskCheckpointDisabled() const + { + return mDisableMicroTaskCheckpoint; + } + + void DisableMicroTaskCheckpoint(bool aDisable) + { + mDisableMicroTaskCheckpoint = aDisable; + } + + class MOZ_RAII AutoDisableMicroTaskCheckpoint + { + public: + AutoDisableMicroTaskCheckpoint() + : mCCJSCX(CycleCollectedJSContext::Get()) + { + mOldValue = mCCJSCX->MicroTaskCheckpointDisabled(); + mCCJSCX->DisableMicroTaskCheckpoint(true); + } + + ~AutoDisableMicroTaskCheckpoint() + { + mCCJSCX->DisableMicroTaskCheckpoint(mOldValue); + } + + CycleCollectedJSContext* mCCJSCX; + bool mOldValue; + }; + +protected: + JSContext* MaybeContext() const { return mJSContext; } + +public: + // nsThread entrypoints + virtual void BeforeProcessTask(bool aMightBlock) { }; + virtual void AfterProcessTask(uint32_t aRecursionDepth); + + // microtask processor entry point + void AfterProcessMicrotask(); + + uint32_t RecursionDepth(); + + // Run in stable state (call through nsContentUtils) + void RunInStableState(already_AddRefed&& aRunnable); + // This isn't in the spec at all yet, but this gets the behavior we want for IDB. + // Runs after the current microtask completes. + void RunInMetastableState(already_AddRefed&& aRunnable); + + // Get the current thread's CycleCollectedJSContext. Returns null if there + // isn't one. + static CycleCollectedJSContext* Get(); + + // Add aZone to the set of zones waiting for a GC. + void AddZoneWaitingForGC(JS::Zone* aZone) + { + mZonesWaitingForGC.PutEntry(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(); + + // Queue an async microtask to the current main or worker thread. + virtual void DispatchToMicroTask(already_AddRefed aRunnable); + + // Storage for watching rejected promises waiting for some client to + // consume their rejection. +#ifdef SPIDERMONKEY_PROMISE + // 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; +#else + // We store values as `nsISupports` to avoid adding compile-time dependencies + // from xpcom to dom/promise, but they can really only have a single concrete + // type. + nsTArray> mUncaughtRejections; + nsTArray> mConsumedRejections; +#endif // SPIDERMONKEY_PROMISE + nsTArray> mUncaughtRejectionObservers; + +private: + JSGCThingParticipant mGCThingCycleCollectorGlobal; + + JSZoneParticipant mJSZoneCycleCollectorGlobal; + + JSContext* mJSContext; + + JS::GCSliceCallback mPrevGCSliceCallback; + JS::GCNurseryCollectionCallback mPrevGCNurseryCollectionCallback; + + nsDataHashtable, nsScriptObjectTracer*> mJSHolders; + + typedef nsDataHashtable, void*> + DeferredFinalizerTable; + DeferredFinalizerTable mDeferredFinalizerTable; + + RefPtr mFinalizeRunnable; + + nsCOMPtr mPendingException; + nsThread* mOwningThread; // Manual refcounting to avoid include hell. + + struct RunInMetastableStateData + { + nsCOMPtr mRunnable; + uint32_t mRecursionDepth; + }; + + nsTArray> mStableStateEvents; + nsTArray mMetastableStateEvents; + uint32_t mBaseRecursionDepth; + bool mDoingStableStates; + + bool mDisableMicroTaskCheckpoint; + + OOMState mOutOfMemoryState; + OOMState mLargeAllocationFailureState; + + static const size_t kSegmentSize = 512; + SegmentedVector + mNurseryObjects; + SegmentedVector, kSegmentSize, + InfallibleAllocPolicy> + mPreservedNurseryObjects; + + nsTHashtable> mZonesWaitingForGC; + + struct EnvironmentPreparer : public js::ScriptEnvironmentPreparer { + void invoke(JS::HandleObject scope, Closure& closure) override; + }; + EnvironmentPreparer mEnvironmentPreparer; +}; + +void TraceScriptHolder(nsISupports* aHolder, JSTracer* aTracer); + +// Returns true if the JS::TraceKind is one the cycle collector cares about. +inline bool AddToCCKind(JS::TraceKind aKind) +{ + return aKind == JS::TraceKind::Object || aKind == JS::TraceKind::Script || aKind == JS::TraceKind::Scope; +} + +bool +GetBuildId(JS::BuildIdCharVector* aBuildID); + +} // namespace mozilla + +#endif // mozilla_CycleCollectedJSContext_h__ diff --git a/xpcom/base/Debug.cpp b/xpcom/base/Debug.cpp new file mode 100644 index 000000000..cc5272da0 --- /dev/null +++ b/xpcom/base/Debug.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/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 000000000..2479799ae --- /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 000000000..96f4cddcb --- /dev/null +++ b/xpcom/base/DebuggerOnGCRunnable.cpp @@ -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/. */ + +#include "mozilla/DebuggerOnGCRunnable.h" + +#include "mozilla/dom/ScriptSettings.h" +#include "mozilla/CycleCollectedJSContext.h" +#include "mozilla/Move.h" +#include "js/Debug.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(Move(gcEvent)); + return NS_DispatchToCurrentThread(runOnGC); +} + +NS_IMETHODIMP +DebuggerOnGCRunnable::Run() +{ + AutoJSAPI jsapi; + jsapi.Init(); + if (!JS::dbg::FireOnGarbageCollectionHook(jsapi.cx(), 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 000000000..8f9621613 --- /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 "nsThreadUtils.h" +#include "js/GCAPI.h" +#include "mozilla/Move.h" +#include "mozilla/UniquePtr.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) + : mGCData(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 000000000..b9a71b81c --- /dev/null +++ b/xpcom/base/DeferredFinalize.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/DeferredFinalize.h" + +#include "mozilla/Assertions.h" +#include "mozilla/CycleCollectedJSContext.h" + +void +mozilla::DeferredFinalize(nsISupports* aSupports) +{ + CycleCollectedJSContext* cx = CycleCollectedJSContext::Get(); + MOZ_ASSERT(cx, "Should have a CycleCollectedJSContext by now"); + cx->DeferredFinalize(aSupports); +} + +void +mozilla::DeferredFinalize(DeferredFinalizeAppendFunction aAppendFunc, + DeferredFinalizeFunction aFunc, + void* aThing) +{ + CycleCollectedJSContext* cx = CycleCollectedJSContext::Get(); + MOZ_ASSERT(cx, "Should have a CycleCollectedJSContext by now"); + cx->DeferredFinalize(aAppendFunc, aFunc, aThing); +} diff --git a/xpcom/base/DeferredFinalize.h b/xpcom/base/DeferredFinalize.h new file mode 100644 index 000000000..7d9c58881 --- /dev/null +++ b/xpcom/base/DeferredFinalize.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 mozilla_DeferredFinalize_h +#define mozilla_DeferredFinalize_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); + +void DeferredFinalize(nsISupports* aSupports); + +} // namespace mozilla + +#endif // mozilla_DeferredFinalize_h diff --git a/xpcom/base/ErrorList.h b/xpcom/base/ErrorList.h new file mode 100644 index 000000000..cfa461fe4 --- /dev/null +++ b/xpcom/base/ErrorList.h @@ -0,0 +1,1026 @@ +// IWYU pragma: private, include "nsError.h" +/* Helper file for nsError.h, via preprocessor magic */ + /* Standard "it worked" return value */ + ERROR(NS_OK, 0), + + /* ======================================================================= */ + /* Core errors, not part of any modules */ + /* ======================================================================= */ + ERROR(NS_ERROR_BASE, 0xC1F30000), + /* Returned when an instance is not initialized */ + ERROR(NS_ERROR_NOT_INITIALIZED, NS_ERROR_BASE + 1), + /* Returned when an instance is already initialized */ + ERROR(NS_ERROR_ALREADY_INITIALIZED, NS_ERROR_BASE + 2), + /* Returned by a not implemented function */ + ERROR(NS_ERROR_NOT_IMPLEMENTED, 0x80004001), + /* Returned when a given interface is not supported. */ + ERROR(NS_NOINTERFACE, 0x80004002), + ERROR(NS_ERROR_NO_INTERFACE, NS_NOINTERFACE), + /* Returned when a function aborts */ + ERROR(NS_ERROR_ABORT, 0x80004004), + /* Returned when a function fails */ + ERROR(NS_ERROR_FAILURE, 0x80004005), + /* Returned when an unexpected error occurs */ + ERROR(NS_ERROR_UNEXPECTED, 0x8000ffff), + /* Returned when a memory allocation fails */ + ERROR(NS_ERROR_OUT_OF_MEMORY, 0x8007000e), + /* Returned when an illegal value is passed */ + ERROR(NS_ERROR_ILLEGAL_VALUE, 0x80070057), + ERROR(NS_ERROR_INVALID_ARG, NS_ERROR_ILLEGAL_VALUE), + ERROR(NS_ERROR_INVALID_POINTER, NS_ERROR_INVALID_ARG), + ERROR(NS_ERROR_NULL_POINTER, NS_ERROR_INVALID_ARG), + /* Returned when a class doesn't allow aggregation */ + ERROR(NS_ERROR_NO_AGGREGATION, 0x80040110), + /* Returned when an operation can't complete due to an unavailable resource */ + ERROR(NS_ERROR_NOT_AVAILABLE, 0x80040111), + /* Returned when a class is not registered */ + ERROR(NS_ERROR_FACTORY_NOT_REGISTERED, 0x80040154), + /* Returned when a class cannot be registered, but may be tried again later */ + ERROR(NS_ERROR_FACTORY_REGISTER_AGAIN, 0x80040155), + /* Returned when a dynamically loaded factory couldn't be found */ + ERROR(NS_ERROR_FACTORY_NOT_LOADED, 0x800401f8), + /* Returned when a factory doesn't support signatures */ + ERROR(NS_ERROR_FACTORY_NO_SIGNATURE_SUPPORT, NS_ERROR_BASE + 0x101), + /* Returned when a factory already is registered */ + ERROR(NS_ERROR_FACTORY_EXISTS, NS_ERROR_BASE + 0x100), + + + /* ======================================================================= */ + /* 1: NS_ERROR_MODULE_XPCOM */ + /* ======================================================================= */ +#define MODULE NS_ERROR_MODULE_XPCOM + /* Result codes used by nsIVariant */ + ERROR(NS_ERROR_CANNOT_CONVERT_DATA, FAILURE(1)), + ERROR(NS_ERROR_OBJECT_IS_IMMUTABLE, FAILURE(2)), + ERROR(NS_ERROR_LOSS_OF_SIGNIFICANT_DATA, FAILURE(3)), + /* Result code used by nsIThreadManager */ + ERROR(NS_ERROR_NOT_SAME_THREAD, FAILURE(4)), + /* Various operations are not permitted during XPCOM shutdown and will fail + * with this exception. */ + ERROR(NS_ERROR_ILLEGAL_DURING_SHUTDOWN, FAILURE(30)), + ERROR(NS_ERROR_SERVICE_NOT_AVAILABLE, FAILURE(22)), + + ERROR(NS_SUCCESS_LOSS_OF_INSIGNIFICANT_DATA, SUCCESS(1)), + /* Used by nsCycleCollectionParticipant */ + ERROR(NS_SUCCESS_INTERRUPTED_TRAVERSE, SUCCESS(2)), + /* DEPRECATED */ + ERROR(NS_ERROR_SERVICE_NOT_FOUND, SUCCESS(22)), + /* DEPRECATED */ + ERROR(NS_ERROR_SERVICE_IN_USE, SUCCESS(23)), +#undef MODULE + + + /* ======================================================================= */ + /* 2: NS_ERROR_MODULE_BASE */ + /* ======================================================================= */ +#define MODULE NS_ERROR_MODULE_BASE + /* I/O Errors */ + + /* Stream closed */ + ERROR(NS_BASE_STREAM_CLOSED, FAILURE(2)), + /* Error from the operating system */ + ERROR(NS_BASE_STREAM_OSERROR, FAILURE(3)), + /* Illegal arguments */ + ERROR(NS_BASE_STREAM_ILLEGAL_ARGS, FAILURE(4)), + /* For unichar streams */ + ERROR(NS_BASE_STREAM_NO_CONVERTER, FAILURE(5)), + /* For unichar streams */ + ERROR(NS_BASE_STREAM_BAD_CONVERSION, FAILURE(6)), + ERROR(NS_BASE_STREAM_WOULD_BLOCK, FAILURE(7)), +#undef MODULE + + + /* ======================================================================= */ + /* 3: NS_ERROR_MODULE_GFX */ + /* ======================================================================= */ +#define MODULE NS_ERROR_MODULE_GFX + /* no printer available (e.g. cannot find _any_ printer) */ + ERROR(NS_ERROR_GFX_PRINTER_NO_PRINTER_AVAILABLE, FAILURE(1)), + /* _specified_ (by name) printer not found */ + ERROR(NS_ERROR_GFX_PRINTER_NAME_NOT_FOUND, FAILURE(2)), + /* print-to-file: could not open output file */ + ERROR(NS_ERROR_GFX_PRINTER_COULD_NOT_OPEN_FILE, FAILURE(3)), + /* print: starting document */ + ERROR(NS_ERROR_GFX_PRINTER_STARTDOC, FAILURE(4)), + /* print: ending document */ + ERROR(NS_ERROR_GFX_PRINTER_ENDDOC, FAILURE(5)), + /* print: starting page */ + ERROR(NS_ERROR_GFX_PRINTER_STARTPAGE, FAILURE(6)), + /* The document is still being loaded */ + ERROR(NS_ERROR_GFX_PRINTER_DOC_IS_BUSY, FAILURE(7)), + + /* Font cmap is strangely structured - avoid this font! */ + ERROR(NS_ERROR_GFX_CMAP_MALFORMED, FAILURE(51)), +#undef MODULE + + + /* ======================================================================= */ + /* 4: NS_ERROR_MODULE_WIDGET */ + /* ======================================================================= */ +#define MODULE NS_ERROR_MODULE_WIDGET + /* Used by: + * - nsIWidget::NotifyIME() + * - nsIWidget::OnWindowedPluginKeyEvent() + * Returned when the notification or the event is handled and it's consumed + * by somebody. */ + ERROR(NS_SUCCESS_EVENT_CONSUMED, SUCCESS(1)), + /* Used by: + * - nsIWidget::OnWindowedPluginKeyEvent() + * Returned when the event is handled correctly but the result will be + * notified asynchronously. */ + ERROR(NS_SUCCESS_EVENT_HANDLED_ASYNCHRONOUSLY, SUCCESS(2)), +#undef MODULE + + + /* ======================================================================= */ + /* 6: NS_ERROR_MODULE_NETWORK */ + /* ======================================================================= */ +#define MODULE NS_ERROR_MODULE_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. */ + ERROR(NS_BINDING_SUCCEEDED, NS_OK), + + /* The async request failed for some unknown reason. */ + ERROR(NS_BINDING_FAILED, FAILURE(1)), + /* The async request failed because it was aborted by some user action. */ + ERROR(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. */ + ERROR(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. */ + ERROR(NS_BINDING_RETARGETED, FAILURE(4)), + + /* Miscellaneous error codes: These errors are not typically passed via + * onStopRequest. */ + + /* The URI is malformed. */ + ERROR(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). */ + ERROR(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. */ + ERROR(NS_ERROR_NO_CONTENT, FAILURE(17)), + /* The URI scheme corresponds to an unknown protocol handler. */ + ERROR(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 */ + ERROR(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. */ + ERROR(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. */ + ERROR(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 */ + ERROR(NS_ERROR_FIRST_HEADER_FIELD_COMPONENT_EMPTY, FAILURE(34)), + /* Returned from nsIChannel::asyncOpen when trying to open the channel again + * (reopening is not supported). */ + ERROR(NS_ERROR_ALREADY_OPENED, FAILURE(73)), + + /* Connectivity error codes: */ + + /* The connection is already established. XXX unused - consider removing. */ + ERROR(NS_ERROR_ALREADY_CONNECTED, FAILURE(11)), + /* The connection does not exist. XXX unused - consider removing. */ + ERROR(NS_ERROR_NOT_CONNECTED, FAILURE(12)), + /* The connection attempt failed, for example, because no server was + * listening at specified host:port. */ + ERROR(NS_ERROR_CONNECTION_REFUSED, FAILURE(13)), + /* The connection was lost due to a timeout error. */ + ERROR(NS_ERROR_NET_TIMEOUT, FAILURE(14)), + /* The requested action could not be completed while the networking library + * is in the offline state. */ + ERROR(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. */ + ERROR(NS_ERROR_PORT_ACCESS_NOT_ALLOWED, FAILURE(19)), + /* The connection was established, but no data was ever received. */ + ERROR(NS_ERROR_NET_RESET, FAILURE(20)), + /* The connection was established, but the data transfer was interrupted. */ + ERROR(NS_ERROR_NET_INTERRUPT, FAILURE(71)), + /* The connection attempt to a proxy failed. */ + ERROR(NS_ERROR_PROXY_CONNECTION_REFUSED, FAILURE(72)), + /* A transfer was only partially done when it completed. */ + ERROR(NS_ERROR_NET_PARTIAL_TRANSFER, FAILURE(76)), + /* HTTP/2 detected invalid TLS configuration */ + ERROR(NS_ERROR_NET_INADEQUATE_SECURITY, FAILURE(82)), + + /* 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. */ + ERROR(NS_ERROR_NOT_RESUMABLE, FAILURE(25)), + /* The request failed as a result of a detected redirection loop. */ + ERROR(NS_ERROR_REDIRECT_LOOP, FAILURE(31)), + /* It was attempted to resume the request, but the entity has changed in the + * meantime. */ + ERROR(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). */ + ERROR(NS_ERROR_UNSAFE_CONTENT_TYPE, FAILURE(74)), + /* The request failed because the user tried to access to a remote XUL + * document from a website that is not in its white-list. */ + ERROR(NS_ERROR_REMOTE_XUL, FAILURE(75)), + /* The request resulted in an error page being displayed. */ + ERROR(NS_ERROR_LOAD_SHOWED_ERRORPAGE, FAILURE(77)), + + + /* FTP specific error codes: */ + + ERROR(NS_ERROR_FTP_LOGIN, FAILURE(21)), + ERROR(NS_ERROR_FTP_CWD, FAILURE(22)), + ERROR(NS_ERROR_FTP_PASV, FAILURE(23)), + ERROR(NS_ERROR_FTP_PWD, FAILURE(24)), + ERROR(NS_ERROR_FTP_LIST, FAILURE(28)), + + /* DNS specific error codes: */ + + /* The lookup of a hostname failed. This generally refers to the hostname + * from the URL being loaded. */ + ERROR(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 */ + ERROR(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. */ + ERROR(NS_ERROR_UNKNOWN_PROXY_HOST, FAILURE(42)), + + + /* Socket specific error codes: */ + + /* The specified socket type does not exist. */ + ERROR(NS_ERROR_UNKNOWN_SOCKET_TYPE, FAILURE(51)), + /* The specified socket type could not be created. */ + ERROR(NS_ERROR_SOCKET_CREATE_FAILED, FAILURE(52)), + /* The operating system doesn't support the given type of address. */ + ERROR(NS_ERROR_SOCKET_ADDRESS_NOT_SUPPORTED, FAILURE(53)), + /* The address to which we tried to bind the socket was busy. */ + ERROR(NS_ERROR_SOCKET_ADDRESS_IN_USE, FAILURE(54)), + + /* Cache specific error codes: */ + ERROR(NS_ERROR_CACHE_KEY_NOT_FOUND, FAILURE(61)), + ERROR(NS_ERROR_CACHE_DATA_IS_STREAM, FAILURE(62)), + ERROR(NS_ERROR_CACHE_DATA_IS_NOT_STREAM, FAILURE(63)), + ERROR(NS_ERROR_CACHE_WAIT_FOR_VALIDATION, FAILURE(64)), + ERROR(NS_ERROR_CACHE_ENTRY_DOOMED, FAILURE(65)), + ERROR(NS_ERROR_CACHE_READ_ACCESS_DENIED, FAILURE(66)), + ERROR(NS_ERROR_CACHE_WRITE_ACCESS_DENIED, FAILURE(67)), + ERROR(NS_ERROR_CACHE_IN_USE, FAILURE(68)), + /* Error passed through onStopRequest if the document could not be fetched + * from the cache. */ + ERROR(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. */ + ERROR(NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS, FAILURE(80)), + /* The host string is an IP address. */ + ERROR(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. */ + ERROR(NS_SUCCESS_ADOPTED_DATA, SUCCESS(90)), + + /* FTP */ + ERROR(NS_NET_STATUS_BEGIN_FTP_TRANSACTION, SUCCESS(27)), + ERROR(NS_NET_STATUS_END_FTP_TRANSACTION, SUCCESS(28)), + + /* 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. */ + ERROR(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 */ + ERROR(NS_NET_STATUS_READING, FAILURE(8)), + ERROR(NS_NET_STATUS_WRITING, FAILURE(9)), + + /* nsISocketTransport */ + ERROR(NS_NET_STATUS_RESOLVING_HOST, FAILURE(3)), + ERROR(NS_NET_STATUS_RESOLVED_HOST, FAILURE(11)), + ERROR(NS_NET_STATUS_CONNECTING_TO, FAILURE(7)), + ERROR(NS_NET_STATUS_CONNECTED_TO, FAILURE(4)), + ERROR(NS_NET_STATUS_SENDING_TO, FAILURE(5)), + ERROR(NS_NET_STATUS_WAITING_FOR, FAILURE(10)), + ERROR(NS_NET_STATUS_RECEIVING_FROM, FAILURE(6)), + + /* nsIInterceptedChannel */ + /* Generic error for non-specific failures during service worker interception */ + ERROR(NS_ERROR_INTERCEPTION_FAILED, FAILURE(100)), +#undef MODULE + + + /* ======================================================================= */ + /* 7: NS_ERROR_MODULE_PLUGINS */ + /* ======================================================================= */ +#define MODULE NS_ERROR_MODULE_PLUGINS + ERROR(NS_ERROR_PLUGINS_PLUGINSNOTCHANGED, FAILURE(1000)), + ERROR(NS_ERROR_PLUGIN_DISABLED, FAILURE(1001)), + ERROR(NS_ERROR_PLUGIN_BLOCKLISTED, FAILURE(1002)), + ERROR(NS_ERROR_PLUGIN_TIME_RANGE_NOT_SUPPORTED, FAILURE(1003)), + ERROR(NS_ERROR_PLUGIN_CLICKTOPLAY, FAILURE(1004)), + ERROR(NS_PLUGIN_INIT_PENDING, SUCCESS(1005)), +#undef MODULE + + + /* ======================================================================= */ + /* 8: NS_ERROR_MODULE_LAYOUT */ + /* ======================================================================= */ +#define MODULE NS_ERROR_MODULE_LAYOUT + /* Return code for nsITableLayout */ + ERROR(NS_TABLELAYOUT_CELL_NOT_FOUND, SUCCESS(0)), + /* Return code for nsFrame::GetNextPrevLineFromeBlockFrame */ + ERROR(NS_POSITION_BEFORE_TABLE, SUCCESS(3)), + /** Return codes for nsPresState::GetProperty() */ + /* Returned if the property exists */ + ERROR(NS_STATE_PROPERTY_EXISTS, NS_OK), + /* Returned if the property does not exist */ + ERROR(NS_STATE_PROPERTY_NOT_THERE, SUCCESS(5)), +#undef MODULE + + + /* ======================================================================= */ + /* 9: NS_ERROR_MODULE_HTMLPARSER */ + /* ======================================================================= */ +#define MODULE NS_ERROR_MODULE_HTMLPARSER + ERROR(NS_ERROR_HTMLPARSER_CONTINUE, NS_OK), + + ERROR(NS_ERROR_HTMLPARSER_EOF, FAILURE(1000)), + ERROR(NS_ERROR_HTMLPARSER_UNKNOWN, FAILURE(1001)), + ERROR(NS_ERROR_HTMLPARSER_CANTPROPAGATE, FAILURE(1002)), + ERROR(NS_ERROR_HTMLPARSER_CONTEXTMISMATCH, FAILURE(1003)), + ERROR(NS_ERROR_HTMLPARSER_BADFILENAME, FAILURE(1004)), + ERROR(NS_ERROR_HTMLPARSER_BADURL, FAILURE(1005)), + ERROR(NS_ERROR_HTMLPARSER_INVALIDPARSERCONTEXT, FAILURE(1006)), + ERROR(NS_ERROR_HTMLPARSER_INTERRUPTED, FAILURE(1007)), + ERROR(NS_ERROR_HTMLPARSER_BLOCK, FAILURE(1008)), + ERROR(NS_ERROR_HTMLPARSER_BADTOKENIZER, FAILURE(1009)), + ERROR(NS_ERROR_HTMLPARSER_BADATTRIBUTE, FAILURE(1010)), + ERROR(NS_ERROR_HTMLPARSER_UNRESOLVEDDTD, FAILURE(1011)), + ERROR(NS_ERROR_HTMLPARSER_MISPLACEDTABLECONTENT, FAILURE(1012)), + ERROR(NS_ERROR_HTMLPARSER_BADDTD, FAILURE(1013)), + ERROR(NS_ERROR_HTMLPARSER_BADCONTEXT, FAILURE(1014)), + ERROR(NS_ERROR_HTMLPARSER_STOPPARSING, FAILURE(1015)), + ERROR(NS_ERROR_HTMLPARSER_UNTERMINATEDSTRINGLITERAL, FAILURE(1016)), + ERROR(NS_ERROR_HTMLPARSER_HIERARCHYTOODEEP, FAILURE(1017)), + ERROR(NS_ERROR_HTMLPARSER_FAKE_ENDTAG, FAILURE(1018)), + ERROR(NS_ERROR_HTMLPARSER_INVALID_COMMENT, FAILURE(1019)), + + ERROR(NS_HTMLTOKENS_NOT_AN_ENTITY, SUCCESS(2000)), + ERROR(NS_HTMLPARSER_VALID_META_CHARSET, SUCCESS(3000)), +#undef MODULE + + + /* ======================================================================= */ + /* 10: NS_ERROR_MODULE_RDF */ + /* ======================================================================= */ +#define MODULE NS_ERROR_MODULE_RDF + /* Returned from nsIRDFDataSource::Assert() and Unassert() if the assertion + * (or unassertion was accepted by the datasource */ + ERROR(NS_RDF_ASSERTION_ACCEPTED, NS_OK), + /* Returned from nsIRDFCursor::Advance() if the cursor has no more + * elements to enumerate */ + ERROR(NS_RDF_CURSOR_EMPTY, SUCCESS(1)), + /* Returned from nsIRDFDataSource::GetSource() and GetTarget() if the + * source/target has no value */ + ERROR(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. */ + ERROR(NS_RDF_ASSERTION_REJECTED, SUCCESS(3)), + /* Return this from rdfITripleVisitor to stop cycling */ + ERROR(NS_RDF_STOP_VISIT, SUCCESS(4)), +#undef MODULE + + + /* ======================================================================= */ + /* 11: NS_ERROR_MODULE_UCONV */ + /* ======================================================================= */ +#define MODULE NS_ERROR_MODULE_UCONV + ERROR(NS_ERROR_UCONV_NOCONV, FAILURE(1)), + ERROR(NS_ERROR_UDEC_ILLEGALINPUT, FAILURE(14)), + + ERROR(NS_SUCCESS_USING_FALLBACK_LOCALE, SUCCESS(2)), + ERROR(NS_OK_UDEC_EXACTLENGTH, SUCCESS(11)), + ERROR(NS_OK_UDEC_MOREINPUT, SUCCESS(12)), + ERROR(NS_OK_UDEC_MOREOUTPUT, SUCCESS(13)), + ERROR(NS_OK_UDEC_NOBOMFOUND, SUCCESS(14)), + ERROR(NS_OK_UENC_EXACTLENGTH, SUCCESS(33)), + ERROR(NS_OK_UENC_MOREOUTPUT, SUCCESS(34)), + ERROR(NS_ERROR_UENC_NOMAPPING, SUCCESS(35)), + ERROR(NS_OK_UENC_MOREINPUT, SUCCESS(36)), + + /* BEGIN DEPRECATED */ + ERROR(NS_EXACT_LENGTH, NS_OK_UDEC_EXACTLENGTH), + ERROR(NS_PARTIAL_MORE_INPUT, NS_OK_UDEC_MOREINPUT), + ERROR(NS_PARTIAL_MORE_OUTPUT, NS_OK_UDEC_MOREOUTPUT), + ERROR(NS_ERROR_ILLEGAL_INPUT, NS_ERROR_UDEC_ILLEGALINPUT), + /* END DEPRECATED */ +#undef MODULE + + + /* ======================================================================= */ + /* 13: NS_ERROR_MODULE_FILES */ + /* ======================================================================= */ +#define MODULE NS_ERROR_MODULE_FILES + ERROR(NS_ERROR_FILE_UNRECOGNIZED_PATH, FAILURE(1)), + ERROR(NS_ERROR_FILE_UNRESOLVABLE_SYMLINK, FAILURE(2)), + ERROR(NS_ERROR_FILE_EXECUTION_FAILED, FAILURE(3)), + ERROR(NS_ERROR_FILE_UNKNOWN_TYPE, FAILURE(4)), + ERROR(NS_ERROR_FILE_DESTINATION_NOT_DIR, FAILURE(5)), + ERROR(NS_ERROR_FILE_TARGET_DOES_NOT_EXIST, FAILURE(6)), + ERROR(NS_ERROR_FILE_COPY_OR_MOVE_FAILED, FAILURE(7)), + ERROR(NS_ERROR_FILE_ALREADY_EXISTS, FAILURE(8)), + ERROR(NS_ERROR_FILE_INVALID_PATH, FAILURE(9)), + ERROR(NS_ERROR_FILE_DISK_FULL, FAILURE(10)), + ERROR(NS_ERROR_FILE_CORRUPTED, FAILURE(11)), + ERROR(NS_ERROR_FILE_NOT_DIRECTORY, FAILURE(12)), + ERROR(NS_ERROR_FILE_IS_DIRECTORY, FAILURE(13)), + ERROR(NS_ERROR_FILE_IS_LOCKED, FAILURE(14)), + ERROR(NS_ERROR_FILE_TOO_BIG, FAILURE(15)), + ERROR(NS_ERROR_FILE_NO_DEVICE_SPACE, FAILURE(16)), + ERROR(NS_ERROR_FILE_NAME_TOO_LONG, FAILURE(17)), + ERROR(NS_ERROR_FILE_NOT_FOUND, FAILURE(18)), + ERROR(NS_ERROR_FILE_READ_ONLY, FAILURE(19)), + ERROR(NS_ERROR_FILE_DIR_NOT_EMPTY, FAILURE(20)), + ERROR(NS_ERROR_FILE_ACCESS_DENIED, FAILURE(21)), + + ERROR(NS_SUCCESS_FILE_DIRECTORY_EMPTY, SUCCESS(1)), + /* Result codes used by nsIDirectoryServiceProvider2 */ + ERROR(NS_SUCCESS_AGGREGATE_RESULT, SUCCESS(2)), +#undef MODULE + + + /* ======================================================================= */ + /* 14: NS_ERROR_MODULE_DOM */ + /* ======================================================================= */ +#define MODULE NS_ERROR_MODULE_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 */ + ERROR(NS_ERROR_DOM_INDEX_SIZE_ERR, FAILURE(1)), + ERROR(NS_ERROR_DOM_HIERARCHY_REQUEST_ERR, FAILURE(3)), + ERROR(NS_ERROR_DOM_WRONG_DOCUMENT_ERR, FAILURE(4)), + ERROR(NS_ERROR_DOM_INVALID_CHARACTER_ERR, FAILURE(5)), + ERROR(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR, FAILURE(7)), + ERROR(NS_ERROR_DOM_NOT_FOUND_ERR, FAILURE(8)), + ERROR(NS_ERROR_DOM_NOT_SUPPORTED_ERR, FAILURE(9)), + ERROR(NS_ERROR_DOM_INUSE_ATTRIBUTE_ERR, FAILURE(10)), + ERROR(NS_ERROR_DOM_INVALID_STATE_ERR, FAILURE(11)), + ERROR(NS_ERROR_DOM_SYNTAX_ERR, FAILURE(12)), + ERROR(NS_ERROR_DOM_INVALID_MODIFICATION_ERR, FAILURE(13)), + ERROR(NS_ERROR_DOM_NAMESPACE_ERR, FAILURE(14)), + ERROR(NS_ERROR_DOM_INVALID_ACCESS_ERR, FAILURE(15)), + ERROR(NS_ERROR_DOM_TYPE_MISMATCH_ERR, FAILURE(17)), + ERROR(NS_ERROR_DOM_SECURITY_ERR, FAILURE(18)), + ERROR(NS_ERROR_DOM_NETWORK_ERR, FAILURE(19)), + ERROR(NS_ERROR_DOM_ABORT_ERR, FAILURE(20)), + ERROR(NS_ERROR_DOM_URL_MISMATCH_ERR, FAILURE(21)), + ERROR(NS_ERROR_DOM_QUOTA_EXCEEDED_ERR, FAILURE(22)), + ERROR(NS_ERROR_DOM_TIMEOUT_ERR, FAILURE(23)), + ERROR(NS_ERROR_DOM_INVALID_NODE_TYPE_ERR, FAILURE(24)), + ERROR(NS_ERROR_DOM_DATA_CLONE_ERR, FAILURE(25)), + /* XXX Should be JavaScript native errors */ + ERROR(NS_ERROR_TYPE_ERR, FAILURE(26)), + ERROR(NS_ERROR_RANGE_ERR, FAILURE(27)), + /* StringEncoding API errors from http://wiki.whatwg.org/wiki/StringEncoding */ + ERROR(NS_ERROR_DOM_ENCODING_NOT_SUPPORTED_ERR, FAILURE(28)), + ERROR(NS_ERROR_DOM_INVALID_POINTER_ERR, FAILURE(29)), + /* WebCrypto API errors from http://www.w3.org/TR/WebCryptoAPI/ */ + ERROR(NS_ERROR_DOM_UNKNOWN_ERR, FAILURE(30)), + ERROR(NS_ERROR_DOM_DATA_ERR, FAILURE(31)), + ERROR(NS_ERROR_DOM_OPERATION_ERR, FAILURE(32)), + /* https://heycam.github.io/webidl/#notallowederror */ + ERROR(NS_ERROR_DOM_NOT_ALLOWED_ERR, FAILURE(33)), + /* DOM error codes defined by us */ + ERROR(NS_ERROR_DOM_SECMAN_ERR, FAILURE(1001)), + ERROR(NS_ERROR_DOM_WRONG_TYPE_ERR, FAILURE(1002)), + ERROR(NS_ERROR_DOM_NOT_OBJECT_ERR, FAILURE(1003)), + ERROR(NS_ERROR_DOM_NOT_XPC_OBJECT_ERR, FAILURE(1004)), + ERROR(NS_ERROR_DOM_NOT_NUMBER_ERR, FAILURE(1005)), + ERROR(NS_ERROR_DOM_NOT_BOOLEAN_ERR, FAILURE(1006)), + ERROR(NS_ERROR_DOM_NOT_FUNCTION_ERR, FAILURE(1007)), + ERROR(NS_ERROR_DOM_TOO_FEW_PARAMETERS_ERR, FAILURE(1008)), + ERROR(NS_ERROR_DOM_BAD_DOCUMENT_DOMAIN, FAILURE(1009)), + ERROR(NS_ERROR_DOM_PROP_ACCESS_DENIED, FAILURE(1010)), + ERROR(NS_ERROR_DOM_XPCONNECT_ACCESS_DENIED, FAILURE(1011)), + ERROR(NS_ERROR_DOM_BAD_URI, FAILURE(1012)), + ERROR(NS_ERROR_DOM_RETVAL_UNDEFINED, FAILURE(1013)), + ERROR(NS_ERROR_DOM_QUOTA_REACHED, FAILURE(1014)), + ERROR(NS_ERROR_DOM_JS_EXCEPTION, FAILURE(1015)), + + /* A way to represent uncatchable exceptions */ + ERROR(NS_ERROR_UNCATCHABLE_EXCEPTION, FAILURE(1016)), + + /* An nsresult value to use in ErrorResult to indicate that we want to throw + a DOMException */ + ERROR(NS_ERROR_DOM_DOMEXCEPTION, FAILURE(1017)), + + /* An nsresult value to use in ErrorResult to indicate that we + * should just rethrow whatever is on the JSContext (which might be + * nothing if an uncatchable exception was thrown). + */ + ERROR(NS_ERROR_DOM_EXCEPTION_ON_JSCONTEXT, FAILURE(1018)), + + ERROR(NS_ERROR_DOM_MALFORMED_URI, FAILURE(1019)), + ERROR(NS_ERROR_DOM_INVALID_HEADER_NAME, FAILURE(1020)), + + ERROR(NS_ERROR_DOM_INVALID_STATE_XHR_HAS_INVALID_CONTEXT, FAILURE(1021)), + ERROR(NS_ERROR_DOM_INVALID_STATE_XHR_MUST_BE_OPENED, FAILURE(1022)), + ERROR(NS_ERROR_DOM_INVALID_STATE_XHR_MUST_NOT_BE_SENDING, FAILURE(1023)), + ERROR(NS_ERROR_DOM_INVALID_STATE_XHR_MUST_NOT_BE_LOADING_OR_DONE, FAILURE(1024)), + ERROR(NS_ERROR_DOM_INVALID_STATE_XHR_HAS_WRONG_RESPONSETYPE_FOR_RESPONSEXML, FAILURE(1025)), + ERROR(NS_ERROR_DOM_INVALID_STATE_XHR_HAS_WRONG_RESPONSETYPE_FOR_RESPONSETEXT, FAILURE(1026)), + ERROR(NS_ERROR_DOM_INVALID_STATE_XHR_CHUNKED_RESPONSETYPES_UNSUPPORTED_FOR_SYNC, FAILURE(1027)), + ERROR(NS_ERROR_DOM_INVALID_ACCESS_XHR_TIMEOUT_AND_RESPONSETYPE_UNSUPPORTED_FOR_SYNC, FAILURE(1028)), + + /* 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. + */ + ERROR(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. + */ + ERROR(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. + */ + ERROR(NS_SUCCESS_DOM_SCRIPT_EVALUATION_THREW_UNCATCHABLE, SUCCESS(3)), +#undef MODULE + + + /* ======================================================================= */ + /* 15: NS_ERROR_MODULE_IMGLIB */ + /* ======================================================================= */ +#define MODULE NS_ERROR_MODULE_IMGLIB + ERROR(NS_IMAGELIB_SUCCESS_LOAD_FINISHED, SUCCESS(0)), + ERROR(NS_IMAGELIB_CHANGING_OWNER, SUCCESS(1)), + + ERROR(NS_IMAGELIB_ERROR_FAILURE, FAILURE(5)), + ERROR(NS_IMAGELIB_ERROR_NO_DECODER, FAILURE(6)), + ERROR(NS_IMAGELIB_ERROR_NOT_FINISHED, FAILURE(7)), + ERROR(NS_IMAGELIB_ERROR_NO_ENCODER, FAILURE(9)), +#undef MODULE + + + /* ======================================================================= */ + /* 17: NS_ERROR_MODULE_EDITOR */ + /* ======================================================================= */ +#define MODULE NS_ERROR_MODULE_EDITOR + ERROR(NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND, SUCCESS(1)), + ERROR(NS_SUCCESS_EDITOR_FOUND_TARGET, SUCCESS(2)), +#undef MODULE + + + /* ======================================================================= */ + /* 18: NS_ERROR_MODULE_XPCONNECT */ + /* ======================================================================= */ +#define MODULE NS_ERROR_MODULE_XPCONNECT + ERROR(NS_ERROR_XPC_NOT_ENOUGH_ARGS, FAILURE(1)), + ERROR(NS_ERROR_XPC_NEED_OUT_OBJECT, FAILURE(2)), + ERROR(NS_ERROR_XPC_CANT_SET_OUT_VAL, FAILURE(3)), + ERROR(NS_ERROR_XPC_NATIVE_RETURNED_FAILURE, FAILURE(4)), + ERROR(NS_ERROR_XPC_CANT_GET_INTERFACE_INFO, FAILURE(5)), + ERROR(NS_ERROR_XPC_CANT_GET_PARAM_IFACE_INFO, FAILURE(6)), + ERROR(NS_ERROR_XPC_CANT_GET_METHOD_INFO, FAILURE(7)), + ERROR(NS_ERROR_XPC_UNEXPECTED, FAILURE(8)), + ERROR(NS_ERROR_XPC_BAD_CONVERT_JS, FAILURE(9)), + ERROR(NS_ERROR_XPC_BAD_CONVERT_NATIVE, FAILURE(10)), + ERROR(NS_ERROR_XPC_BAD_CONVERT_JS_NULL_REF, FAILURE(11)), + ERROR(NS_ERROR_XPC_BAD_OP_ON_WN_PROTO, FAILURE(12)), + ERROR(NS_ERROR_XPC_CANT_CONVERT_WN_TO_FUN, FAILURE(13)), + ERROR(NS_ERROR_XPC_CANT_DEFINE_PROP_ON_WN, FAILURE(14)), + ERROR(NS_ERROR_XPC_CANT_WATCH_WN_STATIC, FAILURE(15)), + ERROR(NS_ERROR_XPC_CANT_EXPORT_WN_STATIC, FAILURE(16)), + ERROR(NS_ERROR_XPC_SCRIPTABLE_CALL_FAILED, FAILURE(17)), + ERROR(NS_ERROR_XPC_SCRIPTABLE_CTOR_FAILED, FAILURE(18)), + ERROR(NS_ERROR_XPC_CANT_CALL_WO_SCRIPTABLE, FAILURE(19)), + ERROR(NS_ERROR_XPC_CANT_CTOR_WO_SCRIPTABLE, FAILURE(20)), + ERROR(NS_ERROR_XPC_CI_RETURNED_FAILURE, FAILURE(21)), + ERROR(NS_ERROR_XPC_GS_RETURNED_FAILURE, FAILURE(22)), + ERROR(NS_ERROR_XPC_BAD_CID, FAILURE(23)), + ERROR(NS_ERROR_XPC_BAD_IID, FAILURE(24)), + ERROR(NS_ERROR_XPC_CANT_CREATE_WN, FAILURE(25)), + ERROR(NS_ERROR_XPC_JS_THREW_EXCEPTION, FAILURE(26)), + ERROR(NS_ERROR_XPC_JS_THREW_NATIVE_OBJECT, FAILURE(27)), + ERROR(NS_ERROR_XPC_JS_THREW_JS_OBJECT, FAILURE(28)), + ERROR(NS_ERROR_XPC_JS_THREW_NULL, FAILURE(29)), + ERROR(NS_ERROR_XPC_JS_THREW_STRING, FAILURE(30)), + ERROR(NS_ERROR_XPC_JS_THREW_NUMBER, FAILURE(31)), + ERROR(NS_ERROR_XPC_JAVASCRIPT_ERROR, FAILURE(32)), + ERROR(NS_ERROR_XPC_JAVASCRIPT_ERROR_WITH_DETAILS, FAILURE(33)), + ERROR(NS_ERROR_XPC_CANT_CONVERT_PRIMITIVE_TO_ARRAY, FAILURE(34)), + ERROR(NS_ERROR_XPC_CANT_CONVERT_OBJECT_TO_ARRAY, FAILURE(35)), + ERROR(NS_ERROR_XPC_NOT_ENOUGH_ELEMENTS_IN_ARRAY, FAILURE(36)), + ERROR(NS_ERROR_XPC_CANT_GET_ARRAY_INFO, FAILURE(37)), + ERROR(NS_ERROR_XPC_NOT_ENOUGH_CHARS_IN_STRING, FAILURE(38)), + ERROR(NS_ERROR_XPC_SECURITY_MANAGER_VETO, FAILURE(39)), + ERROR(NS_ERROR_XPC_INTERFACE_NOT_SCRIPTABLE, FAILURE(40)), + ERROR(NS_ERROR_XPC_INTERFACE_NOT_FROM_NSISUPPORTS, FAILURE(41)), + ERROR(NS_ERROR_XPC_CANT_GET_JSOBJECT_OF_DOM_OBJECT, FAILURE(42)), + ERROR(NS_ERROR_XPC_CANT_SET_READ_ONLY_CONSTANT, FAILURE(43)), + ERROR(NS_ERROR_XPC_CANT_SET_READ_ONLY_ATTRIBUTE, FAILURE(44)), + ERROR(NS_ERROR_XPC_CANT_SET_READ_ONLY_METHOD, FAILURE(45)), + ERROR(NS_ERROR_XPC_CANT_ADD_PROP_TO_WRAPPED_NATIVE, FAILURE(46)), + ERROR(NS_ERROR_XPC_CALL_TO_SCRIPTABLE_FAILED, FAILURE(47)), + ERROR(NS_ERROR_XPC_JSOBJECT_HAS_NO_FUNCTION_NAMED, FAILURE(48)), + ERROR(NS_ERROR_XPC_BAD_ID_STRING, FAILURE(49)), + ERROR(NS_ERROR_XPC_BAD_INITIALIZER_NAME, FAILURE(50)), + ERROR(NS_ERROR_XPC_HAS_BEEN_SHUTDOWN, FAILURE(51)), + ERROR(NS_ERROR_XPC_CANT_MODIFY_PROP_ON_WN, FAILURE(52)), + ERROR(NS_ERROR_XPC_BAD_CONVERT_JS_ZERO_ISNOT_NULL, FAILURE(53)), + ERROR(NS_ERROR_XPC_CANT_PASS_CPOW_TO_NATIVE, FAILURE(54)), + /* any new errors here should have an associated entry added in xpc.msg */ +#undef MODULE + + + /* ======================================================================= */ + /* 19: NS_ERROR_MODULE_PROFILE */ + /* ======================================================================= */ +#define MODULE NS_ERROR_MODULE_PROFILE + ERROR(NS_ERROR_LAUNCHED_CHILD_PROCESS, FAILURE(200)), +#undef MODULE + + + /* ======================================================================= */ + /* 21: NS_ERROR_MODULE_SECURITY */ + /* ======================================================================= */ +#define MODULE NS_ERROR_MODULE_SECURITY + /* Error code for CSP */ + ERROR(NS_ERROR_CSP_FORM_ACTION_VIOLATION, FAILURE(98)), + ERROR(NS_ERROR_CSP_FRAME_ANCESTOR_VIOLATION, FAILURE(99)), + + /* Error code for Sub-Resource Integrity */ + ERROR(NS_ERROR_SRI_CORRUPT, FAILURE(200)), + ERROR(NS_ERROR_SRI_DISABLED, FAILURE(201)), + ERROR(NS_ERROR_SRI_NOT_ELIGIBLE, FAILURE(202)), + ERROR(NS_ERROR_SRI_UNEXPECTED_HASH_TYPE, FAILURE(203)), + ERROR(NS_ERROR_SRI_IMPORT, FAILURE(204)), + + /* CMS specific nsresult error codes. Note: the numbers used here correspond + * to the values in nsICMSMessageErrors.idl. */ + ERROR(NS_ERROR_CMS_VERIFY_NOT_SIGNED, FAILURE(1024)), + ERROR(NS_ERROR_CMS_VERIFY_NO_CONTENT_INFO, FAILURE(1025)), + ERROR(NS_ERROR_CMS_VERIFY_BAD_DIGEST, FAILURE(1026)), + ERROR(NS_ERROR_CMS_VERIFY_NOCERT, FAILURE(1028)), + ERROR(NS_ERROR_CMS_VERIFY_UNTRUSTED, FAILURE(1029)), + ERROR(NS_ERROR_CMS_VERIFY_ERROR_UNVERIFIED, FAILURE(1031)), + ERROR(NS_ERROR_CMS_VERIFY_ERROR_PROCESSING, FAILURE(1032)), + ERROR(NS_ERROR_CMS_VERIFY_BAD_SIGNATURE, FAILURE(1033)), + ERROR(NS_ERROR_CMS_VERIFY_DIGEST_MISMATCH, FAILURE(1034)), + ERROR(NS_ERROR_CMS_VERIFY_UNKNOWN_ALGO, FAILURE(1035)), + ERROR(NS_ERROR_CMS_VERIFY_UNSUPPORTED_ALGO, FAILURE(1036)), + ERROR(NS_ERROR_CMS_VERIFY_MALFORMED_SIGNATURE, FAILURE(1037)), + ERROR(NS_ERROR_CMS_VERIFY_HEADER_MISMATCH, FAILURE(1038)), + ERROR(NS_ERROR_CMS_VERIFY_NOT_YET_ATTEMPTED, FAILURE(1039)), + ERROR(NS_ERROR_CMS_VERIFY_CERT_WITHOUT_ADDRESS, FAILURE(1040)), + ERROR(NS_ERROR_CMS_ENCRYPT_NO_BULK_ALG, FAILURE(1056)), + ERROR(NS_ERROR_CMS_ENCRYPT_INCOMPLETE, FAILURE(1057)), +#undef MODULE + + + /* ======================================================================= */ + /* 22: NS_ERROR_MODULE_DOM_XPATH */ + /* ======================================================================= */ +#define MODULE NS_ERROR_MODULE_DOM_XPATH + /* DOM error codes from http://www.w3.org/TR/DOM-Level-3-XPath/ */ + ERROR(NS_ERROR_DOM_INVALID_EXPRESSION_ERR, FAILURE(51)), + ERROR(NS_ERROR_DOM_TYPE_ERR, FAILURE(52)), +#undef MODULE + + + /* ======================================================================= */ + /* 24: NS_ERROR_MODULE_URILOADER */ + /* ======================================================================= */ +#define MODULE NS_ERROR_MODULE_URILOADER + ERROR(NS_ERROR_WONT_HANDLE_CONTENT, FAILURE(1)), + /* The load has been cancelled because it was found on a malware or phishing + * blacklist. */ + ERROR(NS_ERROR_MALWARE_URI, FAILURE(30)), + ERROR(NS_ERROR_PHISHING_URI, FAILURE(31)), + ERROR(NS_ERROR_TRACKING_URI, FAILURE(34)), + ERROR(NS_ERROR_UNWANTED_URI, FAILURE(35)), + ERROR(NS_ERROR_BLOCKED_URI, FAILURE(37)), + /* Used when "Save Link As..." doesn't see the headers quickly enough to + * choose a filename. See nsContextMenu.js. */ + ERROR(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. */ + ERROR(NS_ERROR_PARSED_DATA_CACHED, FAILURE(33)), + + /* This success code indicates that a refresh header was found and + * successfully setup. */ + ERROR(NS_REFRESHURI_HEADER_FOUND, SUCCESS(2)), +#undef MODULE + + + /* ======================================================================= */ + /* 25: NS_ERROR_MODULE_CONTENT */ + /* ======================================================================= */ +#define MODULE NS_ERROR_MODULE_CONTENT + /* Error codes for image loading */ + ERROR(NS_ERROR_IMAGE_SRC_CHANGED, FAILURE(4)), + ERROR(NS_ERROR_IMAGE_BLOCKED, FAILURE(5)), + /* Error codes for content policy blocking */ + ERROR(NS_ERROR_CONTENT_BLOCKED, FAILURE(6)), + ERROR(NS_ERROR_CONTENT_BLOCKED_SHOW_ALT, FAILURE(7)), + /* Success variations of content policy blocking */ + ERROR(NS_PROPTABLE_PROP_NOT_THERE, FAILURE(10)), + /* Error code for XBL */ + ERROR(NS_ERROR_XBL_BLOCKED, FAILURE(15)), + /* Error code for when the content process crashed */ + ERROR(NS_ERROR_CONTENT_CRASHED, FAILURE(16)), + + /* XXX this is not really used */ + ERROR(NS_HTML_STYLE_PROPERTY_NOT_THERE, SUCCESS(2)), + ERROR(NS_CONTENT_BLOCKED, SUCCESS(8)), + ERROR(NS_CONTENT_BLOCKED_SHOW_ALT, SUCCESS(9)), + ERROR(NS_PROPTABLE_PROP_OVERWRITTEN, SUCCESS(11)), + /* Error codes for FindBroadcaster in XULDocument.cpp */ + ERROR(NS_FINDBROADCASTER_NOT_FOUND, SUCCESS(12)), + ERROR(NS_FINDBROADCASTER_FOUND, SUCCESS(13)), + ERROR(NS_FINDBROADCASTER_AWAIT_OVERLAYS, SUCCESS(14)), +#undef MODULE + + + /* ======================================================================= */ + /* 27: NS_ERROR_MODULE_XSLT */ + /* ======================================================================= */ +#define MODULE NS_ERROR_MODULE_XSLT + ERROR(NS_ERROR_XPATH_INVALID_ARG, NS_ERROR_INVALID_ARG), + + ERROR(NS_ERROR_XSLT_PARSE_FAILURE, FAILURE(1)), + ERROR(NS_ERROR_XPATH_PARSE_FAILURE, FAILURE(2)), + ERROR(NS_ERROR_XSLT_ALREADY_SET, FAILURE(3)), + ERROR(NS_ERROR_XSLT_EXECUTION_FAILURE, FAILURE(4)), + ERROR(NS_ERROR_XPATH_UNKNOWN_FUNCTION, FAILURE(5)), + ERROR(NS_ERROR_XSLT_BAD_RECURSION, FAILURE(6)), + ERROR(NS_ERROR_XSLT_BAD_VALUE, FAILURE(7)), + ERROR(NS_ERROR_XSLT_NODESET_EXPECTED, FAILURE(8)), + ERROR(NS_ERROR_XSLT_ABORTED, FAILURE(9)), + ERROR(NS_ERROR_XSLT_NETWORK_ERROR, FAILURE(10)), + ERROR(NS_ERROR_XSLT_WRONG_MIME_TYPE, FAILURE(11)), + ERROR(NS_ERROR_XSLT_LOAD_RECURSION, FAILURE(12)), + ERROR(NS_ERROR_XPATH_BAD_ARGUMENT_COUNT, FAILURE(13)), + ERROR(NS_ERROR_XPATH_BAD_EXTENSION_FUNCTION, FAILURE(14)), + ERROR(NS_ERROR_XPATH_PAREN_EXPECTED, FAILURE(15)), + ERROR(NS_ERROR_XPATH_INVALID_AXIS, FAILURE(16)), + ERROR(NS_ERROR_XPATH_NO_NODE_TYPE_TEST, FAILURE(17)), + ERROR(NS_ERROR_XPATH_BRACKET_EXPECTED, FAILURE(18)), + ERROR(NS_ERROR_XPATH_INVALID_VAR_NAME, FAILURE(19)), + ERROR(NS_ERROR_XPATH_UNEXPECTED_END, FAILURE(20)), + ERROR(NS_ERROR_XPATH_OPERATOR_EXPECTED, FAILURE(21)), + ERROR(NS_ERROR_XPATH_UNCLOSED_LITERAL, FAILURE(22)), + ERROR(NS_ERROR_XPATH_BAD_COLON, FAILURE(23)), + ERROR(NS_ERROR_XPATH_BAD_BANG, FAILURE(24)), + ERROR(NS_ERROR_XPATH_ILLEGAL_CHAR, FAILURE(25)), + ERROR(NS_ERROR_XPATH_BINARY_EXPECTED, FAILURE(26)), + ERROR(NS_ERROR_XSLT_LOAD_BLOCKED_ERROR, FAILURE(27)), + ERROR(NS_ERROR_XPATH_INVALID_EXPRESSION_EVALUATED, FAILURE(28)), + ERROR(NS_ERROR_XPATH_UNBALANCED_CURLY_BRACE, FAILURE(29)), + ERROR(NS_ERROR_XSLT_BAD_NODE_NAME, FAILURE(30)), + ERROR(NS_ERROR_XSLT_VAR_ALREADY_SET, FAILURE(31)), + ERROR(NS_ERROR_XSLT_CALL_TO_KEY_NOT_ALLOWED, FAILURE(32)), + + ERROR(NS_XSLT_GET_NEW_HANDLER, SUCCESS(1)), +#undef MODULE + + + /* ======================================================================= */ + /* 28: NS_ERROR_MODULE_IPC */ + /* ======================================================================= */ +#define MODULE NS_ERROR_MODULE_IPC + // Initial creation of a Transport object failed internally for unknown reasons. + ERROR(NS_ERROR_TRANSPORT_INIT, FAILURE(1)), + // Generic error related to duplicating handle failures. + ERROR(NS_ERROR_DUPLICATE_HANDLE, FAILURE(2)), + // Bridging: failure trying to open the connection to the parent + ERROR(NS_ERROR_BRIDGE_OPEN_PARENT, FAILURE(3)), + // Bridging: failure trying to open the connection to the child + ERROR(NS_ERROR_BRIDGE_OPEN_CHILD, FAILURE(4)), +#undef MODULE + + /* ======================================================================= */ + /* 29: NS_ERROR_MODULE_SVG */ + /* ======================================================================= */ +#define MODULE NS_ERROR_MODULE_SVG + /* SVG DOM error codes from http://www.w3.org/TR/SVG11/svgdom.html */ + ERROR(NS_ERROR_DOM_SVG_WRONG_TYPE_ERR, FAILURE(0)), + /* Yes, the spec says "INVERTABLE", not "INVERTIBLE" */ + ERROR(NS_ERROR_DOM_SVG_MATRIX_NOT_INVERTABLE, FAILURE(2)), +#undef MODULE + + + /* ======================================================================= */ + /* 30: NS_ERROR_MODULE_STORAGE */ + /* ======================================================================= */ +#define MODULE NS_ERROR_MODULE_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")' */ + ERROR(NS_ERROR_STORAGE_BUSY, FAILURE(1)), + ERROR(NS_ERROR_STORAGE_IOERR, FAILURE(2)), + ERROR(NS_ERROR_STORAGE_CONSTRAINT, FAILURE(3)), +#undef MODULE + + + /* ======================================================================= */ + /* 32: NS_ERROR_MODULE_DOM_FILE */ + /* ======================================================================= */ +#define MODULE NS_ERROR_MODULE_DOM_FILE + ERROR(NS_ERROR_DOM_FILE_NOT_FOUND_ERR, FAILURE(0)), + ERROR(NS_ERROR_DOM_FILE_NOT_READABLE_ERR, FAILURE(1)), + ERROR(NS_ERROR_DOM_FILE_ABORT_ERR, FAILURE(2)), +#undef MODULE + + + /* ======================================================================= */ + /* 33: NS_ERROR_MODULE_DOM_INDEXEDDB */ + /* ======================================================================= */ +#define MODULE NS_ERROR_MODULE_DOM_INDEXEDDB + /* IndexedDB error codes http://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html */ + ERROR(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR, FAILURE(1)), + ERROR(NS_ERROR_DOM_INDEXEDDB_NOT_FOUND_ERR, FAILURE(3)), + ERROR(NS_ERROR_DOM_INDEXEDDB_CONSTRAINT_ERR, FAILURE(4)), + ERROR(NS_ERROR_DOM_INDEXEDDB_DATA_ERR, FAILURE(5)), + ERROR(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR, FAILURE(6)), + ERROR(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR, FAILURE(7)), + ERROR(NS_ERROR_DOM_INDEXEDDB_ABORT_ERR, FAILURE(8)), + ERROR(NS_ERROR_DOM_INDEXEDDB_READ_ONLY_ERR, FAILURE(9)), + ERROR(NS_ERROR_DOM_INDEXEDDB_TIMEOUT_ERR, FAILURE(10)), + ERROR(NS_ERROR_DOM_INDEXEDDB_QUOTA_ERR, FAILURE(11)), + ERROR(NS_ERROR_DOM_INDEXEDDB_VERSION_ERR, FAILURE(12)), + ERROR(NS_ERROR_DOM_INDEXEDDB_RECOVERABLE_ERR, FAILURE(1001)), +#undef MODULE + + + /* ======================================================================= */ + /* 34: NS_ERROR_MODULE_DOM_FILEHANDLE */ + /* ======================================================================= */ +#define MODULE NS_ERROR_MODULE_DOM_FILEHANDLE + ERROR(NS_ERROR_DOM_FILEHANDLE_UNKNOWN_ERR, FAILURE(1)), + ERROR(NS_ERROR_DOM_FILEHANDLE_NOT_ALLOWED_ERR, FAILURE(2)), + ERROR(NS_ERROR_DOM_FILEHANDLE_INACTIVE_ERR, FAILURE(3)), + ERROR(NS_ERROR_DOM_FILEHANDLE_ABORT_ERR, FAILURE(4)), + ERROR(NS_ERROR_DOM_FILEHANDLE_READ_ONLY_ERR, FAILURE(5)), + ERROR(NS_ERROR_DOM_FILEHANDLE_QUOTA_ERR, FAILURE(6)), +#undef MODULE + + /* ======================================================================= */ + /* 35: NS_ERROR_MODULE_SIGNED_JAR */ + /* ======================================================================= */ +#define MODULE NS_ERROR_MODULE_SIGNED_JAR + ERROR(NS_ERROR_SIGNED_JAR_NOT_SIGNED, FAILURE(1)), + ERROR(NS_ERROR_SIGNED_JAR_MODIFIED_ENTRY, FAILURE(2)), + ERROR(NS_ERROR_SIGNED_JAR_UNSIGNED_ENTRY, FAILURE(3)), + ERROR(NS_ERROR_SIGNED_JAR_ENTRY_MISSING, FAILURE(4)), + ERROR(NS_ERROR_SIGNED_JAR_WRONG_SIGNATURE, FAILURE(5)), + ERROR(NS_ERROR_SIGNED_JAR_ENTRY_TOO_LARGE, FAILURE(6)), + ERROR(NS_ERROR_SIGNED_JAR_ENTRY_INVALID, FAILURE(7)), + ERROR(NS_ERROR_SIGNED_JAR_MANIFEST_INVALID, FAILURE(8)), +#undef MODULE + + /* ======================================================================= */ + /* 36: NS_ERROR_MODULE_DOM_FILESYSTEM */ + /* ======================================================================= */ +#define MODULE NS_ERROR_MODULE_DOM_FILESYSTEM + ERROR(NS_ERROR_DOM_FILESYSTEM_INVALID_PATH_ERR, FAILURE(1)), + ERROR(NS_ERROR_DOM_FILESYSTEM_INVALID_MODIFICATION_ERR, FAILURE(2)), + ERROR(NS_ERROR_DOM_FILESYSTEM_NO_MODIFICATION_ALLOWED_ERR, FAILURE(3)), + ERROR(NS_ERROR_DOM_FILESYSTEM_PATH_EXISTS_ERR, FAILURE(4)), + ERROR(NS_ERROR_DOM_FILESYSTEM_TYPE_MISMATCH_ERR, FAILURE(5)), + ERROR(NS_ERROR_DOM_FILESYSTEM_UNKNOWN_ERR, FAILURE(6)), +#undef MODULE + + /* ======================================================================= */ + /* 38: NS_ERROR_MODULE_SIGNED_APP */ + /* ======================================================================= */ +#define MODULE NS_ERROR_MODULE_SIGNED_APP + ERROR(NS_ERROR_SIGNED_APP_MANIFEST_INVALID, FAILURE(1)), +#undef MODULE + + /* ======================================================================= */ + /* 39: NS_ERROR_MODULE_DOM_ANIM */ + /* ======================================================================= */ +#define MODULE NS_ERROR_MODULE_DOM_ANIM + ERROR(NS_ERROR_DOM_ANIM_MISSING_PROPS_ERR, FAILURE(1)), +#undef MODULE + + /* ======================================================================= */ + /* 40: NS_ERROR_MODULE_DOM_PUSH */ + /* ======================================================================= */ +#define MODULE NS_ERROR_MODULE_DOM_PUSH + ERROR(NS_ERROR_DOM_PUSH_INVALID_REGISTRATION_ERR, FAILURE(1)), + ERROR(NS_ERROR_DOM_PUSH_DENIED_ERR, FAILURE(2)), + ERROR(NS_ERROR_DOM_PUSH_ABORT_ERR, FAILURE(3)), + ERROR(NS_ERROR_DOM_PUSH_SERVICE_UNREACHABLE, FAILURE(4)), + ERROR(NS_ERROR_DOM_PUSH_INVALID_KEY_ERR, FAILURE(5)), + ERROR(NS_ERROR_DOM_PUSH_MISMATCHED_KEY_ERR, FAILURE(6)), +#undef MODULE + + /* ======================================================================= */ + /* 41: NS_ERROR_MODULE_DOM_MEDIA */ + /* ======================================================================= */ +#define MODULE NS_ERROR_MODULE_DOM_MEDIA + /* HTMLMediaElement API errors from https://html.spec.whatwg.org/multipage/embedded-content.html#media-elements */ + ERROR(NS_ERROR_DOM_MEDIA_ABORT_ERR, FAILURE(1)), + ERROR(NS_ERROR_DOM_MEDIA_NOT_ALLOWED_ERR, FAILURE(2)), + ERROR(NS_ERROR_DOM_MEDIA_NOT_SUPPORTED_ERR, FAILURE(3)), + + /* HTMLMediaElement internal decoding error */ + ERROR(NS_ERROR_DOM_MEDIA_DECODE_ERR, FAILURE(4)), + ERROR(NS_ERROR_DOM_MEDIA_FATAL_ERR, FAILURE(5)), + ERROR(NS_ERROR_DOM_MEDIA_METADATA_ERR, FAILURE(6)), + ERROR(NS_ERROR_DOM_MEDIA_OVERFLOW_ERR, FAILURE(7)), + ERROR(NS_ERROR_DOM_MEDIA_END_OF_STREAM, FAILURE(8)), + ERROR(NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA, FAILURE(9)), + ERROR(NS_ERROR_DOM_MEDIA_CANCELED, FAILURE(10)), + ERROR(NS_ERROR_DOM_MEDIA_MEDIASINK_ERR, FAILURE(11)), + ERROR(NS_ERROR_DOM_MEDIA_DEMUXER_ERR, FAILURE(12)), + ERROR(NS_ERROR_DOM_MEDIA_CDM_ERR, FAILURE(13)), + ERROR(NS_ERROR_DOM_MEDIA_NEED_NEW_DECODER, FAILURE(14)), + + /* Internal platform-related errors */ + ERROR(NS_ERROR_DOM_MEDIA_CUBEB_INITIALIZATION_ERR, FAILURE(101)), +#undef MODULE + + /* ======================================================================= */ + /* 51: NS_ERROR_MODULE_GENERAL */ + /* ======================================================================= */ +#define MODULE NS_ERROR_MODULE_GENERAL + /* Error code used internally by the incremental downloader to cancel the + * network channel when the download is already complete. */ + ERROR(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. */ + ERROR(NS_ERROR_DOWNLOAD_NOT_PARTIAL, FAILURE(2)), + ERROR(NS_ERROR_UNORM_MOREOUTPUT, FAILURE(33)), + + ERROR(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 */ + ERROR(NS_ERROR_DOCUMENT_IS_PRINTMODE, FAILURE(2001)), + + ERROR(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. */ + ERROR(NS_SUCCESS_RESTART_APP, SUCCESS(1)), + ERROR(NS_SUCCESS_RESTART_APP_NOT_SAME_PROFILE, SUCCESS(3)), + ERROR(NS_SUCCESS_UNORM_NOTFOUND, SUCCESS(17)), + + + /* a11y */ + /* raised when current pivot's position is needed but it's not in the tree */ + ERROR(NS_ERROR_NOT_IN_TREE, FAILURE(38)), + + /* see nsTextEquivUtils */ + ERROR(NS_OK_NO_NAME_CLAUSE_HANDLED, SUCCESS(34)) +#undef MODULE diff --git a/xpcom/base/ErrorNames.cpp b/xpcom/base/ErrorNames.cpp new file mode 100644 index 000000000..165a1a0fc --- /dev/null +++ b/xpcom/base/ErrorNames.cpp @@ -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/. */ + +#include "mozilla/ArrayUtils.h" +#include "mozilla/ErrorNames.h" +#include "nsString.h" +#include "prerror.h" + +namespace { + +struct ErrorEntry +{ + nsresult value; + const char * name; +}; + +#undef ERROR +#define ERROR(key, val) {key, #key} + +const ErrorEntry errors[] = { + #include "ErrorList.h" +}; + +#undef ERROR + +} // unnamed namespace + +namespace mozilla { + +void +GetErrorName(nsresult rv, nsACString& name) +{ + for (size_t i = 0; i < ArrayLength(errors); ++i) { + if (errors[i].value == rv) { + name.AssignASCII(errors[i].name); + 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); + + name.AssignASCII(NS_SUCCEEDED(rv) ? "NS_ERROR_GENERATE_SUCCESS(" + : "NS_ERROR_GENERATE_FAILURE("); + + if (isSecurityError) { + name.AppendASCII("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.AppendASCII(", "); + + const char * nsprName = nullptr; + if (isSecurityError) { + // 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.AppendASCII(")"); +} + +} // namespace mozilla diff --git a/xpcom/base/ErrorNames.h b/xpcom/base/ErrorNames.h new file mode 100644 index 000000000..9fdba7ace --- /dev/null +++ b/xpcom/base/ErrorNames.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_ErrorNames_h +#define mozilla_ErrorNames_h + +#include "nsError.h" + +class nsACString; + +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); + +} // namespace mozilla + +#endif // mozilla_ErrorNames_h diff --git a/xpcom/base/HoldDropJSObjects.cpp b/xpcom/base/HoldDropJSObjects.cpp new file mode 100644 index 000000000..eeecc7121 --- /dev/null +++ b/xpcom/base/HoldDropJSObjects.cpp @@ -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/. */ + +#include "mozilla/HoldDropJSObjects.h" + +#include "mozilla/Assertions.h" +#include "mozilla/CycleCollectedJSContext.h" + +namespace mozilla { +namespace cyclecollector { + +void +HoldJSObjectsImpl(void* aHolder, nsScriptObjectTracer* aTracer) +{ + CycleCollectedJSContext* cx = CycleCollectedJSContext::Get(); + MOZ_ASSERT(cx, "Should have a CycleCollectedJSContext by now"); + cx->AddJSHolder(aHolder, aTracer); +} + +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) +{ + CycleCollectedJSContext* cx = CycleCollectedJSContext::Get(); + MOZ_ASSERT(cx, "Should have a CycleCollectedJSContext by now"); + cx->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 + +#ifdef DEBUG +bool +IsJSHolder(void* aHolder) +{ + CycleCollectedJSContext* cx = CycleCollectedJSContext::Get(); + MOZ_ASSERT(cx, "Should have a CycleCollectedJSContext by now"); + return cx->IsJSHolder(aHolder); +} +#endif + +} // namespace mozilla diff --git a/xpcom/base/HoldDropJSObjects.h b/xpcom/base/HoldDropJSObjects.h new file mode 100644 index 000000000..1a500a94a --- /dev/null +++ b/xpcom/base/HoldDropJSObjects.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_HoldDropJSObjects_h +#define mozilla_HoldDropJSObjects_h + +#include "mozilla/TypeTraits.h" +#include "nsCycleCollectionParticipant.h" + +class nsISupports; +class nsScriptObjectTracer; + +// Only HoldJSObjects and DropJSObjects should be called directly. + +namespace mozilla { +namespace cyclecollector { + +void HoldJSObjectsImpl(void* aHolder, nsScriptObjectTracer* aTracer); +void HoldJSObjectsImpl(nsISupports* aHolder); +void DropJSObjectsImpl(void* aHolder); +void DropJSObjectsImpl(nsISupports* aHolder); + +} // namespace cyclecollector + + +template::value> +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)); + } +}; + + +template +void +HoldJSObjects(T* aHolder) +{ + HoldDropJSObjectsHelper::Hold(aHolder); +} + +template +void +DropJSObjects(T* aHolder) +{ + HoldDropJSObjectsHelper::Drop(aHolder); +} + +#ifdef DEBUG +bool IsJSHolder(void* aHolder); +#endif + +} // namespace mozilla + +#endif // mozilla_HoldDropJSObjects_h diff --git a/xpcom/base/JSObjectHolder.cpp b/xpcom/base/JSObjectHolder.cpp new file mode 100644 index 000000000..5bcc3cabb --- /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 000000000..7b83b813d --- /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() {} + + JS::PersistentRooted mJSObject; +}; + +} // namespace mozilla + +#endif // mozilla_JSObjectHolder_h diff --git a/xpcom/base/LinuxUtils.cpp b/xpcom/base/LinuxUtils.cpp new file mode 100644 index 000000000..331c82be9 --- /dev/null +++ b/xpcom/base/LinuxUtils.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 "LinuxUtils.h" + +#if defined(XP_LINUX) + +#include +#include + +#include "nsPrintfCString.h" + +namespace mozilla { + +void +LinuxUtils::GetThreadName(pid_t aTid, nsACString& aName) +{ + aName.Truncate(); + if (aTid <= 0) { + return; + } + + const size_t kBuffSize = 16; // 15 chars max + '\n' + char buf[kBuffSize]; + nsPrintfCString path("/proc/%d/comm", aTid); + FILE* fp = fopen(path.get(), "r"); + if (!fp) { + // The fopen could also fail if the thread exited before we got here. + return; + } + + size_t len = fread(buf, 1, kBuffSize, fp); + fclose(fp); + + // No need to strip the '\n', since isspace() includes it. + while (len > 0 && + (isspace(buf[len - 1]) || isdigit(buf[len - 1]) || + buf[len - 1] == '#' || buf[len - 1] == '_')) { + --len; + } + + aName.Assign(buf, len); +} + +} + +#endif // XP_LINUX diff --git a/xpcom/base/LinuxUtils.h b/xpcom/base/LinuxUtils.h new file mode 100644 index 000000000..e82c15e08 --- /dev/null +++ b/xpcom/base/LinuxUtils.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 mozilla_LinuxUtils_h +#define mozilla_LinuxUtils_h + +#if defined(XP_LINUX) + +#include +#include "nsString.h" + +namespace mozilla { + +class LinuxUtils +{ +public: + // Obtain the name of a thread, omitting any numeric suffix added by a + // thread pool library (as in, e.g., "Binder_2" or "mozStorage #1"). + // The empty string is returned on error. + // + // Note: if this is ever needed on kernels older than 2.6.33 (early 2010), + // it will have to parse /proc//status instead, because + // /proc//comm didn't exist before then. + static void GetThreadName(pid_t aTid, nsACString& aName); +}; + +} + +#endif // XP_LINUX + +#endif diff --git a/xpcom/base/LogModulePrefWatcher.cpp b/xpcom/base/LogModulePrefWatcher.cpp new file mode 100644 index 000000000..bd04eda98 --- /dev/null +++ b/xpcom/base/LogModulePrefWatcher.cpp @@ -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/. */ + +#include "LogModulePrefWatcher.h" + +#include "mozilla/Logging.h" +#include "mozilla/Preferences.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"; + +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. + */ +void ResetExistingPrefs() +{ + uint32_t count; + char** names; + nsresult rv = Preferences::GetRootBranch()-> + GetChildList(kLoggingPrefPrefix, &count, &names); + if (NS_SUCCEEDED(rv) && count) { + for (size_t i = 0; i < count; i++) { + // Clearing the pref will cause it to reload, thus resetting the log level + Preferences::ClearUser(names[i]); + } + NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(count, names); + } +} + +/** + * 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(), "%PID")) { + prefValue.Append("%PID"); + } + + 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); + } + 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); +} + +void +LoadExistingPrefs() +{ + nsIPrefBranch* root = Preferences::GetRootBranch(); + if (!root) { + return; + } + + uint32_t count; + char** names; + nsresult rv = root->GetChildList(kLoggingPrefPrefix, &count, &names); + if (NS_SUCCEEDED(rv) && count) { + for (size_t i = 0; i < count; i++) { + LoadPrefValue(names[i]); + } + NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(count, names); + } +} + +LogModulePrefWatcher::LogModulePrefWatcher() +{ +} + +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 000000000..657e54f01 --- /dev/null +++ b/xpcom/base/LogModulePrefWatcher.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 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() + { + } +}; +} // namespace mozilla + +#endif // LogModulePrefWatcher_h diff --git a/xpcom/base/Logging.cpp b/xpcom/base/Logging.cpp new file mode 100644 index 000000000..e87df91e4 --- /dev/null +++ b/xpcom/base/Logging.cpp @@ -0,0 +1,568 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. 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 "mozilla/ClearOnShutdown.h" +#include "mozilla/FileUtils.h" +#include "mozilla/Mutex.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/Sprintf.h" +#include "mozilla/Atomics.h" +#include "mozilla/UniquePtrExtensions.h" +#include "nsClassHashtable.h" +#include "nsDebug.h" +#include "NSPRLogModulesParser.h" + +#include "prenv.h" +#include "prprf.h" +#ifdef XP_WIN +#include +#else +#include +#include +#endif + +// NB: Initial amount determined by auditing the codebase for the total amount +// of unique module names 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. +const uint32_t kRotateFilesNumber = 4; + +namespace mozilla { + +namespace detail { + +void log_print(const PRLogModuleInfo* aModule, + LogLevel aLevel, + const char* aFmt, ...) +{ + va_list ap; + va_start(ap, aFmt); + char* buff = PR_vsmprintf(aFmt, ap); + PR_LogPrint("%s", buff); + PR_smprintf_free(buff); + va_end(ap); +} + +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); +} + +} // 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); +} + +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; +}; + +const char* +ExpandPIDMarker(const char* aFilename, char (&buffer)[2048]) +{ + MOZ_ASSERT(aFilename); + static const char kPIDToken[] = "%PID"; + const char* pidTokenPtr = strstr(aFilename, kPIDToken); + if (pidTokenPtr && + SprintfLiteral(buffer, "%.*s%s%d%s", + static_cast(pidTokenPtr - aFilename), aFilename, + XRE_IsParentProcess() ? "-main." : "-child.", + base::GetCurrentProcId(), + pidTokenPtr + strlen(kPIDToken)) > 0) + { + return buffer; + } + + return aFilename; +} + +} // 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); + } +} + +class LogModuleManager +{ +public: + LogModuleManager() + : mModulesLock("logmodules") + , mModules(kInitialModuleCount) + , mPrintEntryCount(0) + , mOutFile(nullptr) + , mToReleaseFile(nullptr) + , mOutFileNum(0) + , mOutFilePath(strdup("")) + , mMainThread(PR_GetCurrentThread()) + , mSetFromEnv(false) + , mAddTimestamp(false) + , mIsSync(false) + , mRotate(0) + { + } + + ~LogModuleManager() + { + detail::LogFile* logFile = mOutFile.exchange(nullptr); + delete logFile; + } + + /** + * Loads config from env vars if present. + */ + void Init() + { + bool shouldAppend = false; + bool addTimestamp = false; + bool isSync = false; + int32_t rotate = 0; + 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."); + } + } + + NSPRLogModulesParser(modules, + [&shouldAppend, &addTimestamp, &isSync, &rotate] + (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, "rotate") == 0) { + rotate = (aValue << 20) / kRotateFilesNumber; + } else { + LogModule::Get(aName)->SetLevel(aLevel); + } + }); + + // Rotate implies timestamp to make the files readable + mAddTimestamp = addTimestamp || rotate > 0; + mIsSync = isSync; + mRotate = rotate; + + if (rotate > 0 && shouldAppend) { + NS_WARNING("MOZ_LOG: when you rotate the log, you cannot use append!"); + } + + 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::ExpandPIDMarker(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); + mSetFromEnv = true; + } + } + + 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::ExpandPIDMarker(filename, buf); + + // Can't use rotate 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 SetAddTimestamp(bool aAddTimestamp) + { + mAddTimestamp = aAddTimestamp; + } + + detail::LogFile* OpenFile(bool aShouldAppend, uint32_t aFileNum) + { + FILE* file; + + if (mRotate > 0) { + char buf[2048]; + SprintfLiteral(buf, "%s.%d", mOutFilePath.get(), aFileNum); + + // rotate doesn't support append. + file = fopen(buf, "w"); + } 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); + LogModule* module = nullptr; + if (!mModules.Get(aName, &module)) { + module = new LogModule(aName, LogLevel::Disabled); + mModules.Put(aName, module); + } + + return module; + } + + void Print(const char* aName, LogLevel aLevel, const char* aFmt, va_list aArgs) + { + const size_t kBuffSize = 1024; + char buff[kBuffSize]; + + char* buffToWrite = buff; + + // For backwards compat we need to use the NSPR format string versions + // of sprintf and friends and then hand off to printf. + va_list argsCopy; + va_copy(argsCopy, aArgs); + size_t charsWritten = PR_vsnprintf(buff, kBuffSize, aFmt, argsCopy); + va_end(argsCopy); + + if (charsWritten == kBuffSize - 1) { + // We may have maxed out, allocate a buffer instead. + buffToWrite = PR_vsmprintf(aFmt, aArgs); + charsWritten = strlen(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) { + fprintf_stderr(out, + "[%s]: %s/%s %s%s", + currentThreadName, ToLogStr(aLevel), + aName, buffToWrite, newline); + } else { + PRExplodedTime now; + PR_ExplodeTime(PR_Now(), PR_GMTParameters, &now); + fprintf_stderr( + out, + "%04d-%02d-%02d %02d:%02d:%02d.%06d UTC - [%s]: %s/%s %s%s", + now.tm_year, now.tm_month + 1, now.tm_mday, + now.tm_hour, now.tm_min, now.tm_sec, now.tm_usec, + currentThreadName, ToLogStr(aLevel), + aName, buffToWrite, newline); + } + + if (mIsSync) { + fflush(out); + } + + if (buffToWrite != buff) { + PR_smprintf_free(buffToWrite); + } + + 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; + + // 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 mIsSync; + int32_t mRotate; +}; + +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::Init() +{ + // NB: This method is not threadsafe; it is expected to be called very early + // in startup prior to any other threads being run. + 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. + sLogModuleManager = new LogModuleManager(); + sLogModuleManager->Init(); +} + +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); +} + +} // namespace mozilla diff --git a/xpcom/base/Logging.h b/xpcom/base/Logging.h new file mode 100644 index 000000000..040fb9c49 --- /dev/null +++ b/xpcom/base/Logging.h @@ -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/. */ + +#ifndef mozilla_logging_h +#define mozilla_logging_h + +#include +#include + +#include "prlog.h" + +#include "mozilla/Assertions.h" +#include "mozilla/Atomics.h" +#include "mozilla/Likely.h" +#include "mozilla/MacroForEach.h" + +// This file is a placeholder for a replacement to the NSPR logging framework +// that is defined in prlog.h. Currently it is just a pass through, but as +// work progresses more functionality will be swapped out in favor of +// mozilla logging implementations. + +// 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 + +namespace mozilla { + +// 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); + + static void Init(); + + /** + * 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); + + /** + * 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) { mLevel = level; } + + /** + * Print a log message for this module. + */ + void Printv(LogLevel aLevel, const char* aFmt, va_list aArgs) const; + + /** + * 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) + { + } + + 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 PRLogModuleInfo* module, LogLevel level) { + MOZ_ASSERT(level != LogLevel::Disabled); + return module && module->level >= static_cast(level); +} + +/** + * A rather inefficient wrapper for PR_LogPrint that always allocates. + * PR_LogModuleInfo is deprecated so it's not worth the effort to do + * any better. + */ +void log_print(const PRLogModuleInfo* aModule, + LogLevel aLevel, + const char* aFmt, ...); + +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, ...); +} // 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) 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... +// } +// +// This also has the nice property that no special definition of MOZ_LOG is +// required when logging is disabled. +#define MOZ_LOG_TEST(_module,_level) false +#endif + +#define MOZ_LOG(_module,_level,_args) \ + PR_BEGIN_MACRO \ + if (MOZ_LOG_TEST(_module,_level)) { \ + mozilla::detail::log_print(_module, _level, MOZ_LOG_EXPAND_ARGS _args); \ + } \ + PR_END_MACRO + +#undef PR_LOG +#undef PR_LOG_TEST + +// 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 000000000..9716ae3f2 --- /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 000000000..19d0d8900 --- /dev/null +++ b/xpcom/base/MacHelpers.mm @@ -0,0 +1,41 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. 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 "nsObjCExceptions.h" + +#import + +namespace mozilla { + +nsresult +GetSelectedCityInfo(nsAString& aCountryCode) +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; + + // 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; + } + + const char* countryCodeUTF8 = [(NSString*)countryCode UTF8String]; + + if (!countryCodeUTF8) { + return NS_ERROR_FAILURE; + } + + AppendUTF8toUTF16(countryCodeUTF8, aCountryCode); + return NS_OK; + + NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; +} + +} // namespace Mozilla + diff --git a/xpcom/base/NSPRLogModulesParser.cpp b/xpcom/base/NSPRLogModulesParser.cpp new file mode 100644 index 000000000..21090925c --- /dev/null +++ b/xpcom/base/NSPRLogModulesParser.cpp @@ -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/. */ + +#include "NSPRLogModulesParser.h" + +#include "mozilla/Tokenizer.h" + +const char kDelimiters[] = ", "; +const char kAdditionalWordChars[] = "_-"; + +namespace mozilla { + +void +NSPRLogModulesParser(const char* aLogModules, + function aCallback) +{ + if (!aLogModules) { + return; + } + + Tokenizer parser(aLogModules, kDelimiters, kAdditionalWordChars); + nsAutoCString moduleName; + + // Format: LOG_MODULES="Foo:2,Bar, Baz:5" + while (parser.ReadWord(moduleName)) { + // Next should be :, default to Error if not provided. + LogLevel logLevel = LogLevel::Error; + int32_t levelValue = 0; + if (parser.CheckChar(':')) { + // Check if a negative value is provided. + int32_t multiplier = 1; + if (parser.CheckChar([](const char aChar) { return aChar == '-'; })) { + multiplier = -1; + } + + // 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.ReadInteger(&levelValue)) { + logLevel = ToLogLevel(levelValue * multiplier); + } + } + + 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 000000000..38aab14a3 --- /dev/null +++ b/xpcom/base/NSPRLogModulesParser.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/. */ + +#include "mozilla/Logging.h" +#include "mozilla/Function.h" + +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, + function aCallback); + +} // namespace mozilla diff --git a/xpcom/base/OwningNonNull.h b/xpcom/base/OwningNonNull.h new file mode 100644 index 000000000..b72a250c4 --- /dev/null +++ b/xpcom/base/OwningNonNull.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 class for non-null strong pointers to reference-counted objects. */ + +#ifndef mozilla_OwningNonNull_h +#define mozilla_OwningNonNull_h + +#include "nsAutoPtr.h" +#include "nsCycleCollectionNoteChild.h" + +namespace mozilla { + +template +class OwningNonNull +{ +public: + OwningNonNull() {} + + MOZ_IMPLICIT OwningNonNull(T& aValue) + { + init(&aValue); + } + + template + MOZ_IMPLICIT OwningNonNull(already_AddRefed&& aValue) + { + init(aValue); + } + + template + MOZ_IMPLICIT OwningNonNull(const OwningNonNull& aValue) + { + init(aValue); + } + + // This is no worse than get() in terms of const handling. + operator T&() const + { + MOZ_ASSERT(mInited); + MOZ_ASSERT(mPtr, "OwningNonNull was set to null"); + return *mPtr; + } + + operator T*() const + { + MOZ_ASSERT(mInited); + MOZ_ASSERT(mPtr, "OwningNonNull was set to null"); + return mPtr; + } + + // Conversion to bool is always true, so delete to catch errors + explicit operator bool() const = delete; + + T* + operator->() const + { + MOZ_ASSERT(mInited); + MOZ_ASSERT(mPtr, "OwningNonNull was set to null"); + return mPtr; + } + + 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=(const OwningNonNull& aValue) + { + init(aValue); + return *this; + } + + // Don't allow assigning nullptr, it makes no sense + void operator=(decltype(nullptr)) = delete; + + already_AddRefed forget() + { +#ifdef DEBUG + mInited = false; +#endif + return mPtr.forget(); + } + + template + void + forget(U** aOther) + { +#ifdef DEBUG + mInited = false; +#endif + mPtr.forget(aOther); + } + + // Make us work with smart pointer helpers that expect a get(). + T* get() const + { + MOZ_ASSERT(mInited); + MOZ_ASSERT(mPtr); + 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; + } + +protected: + template + void init(U&& aValue) + { + mPtr = aValue; + MOZ_ASSERT(mPtr); +#ifdef DEBUG + mInited = true; +#endif + } + + RefPtr mPtr; +#ifdef DEBUG + bool mInited = false; +#endif +}; + +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/StaticMutex.h b/xpcom/base/StaticMutex.h new file mode 100644 index 000000000..731e69405 --- /dev/null +++ b/xpcom/base/StaticMutex.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 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 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() + { + Mutex()->Lock(); + } + + void Unlock() + { + Mutex()->Unlock(); + } + + void AssertCurrentThreadOwns() + { +#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) CPP_THROW_NEW; + static void operator delete(void*); +}; + +typedef BaseAutoLock StaticMutexAutoLock; +typedef BaseAutoUnlock StaticMutexAutoUnlock; + +} // namespace mozilla + +#endif diff --git a/xpcom/base/StaticPtr.h b/xpcom/base/StaticPtr.h new file mode 100644 index 000000000..f2c820a93 --- /dev/null +++ b/xpcom/base/StaticPtr.h @@ -0,0 +1,270 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 nsAutoPtr and nsRefPtr, 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 (in release builds, anyway). + * + * In order to accomplish this, Static{Auto,Ref}Ptr must have a trivial + * constructor and destructor. As a consequence, it cannot initialize its raw + * pointer to 0 on construction, and it cannot delete/release its raw pointer + * upon destruction. + * + * Since the compiler guarantees that all global variables are initialized to + * 0, these trivial constructors are safe. 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: + // In debug builds, check that mRawPtr 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 + StaticAutoPtr() + { + MOZ_ASSERT(!mRawPtr); + } +#endif + + 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(); } + +private: + // 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 + StaticAutoPtr(StaticAutoPtr& aOther); +#endif + + void Assign(T* aNewPtr) + { + MOZ_ASSERT(!aNewPtr || mRawPtr != aNewPtr); + T* oldPtr = mRawPtr; + mRawPtr = aNewPtr; + delete oldPtr; + } + + T* mRawPtr; +}; + +template +class MOZ_ONLY_USED_TO_AVOID_STATIC_CONSTRUCTORS StaticRefPtr +{ +public: + // In debug builds, check that mRawPtr 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 + StaticRefPtr() + { + MOZ_ASSERT(!mRawPtr); + } +#endif + + 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; + } + + 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) { + aNewPtr->AddRef(); + } + AssignAssumingAddRef(aNewPtr); + } + + void AssignAssumingAddRef(T* aNewPtr) + { + T* oldPtr = mRawPtr; + mRawPtr = aNewPtr; + if (oldPtr) { + oldPtr->Release(); + } + } + + T* MOZ_OWNING_REF mRawPtr; +}; + +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()); +} + +#endif diff --git a/xpcom/base/SystemMemoryReporter.cpp b/xpcom/base/SystemMemoryReporter.cpp new file mode 100644 index 000000000..105b9c8cf --- /dev/null +++ b/xpcom/base/SystemMemoryReporter.cpp @@ -0,0 +1,989 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. 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/SystemMemoryReporter.h" + +#include "mozilla/Attributes.h" +#include "mozilla/LinuxUtils.h" +#include "mozilla/PodOperations.h" +#include "mozilla/Preferences.h" +#include "mozilla/TaggedAnonymousMemory.h" +#include "mozilla/Unused.h" + +#include "nsDataHashtable.h" +#include "nsIMemoryReporter.h" +#include "nsPrintfCString.h" +#include "nsString.h" +#include "nsTHashtable.h" +#include "nsHashKeys.h" + +#include +#include +#include +#include +#include +#include +#include + +// This file implements a Linux-specific, system-wide memory reporter. It +// gathers all the useful memory measurements obtainable from the OS in a +// single place, giving a high-level view of memory consumption for the entire +// machine/device. +// +// Other memory reporters measure part of a single process's memory consumption. +// This reporter is different in that it measures memory consumption of many +// processes, and they end up in a single reports tree. This is a slight abuse +// of the memory reporting infrastructure, and therefore the results are given +// their own "process" called "System", which means they show up in about:memory +// in their own section, distinct from the per-process sections. + +namespace mozilla { +namespace SystemMemoryReporter { + +#if !defined(XP_LINUX) +#error "This won't work if we're not on Linux." +#endif + +/** + * RAII helper that will close an open DIR handle. + */ +struct MOZ_STACK_CLASS AutoDir +{ + explicit AutoDir(DIR* aDir) : mDir(aDir) {} + ~AutoDir() { if (mDir) closedir(mDir); }; + DIR* mDir; +}; + +/** + * RAII helper that will close an open FILE handle. + */ +struct MOZ_STACK_CLASS AutoFile +{ + explicit AutoFile(FILE* aFile) : mFile(aFile) {} + ~AutoFile() { if (mFile) fclose(mFile); } + FILE* mFile; +}; + +static bool +EndsWithLiteral(const nsCString& aHaystack, const char* aNeedle) +{ + int32_t idx = aHaystack.RFind(aNeedle); + return idx != -1 && idx + strlen(aNeedle) == aHaystack.Length(); +} + +static void +GetDirname(const nsCString& aPath, nsACString& aOut) +{ + int32_t idx = aPath.RFind("/"); + if (idx == -1) { + aOut.Truncate(); + } else { + aOut.Assign(Substring(aPath, 0, idx)); + } +} + +static void +GetBasename(const nsCString& aPath, nsACString& aOut) +{ + nsCString out; + int32_t idx = aPath.RFind("/"); + if (idx == -1) { + out.Assign(aPath); + } else { + out.Assign(Substring(aPath, idx + 1)); + } + + // On Android, some entries in /dev/ashmem end with "(deleted)" (e.g. + // "/dev/ashmem/libxul.so(deleted)"). We don't care about this modifier, so + // cut it off when getting the entry's basename. + if (EndsWithLiteral(out, "(deleted)")) { + out.Assign(Substring(out, 0, out.RFind("(deleted)"))); + } + out.StripChars(" "); + + aOut.Assign(out); +} + +static bool +IsNumeric(const char* aStr) +{ + MOZ_ASSERT(*aStr); // shouldn't see empty strings + while (*aStr) { + if (!isdigit(*aStr)) { + return false; + } + ++aStr; + } + return true; +} + +static bool +IsAnonymous(const nsACString& aName) +{ + // Recent kernels have multiple [stack:nnnn] entries, where |nnnn| is a + // thread ID. However, the entire virtual memory area containing a thread's + // stack pointer is considered the stack for that thread, even if it was + // merged with an adjacent area containing non-stack data. So we treat them + // as regular anonymous memory. However, see below about tagged anonymous + // memory. + return aName.IsEmpty() || + StringBeginsWith(aName, NS_LITERAL_CSTRING("[stack:")); +} + +class SystemReporter final : public nsIMemoryReporter +{ + ~SystemReporter() {} + +public: + NS_DECL_THREADSAFE_ISUPPORTS + +#define REPORT(_path, _units, _amount, _desc) \ + do { \ + size_t __amount = _amount; /* evaluate _amount only once */ \ + if (__amount > 0) { \ + aHandleReport->Callback(NS_LITERAL_CSTRING("System"), _path, \ + KIND_OTHER, _units, __amount, _desc, aData); \ + } \ + } while (0) + + NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, bool aAnonymize) override + { + // There is lots of privacy-sensitive data in /proc. Just skip this + // reporter entirely when anonymization is required. + if (aAnonymize) { + return NS_OK; + } + + if (!Preferences::GetBool("memory.system_memory_reporter")) { + return NS_OK; + } + + // Read relevant fields from /proc/meminfo. + int64_t memTotal = 0, memFree = 0; + nsresult rv1 = ReadMemInfo(&memTotal, &memFree); + + // Collect per-process reports from /proc//smaps. + int64_t totalPss = 0; + nsresult rv2 = CollectProcessReports(aHandleReport, aData, &totalPss); + + // Report the non-process numbers. + if (NS_SUCCEEDED(rv1) && NS_SUCCEEDED(rv2)) { + int64_t other = memTotal - memFree - totalPss; + REPORT(NS_LITERAL_CSTRING("mem/other"), UNITS_BYTES, other, + NS_LITERAL_CSTRING( +"Memory which is neither owned by any user-space process nor free. Note that " +"this includes memory holding cached files from the disk which can be " +"reclaimed by the OS at any time.")); + + REPORT(NS_LITERAL_CSTRING("mem/free"), UNITS_BYTES, memFree, + NS_LITERAL_CSTRING( +"Memory which is free and not being used for any purpose.")); + } + + // Report reserved memory not included in memTotal. + CollectPmemReports(aHandleReport, aData); + + // Report zram usage statistics. + CollectZramReports(aHandleReport, aData); + + // Report kgsl graphics memory usage. + CollectKgslReports(aHandleReport, aData); + + // Report ION memory usage. + CollectIonReports(aHandleReport, aData); + + return NS_OK; + } + +private: + // These are the cross-cutting measurements across all processes. + class ProcessSizes + { + public: + void Add(const nsACString& aKey, size_t aSize) + { + mTagged.Put(aKey, mTagged.Get(aKey) + aSize); + } + + void Report(nsIHandleReportCallback* aHandleReport, nsISupports* aData) + { + for (auto iter = mTagged.Iter(); !iter.Done(); iter.Next()) { + nsCStringHashKey::KeyType key = iter.Key(); + size_t amount = iter.UserData(); + + nsAutoCString path("processes/"); + path.Append(key); + + nsAutoCString desc("This is the sum of all processes' '"); + desc.Append(key); + desc.AppendLiteral("' numbers."); + + REPORT(path, UNITS_BYTES, amount, desc); + } + } + + private: + nsDataHashtable mTagged; + }; + + nsresult ReadMemInfo(int64_t* aMemTotal, int64_t* aMemFree) + { + FILE* f = fopen("/proc/meminfo", "r"); + if (!f) { + return NS_ERROR_FAILURE; + } + + int n1 = fscanf(f, "MemTotal: %" SCNd64 " kB\n", aMemTotal); + int n2 = fscanf(f, "MemFree: %" SCNd64 " kB\n", aMemFree); + + fclose(f); + + if (n1 != 1 || n2 != 1) { + return NS_ERROR_FAILURE; + } + + // Convert from KB to B. + *aMemTotal *= 1024; + *aMemFree *= 1024; + + return NS_OK; + } + + nsresult CollectProcessReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, + int64_t* aTotalPss) + { + *aTotalPss = 0; + ProcessSizes processSizes; + + DIR* d = opendir("/proc"); + if (NS_WARN_IF(!d)) { + return NS_ERROR_FAILURE; + } + struct dirent* ent; + while ((ent = readdir(d))) { + struct stat statbuf; + const char* pidStr = ent->d_name; + // Don't check the return value of stat() -- it can return -1 for these + // directories even when it has succeeded, apparently. + stat(pidStr, &statbuf); + if (S_ISDIR(statbuf.st_mode) && IsNumeric(pidStr)) { + nsCString processName("process("); + + // Get the command name from cmdline. If that fails, the pid is still + // shown. + nsPrintfCString cmdlinePath("/proc/%s/cmdline", pidStr); + FILE* f = fopen(cmdlinePath.get(), "r"); + if (f) { + static const size_t len = 256; + char buf[len]; + if (fgets(buf, len, f)) { + processName.Append(buf); + // A hack: replace forward slashes with '\\' so they aren't treated + // as path separators. Consumers of this reporter (such as + // about:memory) have to undo this change. + processName.ReplaceChar('/', '\\'); + processName.AppendLiteral(", "); + } + fclose(f); + } + processName.AppendLiteral("pid="); + processName.Append(pidStr); + processName.Append(')'); + + // Read the PSS values from the smaps file. + nsPrintfCString smapsPath("/proc/%s/smaps", pidStr); + f = fopen(smapsPath.get(), "r"); + if (!f) { + // Processes can terminate between the readdir() call above and now, + // so just skip if we can't open the file. + continue; + } + ParseMappings(f, processName, aHandleReport, aData, &processSizes, + aTotalPss); + fclose(f); + + // Report the open file descriptors for this process. + nsPrintfCString procFdPath("/proc/%s/fd", pidStr); + CollectOpenFileReports(aHandleReport, aData, procFdPath, processName); + } + } + closedir(d); + + // Report the "processes/" tree. + processSizes.Report(aHandleReport, aData); + + return NS_OK; + } + + void ParseMappings(FILE* aFile, + const nsACString& aProcessName, + nsIHandleReportCallback* aHandleReport, + nsISupports* aData, + ProcessSizes* aProcessSizes, + int64_t* aTotalPss) + { + // The first line of an entry in /proc//smaps looks just like an entry + // in /proc//maps: + // + // address perms offset dev inode pathname + // 02366000-025d8000 rw-p 00000000 00:00 0 [heap] + // + // Each of the following lines contains a key and a value, separated + // by ": ", where the key does not contain either of those characters. + // Assuming more than this about the structure of those lines has + // failed to be future-proof in the past, so we avoid doing so. + // + // This makes it difficult to detect the start of a new entry + // until it's been removed from the stdio buffer, so we just loop + // over all lines in the file in this routine. + + const int argCount = 8; + + unsigned long long addrStart, addrEnd; + char perms[5]; + unsigned long long offset; + // The 2.6 and 3.0 kernels allocate 12 bits for the major device number and + // 20 bits for the minor device number. Future kernels might allocate more. + // 64 bits ought to be enough for anybody. + char devMajor[17]; + char devMinor[17]; + unsigned int inode; + char line[1025]; + + // This variable holds the path of the current entry, or is void + // if we're scanning for the start of a new entry. + nsAutoCString currentPath; + int pathOffset; + + currentPath.SetIsVoid(true); + while (fgets(line, sizeof(line), aFile)) { + if (currentPath.IsVoid()) { + int n = sscanf(line, + "%llx-%llx %4s %llx " + "%16[0-9a-fA-F]:%16[0-9a-fA-F] %u %n", + &addrStart, &addrEnd, perms, &offset, devMajor, + devMinor, &inode, &pathOffset); + + if (n >= argCount - 1) { + currentPath.Assign(line + pathOffset); + currentPath.StripChars("\n"); + } + continue; + } + + // Now that we have a name and other metadata, scan for the PSS. + size_t pss_kb; + int n = sscanf(line, "Pss: %zu", &pss_kb); + if (n < 1) { + continue; + } + + size_t pss = pss_kb * 1024; + if (pss > 0) { + nsAutoCString name, description, tag; + GetReporterNameAndDescription(currentPath.get(), perms, name, description, tag); + + nsAutoCString processMemPath("mem/processes/"); + processMemPath.Append(aProcessName); + processMemPath.Append('/'); + processMemPath.Append(name); + + REPORT(processMemPath, UNITS_BYTES, pss, description); + + // Increment the appropriate aProcessSizes values, and the total. + aProcessSizes->Add(tag, pss); + *aTotalPss += pss; + } + + // Now that we've seen the PSS, we're done with this entry. + currentPath.SetIsVoid(true); + } + } + + void GetReporterNameAndDescription(const char* aPath, + const char* aPerms, + nsACString& aName, + nsACString& aDesc, + nsACString& aTag) + { + aName.Truncate(); + aDesc.Truncate(); + aTag.Truncate(); + + // If aPath points to a file, we have its absolute path; it might + // also be a bracketed pseudo-name (see below). In either case + // there is also some whitespace to trim. + nsAutoCString absPath; + absPath.Append(aPath); + absPath.StripChars(" "); + + if (absPath.EqualsLiteral("[heap]")) { + aName.AppendLiteral("anonymous/brk-heap"); + aDesc.AppendLiteral( + "Memory in anonymous mappings within the boundaries defined by " + "brk() / sbrk(). This is likely to be just a portion of the " + "application's heap; the remainder lives in other anonymous mappings. " + "This corresponds to '[heap]' in /proc//smaps."); + aTag = aName; + } else if (absPath.EqualsLiteral("[stack]")) { + aName.AppendLiteral("stack/main-thread"); + aDesc.AppendPrintf( + "The stack size of the process's main thread. This corresponds to " + "'[stack]' in /proc//smaps."); + aTag = aName; + } else if (MozTaggedMemoryIsSupported() && + StringBeginsWith(absPath, NS_LITERAL_CSTRING("[stack:"))) { + // If tagged memory is supported, we can be reasonably sure that + // the virtual memory area containing the stack hasn't been + // merged with unrelated heap memory. (This prevents the + // "[stack:" entries from reaching the IsAnonymous case below.) + pid_t tid = atoi(absPath.get() + 7); + nsAutoCString threadName, escapedThreadName; + LinuxUtils::GetThreadName(tid, threadName); + if (threadName.IsEmpty()) { + threadName.AssignLiteral(""); + } + escapedThreadName.Assign(threadName); + escapedThreadName.StripChars("()"); + escapedThreadName.ReplaceChar('/', '\\'); + + aName.AppendLiteral("stack/non-main-thread"); + aName.AppendLiteral("/name("); + aName.Append(escapedThreadName); + aName.Append(')'); + aTag = aName; + aName.AppendPrintf("/thread(%d)", tid); + + aDesc.AppendPrintf("The stack size of a non-main thread named '%s' with " + "thread ID %d. This corresponds to '[stack:%d]' " + "in /proc/%d/smaps.", threadName.get(), tid, tid); + } else if (absPath.EqualsLiteral("[vdso]")) { + aName.AppendLiteral("vdso"); + aDesc.AppendLiteral( + "The virtual dynamically-linked shared object, also known as the " + "'vsyscall page'. This is a memory region mapped by the operating " + "system for the purpose of allowing processes to perform some " + "privileged actions without the overhead of a syscall."); + aTag = aName; + } else if (StringBeginsWith(absPath, NS_LITERAL_CSTRING("[anon:")) && + EndsWithLiteral(absPath, "]")) { + // It's tagged memory; see also "mfbt/TaggedAnonymousMemory.h". + nsAutoCString tag(Substring(absPath, 6, absPath.Length() - 7)); + + aName.AppendLiteral("anonymous/"); + aName.Append(tag); + aTag = aName; + aDesc.AppendLiteral("Memory in anonymous mappings tagged with '"); + aDesc.Append(tag); + aDesc.Append('\''); + } else if (!IsAnonymous(absPath)) { + // We now know it's an actual file. Truncate this to its + // basename, and put the absolute path in the description. + nsAutoCString basename, dirname; + GetBasename(absPath, basename); + GetDirname(absPath, dirname); + + // Hack: A file is a shared library if the basename contains ".so" and + // its dirname contains "/lib", or if the basename ends with ".so". + if (EndsWithLiteral(basename, ".so") || + (basename.Find(".so") != -1 && dirname.Find("/lib") != -1)) { + aName.AppendLiteral("shared-libraries/"); + aTag = aName; + + if (strncmp(aPerms, "r-x", 3) == 0) { + aTag.AppendLiteral("read-executable"); + } else if (strncmp(aPerms, "rw-", 3) == 0) { + aTag.AppendLiteral("read-write"); + } else if (strncmp(aPerms, "r--", 3) == 0) { + aTag.AppendLiteral("read-only"); + } else { + aTag.AppendLiteral("other"); + } + + } else { + aName.AppendLiteral("other-files"); + if (EndsWithLiteral(basename, ".xpi")) { + aName.AppendLiteral("/extensions"); + } else if (dirname.Find("/fontconfig") != -1) { + aName.AppendLiteral("/fontconfig"); + } else { + aName.AppendLiteral("/misc"); + } + aTag = aName; + aName.Append('/'); + } + + aName.Append(basename); + aDesc.Append(absPath); + } else { + if (MozTaggedMemoryIsSupported()) { + aName.AppendLiteral("anonymous/untagged"); + aDesc.AppendLiteral("Memory in untagged anonymous mappings."); + aTag = aName; + } else { + aName.AppendLiteral("anonymous/outside-brk"); + aDesc.AppendLiteral("Memory in anonymous mappings outside the " + "boundaries defined by brk() / sbrk()."); + aTag = aName; + } + } + + aName.AppendLiteral("/["); + aName.Append(aPerms); + aName.Append(']'); + + // Append the permissions. This is useful for non-verbose mode in + // about:memory when the filename is long and goes of the right side of the + // window. + aDesc.AppendLiteral(" ["); + aDesc.Append(aPerms); + aDesc.Append(']'); + } + + void CollectPmemReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData) + { + // The pmem subsystem allocates physically contiguous memory for + // interfacing with hardware. In order to ensure availability, + // this memory is reserved during boot, and allocations are made + // within these regions at runtime. + // + // There are typically several of these pools allocated at boot. + // The /sys/kernel/pmem_regions directory contains a subdirectory + // for each one. Within each subdirectory, the files we care + // about are "size" (the total amount of physical memory) and + // "mapped_regions" (a list of the current allocations within that + // area). + DIR* d = opendir("/sys/kernel/pmem_regions"); + if (!d) { + return; + } + + struct dirent* ent; + while ((ent = readdir(d))) { + const char* name = ent->d_name; + uint64_t size; + int scanned; + + // Skip "." and ".." (and any other dotfiles). + if (name[0] == '.') { + continue; + } + + // Read the total size. The file gives the size in decimal and + // hex, in the form "13631488(0xd00000)"; we parse the former. + nsPrintfCString sizePath("/sys/kernel/pmem_regions/%s/size", name); + FILE* sizeFile = fopen(sizePath.get(), "r"); + if (NS_WARN_IF(!sizeFile)) { + continue; + } + scanned = fscanf(sizeFile, "%" SCNu64, &size); + fclose(sizeFile); + if (NS_WARN_IF(scanned != 1)) { + continue; + } + + // Read mapped regions; format described below. + uint64_t freeSize = size; + nsPrintfCString regionsPath("/sys/kernel/pmem_regions/%s/mapped_regions", + name); + FILE* regionsFile = fopen(regionsPath.get(), "r"); + if (regionsFile) { + static const size_t bufLen = 4096; + char buf[bufLen]; + while (fgets(buf, bufLen, regionsFile)) { + int pid; + + // Skip header line. + if (strncmp(buf, "pid #", 5) == 0) { + continue; + } + // Line format: "pid N:" + zero or more "(Start,Len) ". + // N is decimal; Start and Len are in hex. + scanned = sscanf(buf, "pid %d", &pid); + if (NS_WARN_IF(scanned != 1)) { + continue; + } + for (const char* nextParen = strchr(buf, '('); + nextParen != nullptr; + nextParen = strchr(nextParen + 1, '(')) { + uint64_t mapStart, mapLen; + + scanned = sscanf(nextParen + 1, "%" SCNx64 ",%" SCNx64, + &mapStart, &mapLen); + if (NS_WARN_IF(scanned != 2)) { + break; + } + + nsPrintfCString path("mem/pmem/used/%s/segment(pid=%d, " + "offset=0x%" PRIx64 ")", name, pid, mapStart); + nsPrintfCString desc("Physical memory reserved for the \"%s\" pool " + "and allocated to a buffer.", name); + REPORT(path, UNITS_BYTES, mapLen, desc); + freeSize -= mapLen; + } + } + fclose(regionsFile); + } + + nsPrintfCString path("mem/pmem/free/%s", name); + nsPrintfCString desc("Physical memory reserved for the \"%s\" pool and " + "unavailable to the rest of the system, but not " + "currently allocated.", name); + REPORT(path, UNITS_BYTES, freeSize, desc); + } + closedir(d); + } + + void + CollectIonReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData) + { + // ION is a replacement for PMEM (and other similar allocators). + // + // More details from http://lwn.net/Articles/480055/ + // "Like its PMEM-like predecessors, ION manages one or more memory pools, + // some of which are set aside at boot time to combat fragmentation or to + // serve special hardware needs. GPUs, display controllers, and cameras + // are some of the hardware blocks that may have special memory + // requirements." + // + // The file format starts as follows: + // client pid size + // ---------------------------------------------------- + // adsprpc-smd 1 4096 + // fd900000.qcom,mdss_mdp 1 1658880 + // ---------------------------------------------------- + // orphaned allocations (info is from last known client): + // Homescreen 24100 294912 0 1 + // b2g 23987 1658880 0 1 + // mdss_fb0 401 1658880 0 1 + // b2g 23987 4096 0 1 + // Built-in Keyboa 24205 61440 0 1 + // ---------------------------------------------------- + // + // + // For our purposes we only care about the first portion of the file noted + // above which contains memory alloations (both sections). The term + // "orphaned" is misleading, it appears that every allocation not by the + // first process is considered orphaned on FxOS devices. + + // The first three fields of each entry interest us: + // 1) client - Essentially the process name. We limit client names to 63 + // characters, in theory they should never be greater than 15 + // due to thread name length limitations. + // 2) pid - The ID of the allocating process, read as a uint32_t. + // 3) size - The size of the allocation in bytes, read as as a uint64_t. + const char* const kFormatString = "%63s %" SCNu32 " %" SCNu64; + const int kNumFields = 3; + const size_t kStringSize = 64; + const char* const kIonIommuPath = "/sys/kernel/debug/ion/iommu"; + + FILE* iommu = fopen(kIonIommuPath, "r"); + if (!iommu) { + return; + } + + AutoFile iommuGuard(iommu); + + const size_t kBufferLen = 256; + char buffer[kBufferLen]; + char client[kStringSize]; + uint32_t pid; + uint64_t size; + + // Ignore the header line. + Unused << fgets(buffer, kBufferLen, iommu); + + // Ignore the separator line. + Unused << fgets(buffer, kBufferLen, iommu); + + const char* const kSep = "----"; + const size_t kSepLen = 4; + + // Read non-orphaned entries. + while (fgets(buffer, kBufferLen, iommu) && + strncmp(kSep, buffer, kSepLen) != 0) { + if (sscanf(buffer, kFormatString, client, &pid, &size) == kNumFields) { + nsPrintfCString entryPath("ion-memory/%s (pid=%d)", client, pid); + REPORT(entryPath, UNITS_BYTES, size, + NS_LITERAL_CSTRING("An ION kernel memory allocation.")); + } + } + + // Ignore the orphaned header. + Unused << fgets(buffer, kBufferLen, iommu); + + // Read orphaned entries. + while (fgets(buffer, kBufferLen, iommu) && + strncmp(kSep, buffer, kSepLen) != 0) { + if (sscanf(buffer, kFormatString, client, &pid, &size) == kNumFields) { + nsPrintfCString entryPath("ion-memory/%s (pid=%d)", client, pid); + REPORT(entryPath, UNITS_BYTES, size, + NS_LITERAL_CSTRING("An ION kernel memory allocation.")); + } + } + + // Ignore the rest of the file. + } + + uint64_t + ReadSizeFromFile(const char* aFilename) + { + FILE* sizeFile = fopen(aFilename, "r"); + if (NS_WARN_IF(!sizeFile)) { + return 0; + } + + uint64_t size = 0; + Unused << fscanf(sizeFile, "%" SCNu64, &size); + fclose(sizeFile); + + return size; + } + + void + CollectZramReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData) + { + // zram usage stats files can be found under: + // /sys/block/zram + // |--> disksize - Maximum amount of uncompressed data that can be + // stored on the disk (bytes) + // |--> orig_data_size - Uncompressed size of data in the disk (bytes) + // |--> compr_data_size - Compressed size of the data in the disk (bytes) + // |--> num_reads - Number of attempted reads to the disk (count) + // |--> num_writes - Number of attempted writes to the disk (count) + // + // Each file contains a single integer value in decimal form. + + DIR* d = opendir("/sys/block"); + if (!d) { + return; + } + + struct dirent* ent; + while ((ent = readdir(d))) { + const char* name = ent->d_name; + + // Skip non-zram entries. + if (strncmp("zram", name, 4) != 0) { + continue; + } + + // Report disk size statistics. + nsPrintfCString diskSizeFile("/sys/block/%s/disksize", name); + nsPrintfCString origSizeFile("/sys/block/%s/orig_data_size", name); + + uint64_t diskSize = ReadSizeFromFile(diskSizeFile.get()); + uint64_t origSize = ReadSizeFromFile(origSizeFile.get()); + uint64_t unusedSize = diskSize - origSize; + + nsPrintfCString diskUsedPath("zram-disksize/%s/used", name); + nsPrintfCString diskUsedDesc( + "The uncompressed size of data stored in \"%s.\" " + "This excludes zero-filled pages since " + "no memory is allocated for them.", name); + REPORT(diskUsedPath, UNITS_BYTES, origSize, diskUsedDesc); + + nsPrintfCString diskUnusedPath("zram-disksize/%s/unused", name); + nsPrintfCString diskUnusedDesc( + "The amount of uncompressed data that can still be " + "be stored in \"%s\"", name); + REPORT(diskUnusedPath, UNITS_BYTES, unusedSize, diskUnusedDesc); + + // Report disk accesses. + nsPrintfCString readsFile("/sys/block/%s/num_reads", name); + nsPrintfCString writesFile("/sys/block/%s/num_writes", name); + + uint64_t reads = ReadSizeFromFile(readsFile.get()); + uint64_t writes = ReadSizeFromFile(writesFile.get()); + + nsPrintfCString readsDesc( + "The number of reads (failed or successful) done on " + "\"%s\"", name); + nsPrintfCString readsPath("zram-accesses/%s/reads", name); + REPORT(readsPath, UNITS_COUNT_CUMULATIVE, reads, readsDesc); + + nsPrintfCString writesDesc( + "The number of writes (failed or successful) done " + "on \"%s\"", name); + nsPrintfCString writesPath("zram-accesses/%s/writes", name); + REPORT(writesPath, UNITS_COUNT_CUMULATIVE, writes, writesDesc); + + // Report compressed data size. + nsPrintfCString comprSizeFile("/sys/block/%s/compr_data_size", name); + uint64_t comprSize = ReadSizeFromFile(comprSizeFile.get()); + + nsPrintfCString comprSizeDesc( + "The compressed size of data stored in \"%s\"", + name); + nsPrintfCString comprSizePath("zram-compr-data-size/%s", name); + REPORT(comprSizePath, UNITS_BYTES, comprSize, comprSizeDesc); + } + + closedir(d); + } + + void + CollectOpenFileReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, + const nsACString& aProcPath, + const nsACString& aProcessName) + { + // All file descriptors opened by a process are listed under + // /proc//fd/. Each entry is a symlink that points to the + // path that was opened. This can be an actual file, a socket, a pipe, an + // anon_inode, or possibly an uncategorized device. + const char kFilePrefix[] = "/"; + const char kSocketPrefix[] = "socket:"; + const char kPipePrefix[] = "pipe:"; + const char kAnonInodePrefix[] = "anon_inode:"; + + const nsCString procPath(aProcPath); + DIR* d = opendir(procPath.get()); + if (!d) { + return; + } + + char linkPath[PATH_MAX + 1]; + struct dirent* ent; + while ((ent = readdir(d))) { + const char* fd = ent->d_name; + + // Skip "." and ".." (and any other dotfiles). + if (fd[0] == '.') { + continue; + } + + nsPrintfCString fullPath("%s/%s", procPath.get(), fd); + ssize_t linkPathSize = readlink(fullPath.get(), linkPath, PATH_MAX); + if (linkPathSize > 0) { + linkPath[linkPathSize] = '\0'; + +#define CHECK_PREFIX(prefix) \ + (strncmp(linkPath, prefix, sizeof(prefix) - 1) == 0) + + const char* category = nullptr; + const char* descriptionPrefix = nullptr; + + if (CHECK_PREFIX(kFilePrefix)) { + category = "files"; // No trailing slash, the file path will have one + descriptionPrefix = "An open"; + } else if (CHECK_PREFIX(kSocketPrefix)) { + category = "sockets/"; + descriptionPrefix = "A socket"; + } else if (CHECK_PREFIX(kPipePrefix)) { + category = "pipes/"; + descriptionPrefix = "A pipe"; + } else if (CHECK_PREFIX(kAnonInodePrefix)) { + category = "anon_inodes/"; + descriptionPrefix = "An anon_inode"; + } else { + category = ""; + descriptionPrefix = "An uncategorized"; + } + +#undef CHECK_PREFIX + + const nsCString processName(aProcessName); + nsPrintfCString entryPath("open-fds/%s/%s%s/%s", + processName.get(), category, linkPath, fd); + nsPrintfCString entryDescription("%s file descriptor opened by the process", + descriptionPrefix); + REPORT(entryPath, UNITS_COUNT, 1, entryDescription); + } + } + + closedir(d); + } + + void + CollectKgslReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData) + { + // Each process that uses kgsl memory will have an entry under + // /sys/kernel/debug/kgsl/proc//mem. This file format includes a + // header and then entries with types as follows: + // gpuaddr useraddr size id flags type usage sglen + // hexaddr hexaddr int int str str str int + // We care primarily about the usage and size. + + // For simplicity numbers will be uint64_t, strings 63 chars max. + const char* const kScanFormat = + "%" SCNx64 " %" SCNx64 " %" SCNu64 " %" SCNu64 + " %63s %63s %63s %" SCNu64; + const int kNumFields = 8; + const size_t kStringSize = 64; + + DIR* d = opendir("/sys/kernel/debug/kgsl/proc/"); + if (!d) { + return; + } + + AutoDir dirGuard(d); + + struct dirent* ent; + while ((ent = readdir(d))) { + const char* pid = ent->d_name; + + // Skip "." and ".." (and any other dotfiles). + if (pid[0] == '.') { + continue; + } + + nsPrintfCString memPath("/sys/kernel/debug/kgsl/proc/%s/mem", pid); + FILE* memFile = fopen(memPath.get(), "r"); + if (NS_WARN_IF(!memFile)) { + continue; + } + + AutoFile fileGuard(memFile); + + // Attempt to map the pid to a more useful name. + nsAutoCString procName; + LinuxUtils::GetThreadName(atoi(pid), procName); + + if (procName.IsEmpty()) { + procName.Append("pid="); + procName.Append(pid); + } else { + procName.Append(" (pid="); + procName.Append(pid); + procName.Append(")"); + } + + uint64_t gpuaddr, useraddr, size, id, sglen; + char flags[kStringSize]; + char type[kStringSize]; + char usage[kStringSize]; + + // Bypass the header line. + char buff[1024]; + Unused << fgets(buff, 1024, memFile); + + while (fscanf(memFile, kScanFormat, &gpuaddr, &useraddr, &size, &id, + flags, type, usage, &sglen) == kNumFields) { + nsPrintfCString entryPath("kgsl-memory/%s/%s", procName.get(), usage); + REPORT(entryPath, UNITS_BYTES, size, + NS_LITERAL_CSTRING("A kgsl graphics memory allocation.")); + } + } + } +}; + +NS_IMPL_ISUPPORTS(SystemReporter, nsIMemoryReporter) + +void +Init() +{ + RegisterStrongMemoryReporter(new SystemReporter()); +} + +} // namespace SystemMemoryReporter +} // namespace mozilla diff --git a/xpcom/base/SystemMemoryReporter.h b/xpcom/base/SystemMemoryReporter.h new file mode 100644 index 000000000..e52487613 --- /dev/null +++ b/xpcom/base/SystemMemoryReporter.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_SystemMemoryReporter_h_ +#define mozilla_SystemMemoryReporter_h_ + +namespace mozilla { +namespace SystemMemoryReporter { + +// This only works on Linux, but to make callers' lives easier, we stub out +// empty functions on other platforms. + +#if defined(XP_LINUX) +void +Init(); +#else +void +Init() +{ +} +#endif + +} // namespace SystemMemoryReporter +} // namespace mozilla + +#endif diff --git a/xpcom/base/moz.build b/xpcom/base/moz.build new file mode 100644 index 000000000..d6a336b40 --- /dev/null +++ b/xpcom/base/moz.build @@ -0,0 +1,159 @@ +# -*- 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 += [ + 'nsIConsoleListener.idl', + 'nsIConsoleMessage.idl', + 'nsIConsoleService.idl', + 'nsICycleCollectorListener.idl', + 'nsIDebug2.idl', + 'nsIErrorService.idl', + 'nsIException.idl', + 'nsIGZFileWriter.idl', + 'nsIInterfaceRequestor.idl', + 'nsIMemory.idl', + 'nsIMemoryInfoDumper.idl', + 'nsIMemoryReporter.idl', + 'nsIMessageLoop.idl', + 'nsIMutable.idl', + 'nsIProgrammingLanguage.idl', + 'nsISecurityConsoleMessage.idl', + 'nsIStatusReporter.idl', + 'nsISupports.idl', + 'nsIUUIDGenerator.idl', + 'nsIVersionComparator.idl', + 'nsIWeakReference.idl', + 'nsrootidl.idl', +] + +if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa': + XPIDL_SOURCES += [ + 'nsIMacUtils.idl', + ] + EXPORTS.mozilla += [ + 'MacHelpers.h', + ] + UNIFIED_SOURCES += [ + 'MacHelpers.mm', + ] + +XPIDL_MODULE = 'xpcom_base' + +EXPORTS += [ + 'CodeAddressService.h', + 'ErrorList.h', + 'nsAgg.h', + 'nsAlgorithm.h', + 'nsAutoPtr.h', + 'nsAutoRef.h', + 'nsCom.h', + 'nscore.h', + 'nsCycleCollector.h', + 'nsDebugImpl.h', + 'nsDumpUtils.h', + 'nsError.h', + 'nsGZFileWriter.h', + 'nsIID.h', + 'nsInterfaceRequestorAgg.h', + 'nsISizeOf.h', + 'nsISupportsBase.h', + 'nsObjCExceptions.h', + 'nsQueryObject.h', + 'nsTraceRefcnt.h', + 'nsWeakPtr.h', +] + +if CONFIG['OS_ARCH'] == 'WINNT': + EXPORTS += [ + 'nsWindowsHelpers.h', + ] + +EXPORTS.mozilla += [ + 'AvailableMemoryTracker.h', + 'ClearOnShutdown.h', + 'CountingAllocatorBase.h', + 'CycleCollectedJSContext.h', + 'Debug.h', + 'DebuggerOnGCRunnable.h', + 'DeferredFinalize.h', + 'ErrorNames.h', + 'HoldDropJSObjects.h', + 'JSObjectHolder.h', + 'LinuxUtils.h', + 'Logging.h', + 'nsMemoryInfoDumper.h', + 'OwningNonNull.h', + 'StaticMutex.h', + 'StaticPtr.h', + 'SystemMemoryReporter.h', +] + +# nsDebugImpl isn't unified because we disable PGO so that NS_ABORT_OOM isn't +# optimized away oddly. +SOURCES += [ + 'nsDebugImpl.cpp', +] +SOURCES['nsDebugImpl.cpp'].no_pgo = True + +UNIFIED_SOURCES += [ + 'AvailableMemoryTracker.cpp', + 'ClearOnShutdown.cpp', + 'CycleCollectedJSContext.cpp', + 'Debug.cpp', + 'DebuggerOnGCRunnable.cpp', + 'DeferredFinalize.cpp', + 'ErrorNames.cpp', + 'HoldDropJSObjects.cpp', + 'JSObjectHolder.cpp', + 'Logging.cpp', + 'LogModulePrefWatcher.cpp', + 'nsConsoleMessage.cpp', + 'nsConsoleService.cpp', + 'nsCycleCollector.cpp', + 'nsCycleCollectorTraceJSHelpers.cpp', + 'nsDumpUtils.cpp', + 'nsErrorService.cpp', + 'nsGZFileWriter.cpp', + 'nsInterfaceRequestorAgg.cpp', + 'nsMemoryImpl.cpp', + 'nsMemoryInfoDumper.cpp', + 'nsMemoryReporterManager.cpp', + 'nsMessageLoop.cpp', + 'NSPRLogModulesParser.cpp', + 'nsSecurityConsoleMessage.cpp', + 'nsStatusReporterManager.cpp', + 'nsSystemInfo.cpp', + 'nsTraceRefcnt.cpp', + 'nsUUIDGenerator.cpp', + 'nsVersionComparatorImpl.cpp', +] + +if CONFIG['OS_ARCH'] == 'Linux': + SOURCES += [ + 'LinuxUtils.cpp', + 'SystemMemoryReporter.cpp', + ] + +if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa': + SOURCES += [ + 'nsMacUtilsImpl.cpp', + ] +elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows': + SOURCES += [ + 'nsCrashOnException.cpp', + ] + +include('/ipc/chromium/chromium-config.mozbuild') + +FINAL_LIBRARY = 'xul' + +LOCAL_INCLUDES += [ + '../build', + '/xpcom/ds', +] + +if 'gtk' in CONFIG['MOZ_WIDGET_TOOLKIT']: + CXXFLAGS += CONFIG['TK_CFLAGS'] diff --git a/xpcom/base/nsAgg.h b/xpcom/base/nsAgg.h new file mode 100644 index 000000000..8dcf8067e --- /dev/null +++ b/xpcom/base/nsAgg.h @@ -0,0 +1,336 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 nsAgg_h___ +#define nsAgg_h___ + +#include "nsISupports.h" +#include "nsCycleCollectionParticipant.h" + + +//////////////////////////////////////////////////////////////////////////////// + +// Put NS_DECL_AGGREGATED or NS_DECL_CYCLE_COLLECTING_AGGREGATED in your class's +// declaration. +#define NS_DECL_AGGREGATED \ + NS_DECL_ISUPPORTS \ + NS_DECL_AGGREGATED_HELPER + +#define NS_DECL_CYCLE_COLLECTING_AGGREGATED \ + NS_DECL_CYCLE_COLLECTING_ISUPPORTS \ + NS_DECL_AGGREGATED_HELPER + +#define NS_DECL_AGGREGATED_HELPER \ +public: \ + \ + /** \ + * Returns the nsISupports pointer of the inner object (aka the \ + * aggregatee). This pointer is really only useful to the outer object \ + * (aka the aggregator), which can use it to hold on to the inner \ + * object. Anything else wants the nsISupports pointer of the outer \ + * object (gotten by QI'ing inner or outer to nsISupports). This method \ + * returns a non-addrefed pointer. \ + * @return the nsISupports pointer of the inner object \ + */ \ + nsISupports* InnerObject(void) { return &fAggregated; } \ + \ + /** \ + * Returns true if this object is part of an aggregated object. \ + */ \ + bool IsPartOfAggregated(void) { return fOuter != InnerObject(); } \ + \ +private: \ + \ + /* You must implement this operation instead of the nsISupports */ \ + /* methods. */ \ + nsresult \ + AggregatedQueryInterface(const nsIID& aIID, void** aInstancePtr); \ + \ + class Internal : public nsISupports { \ + public: \ + \ + Internal() {} \ + \ + NS_IMETHOD QueryInterface(const nsIID& aIID, void** aInstancePtr); \ + NS_IMETHOD_(MozExternalRefCountType) AddRef(void); \ + NS_IMETHOD_(MozExternalRefCountType) Release(void); \ + \ + NS_DECL_OWNINGTHREAD \ + }; \ + \ + friend class Internal; \ + \ + nsISupports* MOZ_UNSAFE_REF("fOuter can either point to fAggregated " \ + "or to an outer object, and the safety " \ + "of this reference depends on the exact " \ + "lifetime semantics of the AddRef/Release " \ + "functions created by these macros.") \ + fOuter; \ + Internal fAggregated; \ + \ +public: \ + +#define NS_DECL_AGGREGATED_CYCLE_COLLECTION_CLASS(_class) \ +class NS_CYCLE_COLLECTION_INNERCLASS \ + : public nsXPCOMCycleCollectionParticipant \ +{ \ +public: \ + NS_IMETHOD_(void) Unlink(void *p) override; \ + NS_IMETHOD Traverse(void *p, nsCycleCollectionTraversalCallback &cb) \ + override; \ + NS_DECL_CYCLE_COLLECTION_CLASS_NAME_METHOD(_class) \ + NS_IMETHOD_(void) DeleteCycleCollectable(void* p) override \ + { \ + NS_CYCLE_COLLECTION_CLASSNAME(_class):: \ + Downcast(static_cast(p))->DeleteCycleCollectable(); \ + } \ + static _class* Downcast(nsISupports* s) \ + { \ + return (_class*)((char*)(s) - offsetof(_class, fAggregated)); \ + } \ + static nsISupports* Upcast(_class *p) \ + { \ + return p->InnerObject(); \ + } \ + static nsXPCOMCycleCollectionParticipant* GetParticipant() \ + { \ + return &_class::NS_CYCLE_COLLECTION_INNERNAME; \ + } \ +}; \ +NS_CHECK_FOR_RIGHT_PARTICIPANT_IMPL(_class); \ +static NS_CYCLE_COLLECTION_INNERCLASS NS_CYCLE_COLLECTION_INNERNAME; + +// Put this in your class's constructor: +#define NS_INIT_AGGREGATED(outer) \ + PR_BEGIN_MACRO \ + fOuter = outer ? outer : &fAggregated; \ + PR_END_MACRO + + +// Put this in your class's implementation file: +#define NS_IMPL_AGGREGATED(_class) \ + \ +NS_IMPL_AGGREGATED_HELPER(_class) \ + \ +NS_IMETHODIMP_(MozExternalRefCountType) \ +_class::Internal::AddRef(void) \ +{ \ + _class* agg = (_class*)((char*)(this) - offsetof(_class, fAggregated)); \ + MOZ_ASSERT(int32_t(agg->mRefCnt) >= 0, "illegal refcnt"); \ + NS_ASSERT_OWNINGTHREAD(_class); \ + ++agg->mRefCnt; \ + NS_LOG_ADDREF(this, agg->mRefCnt, #_class, sizeof(*this)); \ + return agg->mRefCnt; \ +} \ + \ +NS_IMETHODIMP_(MozExternalRefCountType) \ +_class::Internal::Release(void) \ +{ \ + _class* agg = (_class*)((char*)(this) - offsetof(_class, fAggregated)); \ + MOZ_ASSERT(int32_t(agg->mRefCnt) > 0, "dup release"); \ + NS_ASSERT_OWNINGTHREAD(_class); \ + --agg->mRefCnt; \ + NS_LOG_RELEASE(this, agg->mRefCnt, #_class); \ + if (agg->mRefCnt == 0) { \ + agg->mRefCnt = 1; /* stabilize */ \ + delete agg; \ + return 0; \ + } \ + return agg->mRefCnt; \ +} \ + +#define NS_IMPL_CYCLE_COLLECTING_AGGREGATED(_class) \ + \ +NS_IMPL_AGGREGATED_HELPER(_class) \ + \ +NS_IMETHODIMP_(MozExternalRefCountType) \ +_class::Internal::AddRef(void) \ +{ \ + _class* agg = NS_CYCLE_COLLECTION_CLASSNAME(_class)::Downcast(this); \ + MOZ_ASSERT(int32_t(agg->mRefCnt) >= 0, "illegal refcnt"); \ + NS_ASSERT_OWNINGTHREAD_AGGREGATE(agg, _class); \ + nsrefcnt count = agg->mRefCnt.incr(this); \ + NS_LOG_ADDREF(this, count, #_class, sizeof(*agg)); \ + return count; \ +} \ +NS_IMETHODIMP_(MozExternalRefCountType) \ +_class::Internal::Release(void) \ +{ \ + _class* agg = NS_CYCLE_COLLECTION_CLASSNAME(_class)::Downcast(this); \ + MOZ_ASSERT(int32_t(agg->mRefCnt) > 0, "dup release"); \ + NS_ASSERT_OWNINGTHREAD_AGGREGATE(agg, _class); \ + nsrefcnt count = agg->mRefCnt.decr(this); \ + NS_LOG_RELEASE(this, count, #_class); \ + return count; \ +} \ +NS_IMETHODIMP_(void) \ +_class::DeleteCycleCollectable(void) \ +{ \ + delete this; \ +} + +#define NS_IMPL_AGGREGATED_HELPER(_class) \ +NS_IMETHODIMP \ +_class::QueryInterface(const nsIID& aIID, void** aInstancePtr) \ +{ \ + return fOuter->QueryInterface(aIID, aInstancePtr); \ +} \ + \ +NS_IMETHODIMP_(MozExternalRefCountType) \ +_class::AddRef(void) \ +{ \ + return fOuter->AddRef(); \ +} \ + \ +NS_IMETHODIMP_(MozExternalRefCountType) \ +_class::Release(void) \ +{ \ + return fOuter->Release(); \ +} \ + \ +NS_IMETHODIMP \ +_class::Internal::QueryInterface(const nsIID& aIID, void** aInstancePtr) \ +{ \ + _class* agg = (_class*)((char*)(this) - offsetof(_class, fAggregated)); \ + return agg->AggregatedQueryInterface(aIID, aInstancePtr); \ +} \ + +/** + * To make aggregated objects participate in cycle collection we need to enable + * the outer object (aggregator) to traverse/unlink the objects held by the + * inner object (the aggregatee). We can't just make the inner object QI'able to + * NS_CYCLECOLLECTIONPARTICIPANT_IID, we don't want to return the inner object's + * nsCycleCollectionParticipant for the outer object (which will happen if the + * outer object doesn't participate in cycle collection itself). + * NS_AGGREGATED_CYCLECOLLECTIONPARTICIPANT_IID enables the outer object to get + * the inner objects nsCycleCollectionParticipant. + * + * There are three cases: + * - No aggregation + * QI'ing to NS_CYCLECOLLECTIONPARTICIPANT_IID will return the inner + * object's nsCycleCollectionParticipant. + * + * - Aggregation and outer object does not participate in cycle collection + * QI'ing to NS_CYCLECOLLECTIONPARTICIPANT_IID will not return anything. + * + * - Aggregation and outer object does participate in cycle collection + * QI'ing to NS_CYCLECOLLECTIONPARTICIPANT_IID will return the outer + * object's nsCycleCollectionParticipant. The outer object's + * nsCycleCollectionParticipant can then QI the inner object to + * NS_AGGREGATED_CYCLECOLLECTIONPARTICIPANT_IID to get the inner object's + * nsCycleCollectionParticipant, which it can use to traverse/unlink the + * objects reachable from the inner object. + */ +#define NS_AGGREGATED_CYCLECOLLECTIONPARTICIPANT_IID \ +{ \ + 0x32889b7e, \ + 0xe4fe, \ + 0x43f4, \ + { 0x85, 0x31, 0xb5, 0x28, 0x23, 0xa2, 0xe9, 0xfc } \ +} + +/** + * Just holds the IID so NS_GET_IID works. + */ +class nsAggregatedCycleCollectionParticipant +{ +public: + NS_DECLARE_STATIC_IID_ACCESSOR(NS_AGGREGATED_CYCLECOLLECTIONPARTICIPANT_IID) +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(nsAggregatedCycleCollectionParticipant, + NS_AGGREGATED_CYCLECOLLECTIONPARTICIPANT_IID) + +// for use with QI macros in nsISupportsUtils.h: + +#define NS_INTERFACE_MAP_BEGIN_AGGREGATED(_class) \ + NS_IMPL_AGGREGATED_QUERY_HEAD(_class) + +#define NS_INTERFACE_MAP_ENTRY_CYCLE_COLLECTION_AGGREGATED(_class) \ + NS_IMPL_QUERY_CYCLE_COLLECTION(_class) + +#define NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION_AGGREGATED(_class) \ + NS_INTERFACE_MAP_ENTRY_CYCLE_COLLECTION_AGGREGATED(_class) \ + NS_INTERFACE_MAP_ENTRY_CYCLE_COLLECTION_ISUPPORTS(_class) + +#define NS_IMPL_AGGREGATED_QUERY_HEAD(_class) \ +nsresult \ +_class::AggregatedQueryInterface(REFNSIID aIID, void** aInstancePtr) \ +{ \ + NS_ASSERTION(aInstancePtr, \ + "AggregatedQueryInterface requires a non-NULL result ptr!"); \ + if ( !aInstancePtr ) \ + return NS_ERROR_NULL_POINTER; \ + nsISupports* foundInterface; \ + if ( aIID.Equals(NS_GET_IID(nsISupports)) ) \ + foundInterface = InnerObject(); \ + else + +#define NS_IMPL_AGGREGATED_QUERY_CYCLE_COLLECTION(_class) \ + if (aIID.Equals(IsPartOfAggregated() ? \ + NS_GET_IID(nsCycleCollectionParticipant) : \ + NS_GET_IID(nsAggregatedCycleCollectionParticipant))) \ + foundInterface = NS_CYCLE_COLLECTION_PARTICIPANT(_class); \ + else + +#define NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_AGGREGATED(_class) \ + NS_IMETHODIMP \ + NS_CYCLE_COLLECTION_CLASSNAME(_class)::Traverse \ + (void *p, nsCycleCollectionTraversalCallback &cb) \ + { \ + nsISupports *s = static_cast(p); \ + MOZ_ASSERT(CheckForRightISupports(s), \ + "not the nsISupports pointer we expect"); \ + _class *tmp = static_cast<_class*>(Downcast(s)); \ + if (!tmp->IsPartOfAggregated()) \ + NS_IMPL_CYCLE_COLLECTION_DESCRIBE(_class, tmp->mRefCnt.get()) + +#define NS_GENERIC_AGGREGATED_CONSTRUCTOR(_InstanceClass) \ +static nsresult \ +_InstanceClass##Constructor(nsISupports *aOuter, REFNSIID aIID, \ + void **aResult) \ +{ \ + *aResult = nullptr; \ + if (NS_WARN_IF(aOuter && !aIID.Equals(NS_GET_IID(nsISupports)))) \ + return NS_ERROR_INVALID_ARG; \ + \ + RefPtr<_InstanceClass> inst = new _InstanceClass(aOuter); \ + if (!inst) { \ + return NS_ERROR_OUT_OF_MEMORY; \ + } \ + \ + nsISupports* inner = inst->InnerObject(); \ + nsresult rv = inner->QueryInterface(aIID, aResult); \ + \ + return rv; \ +} \ + +#define NS_GENERIC_AGGREGATED_CONSTRUCTOR_INIT(_InstanceClass, _InitMethod) \ +static nsresult \ +_InstanceClass##Constructor(nsISupports *aOuter, REFNSIID aIID, \ + void **aResult) \ +{ \ + *aResult = nullptr; \ + if (NS_WARN_IF(aOuter && !aIID.Equals(NS_GET_IID(nsISupports)))) \ + return NS_ERROR_INVALID_ARG; \ + \ + RefPtr<_InstanceClass> inst = new _InstanceClass(aOuter); \ + if (!inst) { \ + return NS_ERROR_OUT_OF_MEMORY; \ + } \ + \ + nsISupports* inner = inst->InnerObject(); \ + NS_ADDREF(inner); \ + nsresult rv = inst->_InitMethod(); \ + if (NS_SUCCEEDED(rv)) { \ + rv = inner->QueryInterface(aIID, aResult); \ + } \ + NS_RELEASE(inner); \ + \ + return rv; \ +} \ + +#endif /* nsAgg_h___ */ diff --git a/xpcom/base/nsAlgorithm.h b/xpcom/base/nsAlgorithm.h new file mode 100644 index 000000000..ceaa3ace3 --- /dev/null +++ b/xpcom/base/nsAlgorithm.h @@ -0,0 +1,75 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 "nsCharTraits.h" // for |nsCharSourceTraits|, |nsCharSinkTraits| + +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; +} + +template +inline OutputIterator& +copy_string(const InputIterator& aFirst, const InputIterator& aLast, + OutputIterator& aResult) +{ + typedef nsCharSourceTraits source_traits; + typedef nsCharSinkTraits sink_traits; + + sink_traits::write(aResult, source_traits::read(aFirst), + source_traits::readable_distance(aFirst, aLast)); + return aResult; +} + +#endif // !defined(nsAlgorithm_h___) diff --git a/xpcom/base/nsAllocator.h b/xpcom/base/nsAllocator.h new file mode 100644 index 000000000..a9ad1c70a --- /dev/null +++ b/xpcom/base/nsAllocator.h @@ -0,0 +1,17 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +//////////////////////////////////////////////////////////////////////////////// +// obsolete +//////////////////////////////////////////////////////////////////////////////// + +#ifndef nsAllocator_h__ +#define nsAllocator_h__ + +#include "nsAgg.h" +#include "nsIFactory.h" + +#endif // nsAllocator_h__ diff --git a/xpcom/base/nsAutoPtr.h b/xpcom/base/nsAutoPtr.h new file mode 100644 index 000000000..b5a15000c --- /dev/null +++ b/xpcom/base/nsAutoPtr.h @@ -0,0 +1,454 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 nsAutoPtr_h +#define nsAutoPtr_h + +#include "nsCOMPtr.h" +#include "mozilla/RefPtr.h" +#include "mozilla/TypeTraits.h" + +#include "nsCycleCollectionNoteChild.h" +#include "mozilla/MemoryReporting.h" + +/*****************************************************************************/ + +// template class nsAutoPtrGetterTransfers; + +template +class nsAutoPtr +{ +private: + static_assert(!mozilla::IsScalar::value, "If you are using " + "nsAutoPtr to hold an array, use UniquePtr instead"); + + void** + begin_assignment() + { + assign(0); + return reinterpret_cast(&mRawPtr); + } + + void + assign(T* aNewPtr) + { + T* oldPtr = mRawPtr; + + if (aNewPtr && aNewPtr == oldPtr) { + NS_RUNTIMEABORT("Logic flaw in the caller"); + } + + mRawPtr = aNewPtr; + delete oldPtr; + } + + // |class Ptr| helps us prevent implicit "copy construction" + // through |operator T*() const| from a |const nsAutoPtr| + // because two implicit conversions in a row aren't allowed. + // It still allows assignment from T* through implicit conversion + // from |T*| to |nsAutoPtr::Ptr| + class Ptr + { + public: + MOZ_IMPLICIT Ptr(T* aPtr) + : mPtr(aPtr) + { + } + + operator T*() const + { + return mPtr; + } + + private: + T* MOZ_NON_OWNING_REF mPtr; + }; + +private: + T* MOZ_OWNING_REF mRawPtr; + +public: + typedef T element_type; + + ~nsAutoPtr() + { + delete mRawPtr; + } + + // Constructors + + nsAutoPtr() + : mRawPtr(0) + // default constructor + { + } + + MOZ_IMPLICIT nsAutoPtr(Ptr aRawPtr) + : mRawPtr(aRawPtr) + // construct from a raw pointer (of the right type) + { + } + + // This constructor shouldn't exist; we should just use the && + // constructor. + nsAutoPtr(nsAutoPtr& aSmartPtr) + : mRawPtr(aSmartPtr.forget()) + // Construct by transferring ownership from another smart pointer. + { + } + + template + MOZ_IMPLICIT nsAutoPtr(nsAutoPtr& aSmartPtr) + : mRawPtr(aSmartPtr.forget()) + // Construct by transferring ownership from another smart pointer. + { + } + + nsAutoPtr(nsAutoPtr&& aSmartPtr) + : mRawPtr(aSmartPtr.forget()) + // Construct by transferring ownership from another smart pointer. + { + } + + template + MOZ_IMPLICIT nsAutoPtr(nsAutoPtr&& aSmartPtr) + : mRawPtr(aSmartPtr.forget()) + // Construct by transferring ownership from another smart pointer. + { + } + + // Assignment operators + + nsAutoPtr& + operator=(T* aRhs) + // assign from a raw pointer (of the right type) + { + assign(aRhs); + return *this; + } + + nsAutoPtr& operator=(nsAutoPtr& aRhs) + // assign by transferring ownership from another smart pointer. + { + assign(aRhs.forget()); + return *this; + } + + template + nsAutoPtr& operator=(nsAutoPtr& aRhs) + // assign by transferring ownership from another smart pointer. + { + assign(aRhs.forget()); + return *this; + } + + nsAutoPtr& operator=(nsAutoPtr&& aRhs) + { + assign(aRhs.forget()); + return *this; + } + + template + nsAutoPtr& operator=(nsAutoPtr&& aRhs) + { + assign(aRhs.forget()); + return *this; + } + + // Other pointer operators + + T* + get() const + /* + Prefer the implicit conversion provided automatically by + |operator T*() const|. Use |get()| _only_ to resolve + ambiguity. + */ + { + return mRawPtr; + } + + operator T*() const + /* + ...makes an |nsAutoPtr| 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 |nsAutoPtr| + substitutable for a raw pointer. + + Prefer the implicit use of this operator to calling |get()|, + except where necessary to resolve ambiguity. + */ + { + return get(); + } + + T* + forget() + { + T* temp = mRawPtr; + mRawPtr = 0; + return temp; + } + + T* + operator->() const + { + NS_PRECONDITION(mRawPtr != 0, + "You can't dereference a NULL nsAutoPtr with operator->()."); + return get(); + } + + template + class Proxy + { + typedef R (T::*member_function)(Args...); + T* mRawPtr; + member_function mFunction; + public: + Proxy(T* aRawPtr, member_function aFunction) + : mRawPtr(aRawPtr), + mFunction(aFunction) + { + } + template + R operator()(ActualArgs&&... aArgs) + { + return ((*mRawPtr).*mFunction)(mozilla::Forward(aArgs)...); + } + }; + + template + Proxy operator->*(R (C::*aFptr)(Args...)) const + { + NS_PRECONDITION(mRawPtr != 0, + "You can't dereference a NULL nsAutoPtr with operator->*()."); + return Proxy(get(), aFptr); + } + + nsAutoPtr* + get_address() + // This is not intended to be used by clients. See |address_of| + // below. + { + return this; + } + + const nsAutoPtr* + get_address() const + // This is not intended to be used by clients. See |address_of| + // below. + { + return this; + } + +public: + T& + operator*() const + { + NS_PRECONDITION(mRawPtr != 0, + "You can't dereference a NULL nsAutoPtr with operator*()."); + return *get(); + } + + T** + StartAssignment() + { +#ifndef NSCAP_FEATURE_INLINE_STARTASSIGNMENT + return reinterpret_cast(begin_assignment()); +#else + assign(0); + return reinterpret_cast(&mRawPtr); +#endif + } +}; + +template +inline nsAutoPtr* +address_of(nsAutoPtr& aPtr) +{ + return aPtr.get_address(); +} + +template +inline const nsAutoPtr* +address_of(const nsAutoPtr& aPtr) +{ + return aPtr.get_address(); +} + +template +class nsAutoPtrGetterTransfers +/* + ... + + This class is designed to be used for anonymous temporary objects in the + argument list of calls that return COM interface pointers, e.g., + + nsAutoPtr fooP; + ...->GetTransferedPointer(getter_Transfers(fooP)) + + DO NOT USE THIS TYPE DIRECTLY IN YOUR CODE. Use |getter_Transfers()| instead. + + When initialized with a |nsAutoPtr|, as in the example above, it returns + a |void**|, a |T**|, or an |nsISupports**| as needed, that the + outer call (|GetTransferedPointer| in this case) can fill in. + + This type should be a nested class inside |nsAutoPtr|. +*/ +{ +public: + explicit + nsAutoPtrGetterTransfers(nsAutoPtr& aSmartPtr) + : mTargetSmartPtr(aSmartPtr) + { + // nothing else to do + } + + operator void**() + { + return reinterpret_cast(mTargetSmartPtr.StartAssignment()); + } + + operator T**() + { + return mTargetSmartPtr.StartAssignment(); + } + + T*& + operator*() + { + return *(mTargetSmartPtr.StartAssignment()); + } + +private: + nsAutoPtr& mTargetSmartPtr; +}; + +template +inline nsAutoPtrGetterTransfers +getter_Transfers(nsAutoPtr& aSmartPtr) +/* + Used around a |nsAutoPtr| when + ...makes the class |nsAutoPtrGetterTransfers| invisible. +*/ +{ + return nsAutoPtrGetterTransfers(aSmartPtr); +} + + + +// Comparing two |nsAutoPtr|s + +template +inline bool +operator==(const nsAutoPtr& aLhs, const nsAutoPtr& aRhs) +{ + return static_cast(aLhs.get()) == static_cast(aRhs.get()); +} + + +template +inline bool +operator!=(const nsAutoPtr& aLhs, const nsAutoPtr& aRhs) +{ + return static_cast(aLhs.get()) != static_cast(aRhs.get()); +} + + +// Comparing an |nsAutoPtr| to a raw pointer + +template +inline bool +operator==(const nsAutoPtr& aLhs, const U* aRhs) +{ + return static_cast(aLhs.get()) == static_cast(aRhs); +} + +template +inline bool +operator==(const U* aLhs, const nsAutoPtr& aRhs) +{ + return static_cast(aLhs) == static_cast(aRhs.get()); +} + +template +inline bool +operator!=(const nsAutoPtr& aLhs, const U* aRhs) +{ + return static_cast(aLhs.get()) != static_cast(aRhs); +} + +template +inline bool +operator!=(const U* aLhs, const nsAutoPtr& aRhs) +{ + return static_cast(aLhs) != static_cast(aRhs.get()); +} + +template +inline bool +operator==(const nsAutoPtr& aLhs, U* aRhs) +{ + return static_cast(aLhs.get()) == const_cast(aRhs); +} + +template +inline bool +operator==(U* aLhs, const nsAutoPtr& aRhs) +{ + return const_cast(aLhs) == static_cast(aRhs.get()); +} + +template +inline bool +operator!=(const nsAutoPtr& aLhs, U* aRhs) +{ + return static_cast(aLhs.get()) != const_cast(aRhs); +} + +template +inline bool +operator!=(U* aLhs, const nsAutoPtr& aRhs) +{ + return const_cast(aLhs) != static_cast(aRhs.get()); +} + + + +// Comparing an |nsAutoPtr| to |nullptr| + +template +inline bool +operator==(const nsAutoPtr& aLhs, decltype(nullptr)) +{ + return aLhs.get() == nullptr; +} + +template +inline bool +operator==(decltype(nullptr), const nsAutoPtr& aRhs) +{ + return nullptr == aRhs.get(); +} + +template +inline bool +operator!=(const nsAutoPtr& aLhs, decltype(nullptr)) +{ + return aLhs.get() != nullptr; +} + +template +inline bool +operator!=(decltype(nullptr), const nsAutoPtr& aRhs) +{ + return nullptr != aRhs.get(); +} + + +/*****************************************************************************/ + +#endif // !defined(nsAutoPtr_h) diff --git a/xpcom/base/nsAutoRef.h b/xpcom/base/nsAutoRef.h new file mode 100644 index 000000000..9986c8a56 --- /dev/null +++ b/xpcom/base/nsAutoRef.h @@ -0,0 +1,672 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 nsAutoRef_h_ +#define nsAutoRef_h_ + +#include "mozilla/Attributes.h" + +#include "nscore.h" // for nullptr, bool + +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 ref-counted resources see also |nsCountedRef|. + * For function return values see |nsReturnRef|. + * + * For each class |T|, |nsAutoRefTraits| or |nsSimpleRef| must be + * specialized to use |nsAutoRef| and |nsCountedRef|. + * + * @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() + { + } + + // 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. + // + // Or, to create another owner of the same reference, use an nsCountedRef. + + 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(); + } + + // 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(). + +private: + // No copy constructor + explicit nsAutoRef(ThisClass& aRefToSteal); +}; + +/** + * template class nsCountedRef + * + * A class that creates (adds) a new reference to a resource on construction + * or assignment and releases on destruction. + * + * This class is similar to nsAutoRef and inherits its methods, but also + * provides copy construction and assignment operators that enable more than + * one concurrent reference to the same resource. + * + * Specialize |nsAutoRefTraits| or |nsSimpleRef| to use this. This + * class assumes that the resource itself counts references and so can only be + * used when |T| represents a reference-counting resource. + */ + +template +class nsCountedRef : public nsAutoRef +{ +protected: + typedef nsCountedRef ThisClass; + typedef nsAutoRef BaseClass; + typedef nsSimpleRef SimpleRef; + typedef typename BaseClass::RawRef RawRef; + +public: + nsCountedRef() + { + } + + // Construction and assignment from a another nsCountedRef + // or a raw ref copies and increments the ref count. + nsCountedRef(const ThisClass& aRefToCopy) + { + SimpleRef::operator=(aRefToCopy); + SafeAddRef(); + } + ThisClass& operator=(const ThisClass& aRefToCopy) + { + if (this == &aRefToCopy) { + return *this; + } + + this->SafeRelease(); + SimpleRef::operator=(aRefToCopy); + SafeAddRef(); + return *this; + } + + // Implicit conversion from another smart ref argument (to a raw ref) is + // accepted here because construction and assignment safely creates a new + // reference without interfering with the reference to copy. + explicit nsCountedRef(RawRef aRefToCopy) + : BaseClass(aRefToCopy) + { + SafeAddRef(); + } + ThisClass& operator=(RawRef aRefToCopy) + { + this->own(aRefToCopy); + SafeAddRef(); + return *this; + } + + // Construction and assignment from an nsReturnRef function return value, + // which expects to give up ownership, transfers ownership. + explicit nsCountedRef(const nsReturningRef& aReturning) + : BaseClass(aReturning) + { + } + ThisClass& operator=(const nsReturningRef& aReturning) + { + BaseClass::operator=(aReturning); + return *this; + } + +protected: + // Increase the reference count if there is a resource. + void SafeAddRef() + { + if (this->HaveResource()) { + this->AddRef(this->get()); + } + } +}; + +/** + * 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() + { + } + + // 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) + { + } + + // Copy construction transfers ownership + nsReturnRef(nsReturnRef& aRefToSteal) + : BaseClass(aRefToSteal) + { + } + + 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| and |nsCountedRef|. + * 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); + * + * // For reference-counted resources, if |nsCountedRef| is to be used, + * // specializations must define AddRef to increment the reference count + * // held by a non-void handle. + * // (AddRef() is not necessary for |nsAutoRef|.) + * static void AddRef(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| + * and thus |nsCountedRef|. + * + * Specialize this class if there is no particular void value for the resource + * handle. A specialized implementation must also provide Release(RawRef), + * and, if |nsCountedRef| is required, AddRef(RawRef), as described in + * nsAutoRefTraits. + */ + +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() + { + } + + // 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 + explicit 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() + { + } + explicit LocalSimpleRef(RawRef aRawRef) + : SimpleRef(aRawRef) + { + } + }; + +private: + ThisClass& operator=(const ThisClass& aSmartRef) = delete; + +public: + 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/nsCom.h b/xpcom/base/nsCom.h new file mode 100644 index 000000000..20353a266 --- /dev/null +++ b/xpcom/base/nsCom.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 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 000000000..e3530745b --- /dev/null +++ b/xpcom/base/nsConsoleMessage.cpp @@ -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/. */ + +/* + * Base implementation for console messages. + */ + +#include "nsConsoleMessage.h" +#include "jsapi.h" + +NS_IMPL_ISUPPORTS(nsConsoleMessage, nsIConsoleMessage) + +nsConsoleMessage::nsConsoleMessage() + : mTimeStamp(0) + , mMessage() +{ +} + +nsConsoleMessage::nsConsoleMessage(const char16_t* aMessage) +{ + mTimeStamp = JS_Now() / 1000; + mMessage.Assign(aMessage); +} + +NS_IMETHODIMP +nsConsoleMessage::GetMessageMoz(char16_t** aResult) +{ + *aResult = ToNewUnicode(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 = mTimeStamp; + return NS_OK; +} + +NS_IMETHODIMP +nsConsoleMessage::ToString(nsACString& /*UTF8*/ aResult) +{ + CopyUTF16toUTF8(mMessage, aResult); + + return NS_OK; +} diff --git a/xpcom/base/nsConsoleMessage.h b/xpcom/base/nsConsoleMessage.h new file mode 100644 index 000000000..64ad921a5 --- /dev/null +++ b/xpcom/base/nsConsoleMessage.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 __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 char16_t* aMessage); + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSICONSOLEMESSAGE + +private: + ~nsConsoleMessage() + { + } + + int64_t mTimeStamp; + nsString mMessage; +}; + +#endif /* __nsconsolemessage_h__ */ diff --git a/xpcom/base/nsConsoleService.cpp b/xpcom/base/nsConsoleService.cpp new file mode 100644 index 000000000..3862a02c4 --- /dev/null +++ b/xpcom/base/nsConsoleService.cpp @@ -0,0 +1,473 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 "nsMemory.h" +#include "nsCOMArray.h" +#include "nsThreadUtils.h" + +#include "nsConsoleService.h" +#include "nsConsoleMessage.h" +#include "nsIClassInfoImpl.h" +#include "nsIConsoleListener.h" +#include "nsPrintfCString.h" +#include "nsProxyRelease.h" +#include "nsIScriptError.h" +#include "nsISupportsPrimitives.h" + +#include "mozilla/Preferences.h" + +#if defined(ANDROID) +#include +#include "mozilla/dom/ContentChild.h" +#endif +#ifdef XP_WIN +#include +#endif + +#ifdef MOZ_TASK_TRACER +#include "GeckoTaskTracer.h" +using namespace mozilla::tasktracer; +#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 bool sLoggingEnabled = true; +static bool sLoggingBuffered = true; +#if defined(ANDROID) +static bool sLoggingLogcat = true; +#endif // defined(ANDROID) + +nsConsoleService::MessageElement::~MessageElement() +{ +} + +nsConsoleService::nsConsoleService() + : mCurrentSize(0) + , mDeliveringMessage(false) + , mLock("nsConsoleService.mLock") +{ + // XXX grab this from a pref! + // hm, but worry about circularity, bc we want to be able to report + // prefs errs... + mMaximumSize = 250; +} + + +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) : mConsole(aConsole) + { + } + + NS_IMETHOD Run() override + { + Preferences::AddBoolVarCache(&sLoggingEnabled, "consoleservice.enabled", true); + Preferences::AddBoolVarCache(&sLoggingBuffered, "consoleservice.buffered", true); +#if defined(ANDROID) + Preferences::AddBoolVarCache(&sLoggingLogcat, "consoleservice.logcat", true); +#endif // defined(ANDROID) + + 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 (!sLoggingBuffered) { + mConsole->Reset(); + } + return NS_OK; + } + +private: + RefPtr mConsole; +}; + +nsresult +nsConsoleService::Init() +{ + NS_DispatchToMainThread(new AddConsolePrefWatchers(this)); + + return NS_OK; +} + +namespace { + +class LogMessageRunnable : public Runnable +{ +public: + LogMessageRunnable(nsIConsoleMessage* aMessage, nsConsoleService* aService) + : mMessage(aMessage) + , mService(aService) + { } + + NS_DECL_NSIRUNNABLE + +private: + nsCOMPtr mMessage; + RefPtr mService; +}; + +NS_IMETHODIMP +LogMessageRunnable::Run() +{ + MOZ_ASSERT(NS_IsMainThread()); + + // 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, OutputToLog); +} + +// This can be called off the main thread. +nsresult +nsConsoleService::LogMessageWithMode(nsIConsoleMessage* aMessage, + nsConsoleService::OutputMode aOutputMode) +{ + if (!aMessage) { + return NS_ERROR_INVALID_ARG; + } + + if (!sLoggingEnabled) { + 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; + } + + RefPtr r; + nsCOMPtr retiredMessage; + + /* + * Lock while updating buffer, and while taking snapshot of + * listeners array. + */ + { + MutexAutoLock lock(mLock); + +#if defined(ANDROID) + if (sLoggingLogcat && 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 (IsDebuggerPresent()) { + nsString msg; + aMessage->GetMessageMoz(getter_Copies(msg)); + msg.Append('\n'); + OutputDebugStringW(msg.get()); + } +#endif +#ifdef MOZ_TASK_TRACER + { + nsCString msg; + aMessage->ToString(msg); + int prefixPos = msg.Find(GetJSLabelPrefix()); + if (prefixPos >= 0) { + nsDependentCSubstring submsg(msg, prefixPos); + AddLabel("%s", submsg.BeginReading()); + } + } +#endif + + if (sLoggingBuffered) { + 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(retiredMessage.forget()); + } + + if (r) { + // avoid failing in XPCShell tests + nsCOMPtr mainThread = do_GetMainThread(); + if (mainThread) { + NS_DispatchToMainThread(r.forget()); + } + } + + return NS_OK; +} + +void +nsConsoleService::CollectCurrentListeners( + nsCOMArray& aListeners) +{ + MutexAutoLock lock(mLock); + for (auto iter = mListeners.Iter(); !iter.Done(); iter.Next()) { + nsIConsoleListener* value = iter.UserData(); + aListeners.AppendObject(value); + } +} + +NS_IMETHODIMP +nsConsoleService::LogStringMessage(const char16_t* aMessage) +{ + if (!sLoggingEnabled) { + return NS_OK; + } + + RefPtr msg(new nsConsoleMessage(aMessage)); + return this->LogMessage(msg); +} + +NS_IMETHODIMP +nsConsoleService::GetMessageArray(uint32_t* aCount, + nsIConsoleMessage*** aMessages) +{ + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + + MutexAutoLock lock(mLock); + + if (mMessages.isEmpty()) { + /* + * Make a 1-length output array so that nobody gets confused, + * and return a count of 0. This should result in a 0-length + * array object when called from script. + */ + nsIConsoleMessage** messageArray = (nsIConsoleMessage**) + moz_xmalloc(sizeof(nsIConsoleMessage*)); + *messageArray = nullptr; + *aMessages = messageArray; + *aCount = 0; + + return NS_OK; + } + + MOZ_ASSERT(mCurrentSize <= mMaximumSize); + nsIConsoleMessage** messageArray = + static_cast(moz_xmalloc(sizeof(nsIConsoleMessage*) + * mCurrentSize)); + + uint32_t i = 0; + for (MessageElement* e = mMessages.getFirst(); e != nullptr; e = e->getNext()) { + nsCOMPtr m = e->Get(); + m.forget(&messageArray[i]); + i++; + } + + MOZ_ASSERT(i == mCurrentSize); + + *aCount = i; + *aMessages = messageArray; + + 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); + + MutexAutoLock lock(mLock); + if (mListeners.GetWeak(canonical)) { + // Reregistering a listener isn't good + return NS_ERROR_FAILURE; + } + mListeners.Put(canonical, 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); + + if (!mListeners.GetWeak(canonical)) { + // Unregistering a listener that was never registered? + return NS_ERROR_FAILURE; + } + mListeners.Remove(canonical); + return NS_OK; +} + +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::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 000000000..089de8106 --- /dev/null +++ b/xpcom/base/nsConsoleService.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/. */ + +/* + * nsConsoleService class declaration. + */ + +#ifndef __nsconsoleservice_h__ +#define __nsconsoleservice_h__ + +#include "mozilla/Attributes.h" +#include "mozilla/Mutex.h" + +#include "nsInterfaceHashtable.h" +#include "nsHashKeys.h" + +#include "nsIConsoleService.h" + +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; + } + + // 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. + + enum OutputMode { + SuppressLog, + OutputToLog + }; + virtual nsresult LogMessageWithMode(nsIConsoleMessage* aMessage, + OutputMode aOutputMode); + + 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(); + + void ClearMessagesForWindowID(const uint64_t innerID); + void ClearMessages(); + + mozilla::LinkedList mMessages; + + // The current size of mMessages. + uint32_t mCurrentSize; + + // The maximum size of mMessages. + 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. + bool mDeliveringMessage; + + // Listeners to notify whenever a new message is logged. + ListenerHash mListeners; + + // 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 000000000..0f8042531 --- /dev/null +++ b/xpcom/base/nsCrashOnException.cpp @@ -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/. */ + +#include "nsCrashOnException.h" +#include "nsCOMPtr.h" +#include "nsServiceManagerUtils.h" + +#ifdef MOZ_CRASHREPORTER +#include "nsICrashReporter.h" +#endif + +namespace mozilla { + +static int +ReportException(EXCEPTION_POINTERS* aExceptionInfo) +{ +#ifdef MOZ_CRASHREPORTER + nsCOMPtr cr = + do_GetService("@mozilla.org/toolkit/crash-reporter;1"); + if (cr) { + cr->WriteMinidumpForException(aExceptionInfo); + } +#endif + 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 +} + +} + diff --git a/xpcom/base/nsCrashOnException.h b/xpcom/base/nsCrashOnException.h new file mode 100644 index 000000000..cdf3fdf09 --- /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); + +} + +#endif diff --git a/xpcom/base/nsCycleCollector.cpp b/xpcom/base/nsCycleCollector.cpp new file mode 100644 index 000000000..ca7057628 --- /dev/null +++ b/xpcom/base/nsCycleCollector.cpp @@ -0,0 +1,4213 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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. Snow-white object +// 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/DebugOnly.h" +#include "mozilla/HoldDropJSObjects.h" +/* This must occur *after* base/process_util.h to avoid typedefs conflicts. */ +#include "mozilla/LinkedList.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/SegmentedVector.h" + +#include "nsCycleCollectionParticipant.h" +#include "nsCycleCollectionNoteRootCallback.h" +#include "nsDeque.h" +#include "nsCycleCollector.h" +#include "nsThreadUtils.h" +#include "nsXULAppAPI.h" +#include "prenv.h" +#include "nsPrintfCString.h" +#include "nsTArray.h" +#include "nsIConsoleService.h" +#include "mozilla/Attributes.h" +#include "nsICycleCollectorListener.h" +#include "nsIMemoryReporter.h" +#include "nsIFile.h" +#include "nsDumpUtils.h" +#include "xpcpublic.h" +#include "GeckoProfiler.h" +#include +#include + +#include "mozilla/AutoGlobalTimelineMarker.h" +#include "mozilla/Likely.h" +#include "mozilla/PoisonIOInterposer.h" +#include "mozilla/Telemetry.h" +#include "mozilla/ThreadLocal.h" + +#ifdef MOZ_CRASHREPORTER +#include "nsExceptionHandler.h" +#endif + +using namespace mozilla; + +//#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_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 +// "plugins", only automatically log plugin 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; + + nsCycleCollectorParams() : + mLogAll(PR_GetEnv("MOZ_CC_LOG_ALL") != nullptr), + mLogShutdown(PR_GetEnv("MOZ_CC_LOG_SHUTDOWN") != nullptr), + mAllTracesAll(false), + mAllTracesShutdown(false) + { + 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_Plugin: + processLogging = !strcmp(logProcessEnv, "plugins"); + 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; + } + } + } + + bool LogThisCC(bool aIsShutdown) + { + return (mLogAll || (aIsShutdown && mLogShutdown)) && 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() + { + } + void Checkpoint(const char* aEvent) + { + } +}; +#endif + + +//////////////////////////////////////////////////////////////////////// +// Base types +//////////////////////////////////////////////////////////////////////// + +struct 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) : mPointer(aOther.mPointer) {} + + 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) \ + PR_BEGIN_MACRO \ + if (NS_IsMainThread()) { \ + Telemetry::Accumulate(Telemetry::CYCLE_COLLECTOR##_name, _value); \ + } else { \ + Telemetry::Accumulate(Telemetry::CYCLE_COLLECTOR_WORKER##_name, _value); \ + } \ + PR_END_MACRO + +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. + +struct PtrInfo +{ + 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() + { + NS_NOTREACHED("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; + } +}; + +/** + * 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() + { + NS_NOTREACHED("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() + { + NS_NOTREACHED("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; +}; + + +// Declarations for mPtrToNodeMap. + +struct PtrToNodeEntry : public PLDHashEntryHdr +{ + // The key is mNode->mPointer + PtrInfo* mNode; +}; + +static bool +PtrToNodeMatchEntry(const PLDHashEntryHdr* aEntry, const void* aKey) +{ + const PtrToNodeEntry* n = static_cast(aEntry); + return n->mNode->mPointer == aKey; +} + +static PLDHashTableOps PtrNodeOps = { + PLDHashTable::HashVoidPtrKeyStub, + PtrToNodeMatchEntry, + PLDHashTable::MoveEntryStub, + PLDHashTable::ClearEntryStub, + nullptr +}; + + +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: + PLDHashTable mPtrToNodeMap; + bool mOutOfMemory; + + static const uint32_t kInitialMapLength = 16384; + +public: + CCGraph() + : mRootCount(0) + , mPtrToNodeMap(&PtrNodeOps, sizeof(PtrToNodeEntry), kInitialMapLength) + , mOutOfMemory(false) + {} + + ~CCGraph() {} + + void Init() + { + MOZ_ASSERT(IsEmpty(), "Failed to call CCGraph::Clear"); + } + + void Clear() + { + mNodes.Clear(); + mEdges.Clear(); + mWeakMaps.Clear(); + mRootCount = 0; + mPtrToNodeMap.ClearAndPrepareForLength(kInitialMapLength); + mOutOfMemory = false; + } + +#ifdef DEBUG + bool IsEmpty() + { + return mNodes.IsEmpty() && mEdges.IsEmpty() && + mWeakMaps.IsEmpty() && mRootCount == 0 && + mPtrToNodeMap.EntryCount() == 0; + } +#endif + + PtrInfo* FindNode(void* aPtr); + PtrToNodeEntry* AddNodeToMap(void* aPtr); + void RemoveObjectFromMap(void* aObject); + + uint32_t MapCount() const + { + return mPtrToNodeMap.EntryCount(); + } + + 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 += mPtrToNodeMap.ShallowSizeOfExcludingThis(aMallocSizeOf); + + return n; + } + +private: + PtrToNodeEntry* FindNodeEntry(void* aPtr) + { + return static_cast(mPtrToNodeMap.Search(aPtr)); + } +}; + +PtrInfo* +CCGraph::FindNode(void* aPtr) +{ + PtrToNodeEntry* e = FindNodeEntry(aPtr); + return e ? e->mNode : nullptr; +} + +PtrToNodeEntry* +CCGraph::AddNodeToMap(void* aPtr) +{ + JS::AutoSuppressGCAnalysis suppress; + if (mOutOfMemory) { + return nullptr; + } + + auto e = static_cast(mPtrToNodeMap.Add(aPtr, fallible)); + if (!e) { + mOutOfMemory = true; + MOZ_ASSERT(false, "Ran out of memory while building cycle collector graph"); + return nullptr; + } + return e; +} + +void +CCGraph::RemoveObjectFromMap(void* aObj) +{ + PtrToNodeEntry* e = FindNodeEntry(aObj); + PtrInfo* pinfo = e ? e->mNode : nullptr; + if (pinfo) { + mPtrToNodeMap.RemoveEntry(e); + + pinfo->mPointer = nullptr; + pinfo->mParticipant = nullptr; + } +} + + +static nsISupports* +CanonicalizeXPCOMParticipant(nsISupports* aIn) +{ + nsISupports* out = nullptr; + aIn->QueryInterface(NS_GET_IID(nsCycleCollectionISupports), + reinterpret_cast(&out)); + return out; +} + +static inline void +ToParticipant(nsISupports* aPtr, nsXPCOMCycleCollectionParticipant** aCp); + +static void +CanonicalizeParticipant(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); + nsparti = CanonicalizeXPCOMParticipant(nsparti); + NS_ASSERTION(nsparti, + "Don't add objects that don't participate in collection!"); + nsXPCOMCycleCollectionParticipant* xcp; + ToParticipant(nsparti, &xcp); + *aParti = nsparti; + *aCp = xcp; + } +} + +struct nsPurpleBufferEntry +{ + union + { + void* mObject; // when low bit unset + nsPurpleBufferEntry* mNextInFreeList; // when low bit set + }; + + nsCycleCollectingAutoRefCnt* mRefCnt; + + nsCycleCollectionParticipant* mParticipant; // nullptr for nsISupports +}; + +class nsCycleCollector; + +struct nsPurpleBuffer +{ +private: + struct PurpleBlock + { + PurpleBlock* mNext; + // Try to match the size of a jemalloc bucket, to minimize slop bytes. + // - On 32-bit platforms sizeof(nsPurpleBufferEntry) is 12, so mEntries + // is 16,380 bytes, which leaves 4 bytes for mNext. + // - On 64-bit platforms sizeof(nsPurpleBufferEntry) is 24, so mEntries + // is 32,544 bytes, which leaves 8 bytes for mNext. + nsPurpleBufferEntry mEntries[1365]; + + PurpleBlock() : mNext(nullptr) + { + // Ensure PurpleBlock is the right size (see above). + static_assert( + sizeof(PurpleBlock) == 16384 || // 32-bit + sizeof(PurpleBlock) == 32768, // 64-bit + "ill-sized nsPurpleBuffer::PurpleBlock" + ); + + InitNextPointers(); + } + + // Put all the entries in the block on the free list. + void InitNextPointers() + { + for (uint32_t i = 1; i < ArrayLength(mEntries); ++i) { + mEntries[i - 1].mNextInFreeList = + (nsPurpleBufferEntry*)(uintptr_t(mEntries + i) | 1); + } + mEntries[ArrayLength(mEntries) - 1].mNextInFreeList = + (nsPurpleBufferEntry*)1; + } + + template + void VisitEntries(nsPurpleBuffer& aBuffer, PurpleVisitor& aVisitor) + { + nsPurpleBufferEntry* eEnd = ArrayEnd(mEntries); + for (nsPurpleBufferEntry* e = mEntries; e != eEnd; ++e) { + MOZ_ASSERT(e->mObject, "There should be no null mObject when we iterate over the purple buffer"); + if (!(uintptr_t(e->mObject) & uintptr_t(1)) && e->mObject) { + aVisitor.Visit(aBuffer, e); + } + } + } + }; + // This class wraps a linked list of the elements in the purple + // buffer. + + uint32_t mCount; + PurpleBlock mFirstBlock; + nsPurpleBufferEntry* mFreeList; + +public: + nsPurpleBuffer() + { + InitBlocks(); + } + + ~nsPurpleBuffer() + { + FreeBlocks(); + } + + template + void VisitEntries(PurpleVisitor& aVisitor) + { + for (PurpleBlock* b = &mFirstBlock; b; b = b->mNext) { + b->VisitEntries(*this, aVisitor); + } + } + + void InitBlocks() + { + mCount = 0; + mFreeList = mFirstBlock.mEntries; + } + + void FreeBlocks() + { + if (mCount > 0) { + UnmarkRemainingPurple(&mFirstBlock); + } + PurpleBlock* b = mFirstBlock.mNext; + while (b) { + if (mCount > 0) { + UnmarkRemainingPurple(b); + } + PurpleBlock* next = b->mNext; + delete b; + b = next; + } + mFirstBlock.mNext = nullptr; + } + + struct UnmarkRemainingPurpleVisitor + { + void + Visit(nsPurpleBuffer& aBuffer, nsPurpleBufferEntry* aEntry) + { + if (aEntry->mRefCnt) { + aEntry->mRefCnt->RemoveFromPurpleBuffer(); + aEntry->mRefCnt = nullptr; + } + aEntry->mObject = nullptr; + --aBuffer.mCount; + } + }; + + void UnmarkRemainingPurple(PurpleBlock* aBlock) + { + UnmarkRemainingPurpleVisitor visitor; + aBlock->VisitEntries(*this, visitor); + } + + void SelectPointers(CCGraphBuilder& aBuilder); + + // RemoveSkippable removes entries from the purple buffer synchronously + // (1) if aAsyncSnowWhiteFreeing is false and nsPurpleBufferEntry::mRefCnt is 0 or + // (2) if the object's nsXPCOMCycleCollectionParticipant::CanSkip() returns true or + // (3) if nsPurpleBufferEntry::mRefCnt->IsPurple() is false. + // (4) If removeChildlessNodes 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, + bool aRemoveChildlessNodes, + bool aAsyncSnowWhiteFreeing, + CC_ForgetSkippableCallback aCb); + + MOZ_ALWAYS_INLINE nsPurpleBufferEntry* NewEntry() + { + if (MOZ_UNLIKELY(!mFreeList)) { + PurpleBlock* b = new PurpleBlock; + mFreeList = b->mEntries; + + // Add the new block as the second block in the list. + b->mNext = mFirstBlock.mNext; + mFirstBlock.mNext = b; + } + + nsPurpleBufferEntry* e = mFreeList; + mFreeList = (nsPurpleBufferEntry*) + (uintptr_t(mFreeList->mNextInFreeList) & ~uintptr_t(1)); + return e; + } + + MOZ_ALWAYS_INLINE void Put(void* aObject, nsCycleCollectionParticipant* aCp, + nsCycleCollectingAutoRefCnt* aRefCnt) + { + nsPurpleBufferEntry* e = NewEntry(); + + ++mCount; + + e->mObject = aObject; + e->mRefCnt = aRefCnt; + e->mParticipant = aCp; + } + + void Remove(nsPurpleBufferEntry* aEntry) + { + MOZ_ASSERT(mCount != 0, "must have entries"); + + if (aEntry->mRefCnt) { + aEntry->mRefCnt->RemoveFromPurpleBuffer(); + aEntry->mRefCnt = nullptr; + } + aEntry->mNextInFreeList = + (nsPurpleBufferEntry*)(uintptr_t(mFreeList) | uintptr_t(1)); + mFreeList = aEntry; + + --mCount; + } + + uint32_t Count() const + { + return mCount; + } + + size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const + { + size_t n = 0; + + // Don't measure mFirstBlock because it's within |this|. + const PurpleBlock* block = mFirstBlock.mNext; + while (block) { + n += aMallocSizeOf(block); + block = block->mNext; + } + + // mFreeList is deliberately not measured because it points into + // the purple buffer, which is within mFirstBlock and thus within |this|. + // + // We also don't measure the things pointed to by mEntries[] because + // those pointers are non-owning. + + return n; + } +}; + +static bool +AddPurpleRoot(CCGraphBuilder& aBuilder, void* aRoot, + nsCycleCollectionParticipant* aParti); + +struct SelectPointersVisitor +{ + explicit SelectPointersVisitor(CCGraphBuilder& aBuilder) + : mBuilder(aBuilder) + { + } + + void + 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); + } + } + +private: + CCGraphBuilder& mBuilder; +}; + +void +nsPurpleBuffer::SelectPointers(CCGraphBuilder& aBuilder) +{ + SelectPointersVisitor visitor(aBuilder); + VisitEntries(visitor); + + NS_ASSERTION(mCount == 0, "AddPurpleRoot failed"); + if (mCount == 0) { + FreeBlocks(); + InitBlocks(); + mFirstBlock.InitNextPointers(); + } +} + +enum ccPhase +{ + IdlePhase, + GraphBuildingPhase, + ScanAndCollectWhitePhase, + CleanupPhase +}; + +enum ccType +{ + SliceCC, /* If a CC is in progress, continue it. Otherwise, start a new one. */ + ManualCC, /* Explicitly triggered. */ + ShutdownCC /* Shutdown CC, used for finding leaks. */ +}; + +//////////////////////////////////////////////////////////////////////// +// 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; + + CycleCollectedJSContext* mJSContext; + + ccPhase mIncrementalPhase; + CCGraph mGraph; + nsAutoPtr mBuilder; + RefPtr mLogger; + +#ifdef DEBUG + void* mThread; +#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 RegisterJSContext(CycleCollectedJSContext* aJSContext); + void ForgetJSContext(); + + void SetBeforeUnlinkCallback(CC_BeforeUnlinkCallback aBeforeUnlinkCB) + { + CheckThreadSafety(); + mBeforeUnlinkCB = aBeforeUnlinkCB; + } + + void SetForgetSkippableCallback(CC_ForgetSkippableCallback aForgetSkippableCB) + { + CheckThreadSafety(); + mForgetSkippableCB = aForgetSkippableCB; + } + + void Suspect(void* aPtr, nsCycleCollectionParticipant* aCp, + nsCycleCollectingAutoRefCnt* aRefCnt); + uint32_t SuspectedCount(); + void ForgetSkippable(bool aRemoveChildlessNodes, bool aAsyncSnowWhiteFreeing); + bool FreeSnowWhite(bool aUntilNoSWInPurpleBuffer); + + // This method assumes its argument is already canonicalized. + void RemoveObjectFromGraph(void* aPtr); + + void PrepareForGarbageCollection(); + void FinishAnyCurrentCollection(); + + bool Collect(ccType aCCType, + SliceBudget& aBudget, + nsICycleCollectorListener* aManualListener, + bool aPreferShorterSlices = false); + 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(); + + CycleCollectedJSContext* Context() { return mJSContext; } + +private: + void CheckThreadSafety(); + void ShutdownCollect(); + + void FixGrayBits(bool aForceGC, TimeLog& aTimeLog); + bool IsIncrementalGCInProgress(); + void FinishAnyIncrementalGCInProgress(); + bool ShouldMergeZones(ccType aCCType); + + void BeginCollection(ccType aCCType, 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); +} + +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 = static_cast(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 CancelableRunnable +{ +public: + explicit LogStringMessageAsync(const nsAString& aMsg) : 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, NS_LITERAL_STRING("Garbage")); + return NS_OK; + } + + NS_IMETHOD CloseCCLog() override + { + if (!mCCLog.mStream) { + return NS_ERROR_UNEXPECTED; + } + CloseLog(&mCCLog, NS_LITERAL_STRING("Cycle")); + 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, + NS_LITERAL_CSTRING("memory-reports")); + 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 + + NS_LITERAL_STRING(" Collector log dumped to ") + 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->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: + NS_NOTREACHED("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) + +nsresult +nsCycleCollectorLoggerConstructor(nsISupports* aOuter, + const nsIID& aIID, + void** aInstancePtr) +{ + if (NS_WARN_IF(aOuter)) { + return NS_ERROR_NO_AGGREGATION; + } + + nsISupports* logger = new nsCycleCollectorLogger(); + + return logger->QueryInterface(aIID, aInstancePtr); +} + +static bool +GCThingIsGrayCCThing(JS::GCCellPtr thing) +{ + return AddToCCKind(thing.kind()) && + JS::GCThingIsMarkedGray(thing); +} + +static bool +ValueIsGrayCCThing(const JS::Value& value) +{ + return AddToCCKind(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; + nsAutoPtr mCurrNode; + +public: + CCGraphBuilder(CCGraph& aGraph, + CycleCollectorResults& aResults, + CycleCollectedJSContext* aJSContext, + 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); + +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); + NS_IMETHOD_(void) NoteJSRoot(JSObject* aRoot); + NS_IMETHOD_(void) NoteNativeRoot(void* aRoot, + nsCycleCollectionParticipant* aParticipant); + NS_IMETHOD_(void) NoteWeakMapping(JSObject* aMap, JS::GCCellPtr aKey, + JSObject* aKdelegate, JS::GCCellPtr aVal); + + // nsCycleCollectionTraversalCallback methods. + NS_IMETHOD_(void) DescribeRefCountedNode(nsrefcnt aRefCount, + const char* aObjName); + NS_IMETHOD_(void) DescribeGCedNode(bool aIsMarked, const char* aObjName, + uint64_t aCompartmentAddress); + + NS_IMETHOD_(void) NoteXPCOMChild(nsISupports* aChild); + NS_IMETHOD_(void) NoteJSChild(const JS::GCCellPtr& aThing); + NS_IMETHOD_(void) NoteNativeChild(void* aChild, + nsCycleCollectionParticipant* aParticipant); + NS_IMETHOD_(void) NoteNextEdgeName(const char* aName); + +private: + void NoteJSChild(JS::GCCellPtr aChild); + + 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, + CycleCollectedJSContext* aJSContext, + nsCycleCollectorLogger* aLogger, + bool aMergeZones) + : mGraph(aGraph) + , mResults(aResults) + , mNodeBuilder(aGraph.mNodes) + , mEdgeBuilder(aGraph.mEdges) + , mJSParticipant(nullptr) + , mJSZoneParticipant(nullptr) + , mLogger(aLogger) + , mMergeZones(aMergeZones) +{ + if (aJSContext) { + mJSParticipant = aJSContext->GCThingParticipant(); + mJSZoneParticipant = aJSContext->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() +{ +} + +PtrInfo* +CCGraphBuilder::AddNode(void* aPtr, nsCycleCollectionParticipant* aParticipant) +{ + PtrToNodeEntry* e = mGraph.AddNodeToMap(aPtr); + if (!e) { + return nullptr; + } + + PtrInfo* result; + if (!e->mNode) { + // New entry. + result = mNodeBuilder.Add(aPtr, aParticipant); + if (!result) { + return nullptr; + } + + e->mNode = result; + NS_ASSERTION(result, "mNodeBuilder.Add returned null"); + } else { + result = e->mNode; + MOZ_ASSERT(result->mParticipant == aParticipant, + "nsCycleCollectionParticipant shouldn't change!"); + } + return result; +} + +bool +CCGraphBuilder::AddPurpleRoot(void* aRoot, nsCycleCollectionParticipant* aParti) +{ + CanonicalizeParticipant(&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 = new NodePool::Enumerator(mGraph.mNodes); +} + +MOZ_NEVER_INLINE bool +CCGraphBuilder::BuildGraph(SliceBudget& aBudget) +{ + const intptr_t kNumNodesBetweenTimeChecks = 1000; + const intptr_t kStep = SliceBudget::CounterReset / kNumNodesBetweenTimeChecks; + + MOZ_ASSERT(mCurrNode); + + while (!aBudget.isOverBudget() && !mCurrNode->IsDone()) { + 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->Traverse(pi->mPointer, *this); + MOZ_RELEASE_ASSERT(!NS_FAILED(rv), "Cycle collector Traverse method failed"); + } + + if (mCurrNode->AtBlockEnd()) { + SetLastChild(); + } + + aBudget.step(kStep); + } + + if (!mCurrNode->IsDone()) { + return false; + } + + if (mGraph.mRootCount > 0) { + SetLastChild(); + } + + mCurrNode = nullptr; + + return true; +} + +NS_IMETHODIMP_(void) +CCGraphBuilder::NoteXPCOMRoot(nsISupports* aRoot) +{ + aRoot = CanonicalizeXPCOMParticipant(aRoot); + NS_ASSERTION(aRoot, + "Don't add objects that don't participate in collection!"); + + nsXPCOMCycleCollectionParticipant* cp; + ToParticipant(aRoot, &cp); + + NoteRoot(aRoot, cp); +} + +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) +{ + MOZ_RELEASE_ASSERT(aRefCount != 0, "CCed refcounted object has zero refcount"); + MOZ_RELEASE_ASSERT(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; + } + + 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; + } + + MOZ_ASSERT(aParticipant, "Need a nsCycleCollectionParticipant!"); + if (!aParticipant->CanSkipThis(aChild) || WantAllTraces()) { + NoteChild(aChild, aParticipant, edgeName); + } +} + +NS_IMETHODIMP_(void) +CCGraphBuilder::NoteJSChild(const JS::GCCellPtr& aChild) +{ + if (!aChild) { + return; + } + + 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); + } +} + +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); + NS_IMETHOD_(void) NoteNativeChild(void* aChild, + nsCycleCollectionParticipant* aHelper); + NS_IMETHOD_(void) NoteJSChild(const JS::GCCellPtr& aThing); + + NS_IMETHOD_(void) DescribeRefCountedNode(nsrefcnt aRefcount, + const char* aObjname) + { + } + NS_IMETHOD_(void) DescribeGCedNode(bool aIsMarked, + const char* aObjname, + uint64_t aCompartmentAddress) + { + } + NS_IMETHOD_(void) NoteNextEdgeName(const char* aName) + { + } + 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(const JS::GCCellPtr& aChild) +{ + if (aChild && JS::GCThingIsMarkedGray(aChild)) { + mMayHaveChild = true; + } +} + +static bool +MayHaveChild(void* aObj, nsCycleCollectionParticipant* aCp) +{ + ChildFinder cf; + aCp->Traverse(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() + { + mReferenceToThis = nullptr; + 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_SCRIPT_OBJECTS +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 + +NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(JSPurpleBuffer, AddRef) +NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(JSPurpleBuffer, Release) + +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: + explicit SnowWhiteKiller(nsCycleCollector* aCollector) + : mCollector(aCollector) + , mObjects(kSegmentSize) + { + MOZ_ASSERT(mCollector, "Calling SnowWhiteKiller after nsCC went away"); + } + + ~SnowWhiteKiller() + { + for (auto iter = mObjects.Iter(); !iter.Done(); iter.Next()) { + SnowWhiteObject& o = iter.Get(); + if (!o.mRefCnt->get() && !o.mRefCnt->IsInPurpleBuffer()) { + mCollector->RemoveObjectFromGraph(o.mPointer); + o.mRefCnt->stabilizeForDeletion(); + { + JS::AutoEnterCycleCollection autocc(mCollector->Context()->Context()); + o.mParticipant->Trace(o.mPointer, *this, nullptr); + } + o.mParticipant->DeleteCycleCollectable(o.mPointer); + } + } + } + + void + Visit(nsPurpleBuffer& aBuffer, nsPurpleBufferEntry* aEntry) + { + MOZ_ASSERT(aEntry->mObject, "Null object in purple buffer"); + if (!aEntry->mRefCnt->get()) { + void* o = aEntry->mObject; + nsCycleCollectionParticipant* cp = aEntry->mParticipant; + CanonicalizeParticipant(&o, &cp); + SnowWhiteObject swo = { o, cp, aEntry->mRefCnt }; + mObjects.InfallibleAppend(swo); + aBuffer.Remove(aEntry); + } + } + + bool HasSnowWhiteObjects() const + { + return !mObjects.IsEmpty(); + } + + virtual void Trace(JS::Heap* aValue, const char* aName, + void* aClosure) const override + { + const JS::Value& val = aValue->unbarrieredGet(); + if (val.isMarkable() && 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(JSObject** aObject, const char* aName, + void* aClosure) const override + { + AppendJSObjectToPurpleBuffer(*aObject); + } + + 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; +}; + +class RemoveSkippableVisitor : public SnowWhiteKiller +{ +public: + RemoveSkippableVisitor(nsCycleCollector* aCollector, + bool aRemoveChildlessNodes, + bool aAsyncSnowWhiteFreeing, + CC_ForgetSkippableCallback aCb) + : SnowWhiteKiller(aCollector) + , 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); + } + } + + void + Visit(nsPurpleBuffer& aBuffer, nsPurpleBufferEntry* aEntry) + { + 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; + } + void* o = aEntry->mObject; + nsCycleCollectionParticipant* cp = aEntry->mParticipant; + CanonicalizeParticipant(&o, &cp); + if (aEntry->mRefCnt->IsPurple() && !cp->CanSkip(o, false) && + (!mRemoveChildlessNodes || MayHaveChild(o, cp))) { + return; + } + aBuffer.Remove(aEntry); + } + +private: + bool mRemoveChildlessNodes; + bool mAsyncSnowWhiteFreeing; + bool mDispatchedDeferredDeletion; + CC_ForgetSkippableCallback mCallback; +}; + +void +nsPurpleBuffer::RemoveSkippable(nsCycleCollector* aCollector, + bool aRemoveChildlessNodes, + bool aAsyncSnowWhiteFreeing, + CC_ForgetSkippableCallback aCb) +{ + RemoveSkippableVisitor visitor(aCollector, aRemoveChildlessNodes, + aAsyncSnowWhiteFreeing, aCb); + VisitEntries(visitor); +} + +bool +nsCycleCollector::FreeSnowWhite(bool aUntilNoSWInPurpleBuffer) +{ + CheckThreadSafety(); + + if (mFreeingSnowWhite) { + return false; + } + + 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; +} + +void +nsCycleCollector::ForgetSkippable(bool aRemoveChildlessNodes, + bool aAsyncSnowWhiteFreeing) +{ + CheckThreadSafety(); + + 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 (mJSContext) { + mJSContext->PrepareForForgetSkippable(); + } + MOZ_ASSERT(!mScanInProgress, + "Don't forget skippable or free snow-white while scan is in progress."); + mPurpleBuf.RemoveSkippable(this, 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); + + JS::AutoEnterCycleCollection autocc(Context()->Context()); + 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) + { + } + + void + 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; + if (!aEntry->mParticipant) { + obj = CanonicalizeXPCOMParticipant(static_cast(obj)); + MOZ_ASSERT(obj, "Don't add objects that don't participate in collection!"); + } + + PtrInfo* pi = mGraph.FindNode(obj); + if (!pi) { + return; + } + 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; + } + FloodBlackNode(mCount, mFailed, pi); + } + +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 hasJSContext = !!mJSContext; + nsCycleCollectionParticipant* jsParticipant = + hasJSContext ? mJSContext->GCThingParticipant() : nullptr; + nsCycleCollectionParticipant* zoneParticipant = + hasJSContext ? mJSContext->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(hasJSContext)) { + // 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; + } + + if (pi->mInternalRefs > pi->mRefCount) { +#ifdef MOZ_CRASHREPORTER + const char* piName = "Unknown"; + if (pi->mParticipant) { + piName = pi->mParticipant->ClassName(); + } + nsPrintfCString msg("More references to an object than its refcount, for class %s", piName); + CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("CycleCollector"), msg); +#endif + MOZ_CRASH(); + } + + // 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(Context()->Context()); + + 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 hasJSContext = !!mJSContext; + nsCycleCollectionParticipant* zoneParticipant = + hasJSContext ? mJSContext->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(mJSContext); + ++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); + } + mJSContext->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 (mJSContext) { + mJSContext->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), + mJSContext(nullptr), + mIncrementalPhase(IdlePhase), +#ifdef DEBUG + mThread(NS_GetCurrentThread()), +#endif + mWhiteNodeCount(0), + mBeforeUnlinkCB(nullptr), + mForgetSkippableCB(nullptr), + mUnmergedNeeded(0), + mMergedInARow(0) +{ +} + +nsCycleCollector::~nsCycleCollector() +{ + UnregisterWeakMemoryReporter(this); +} + +void +nsCycleCollector::RegisterJSContext(CycleCollectedJSContext* aJSContext) +{ + MOZ_RELEASE_ASSERT(!mJSContext, "Multiple registrations of JS context in cycle collector"); + mJSContext = aJSContext; + + 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::ForgetJSContext() +{ + MOZ_RELEASE_ASSERT(mJSContext, "Forgetting JS context in cycle collector before a JS context was registered"); + mJSContext = 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"); + + mPurpleBuf.Put(aPtr, aParti, aRefCnt); +} + +void +nsCycleCollector::CheckThreadSafety() +{ +#ifdef DEBUG + nsIThread* currentThread = NS_GetCurrentThread(); + // XXXkhuey we can be called so late in shutdown that NS_GetCurrentThread + // returns null (after the thread manager has shut down) + MOZ_ASSERT(mThread == currentThread || !currentThread); +#endif +} + +// The cycle collector uses the mark bitmap to discover what JS objects +// were reachable only from XPConnect roots that might participate in +// cycles. We ask the JS context whether we need to force a GC before +// this CC. It returns true on startup (before the mark bits have been set), +// and also when UnmarkGray has run out of stack. We also force GCs on shut +// down to collect cycles involving both DOM and JS. +void +nsCycleCollector::FixGrayBits(bool aForceGC, TimeLog& aTimeLog) +{ + CheckThreadSafety(); + + if (!mJSContext) { + return; + } + + if (!aForceGC) { + mJSContext->FixWeakMappingGrayBits(); + aTimeLog.Checkpoint("FixWeakMappingGrayBits"); + + bool needGC = !mJSContext->AreGCGrayBitsValid(); + // Only do a telemetry ping for non-shutdown CCs. + CC_TELEMETRY(_NEED_GC, needGC); + if (!needGC) { + return; + } + mResults.mForcedGC = true; + } + + mJSContext->GarbageCollect(aForceGC ? JS::gcreason::SHUTDOWN_CC : + JS::gcreason::CC_FORCED); + aTimeLog.Checkpoint("FixGrayBits GC"); +} + +bool +nsCycleCollector::IsIncrementalGCInProgress() +{ + return mJSContext && JS::IsIncrementalGCInProgress(mJSContext->Context()); +} + +void +nsCycleCollector::FinishAnyIncrementalGCInProgress() +{ + if (IsIncrementalGCInProgress()) { + NS_WARNING("Finishing incremental GC in progress during CC"); + JS::PrepareForIncrementalGC(mJSContext->Context()); + JS::FinishIncrementalGC(mJSContext->Context(), JS::gcreason::CC_FORCED); + } +} + +void +nsCycleCollector::CleanupAfterCollection() +{ + TimeLog timeLog; + MOZ_ASSERT(mIncrementalPhase == CleanupPhase); + 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 (mJSContext) { + mJSContext->FinalizeDeferredThings(mResults.mAnyManual + ? CycleCollectedJSContext::FinalizeNow + : CycleCollectedJSContext::FinalizeIncrementally); + mJSContext->EndCycleCollectionCallback(mResults); + timeLog.Checkpoint("CleanupAfterCollection::EndCycleCollectionCallback()"); + } + mIncrementalPhase = IdlePhase; +} + +void +nsCycleCollector::ShutdownCollect() +{ + FinishAnyIncrementalGCInProgress(); + + SliceBudget unlimitedBudget = SliceBudget::unlimited(); + uint32_t i; + for (i = 0; i < DEFAULT_SHUTDOWN_COLLECTIONS; ++i) { + if (!Collect(ShutdownCC, unlimitedBudget, nullptr)) { + break; + } + } + 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(ccType aCCType, + SliceBudget& aBudget, + nsICycleCollectorListener* aManualListener, + bool aPreferShorterSlices) +{ + 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 (aCCType != SliceCC) { + mResults.mAnyManual = true; + } + + ++mResults.mNumSlices; + + bool continueSlice = aBudget.isUnlimited() || !aPreferShorterSlices; + do { + switch (mIncrementalPhase) { + case IdlePhase: + PrintPhase("BeginCollection"); + BeginCollection(aCCType, 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. + PrintPhase("ScanRoots"); + ScanRoots(startedIdle); + PrintPhase("CollectWhite"); + collectedAny = CollectWhite(); + break; + case CleanupPhase: + PrintPhase("CleanupAfterCollection"); + CleanupAfterCollection(); + continueSlice = false; + break; + } + if (continueSlice) { + // Force SliceBudget::isOverBudget to check the time. + aBudget.step(SliceBudget::CounterReset); + continueSlice = !aBudget.isOverBudget(); + } + } while (continueSlice); + + // Clear mActivelyCollecting here to ensure that a recursive call to + // Collect() does something. + mActivelyCollecting = false; + + if (aCCType != SliceCC && !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(aCCType, aBudget, aManualListener)) { + collectedAny = true; + } + } + + MOZ_ASSERT_IF(aCCType != SliceCC, 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(); +} + +void +nsCycleCollector::FinishAnyCurrentCollection() +{ + if (IsIdle()) { + return; + } + + SliceBudget unlimitedBudget = SliceBudget::unlimited(); + PrintPhase("FinishAnyCurrentCollection"); + // Use SliceCC because we only want to finish the CC in progress. + Collect(SliceCC, 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(ccType aCCType) +{ + if (!mJSContext) { + 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 (aCCType == SliceCC && mJSContext->UsefulToMergeZones()) { + mMergedInARow++; + return true; + } else { + mMergedInARow = 0; + return false; + } +} + +void +nsCycleCollector::BeginCollection(ccType aCCType, + nsICycleCollectorListener* aManualListener) +{ + TimeLog timeLog; + MOZ_ASSERT(IsIdle()); + + mCollectionStart = TimeStamp::Now(); + + if (mJSContext) { + mJSContext->BeginCycleCollectionCallback(); + timeLog.Checkpoint("BeginCycleCollectionCallback()"); + } + + bool isShutdown = (aCCType == ShutdownCC); + + // 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(isShutdown)) { + mLogger = new nsCycleCollectorLogger(); + if (mParams.AllTracesThisCC(isShutdown)) { + mLogger->SetAllTraces(); + } + } + + // On a WantAllTraces CC, force a synchronous global GC to prevent + // hijinks from ForgetSkippable and compartmental GCs. + bool forceGC = isShutdown || (mLogger && mLogger->IsAllTraces()); + + // BeginCycleCollectionCallback() might have started an IGC, and we need + // to finish it before we run FixGrayBits. + FinishAnyIncrementalGCInProgress(); + timeLog.Checkpoint("Pre-FixGrayBits finish IGC"); + + FixGrayBits(forceGC, timeLog); + + 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(mJSContext->Context()); + mGraph.Init(); + mResults.Init(); + mResults.mAnyManual = (aCCType != SliceCC); + bool mergeZones = ShouldMergeZones(aCCType); + mResults.mMergedZones = mergeZones; + + MOZ_ASSERT(!mBuilder, "Forgot to clear mBuilder"); + mBuilder = new CCGraphBuilder(mGraph, mResults, mJSContext, mLogger, + mergeZones); + timeLog.Checkpoint("BeginCollection prepare graph builder"); + + if (mJSContext) { + mJSContext->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(); + return mPurpleBuf.Count(); +} + +void +nsCycleCollector::Shutdown(bool aDoCollect) +{ + CheckThreadSafety(); + + // Always delete snow white objects. + FreeSnowWhite(true); + + if (aDoCollect) { + ShutdownCollect(); + } +} + +void +nsCycleCollector::RemoveObjectFromGraph(void* aObj) +{ + if (IsIdle()) { + return; + } + + mGraph.RemoveObjectFromMap(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: + // - mJSContext: 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->RegisterJSContext(aCx); +} + +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->ForgetJSContext(); + 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. + CanonicalizeParticipant(&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(); + + // We should have started the cycle collector by now. + MOZ_ASSERT(data); + + if (MOZ_LIKELY(data->mCollector)) { + data->mCollector->Suspect(aPtr, aCp, aRefCnt); + return; + } + SuspectAfterShutdown(aPtr, aCp, aRefCnt, aShouldDelete); +} + +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(bool aRemoveChildlessNodes, + bool aAsyncSnowWhiteFreeing) +{ + CollectorData* data = sCollectorData.get(); + + // We should have started the cycle collector by now. + MOZ_ASSERT(data); + MOZ_ASSERT(data->mCollector); + + PROFILER_LABEL("nsCycleCollector", "forgetSkippable", + js::ProfileEntry::Category::CC); + + TimeLog timeLog; + data->mCollector->ForgetSkippable(aRemoveChildlessNodes, + aAsyncSnowWhiteFreeing); + timeLog.Checkpoint("ForgetSkippable()"); +} + +void +nsCycleCollector_dispatchDeferredDeletion(bool aContinuation, bool aPurge) +{ + CycleCollectedJSContext* cx = CycleCollectedJSContext::Get(); + if (cx) { + cx->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); +} + +already_AddRefed +nsCycleCollector_createLogSink() +{ + nsCOMPtr sink = new nsCycleCollectorLogSinkToFile(); + return sink.forget(); +} + +void +nsCycleCollector_collect(nsICycleCollectorListener* aManualListener) +{ + CollectorData* data = sCollectorData.get(); + + // We should have started the cycle collector by now. + MOZ_ASSERT(data); + MOZ_ASSERT(data->mCollector); + + PROFILER_LABEL("nsCycleCollector", "collect", + js::ProfileEntry::Category::CC); + + SliceBudget unlimitedBudget = SliceBudget::unlimited(); + data->mCollector->Collect(ManualCC, unlimitedBudget, aManualListener); +} + +void +nsCycleCollector_collectSlice(SliceBudget& budget, + bool aPreferShorterSlices) +{ + CollectorData* data = sCollectorData.get(); + + // We should have started the cycle collector by now. + MOZ_ASSERT(data); + MOZ_ASSERT(data->mCollector); + + PROFILER_LABEL("nsCycleCollector", "collectSlice", + js::ProfileEntry::Category::CC); + + data->mCollector->Collect(SliceCC, 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(); +} + +void +nsCycleCollector_shutdown(bool aDoCollect) +{ + CollectorData* data = sCollectorData.get(); + + if (data) { + MOZ_ASSERT(data->mCollector); + PROFILER_LABEL("nsCycleCollector", "shutdown", + js::ProfileEntry::Category::CC); + + data->mCollector->Shutdown(aDoCollect); + data->mCollector = nullptr; + if (data->mContext) { + // Run any remaining tasks that may have been enqueued via + // RunInStableState during the final cycle collection. + data->mContext->ProcessStableStateQueue(); + } + 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 000000000..cd3fff406 --- /dev/null +++ b/xpcom/base/nsCycleCollector.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 nsCycleCollector_h__ +#define nsCycleCollector_h__ + +class nsICycleCollectorListener; +class nsICycleCollectorLogSink; +class nsISupports; +template struct already_AddRefed; + +#include "nsError.h" +#include "nsID.h" + +#include "js/SliceBudget.h" + +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(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(); + +already_AddRefed nsCycleCollector_createLogSink(); + +void nsCycleCollector_collect(nsICycleCollectorListener* aManualListener); + +void nsCycleCollector_collectSlice(js::SliceBudget& budget, + 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. +void nsCycleCollector_shutdown(bool aDoCollect = true); + +// Helpers for interacting with JS +void nsCycleCollector_registerJSContext(mozilla::CycleCollectedJSContext* aCx); +void nsCycleCollector_forgetJSContext(); + +#define NS_CYCLE_COLLECTOR_LOGGER_CID \ +{ 0x58be81b4, 0x39d2, 0x437c, \ +{ 0x94, 0xea, 0xae, 0xde, 0x2c, 0x62, 0x08, 0xd3 } } + +extern nsresult +nsCycleCollectorLoggerConstructor(nsISupports* aOuter, + const nsIID& aIID, + void** aInstancePtr); + +#endif // nsCycleCollector_h__ diff --git a/xpcom/base/nsCycleCollectorTraceJSHelpers.cpp b/xpcom/base/nsCycleCollectorTraceJSHelpers.cpp new file mode 100644 index 000000000..eb06a389c --- /dev/null +++ b/xpcom/base/nsCycleCollectorTraceJSHelpers.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 "nsCycleCollectionParticipant.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 +nsScriptObjectTracer::NoteJSChild(JS::GCCellPtr aGCThing, const char* aName, + void* aClosure) +{ + nsCycleCollectionTraversalCallback* cb = + static_cast(aClosure); + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*cb, aName); + if (mozilla::AddToCCKind(aGCThing.kind())) { + cb->NoteJSChild(aGCThing); + } +} + +void +TraceCallbackFunc::Trace(JS::Heap* aPtr, const char* aName, + void* aClosure) const +{ + if (aPtr->unbarrieredGet().isMarkable()) { + mCallback(JS::GCCellPtr(aPtr->unbarrieredGet()), aName, aClosure); + } +} + +void +TraceCallbackFunc::Trace(JS::Heap* aPtr, const char* aName, + void* aClosure) const +{ + if (JSID_IS_GCTHING(aPtr->unbarrieredGet())) { + mCallback(JSID_TO_GCTHING(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(JSObject** aPtr, const char* aName, + void* aClosure) const +{ + if (*aPtr) { + mCallback(JS::GCCellPtr(*aPtr), 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/nsDebugImpl.cpp b/xpcom/base/nsDebugImpl.cpp new file mode 100644 index 000000000..36288d203 --- /dev/null +++ b/xpcom/base/nsDebugImpl.cpp @@ -0,0 +1,607 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 "nsDebugImpl.h" +#include "nsDebug.h" +#ifdef MOZ_CRASHREPORTER +# include "nsExceptionHandler.h" +#endif +#include "nsString.h" +#include "nsXULAppAPI.h" +#include "prprf.h" +#include "nsError.h" +#include "prerror.h" +#include "prerr.h" +#include "prenv.h" + +#ifdef ANDROID +#include +#endif + +#ifdef _WIN32 +/* for getenv() */ +#include +#endif + +#include "nsTraceRefcnt.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 + +#include "mozilla/mozalloc_abort.h" + +static void +Abort(const char* aMsg); + +static void +RealBreak(); + +static void +Break(const char* aMsg); + +#if defined(_WIN32) +#include +#include +#include // for _alloca +#elif defined(XP_UNIX) +#include +#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::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(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; +} + +/** + * 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 +{ + FixedBuffer() : curlen(0) + { + buffer[0] = '\0'; + } + + char buffer[500]; + uint32_t curlen; +}; + +static int +StuffFixedBuffer(void* aClosure, const char* aBuf, uint32_t aLen) +{ + if (!aLen) { + return 0; + } + + FixedBuffer* fb = (FixedBuffer*)aClosure; + + // strip the trailing null, we add it again later + if (aBuf[aLen - 1] == '\0') { + --aLen; + } + + if (fb->curlen + aLen >= sizeof(fb->buffer)) { + aLen = sizeof(fb->buffer) - fb->curlen - 1; + } + + if (aLen) { + memcpy(fb->buffer + fb->curlen, aBuf, aLen); + fb->curlen += aLen; + fb->buffer[fb->curlen] = '\0'; + } + + return aLen; +} + +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; + } + +#define PRINT_TO_NONPID_BUFFER(...) PR_sxprintf(StuffFixedBuffer, &nonPIDBuf, __VA_ARGS__) + PRINT_TO_NONPID_BUFFER("%s: ", sevString); + if (aStr) { + PRINT_TO_NONPID_BUFFER("%s: ", aStr); + } + if (aExpr) { + PRINT_TO_NONPID_BUFFER("'%s', ", aExpr); + } + if (aFile) { + PRINT_TO_NONPID_BUFFER("file %s, ", aFile); + } + if (aLine != -1) { + PRINT_TO_NONPID_BUFFER("line %d", aLine); + } +#undef PRINT_TO_NONPID_BUFFER + + // Print "[PID]" or "[Desc PID]" at the beginning of the message. +#define PRINT_TO_BUFFER(...) PR_sxprintf(StuffFixedBuffer, &buf, __VA_ARGS__) + PRINT_TO_BUFFER("["); + if (sMultiprocessDescription) { + PRINT_TO_BUFFER("%s ", sMultiprocessDescription); + } + PRINT_TO_BUFFER("%d] %s", base::GetCurrentProcId(), nonPIDBuf.buffer); +#undef PRINT_TO_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 + + // 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: { +#if defined(MOZ_CRASHREPORTER) + // 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(NS_LITERAL_CSTRING("AbortMessage"), + nsDependentCString(nonPIDBuf.buffer)); + } +#endif // MOZ_CRASHREPORTER + +#if defined(DEBUG) && defined(_WIN32) + RealBreak(); +#endif +#if defined(DEBUG) + nsTraceRefcnt::WalkTheStack(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: + nsTraceRefcnt::WalkTheStack(stderr); + return; + + case NS_ASSERT_STACK_AND_ABORT: + nsTraceRefcnt::WalkTheStack(stderr); + // Fall through to abort + MOZ_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) +{ + mozalloc_abort(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(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__) + RealBreak(); +#elif defined(SOLARIS) + RealBreak(); +#else +#warning do not know how to break on this platform +#endif +} + +nsresult +nsDebugImpl::Create(nsISupports* aOuter, const nsIID& aIID, void** aInstancePtr) +{ + static const nsDebugImpl* sImpl; + + if (NS_WARN_IF(aOuter)) { + return NS_ERROR_NO_AGGREGATION; + } + + 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) +{ +#if defined(MOZ_CRASHREPORTER) + CrashReporter::AnnotateOOMAllocationSize(aSize); +#endif + MOZ_CRASH("OOM"); +} diff --git a/xpcom/base/nsDebugImpl.h b/xpcom/base/nsDebugImpl.h new file mode 100644 index 000000000..23680238a --- /dev/null +++ b/xpcom/base/nsDebugImpl.h @@ -0,0 +1,41 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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(nsISupports* aOuter, const nsIID& aIID, + void** aInstancePtr); + + /* + * 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 000000000..c68862d08 --- /dev/null +++ b/xpcom/base/nsDumpUtils.cpp @@ -0,0 +1,513 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. 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 "prenv.h" +#include +#include "mozilla/Services.h" +#include "nsIObserverService.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/Unused.h" + +#ifdef XP_UNIX // { +#include "mozilla/Preferences.h" +#include +#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* const 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(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() +{ +} + +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(NS_LITERAL_CSTRING("debug_info_trigger")); + 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) { + rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, 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 != EmptyCString()) { + 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 000000000..12a99da18 --- /dev/null +++ b/xpcom/base/nsDumpUtils.h @@ -0,0 +1,203 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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(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. + */ + static const char* const kPrefName; + + static FifoWatcher* GetSingleton(); + + static bool MaybeCreate(); + + void RegisterCallback(const nsCString& aCommand, FifoCallback aCallback); + + virtual ~FifoWatcher(); + + virtual int OpenFd(); + + virtual void OnFileCanReadWithoutBlocking(int aFd); + +private: + nsAutoCString mDirPath; + + static mozilla::StaticRefPtr sSingleton; + + explicit FifoWatcher(nsCString aPath) + : mDirPath(aPath) + , mFifoInfoLock("FifoWatcher.mFifoInfoLock") + { + } + + mozilla::Mutex mFifoInfoLock; // protects mFifoInfo + FifoInfoArray mFifoInfo; +}; + +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(); + + virtual void StopWatching(); + + virtual void OnFileCanReadWithoutBlocking(int aFd); + +private: + static mozilla::StaticRefPtr sSingleton; + + SignalPipeWatcher() + : mSignalInfoLock("SignalPipeWatcher.mSignalInfoLock") + { + MOZ_ASSERT(NS_IsMainThread()); + } + + mozilla::Mutex mSignalInfoLock; // protects mSignalInfo + SignalInfoArray mSignalInfo; +}; + +#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 = EmptyCString(), + Mode aMode = CREATE_UNIQUE); +}; + +#endif diff --git a/xpcom/base/nsError.h b/xpcom/base/nsError.h new file mode 100644 index 000000000..b9e5d23f6 --- /dev/null +++ b/xpcom/base/nsError.h @@ -0,0 +1,219 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 + +/* + * 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. + */ + +#define NS_ERROR_MODULE_XPCOM 1 +#define NS_ERROR_MODULE_BASE 2 +#define NS_ERROR_MODULE_GFX 3 +#define NS_ERROR_MODULE_WIDGET 4 +#define NS_ERROR_MODULE_CALENDAR 5 +#define NS_ERROR_MODULE_NETWORK 6 +#define NS_ERROR_MODULE_PLUGINS 7 +#define NS_ERROR_MODULE_LAYOUT 8 +#define NS_ERROR_MODULE_HTMLPARSER 9 +#define NS_ERROR_MODULE_RDF 10 +#define NS_ERROR_MODULE_UCONV 11 +#define NS_ERROR_MODULE_REG 12 +#define NS_ERROR_MODULE_FILES 13 +#define NS_ERROR_MODULE_DOM 14 +#define NS_ERROR_MODULE_IMGLIB 15 +#define NS_ERROR_MODULE_MAILNEWS 16 +#define NS_ERROR_MODULE_EDITOR 17 +#define NS_ERROR_MODULE_XPCONNECT 18 +#define NS_ERROR_MODULE_PROFILE 19 +#define NS_ERROR_MODULE_LDAP 20 +#define NS_ERROR_MODULE_SECURITY 21 +#define NS_ERROR_MODULE_DOM_XPATH 22 +/* 23 used to be NS_ERROR_MODULE_DOM_RANGE (see bug 711047) */ +#define NS_ERROR_MODULE_URILOADER 24 +#define NS_ERROR_MODULE_CONTENT 25 +#define NS_ERROR_MODULE_PYXPCOM 26 +#define NS_ERROR_MODULE_XSLT 27 +#define NS_ERROR_MODULE_IPC 28 +#define NS_ERROR_MODULE_SVG 29 +#define NS_ERROR_MODULE_STORAGE 30 +#define NS_ERROR_MODULE_SCHEMA 31 +#define NS_ERROR_MODULE_DOM_FILE 32 +#define NS_ERROR_MODULE_DOM_INDEXEDDB 33 +#define NS_ERROR_MODULE_DOM_FILEHANDLE 34 +#define NS_ERROR_MODULE_SIGNED_JAR 35 +#define NS_ERROR_MODULE_DOM_FILESYSTEM 36 +#define NS_ERROR_MODULE_DOM_BLUETOOTH 37 +#define NS_ERROR_MODULE_SIGNED_APP 38 +#define NS_ERROR_MODULE_DOM_ANIM 39 +#define NS_ERROR_MODULE_DOM_PUSH 40 +#define NS_ERROR_MODULE_DOM_MEDIA 41 + +/* 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. + */ +#define NS_ERROR_MODULE_GENERAL 51 + +/** + * @name Severity Code. This flag identifies the level of warning + */ + +#define NS_ERROR_SEVERITY_SUCCESS 0 +#define NS_ERROR_SEVERITY_ERROR 1 + +/** + * @name Mozilla Code. This flag separates consumers of mozilla code + * from the native platform + */ + +#define NS_ERROR_MODULE_BASE_OFFSET 0x45 + +/* Helpers for defining our enum, to be undef'd later */ +#define SUCCESS_OR_FAILURE(sev, module, code) \ + ((uint32_t)(sev) << 31) | \ + ((uint32_t)(module + NS_ERROR_MODULE_BASE_OFFSET) << 16) | \ + (uint32_t)(code) +#define SUCCESS(code) \ + SUCCESS_OR_FAILURE(NS_ERROR_SEVERITY_SUCCESS, MODULE, code) +#define FAILURE(code) \ + SUCCESS_OR_FAILURE(NS_ERROR_SEVERITY_ERROR, MODULE, code) + +/** + * @name Standard return values + */ + +/*@{*/ + +enum class nsresult : uint32_t +{ + #undef ERROR + #define ERROR(key, val) key = val + #include "ErrorList.h" + #undef ERROR +}; + +/* + * enum classes don't place their initializers in the global scope, so we need + * constants for compatibility with old code. + */ +const nsresult + #define ERROR(key, val) key = nsresult::key + #include "ErrorList.h" + #undef ERROR +; + +#undef SUCCESS_OR_FAILURE +#undef SUCCESS +#undef FAILURE + +/** + * @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/nsErrorService.cpp b/xpcom/base/nsErrorService.cpp new file mode 100644 index 000000000..d39b4f31e --- /dev/null +++ b/xpcom/base/nsErrorService.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 "nsErrorService.h" +#include "nsCRTGlue.h" +#include "nsAutoPtr.h" + +NS_IMPL_ISUPPORTS(nsErrorService, nsIErrorService) + +nsresult +nsErrorService::Create(nsISupports* aOuter, const nsIID& aIID, + void** aInstancePtr) +{ + if (NS_WARN_IF(aOuter)) { + return NS_ERROR_NO_AGGREGATION; + } + RefPtr serv = new nsErrorService(); + return serv->QueryInterface(aIID, aInstancePtr); +} + +NS_IMETHODIMP +nsErrorService::RegisterErrorStringBundle(int16_t aErrorModule, + const char* aStringBundleURL) +{ + mErrorStringBundleURLMap.Put(aErrorModule, new nsCString(aStringBundleURL)); + return NS_OK; +} + +NS_IMETHODIMP +nsErrorService::UnregisterErrorStringBundle(int16_t aErrorModule) +{ + mErrorStringBundleURLMap.Remove(aErrorModule); + return NS_OK; +} + +NS_IMETHODIMP +nsErrorService::GetErrorStringBundle(int16_t aErrorModule, char** aResult) +{ + nsCString* bundleURL = mErrorStringBundleURLMap.Get(aErrorModule); + if (!bundleURL) { + return NS_ERROR_FAILURE; + } + *aResult = ToNewCString(*bundleURL); + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/xpcom/base/nsErrorService.h b/xpcom/base/nsErrorService.h new file mode 100644 index 000000000..783c99ef9 --- /dev/null +++ b/xpcom/base/nsErrorService.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 nsErrorService_h__ +#define nsErrorService_h__ + +#include "mozilla/Attributes.h" + +#include "nsIErrorService.h" +#include "nsClassHashtable.h" +#include "nsHashKeys.h" + +class nsErrorService final : public nsIErrorService +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIERRORSERVICE + + nsErrorService() + { + } + + static nsresult + Create(nsISupports* aOuter, const nsIID& aIID, void** aInstancePtr); + +private: + ~nsErrorService() + { + } + + nsClassHashtable mErrorStringBundleURLMap; +}; + +#endif // nsErrorService_h__ diff --git a/xpcom/base/nsGZFileWriter.cpp b/xpcom/base/nsGZFileWriter.cpp new file mode 100644 index 000000000..a5bc5be39 --- /dev/null +++ b/xpcom/base/nsGZFileWriter.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 "nsGZFileWriter.h" +#include "nsIFile.h" +#include "nsString.h" +#include "zlib.h" + +#ifdef XP_WIN +#include +#define _dup dup +#else +#include +#endif + +NS_IMPL_ISUPPORTS(nsGZFileWriter, nsIGZFileWriter) + +nsGZFileWriter::nsGZFileWriter(Operation aMode) + : mMode(aMode) + , mInitialized(false) + , mFinished(false) +{ +} + +nsGZFileWriter::~nsGZFileWriter() +{ + if (mInitialized && !mFinished) { + Finish(); + } +} + +NS_IMETHODIMP +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); +} + +NS_IMETHODIMP +nsGZFileWriter::InitANSIFileDesc(FILE* aFile) +{ + 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; +} + +NS_IMETHODIMP +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; +} + +NS_IMETHODIMP +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 000000000..4bb91173c --- /dev/null +++ b/xpcom/base/nsGZFileWriter.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 nsGZFileWriter_h +#define nsGZFileWriter_h + +#include "nsIGZFileWriter.h" +#include "zlib.h" + +/** + * A simple class for writing .gz files. + */ +class nsGZFileWriter final : public nsIGZFileWriter +{ + virtual ~nsGZFileWriter(); + +public: + + enum Operation { + Append, + Create + }; + + + explicit nsGZFileWriter(Operation aMode = Create); + + NS_DECL_ISUPPORTS + NS_DECL_NSIGZFILEWRITER + + /** + * nsIGZFileWriter exposes two non-virtual overloads of Write(). We + * duplicate them here so that you can call these overloads on a pointer to + * the concrete nsGZFileWriter class. + */ + MOZ_MUST_USE nsresult Write(const char* aStr) + { + return nsIGZFileWriter::Write(aStr); + } + + MOZ_MUST_USE nsresult Write(const char* aStr, uint32_t aLen) + { + return nsIGZFileWriter::Write(aStr, aLen); + } + +private: + Operation mMode; + bool mInitialized; + bool mFinished; + gzFile mGZFile; +}; + +#endif diff --git a/xpcom/base/nsIConsoleListener.idl b/xpcom/base/nsIConsoleListener.idl new file mode 100644 index 000000000..45e6fcb1f --- /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 000000000..bf233b28b --- /dev/null +++ b/xpcom/base/nsIConsoleMessage.idl @@ -0,0 +1,43 @@ +/* -*- 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; + + [binaryname(MessageMoz)] readonly attribute wstring message; + + 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 000000000..fb9e906fb --- /dev/null +++ b/xpcom/base/nsIConsoleService.idl @@ -0,0 +1,56 @@ +/* -*- 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, uuid(0eb81d20-c37e-42d4-82a8-ca9ae96bdf52)] +interface nsIConsoleService : nsISupports +{ + void logMessage(in nsIConsoleMessage message); + + /** + * Convenience method for logging simple messages. + */ + void logStringMessage(in wstring message); + + /** + * Get an array of all the messages logged so far. If no messages + * are logged, this function will return a count of 0, but still + * will allocate one word for messages, so as to show up as a + * 0-length array when called from script. + */ + void getMessageArray([optional] out uint32_t count, + [retval, array, size_is(count)] out nsIConsoleMessage messages); + + /** + * 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(); +}; + + +%{ 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 000000000..cfdf9abe9 --- /dev/null +++ b/xpcom/base/nsICycleCollectorListener.idl @@ -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/. */ + +#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 @mozilla.org/cycle-collector-logger;1 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 a @mozilla.org/cycle-collector-logger;1 + * 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 @mozilla.org/cycle-collector-logger;1, which + * implements this interface. + * + * - 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/nsIDebug2.idl b/xpcom/base/nsIDebug2.idl new file mode 100644 index 000000000..4401f8a91 --- /dev/null +++ b/xpcom/base/nsIDebug2.idl @@ -0,0 +1,82 @@ +/* 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" + +/** + * For use by consumers in scripted languages (JavaScript, Java, Python, + * Perl, ...). + * + * @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. + * + */ + +[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); +}; diff --git a/xpcom/base/nsIErrorService.idl b/xpcom/base/nsIErrorService.idl new file mode 100644 index 000000000..9eeea2382 --- /dev/null +++ b/xpcom/base/nsIErrorService.idl @@ -0,0 +1,49 @@ +/* -*- 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" + +/** + * nsIErrorService: This is an interim service that allows nsresult codes to be mapped to + * string bundles that can be used to look up error messages. String bundle keys can also + * be mapped. + * + * This service will eventually get replaced by extending xpidl to allow errors to be defined. + * (http://bugzilla.mozilla.org/show_bug.cgi?id=13423). + */ +[scriptable, uuid(afe1f190-a3c2-11e3-a5e2-0800200c9a66)] +interface nsIErrorService : nsISupports +{ + /** + * Registers a string bundle URL for an error module. Error modules are obtained from + * nsresult code with NS_ERROR_GET_MODULE. + */ + void registerErrorStringBundle(in short errorModule, in string stringBundleURL); + + /** + * Unregisters a string bundle URL for an error module. + */ + void unregisterErrorStringBundle(in short errorModule); + + /** + * Retrieves a string bundle URL for an error module. + */ + string getErrorStringBundle(in short errorModule); +}; + +%{C++ + +// The global nsIErrorService: +#define NS_ERRORSERVICE_NAME "Error Service" +#define NS_ERRORSERVICE_CONTRACTID "@mozilla.org/xpcom/error-service;1" +#define NS_ERRORSERVICE_CID \ +{ /* 744afd5e-5f8c-11d4-9877-00c04fa0cf4a */ \ + 0x744afd5e, \ + 0x5f8c, \ + 0x11d4, \ + {0x98, 0x77, 0x00, 0xc0, 0x4f, 0xa0, 0xcf, 0x4a} \ +} + +%} diff --git a/xpcom/base/nsIException.idl b/xpcom/base/nsIException.idl new file mode 100644 index 000000000..ff5a402ed --- /dev/null +++ b/xpcom/base/nsIException.idl @@ -0,0 +1,86 @@ +/* -*- 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" + +[scriptable, builtinclass, uuid(28bfb2a2-5ea6-4738-918b-049dc4d51f0b)] +interface nsIStackFrame : nsISupports +{ + // see nsIProgrammingLanguage for list of language consts + readonly attribute uint32_t language; + readonly attribute AUTF8String languageName; + [implicit_jscontext] + readonly attribute AString filename; + [implicit_jscontext] + readonly attribute AString name; + // Valid line numbers begin at '1'. '0' indicates unknown. + [implicit_jscontext] + readonly attribute int32_t lineNumber; + [implicit_jscontext] + readonly attribute int32_t columnNumber; + readonly attribute AUTF8String sourceLine; + [implicit_jscontext] + readonly attribute AString asyncCause; + [implicit_jscontext] + readonly attribute nsIStackFrame asyncCaller; + [implicit_jscontext] + 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] + 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] + AUTF8String toString(); +}; + +[scriptable, builtinclass, uuid(4371b5bf-6845-487f-8d9d-3f1e4a9badd2)] +interface nsIException : nsISupports +{ + // A custom message set by the thrower. + [binaryname(MessageMoz)] readonly attribute AUTF8String message; + // The nsresult associated with this exception. + readonly attribute nsresult result; + // The name of the error code (ie, a string repr of |result|) + readonly attribute AUTF8String name; + + // Filename location. This is the location that caused the + // error, which may or may not be a source file location. + // For example, standard language errors would generally have + // the same location as their top stack entry. File + // parsers may put the location of the file they were parsing, + // etc. + + // null indicates "no data" + [implicit_jscontext] + readonly attribute AString filename; + // Valid line numbers begin at '1'. '0' indicates unknown. + [implicit_jscontext] + readonly attribute uint32_t lineNumber; + // Valid column numbers begin at 0. + // We don't have an unambiguous indicator for unknown. + readonly attribute uint32_t columnNumber; + + // A stack trace, if available. + readonly attribute nsIStackFrame location; + + // Arbitary data for the implementation. + readonly attribute nsISupports data; + + // A generic formatter - make it suitable to print, etc. + [implicit_jscontext] + AUTF8String toString(); +}; diff --git a/xpcom/base/nsIGZFileWriter.idl b/xpcom/base/nsIGZFileWriter.idl new file mode 100644 index 000000000..11e27f5c4 --- /dev/null +++ b/xpcom/base/nsIGZFileWriter.idl @@ -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 "nsISupports.idl" + +%{C++ +#include "nsDependentString.h" +#include +%} + +interface nsIFile; +[ptr] native FILE(FILE); + +/** + * A simple interface 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. + */ +[scriptable, uuid(6bd5642c-1b90-4499-ba4b-199f27efaba5)] +interface nsIGZFileWriter : nsISupports +{ + /** + * 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(). + */ + [must_use] void init(in nsIFile file); + + /** + * Alternate version of init() for use when the file is already opened; + * e.g., with a FileDescriptor passed over IPC. + */ + [noscript, must_use] void initANSIFileDesc(in FILE file); + + /** + * Write the given string to the file. + */ + [must_use] void write(in AUTF8String str); + + /* + * The following two overloads of Write() are C++ because we can't overload + * methods in XPIDL. Anyway, they don't add much functionality for JS + * callers. + */ + %{C++ + /** + * Write the given char* to the file (not including the null-terminator). + */ + MOZ_MUST_USE nsresult Write(const char* str) + { + return Write(str, strlen(str)); + } + + /** + * Write |length| bytes of |str| to the file. + */ + MOZ_MUST_USE nsresult Write(const char* str, uint32_t len) + { + return Write(nsDependentCString(str, len)); + } + %} + + /** + * Close this nsIGZFileWriter. Classes implementing nsIGZFileWriter will run + * this method 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. + */ + void finish(); +}; diff --git a/xpcom/base/nsIID.h b/xpcom/base/nsIID.h new file mode 100644 index 000000000..f2d788576 --- /dev/null +++ b/xpcom/base/nsIID.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 __nsIID_h +#define __nsIID_h +#include "nsID.h" +#endif /* __nsIID_h */ diff --git a/xpcom/base/nsIInterfaceRequestor.idl b/xpcom/base/nsIInterfaceRequestor.idl new file mode 100644 index 000000000..9727c53cb --- /dev/null +++ b/xpcom/base/nsIInterfaceRequestor.idl @@ -0,0 +1,36 @@ +/* -*- 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/nsIMacUtils.idl b/xpcom/base/nsIMacUtils.idl new file mode 100644 index 000000000..9a60df47c --- /dev/null +++ b/xpcom/base/nsIMacUtils.idl @@ -0,0 +1,32 @@ +/* -*- 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" + +/** + * nsIMacUtils: Generic globally-available Mac-specific utilities. + */ + +[scriptable, uuid(5E9072D7-FF95-455E-9466-8AF9841A72EC)] +interface nsIMacUtils : nsISupports +{ + /** + * True when the main executable is a fat file supporting at least + * ppc and x86 (universal binary). + */ + readonly attribute boolean isUniversalBinary; + + /** + * Returns a string containing a list of architectures delimited + * by "-". Architecture sets are always in the same order: + * ppc > i386 > ppc64 > x86_64 > (future additions) + */ + readonly attribute AString architecturesInBinary; + + /** + * True when running under binary translation (Rosetta). + */ + readonly attribute boolean isTranslated; +}; diff --git a/xpcom/base/nsIMemory.idl b/xpcom/base/nsIMemory.idl new file mode 100644 index 000000000..0c1a050b7 --- /dev/null +++ b/xpcom/base/nsIMemory.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" + +/** + * + * nsIMemory: interface to allocate and deallocate memory. Also provides + * for notifications in low-memory situations. + * + * The frozen exported symbols moz_xmalloc, moz_xrealloc, and free + * provide a more efficient way to access XPCOM memory allocation. Using + * those symbols is preferred to using the methods on this interface. + * + * 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 notications + * 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. + * + * "-no-forward" + * This is appended to the above two parameters when the resulting + * notification should not be forwarded to the child processes. + * + * "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. + * + * "alloc-failure" + * This will be passed as the extra data when the pressure + * observer has been asked to flush because a malloc() or + * realloc() has failed. + * + * "lowering-priority" + * This will be passed as the extra data when the priority of a child + * process is lowered. The pressure observers could take the chance to + * clear caches that could be easily regenerated. This type of + * notification only appears in child processes. + */ + +[scriptable, uuid(1e004834-6d8f-425a-bc9c-a2812ed43bb7)] +interface nsIMemory : nsISupports +{ + /** + * 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 an the call + * was not on the application's main thread. + */ + void heapMinimize(in boolean immediate); + + /** + * 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. + */ + boolean isLowMemoryPlatform(); +}; + diff --git a/xpcom/base/nsIMemoryInfoDumper.idl b/xpcom/base/nsIMemoryInfoDumper.idl new file mode 100644 index 000000000..7e08503a4 --- /dev/null +++ b/xpcom/base/nsIMemoryInfoDumper.idl @@ -0,0 +1,162 @@ +/* -*- 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? + * + * 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); + + /** + * 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 000000000..9617877df --- /dev/null +++ b/xpcom/base/nsIMemoryReporter.idl @@ -0,0 +1,581 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. 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 nsIMemoryReporterCallback : 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 nsIMemoryReporterCallback callback, + in nsISupports data, + in boolean anonymize); + + /* + * Kinds. See the |kind| comment in nsIMemoryReporterCallback. + */ + const int32_t KIND_NONHEAP = 0; + const int32_t KIND_HEAP = 1; + const int32_t KIND_OTHER = 2; + + /* + * Units. See the |units| comment in nsIMemoryReporterCallback. + */ + 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, 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 nsIMemoryReporterCallback 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 nsIMemoryReporterCallback 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 nsIMemoryReporterCallback 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. + * + * |imagesContentUsedUncompressed| (UNITS_BYTES) Memory used for decoded + * raster images in content. + * + * |storageSQLite| (UNITS_BYTES) Memory used by SQLite. + * + * |lowMemoryEvents{Virtual,Physical}| (UNITS_COUNT_CUMULATIVE) The number + * of low-{virtual,physical}-memory events that have occurred since the + * process started. + * + * |ghostWindows| (UNITS_COUNT) The number of ghost windows. + * + * |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 imagesContentUsedUncompressed; + + [must_use] readonly attribute int64_t storageSQLite; + + [must_use] readonly attribute int64_t lowMemoryEventsVirtual; + [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, we invoke the given runnable if it's not + * null. + */ + [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 "nsStringGlue.h" +#include "nsTArray.h" + +class nsPIDOMWindowOuter; + +// nsIHandleReportCallback is a better name, but keep nsIMemoryReporterCallback +// around for backwards compatibility. +typedef nsIMemoryReporterCallback nsIHandleReportCallback; + +namespace mozilla { + +// All the following registration/unregistration functions don't use +// MOZ_MUST_USE 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, ImagesContentUsedUncompressed) +DECL_UNREGISTER_DISTINGUISHED_AMOUNT(ImagesContentUsedUncompressed) + +DECL_REGISTER_DISTINGUISHED_AMOUNT(Infallible, StorageSQLite) +DECL_UNREGISTER_DISTINGUISHED_AMOUNT(StorageSQLite) + +DECL_REGISTER_DISTINGUISHED_AMOUNT(Infallible, LowMemoryEventsVirtual) +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 MOZ_MUST_USE nsresult (*JSSizeOfTabFn)(JSObject* aObj, + size_t* aJsObjectsSize, + size_t* aJsStringSize, + size_t* aJsPrivateSize, + size_t* aJsOtherSize); +typedef MOZ_MUST_USE 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); \ + } + +// 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(EmptyCString(), NS_LITERAL_CSTRING(path), \ + kind, units, amount, \ + NS_LITERAL_CSTRING(description), aData) + +%} diff --git a/xpcom/base/nsIMessageLoop.idl b/xpcom/base/nsIMessageLoop.idl new file mode 100644 index 000000000..b10d1c576 --- /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". + */ +[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/nsIMutable.idl b/xpcom/base/nsIMutable.idl new file mode 100644 index 000000000..c5c7baab0 --- /dev/null +++ b/xpcom/base/nsIMutable.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" + +/** + * nsIMutable defines an interface to be implemented by objects which + * can be made immutable. + */ +[scriptable, uuid(321578d0-03c1-4d95-8821-021ac612d18d)] +interface nsIMutable : nsISupports +{ + /** + * Control whether or not this object can be modified. If the flag is + * false, no modification is allowed. Once the flag has been set to false, + * it cannot be reset back to true -- attempts to do so throw + * NS_ERROR_INVALID_ARG. + */ + attribute boolean mutable; +}; diff --git a/xpcom/base/nsIProgrammingLanguage.idl b/xpcom/base/nsIProgrammingLanguage.idl new file mode 100644 index 000000000..57621168b --- /dev/null +++ b/xpcom/base/nsIProgrammingLanguage.idl @@ -0,0 +1,25 @@ +/* -*- 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" + +/** + * Legacy constants for specifying programming languages. + * + * JAVASCRIPT is needed to avoid breaking addons that use it in nsIClassInfo + * to define fields that are no longer needed. + * + * UNKNOWN and JAVASCRIPT are also used in implementations of + * nsIStackFrame::language. + */ + +[scriptable, uuid(02ad9f22-3c98-46f3-be4e-2f5c9299e29a)] +interface nsIProgrammingLanguage : nsISupports +{ + const uint32_t UNKNOWN = 0; + // 1 is unused. + const uint32_t JAVASCRIPT = 2; +}; diff --git a/xpcom/base/nsISecurityConsoleMessage.idl b/xpcom/base/nsISecurityConsoleMessage.idl new file mode 100644 index 000000000..917968d83 --- /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 000000000..35f5de44c --- /dev/null +++ b/xpcom/base/nsISizeOf.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 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/nsIStatusReporter.idl b/xpcom/base/nsIStatusReporter.idl new file mode 100644 index 000000000..9f9245f49 --- /dev/null +++ b/xpcom/base/nsIStatusReporter.idl @@ -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 "nsISupports.idl" + +interface nsISimpleEnumerator; + +/* + * Status reporters show Firefox's service status. + */ + +[scriptable, uuid(ffcb716c-deeb-44ea-9d9d-ab25dc6980a8)] +interface nsIStatusReporter : nsISupports +{ + readonly attribute ACString name; + /* + * The name of the process containing this reporter. Each reporter initially + * has "" in this field, indicating that it applies to the current process. + */ + readonly attribute ACString process; + /* + * A human-readable status description. + */ + readonly attribute AUTF8String description; +}; + +[scriptable, uuid(fd531273-3319-4fcd-90f2-9f53876c3828)] +interface nsIStatusReporterManager : nsISupports +{ + + /* + * Return an enumerator of nsIStatusReporters that are currently registered. + */ + nsISimpleEnumerator enumerateReporters(); + + /* + * Register the given nsIStatusReporter. After a reporter is registered, + * it will be available via enumerateReporters(). The Manager service + * will hold a strong reference to the given reporter. + */ + void registerReporter(in nsIStatusReporter reporter); + + /* + * Unregister the given status reporter. + */ + void unregisterReporter(in nsIStatusReporter reporter); + + /* + * Initialize. + */ + void init(); + + /* + * Dump service status as a json file + */ + void dumpReports(); +}; + +%{C++ + +/* + * Note that this defaults 'process' to "", which is usually what's desired. + */ +#define NS_STATUS_REPORTER_IMPLEMENT(_classname, _name, _desc_Function) \ + class StatusReporter_##_classname final : public nsIStatusReporter { \ + ~StatusReporter_##_classname() {} \ + public: \ + NS_DECL_ISUPPORTS \ + NS_IMETHOD GetName(nsACString &name) override \ + { name.AssignLiteral(_name); return NS_OK; } \ + NS_IMETHOD GetProcess(nsACString &process) override \ + { process.Truncate(); return NS_OK; } \ + NS_IMETHOD GetDescription(nsACString &desc) override \ + { _desc_Function(desc); return NS_OK; } \ + }; \ + NS_IMPL_ISUPPORTS(StatusReporter_##_classname, nsIStatusReporter) + +#define NS_STATUS_REPORTER_NAME(_classname) StatusReporter_##_classname + +nsresult NS_RegisterStatusReporter(nsIStatusReporter *reporter); +nsresult NS_UnregisterStatusReporter(nsIStatusReporter *reporter); +nsresult NS_DumpStatusReporter(); + +#define NS_STATUS_REPORTER_MANAGER_CID \ +{ 0xe8eb4e7e, 0xf2cf, 0x45e5, \ +{ 0xb8, 0xa4, 0x6a, 0x0f, 0x50, 0x18, 0x84, 0x63 } } +%} diff --git a/xpcom/base/nsISupports.idl b/xpcom/base/nsISupports.idl new file mode 100644 index 000000000..45ea44ecb --- /dev/null +++ b/xpcom/base/nsISupports.idl @@ -0,0 +1,44 @@ +/* -*- 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. + */ + +/* In order to get both the right typelib and the right header we force +* the 'real' output from xpidl to be commented out in the generated header +* and includes a copy of the original nsISupports.h. This is all just to deal +* with the Mac specific ": public __comobject" thing. +*/ + +#include "nsrootidl.idl" + +%{C++ +/* + * Start commenting out the C++ versions of the below in the output header + */ +#if 0 +%} + +[scriptable, uuid(00000000-0000-0000-c000-000000000046)] +interface nsISupports { + void QueryInterface(in nsIIDRef uuid, + [iid_is(uuid),retval] out nsQIResult result); + [noscript, notxpcom] nsrefcnt AddRef(); + [noscript, notxpcom] nsrefcnt Release(); +}; + +%{C++ +/* + * End commenting out the C++ versions of the above in the output header + */ +#endif +%} + + +%{C++ +#include "nsISupportsBase.h" +#include "nsISupportsUtils.h" +%} diff --git a/xpcom/base/nsISupportsBase.h b/xpcom/base/nsISupportsBase.h new file mode 100644 index 000000000..bf5518b81 --- /dev/null +++ b/xpcom/base/nsISupportsBase.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/. */ +// IWYU pragma: private, include "nsISupports.h" + +#ifndef nsISupportsBase_h__ +#define nsISupportsBase_h__ + +#ifndef nscore_h___ +#include "nscore.h" +#endif + +#ifndef nsID_h__ +#include "nsID.h" +#endif + + +/*@{*/ +/** + * IID for the nsISupports interface + * {00000000-0000-0000-c000-000000000046} + * + * To maintain binary compatibility with COM's IUnknown, we define the IID + * of nsISupports to be the same as that of COM's IUnknown. + */ +#define NS_ISUPPORTS_IID \ + { 0x00000000, 0x0000, 0x0000, \ + {0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46} } + +/** + * 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. + */ +class NS_NO_VTABLE nsISupports +{ +public: + + NS_DECLARE_STATIC_IID_ACCESSOR(NS_ISUPPORTS_IID) + + /** + * @name Methods + */ + + //@{ + /** + * 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. + */ + NS_IMETHOD QueryInterface(REFNSIID aIID, void** aInstancePtr) = 0; + /** + * 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. + */ + NS_IMETHOD_(MozExternalRefCountType) AddRef(void) = 0; + + /** + * 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. + */ + NS_IMETHOD_(MozExternalRefCountType) Release(void) = 0; + + //@} +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(nsISupports, NS_ISUPPORTS_IID) + +/*@}*/ + +#endif diff --git a/xpcom/base/nsIUUIDGenerator.idl b/xpcom/base/nsIUUIDGenerator.idl new file mode 100644 index 000000000..8715ed622 --- /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. + */ +[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 000000000..1a33cb74e --- /dev/null +++ b/xpcom/base/nsIVersionComparator.idl @@ -0,0 +1,49 @@ +/* -*- 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. + */ +[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 000000000..73390b15f --- /dev/null +++ b/xpcom/base/nsIWeakReference.idl @@ -0,0 +1,74 @@ +/* -*- 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/MemoryReporting.h" +%} + +/** + * 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, 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. + */ + void QueryReferent( in nsIIDRef uuid, [iid_is(uuid), retval] out nsQIResult result ); + +%{C++ + virtual size_t SizeOfOnlyThis(mozilla::MallocSizeOf aMallocSizeOf) const = 0; +%} + }; + + +/** + * |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(); + }; + + +%{C++ +#ifdef MOZILLA_INTERNAL_API +#include "nsIWeakReferenceUtils.h" +#endif +%} + diff --git a/xpcom/base/nsInterfaceRequestorAgg.cpp b/xpcom/base/nsInterfaceRequestorAgg.cpp new file mode 100644 index 000000000..7e5cd83da --- /dev/null +++ b/xpcom/base/nsInterfaceRequestorAgg.cpp @@ -0,0 +1,86 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. 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 = NS_GetCurrentThread(); + } + } + +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(mConsumerTarget, mFirst.forget()); + NS_ProxyRelease(mConsumerTarget, mSecond.forget()); +} + +nsresult +NS_NewInterfaceRequestorAggregation(nsIInterfaceRequestor* aFirst, + nsIInterfaceRequestor* aSecond, + nsIInterfaceRequestor** aResult) +{ + *aResult = new nsInterfaceRequestorAgg(aFirst, aSecond); + if (!*aResult) { + return NS_ERROR_OUT_OF_MEMORY; + } + NS_ADDREF(*aResult); + return NS_OK; +} + +nsresult +NS_NewInterfaceRequestorAggregation(nsIInterfaceRequestor* aFirst, + nsIInterfaceRequestor* aSecond, + nsIEventTarget* aTarget, + nsIInterfaceRequestor** aResult) +{ + *aResult = new nsInterfaceRequestorAgg(aFirst, aSecond, aTarget); + if (!*aResult) { + return NS_ERROR_OUT_OF_MEMORY; + } + NS_ADDREF(*aResult); + return NS_OK; +} diff --git a/xpcom/base/nsInterfaceRequestorAgg.h b/xpcom/base/nsInterfaceRequestorAgg.h new file mode 100644 index 000000000..62b4f61f9 --- /dev/null +++ b/xpcom/base/nsInterfaceRequestorAgg.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 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/nsMacUtilsImpl.cpp b/xpcom/base/nsMacUtilsImpl.cpp new file mode 100644 index 000000000..e2706047a --- /dev/null +++ b/xpcom/base/nsMacUtilsImpl.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 "nsMacUtilsImpl.h" + +#include + +NS_IMPL_ISUPPORTS(nsMacUtilsImpl, nsIMacUtils) + +nsresult +nsMacUtilsImpl::GetArchString(nsAString& aArchString) +{ + if (!mBinaryArchs.IsEmpty()) { + aArchString.Assign(mBinaryArchs); + return NS_OK; + } + + aArchString.Truncate(); + + bool foundPPC = false, + foundX86 = false, + foundPPC64 = false, + foundX86_64 = false; + + 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) { + foundPPC = true; + } else if (archInt == kCFBundleExecutableArchitectureI386) { + foundX86 = true; + } else if (archInt == kCFBundleExecutableArchitecturePPC64) { + foundPPC64 = true; + } else if (archInt == kCFBundleExecutableArchitectureX86_64) { + foundX86_64 = true; + } + } + + ::CFRelease(archList); + + // The order in the string must always be the same so + // don't do this in the loop. + if (foundPPC) { + mBinaryArchs.AppendLiteral("ppc"); + } + + if (foundX86) { + if (!mBinaryArchs.IsEmpty()) { + mBinaryArchs.Append('-'); + } + mBinaryArchs.AppendLiteral("i386"); + } + + if (foundPPC64) { + if (!mBinaryArchs.IsEmpty()) { + mBinaryArchs.Append('-'); + } + mBinaryArchs.AppendLiteral("ppc64"); + } + + if (foundX86_64) { + if (!mBinaryArchs.IsEmpty()) { + mBinaryArchs.Append('-'); + } + mBinaryArchs.AppendLiteral("x86_64"); + } + + aArchString.Assign(mBinaryArchs); + + return (aArchString.IsEmpty() ? NS_ERROR_FAILURE : NS_OK); +} + +NS_IMETHODIMP +nsMacUtilsImpl::GetIsUniversalBinary(bool* aIsUniversalBinary) +{ + if (NS_WARN_IF(!aIsUniversalBinary)) { + return NS_ERROR_INVALID_ARG; + } + *aIsUniversalBinary = false; + + nsAutoString archString; + nsresult rv = GetArchString(archString); + if (NS_FAILED(rv)) { + return rv; + } + + // The delimiter char in the arch string is '-', so if that character + // is in the string we know we have multiple architectures. + *aIsUniversalBinary = (archString.Find("-") > -1); + + return NS_OK; +} + +NS_IMETHODIMP +nsMacUtilsImpl::GetArchitecturesInBinary(nsAString& aArchString) +{ + return GetArchString(aArchString); +} + +// True when running under binary translation (Rosetta). +NS_IMETHODIMP +nsMacUtilsImpl::GetIsTranslated(bool* aIsTranslated) +{ +#ifdef __ppc__ + static bool sInitialized = false; + + // Initialize sIsNative to 1. If the sysctl fails because it doesn't + // exist, then translation is not possible, so the process must not be + // running translated. + static int32_t sIsNative = 1; + + if (!sInitialized) { + size_t sz = sizeof(sIsNative); + sysctlbyname("sysctl.proc_native", &sIsNative, &sz, nullptr, 0); + sInitialized = true; + } + + *aIsTranslated = !sIsNative; +#else + // Translation only exists for ppc code. Other architectures aren't + // translated. + *aIsTranslated = false; +#endif + + return NS_OK; +} diff --git a/xpcom/base/nsMacUtilsImpl.h b/xpcom/base/nsMacUtilsImpl.h new file mode 100644 index 000000000..4a480ffe5 --- /dev/null +++ b/xpcom/base/nsMacUtilsImpl.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 nsMacUtilsImpl_h___ +#define nsMacUtilsImpl_h___ + +#include "nsIMacUtils.h" +#include "nsString.h" +#include "mozilla/Attributes.h" + +class nsMacUtilsImpl final : public nsIMacUtils +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIMACUTILS + + nsMacUtilsImpl() + { + } + +private: + ~nsMacUtilsImpl() + { + } + + nsresult GetArchString(nsAString& aArchString); + + // A string containing a "-" delimited list of architectures + // in our binary. + nsString mBinaryArchs; +}; + +// Global singleton service +// 697BD3FD-43E5-41CE-AD5E-C339175C0818 +#define NS_MACUTILSIMPL_CID \ + {0x697BD3FD, 0x43E5, 0x41CE, {0xAD, 0x5E, 0xC3, 0x39, 0x17, 0x5C, 0x08, 0x18}} +#define NS_MACUTILSIMPL_CONTRACTID "@mozilla.org/xpcom/mac-utils;1" + +#endif /* nsMacUtilsImpl_h___ */ diff --git a/xpcom/base/nsMemoryImpl.cpp b/xpcom/base/nsMemoryImpl.cpp new file mode 100644 index 000000000..1d1576fbd --- /dev/null +++ b/xpcom/base/nsMemoryImpl.cpp @@ -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/. */ + +#include "nsMemoryImpl.h" +#include "nsThreadUtils.h" + +#include "nsIObserver.h" +#include "nsIObserverService.h" +#include "nsISimpleEnumerator.h" + +#include "nsCOMPtr.h" +#include "mozilla/Services.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 +// mobile/android/base/util/HardwareUtils.java +#define LOW_MEMORY_THRESHOLD_KB (384 * 1024) +#endif + +static nsMemoryImpl sGlobalMemory; + +NS_IMPL_QUERY_INTERFACE(nsMemoryImpl, nsIMemory) + +NS_IMETHODIMP +nsMemoryImpl::HeapMinimize(bool aImmediate) +{ + return FlushMemory(u"heap-minimize", aImmediate); +} + +NS_IMETHODIMP +nsMemoryImpl::IsLowMemoryPlatform(bool* aResult) +{ +#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 + *aResult = false; + + // check if MemTotal from /proc/meminfo is less than LOW_MEMORY_THRESHOLD_KB + FILE* fd = fopen("/proc/meminfo", "r"); + if (!fd) { + return NS_OK; + } + uint64_t mem = 0; + int rv = fscanf(fd, "MemTotal: %llu kB", &mem); + if (fclose(fd)) { + return NS_OK; + } + if (rv != 1) { + return NS_OK; + } + sLowMemory = (mem < LOW_MEMORY_THRESHOLD_KB) ? 1 : 0; + } + *aResult = (sLowMemory == 1); +#else + *aResult = false; +#endif + return NS_OK; +} + +/*static*/ nsresult +nsMemoryImpl::Create(nsISupports* aOuter, const nsIID& aIID, void** aResult) +{ + if (NS_WARN_IF(aOuter)) { + return NS_ERROR_NO_AGGREGATION; + } + return sGlobalMemory.QueryInterface(aIID, aResult); +} + +nsresult +nsMemoryImpl::FlushMemory(const char16_t* aReason, bool aImmediate) +{ + nsresult rv = NS_OK; + + 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 an run 'em asynchronously. + if (aImmediate) { + rv = RunFlushers(aReason); + } else { + // Don't broadcast more than once every 1000ms to avoid being noisy + if (PR_IntervalToMicroseconds(now - sLastFlushTime) > 1000) { + sFlushEvent.mReason = aReason; + rv = NS_DispatchToMainThread(&sFlushEvent); + } + } + + sLastFlushTime = now; + return rv; +} + +nsresult +nsMemoryImpl::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; + return NS_OK; +} + +// XXX need NS_IMPL_STATIC_ADDREF/RELEASE +NS_IMETHODIMP_(MozExternalRefCountType) +nsMemoryImpl::FlushEvent::AddRef() +{ + return 2; +} +NS_IMETHODIMP_(MozExternalRefCountType) +nsMemoryImpl::FlushEvent::Release() +{ + return 1; +} +NS_IMPL_QUERY_INTERFACE(nsMemoryImpl::FlushEvent, nsIRunnable) + +NS_IMETHODIMP +nsMemoryImpl::FlushEvent::Run() +{ + sGlobalMemory.RunFlushers(mReason); + return NS_OK; +} + +mozilla::Atomic +nsMemoryImpl::sIsFlushing; + +PRIntervalTime +nsMemoryImpl::sLastFlushTime = 0; + +nsMemoryImpl::FlushEvent +nsMemoryImpl::sFlushEvent; + +nsresult +NS_GetMemoryManager(nsIMemory** aResult) +{ + return sGlobalMemory.QueryInterface(NS_GET_IID(nsIMemory), (void**)aResult); +} diff --git a/xpcom/base/nsMemoryImpl.h b/xpcom/base/nsMemoryImpl.h new file mode 100644 index 000000000..9e2d46d38 --- /dev/null +++ b/xpcom/base/nsMemoryImpl.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 nsMemoryImpl_h__ +#define nsMemoryImpl_h__ + +#include "mozilla/Atomics.h" + +#include "nsIMemory.h" +#include "nsIRunnable.h" + +// nsMemoryImpl is a static object. We can do this because it doesn't have +// a constructor/destructor or any instance members. Please don't add +// instance member variables, only static member variables. + +class nsMemoryImpl : public nsIMemory +{ +public: + // We don't use the generic macros because we are a special static object + NS_IMETHOD QueryInterface(REFNSIID aIID, void** aResult) override; + NS_IMETHOD_(MozExternalRefCountType) AddRef(void) override + { + return 1; + } + NS_IMETHOD_(MozExternalRefCountType) Release(void) override + { + return 1; + } + + NS_DECL_NSIMEMORY + + static nsresult Create(nsISupports* aOuter, + const nsIID& aIID, void** aResult); + + nsresult FlushMemory(const char16_t* aReason, bool aImmediate); + nsresult RunFlushers(const char16_t* aReason); + +protected: + struct FlushEvent : public nsIRunnable + { + constexpr FlushEvent() : mReason(nullptr) {} + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIRUNNABLE + const char16_t* mReason; + }; + + static mozilla::Atomic sIsFlushing; + static FlushEvent sFlushEvent; + static PRIntervalTime sLastFlushTime; +}; + +#endif // nsMemoryImpl_h__ diff --git a/xpcom/base/nsMemoryInfoDumper.cpp b/xpcom/base/nsMemoryInfoDumper.cpp new file mode 100644 index 000000000..06453b126 --- /dev/null +++ b/xpcom/base/nsMemoryInfoDumper.cpp @@ -0,0 +1,830 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. 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 "nsISimpleEnumerator.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 + +#if 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) + : 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) + : 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() {} + + 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 = */ EmptyString(), + /* 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 = */ EmptyString(), + /* 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 = */ EmptyString(), + /* 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 = */ EmptyString(), + 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(NS_LITERAL_CSTRING("memory report"), + doMemoryReport); + fw->RegisterCallback(NS_LITERAL_CSTRING("minimize memory report"), + doMemoryReport); + // Dump GC and CC logs (from the main thread). + fw->RegisterCallback(NS_LITERAL_CSTRING("gc log"), + doGCCCDump); + fw->RegisterCallback(NS_LITERAL_CSTRING("abbreviated gc log"), + 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, + nullptr); + } +} + +} // namespace +#endif // MOZ_SUPPORTS_FIFO } + +NS_IMPL_ISUPPORTS(nsMemoryInfoDumper, nsIMemoryInfoDumper) + +nsMemoryInfoDumper::nsMemoryInfoDumper() +{ +} + +nsMemoryInfoDumper::~nsMemoryInfoDumper() +{ +} + +/* 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, + nullptr); + } +#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 = + do_CreateInstance("@mozilla.org/cycle-collector-logger;1"); + + if (aDumpAllTraces) { + nsCOMPtr allTracesLogger; + logger->AllTraces(getter_AddRefs(allTracesLogger)); + logger = allTracesLogger; + } + + nsCOMPtr logSink; + logger->GetLogSink(getter_AddRefs(logSink)); + + logSink->SetFilenameIdentifier(identifier); + + nsJSContext::CycleCollectNow(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 = + do_CreateInstance("@mozilla.org/cycle-collector-logger;1"); + + if (aDumpAllTraces) { + nsCOMPtr allTracesLogger; + logger->AllTraces(getter_AddRefs(allTracesLogger)); + logger = allTracesLogger; + } + + logger->SetLogSink(aSink); + + nsJSContext::CycleCollectNow(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 : public JSONWriteFunc +{ +public: + explicit GZWriterWrapper(nsGZFileWriter* aGZWriter) + : mGZWriter(aGZWriter) + {} + + void Write(const char* aStr) + { + // Ignore any failure because JSONWriteFunc doesn't have a mechanism for + // handling errors. + Unused << mGZWriter->Write(aStr); + } + + 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(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". + 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.get()); + mWriter->StringProperty("path", PromiseFlatCString(aPath).get()); + mWriter->IntProperty("kind", aKind); + mWriter->IntProperty("units", aUnits); + mWriter->IntProperty("amount", aAmount); + mWriter->StringProperty("description", + PromiseFlatCString(aDescription).get()); + } + 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() {} + + 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(NS_LITERAL_CSTRING("memory-reports")); + 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 = NS_LITERAL_STRING("nsIMemoryInfoDumper dumped reports to "); + msg.Append(path); + return cs->LogStringMessage(msg.get()); + } + +private: + ~TempDirFinishCallback() {} + + 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(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) +{ + 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 = EmptyString(); + return DumpMemoryInfoToFile(reportsFile, aFinishDumping, aFinishDumpingData, + aAnonymize, /* minimizeMemoryUsage = */ false, + 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(NS_LITERAL_CSTRING("incomplete-") + + reportsFinalFilename, + getter_AddRefs(reportsTmpFile), + NS_LITERAL_CSTRING("memory-reports")); + 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), + NS_LITERAL_CSTRING("memory-reports")); + 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. + nsCString path; + rv = dmdFile->GetNativePath(path); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + dmd::StatusMsg("opened %s for writing\n", path.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 000000000..6bba176f2 --- /dev/null +++ b/xpcom/base/nsMemoryInfoDumper.h @@ -0,0 +1,46 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 000000000..aa3d74dfd --- /dev/null +++ b/xpcom/base/nsMemoryReporterManager.cpp @@ -0,0 +1,2717 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsAtomTable.h" +#include "nsAutoPtr.h" +#include "nsCOMPtr.h" +#include "nsCOMArray.h" +#include "nsPrintfCString.h" +#include "nsServiceManagerUtils.h" +#include "nsMemoryReporterManager.h" +#include "nsITimer.h" +#include "nsThreadUtils.h" +#include "nsPIDOMWindow.h" +#include "nsIObserverService.h" +#include "nsIGlobalObject.h" +#include "nsIXPConnect.h" +#if defined(XP_UNIX) || defined(MOZ_DMD) +#include "nsMemoryInfoDumper.h" +#endif +#include "mozilla/Attributes.h" +#include "mozilla/PodOperations.h" +#include "mozilla/Preferences.h" +#include "mozilla/Services.h" +#include "mozilla/Telemetry.h" +#include "mozilla/UniquePtrExtensions.h" +#include "mozilla/dom/PMemoryReportRequestParent.h" // for dom::MemoryReport +#include "mozilla/dom/ContentParent.h" +#include "mozilla/ipc/FileDescriptorUtils.h" + +#ifdef XP_WIN +#include +#ifndef getpid +#define getpid _getpid +#endif +#else +#include +#endif + +using namespace mozilla; + +#if defined(MOZ_MEMORY) +# define HAVE_JEMALLOC_STATS 1 +# include "mozmemory.h" +#endif // MOZ_MEMORY + +#if defined(XP_LINUX) + +#include +#include +#include + +static MOZ_MUST_USE 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; +} + +static MOZ_MUST_USE nsresult +GetProcSelfSmapsPrivate(int64_t* aN) +{ + // 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. + + FILE* f = fopen("/proc/self/smaps", "r"); + if (NS_WARN_IF(!f)) { + return NS_ERROR_UNEXPECTED; + } + + // We carry over the end of the buffer to the beginning to make sure we only + // interpret complete lines. + static const uint32_t carryOver = 32; + static const uint32_t readSize = 4096; + + int64_t amount = 0; + char buffer[carryOver + readSize + 1]; + + // Fill the beginning of the buffer with spaces, as a sentinel for the first + // iteration. + memset(buffer, ' ', carryOver); + + for (;;) { + size_t bytes = fread(buffer + carryOver, sizeof(*buffer), readSize, f); + char* end = buffer + bytes; + char* ptr = buffer; + end[carryOver] = '\0'; + // We are looking for lines like "Private_{Clean,Dirty}: 4 kB". + while ((ptr = strstr(ptr, "Private"))) { + if (ptr >= end) { + break; + } + ptr += sizeof("Private_Xxxxx:"); + amount += strtol(ptr, nullptr, 10); + } + if (bytes < readSize) { + // We do not expect any match within the end of the buffer. + MOZ_ASSERT(!strstr(end, "Private")); + break; + } + // Carry the end of the buffer over to the beginning. + memcpy(buffer, end, carryOver); + } + + fclose(f); + // Convert from kB to bytes. + *aN = amount * 1024; + return NS_OK; +} + +#define HAVE_VSIZE_AND_RESIDENT_REPORTERS 1 +static MOZ_MUST_USE nsresult +VsizeDistinguishedAmount(int64_t* aN) +{ + return GetProcSelfStatmField(0, aN); +} + +static MOZ_MUST_USE nsresult +ResidentDistinguishedAmount(int64_t* aN) +{ + return GetProcSelfStatmField(1, aN); +} + +static MOZ_MUST_USE nsresult +ResidentFastDistinguishedAmount(int64_t* aN) +{ + return ResidentDistinguishedAmount(aN); +} + +#define HAVE_RESIDENT_UNIQUE_REPORTER 1 +static MOZ_MUST_USE nsresult +ResidentUniqueDistinguishedAmount(int64_t* aN) +{ + return GetProcSelfSmapsPrivate(aN); +} + +#ifdef HAVE_MALLINFO +#define HAVE_SYSTEM_HEAP_REPORTER 1 +static MOZ_MUST_USE 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 + +static MOZ_MUST_USE nsresult +GetKinfoProcSelf(KINFO_PROC* aProc) +{ + 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 +static MOZ_MUST_USE nsresult +VsizeDistinguishedAmount(int64_t* aN) +{ + KINFO_PROC proc; + nsresult rv = GetKinfoProcSelf(&proc); + if (NS_SUCCEEDED(rv)) { + *aN = KP_SIZE(proc); + } + return rv; +} + +static MOZ_MUST_USE nsresult +ResidentDistinguishedAmount(int64_t* aN) +{ + KINFO_PROC proc; + nsresult rv = GetKinfoProcSelf(&proc); + if (NS_SUCCEEDED(rv)) { + *aN = KP_RSS(proc); + } + return rv; +} + +static MOZ_MUST_USE nsresult +ResidentFastDistinguishedAmount(int64_t* aN) +{ + return ResidentDistinguishedAmount(aN); +} + +#ifdef __FreeBSD__ +#include +#include + +static MOZ_MUST_USE 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 +static MOZ_MUST_USE 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 +static MOZ_MUST_USE 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) +{ + aVsize = -1; + aResident = -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; + for (int i = 0; i < n / sizeof(prxmap_t); i++) { + aVsize += prmapp[i].pr_size; + aResident += prmapp[i].pr_rss * prmapp[i].pr_pagesize; + } + break; + } + free(prmapp); + } + free(prmapp); + } + close(mapfd); + } +} + +#define HAVE_VSIZE_AND_RESIDENT_REPORTERS 1 +static MOZ_MUST_USE nsresult +VsizeDistinguishedAmount(int64_t* aN) +{ + int64_t vsize, resident; + XMappingIter(vsize, resident); + if (vsize == -1) { + return NS_ERROR_FAILURE; + } + *aN = vsize; + return NS_OK; +} + +static MOZ_MUST_USE nsresult +ResidentDistinguishedAmount(int64_t* aN) +{ + int64_t vsize, resident; + XMappingIter(vsize, resident); + if (resident == -1) { + return NS_ERROR_FAILURE; + } + *aN = resident; + return NS_OK; +} + +static MOZ_MUST_USE nsresult +ResidentFastDistinguishedAmount(int64_t* aN) +{ + return ResidentDistinguishedAmount(aN); +} + +#elif defined(XP_MACOSX) + +#include +#include +#include +#include +#include + +static MOZ_MUST_USE 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 +static MOZ_MUST_USE 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. +static MOZ_MUST_USE nsresult +ResidentDistinguishedAmountHelper(int64_t* aN, bool aDoPurge) +{ +#ifdef HAVE_JEMALLOC_STATS +#ifndef MOZ_JEMALLOC4 + if (aDoPurge) { + Telemetry::AutoTimer timer; + jemalloc_purge_freed_pages(); + } +#endif +#endif + + task_basic_info ti; + if (!GetTaskBasicInfo(&ti)) { + return NS_ERROR_FAILURE; + } + *aN = ti.resident_size; + return NS_OK; +} + +static MOZ_MUST_USE nsresult +ResidentFastDistinguishedAmount(int64_t* aN) +{ + return ResidentDistinguishedAmountHelper(aN, /* doPurge = */ false); +} + +static MOZ_MUST_USE 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_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); +} + +static MOZ_MUST_USE nsresult +ResidentUniqueDistinguishedAmount(int64_t* aN) +{ + 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 size = 0; + for (mach_vm_address_t addr = MACH_VM_MIN_ADDRESS; ; addr += size) { + vm_region_top_info_data_t info; + mach_msg_type_number_t infoCount = VM_REGION_TOP_INFO_COUNT; + mach_port_t objectName; + + kern_return_t kr = + mach_vm_region(mach_task_self(), &addr, &size, VM_REGION_TOP_INFO, + reinterpret_cast(&info), + &infoCount, &objectName); + if (kr == KERN_INVALID_ADDRESS) { + // Done iterating VM regions. + break; + } else if (kr != KERN_SUCCESS) { + return NS_ERROR_FAILURE; + } + + if (InSharedRegion(addr, cpu_type) && info.share_mode != SM_PRIVATE) { + continue; + } + + switch (info.share_mode) { + case SM_LARGE_PAGE: + // NB: Large pages are not shareable and always resident. + case SM_PRIVATE: + privatePages += info.private_pages_resident; + privatePages += info.shared_pages_resident; + break; + case SM_COW: + privatePages += info.private_pages_resident; + if (info.ref_count == 1) { + // Treat copy-on-write pages as private if they only have one reference. + privatePages += info.shared_pages_resident; + } + break; + case SM_SHARED: + default: + break; + } + } + + vm_size_t pageSize; + if (host_page_size(mach_host_self(), &pageSize) != KERN_SUCCESS) { + pageSize = PAGE_SIZE; + } + + *aN = privatePages * pageSize; + return NS_OK; +} + +#elif defined(XP_WIN) + +#include +#include +#include + +#define HAVE_VSIZE_AND_RESIDENT_REPORTERS 1 +static MOZ_MUST_USE 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; +} + +static MOZ_MUST_USE 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; +} + +static MOZ_MUST_USE nsresult +ResidentFastDistinguishedAmount(int64_t* aN) +{ + return ResidentDistinguishedAmount(aN); +} + +#define HAVE_RESIDENT_UNIQUE_REPORTER 1 + +static MOZ_MUST_USE nsresult +ResidentUniqueDistinguishedAmount(int64_t* aN) +{ + // Determine how many entries we need. + PSAPI_WORKING_SET_INFORMATION tmp; + DWORD tmpSize = sizeof(tmp); + memset(&tmp, 0, tmpSize); + + HANDLE proc = 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 +static MOZ_MUST_USE 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 +static MOZ_MUST_USE 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. During testing there were multiple +// heaps present but the non-default ones had sizes no more than a few 10s of +// KiBs. So we combine their sizes into a single measurement. +static MOZ_MUST_USE nsresult +SystemHeapSize(int64_t* aSizeOut) +{ + // Get the number of heaps. + DWORD nHeaps = GetProcessHeaps(0, nullptr); + NS_ENSURE_TRUE(nHeaps != 0, NS_ERROR_FAILURE); + + // Get handles to all heaps, checking that the number of heaps hasn't + // changed in the meantime. + UniquePtr heaps(new HANDLE[nHeaps]); + DWORD nHeaps2 = GetProcessHeaps(nHeaps, heaps.get()); + NS_ENSURE_TRUE(nHeaps2 != 0 && nHeaps2 == nHeaps, NS_ERROR_FAILURE); + + // Lock and iterate over each heap to get its size. + int64_t heapsSize = 0; + for (DWORD i = 0; i < nHeaps; i++) { + HANDLE heap = heaps[i]; + + 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); + + heapsSize += heapSize; + } + + *aSizeOut = heapsSize; + 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( + EmptyCString(), path, KIND_OTHER, UNITS_BYTES, entry->mSize, + NS_LITERAL_CSTRING("From MEMORY_BASIC_INFORMATION."), 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))) { + 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."); + } + return NS_OK; + } +}; +NS_IMPL_ISUPPORTS(PrivateReporter, nsIMemoryReporter) +#endif + +#ifdef HAVE_VSIZE_AND_RESIDENT_REPORTERS +class VsizeReporter final : public nsIMemoryReporter +{ + ~VsizeReporter() {} + +public: + NS_DECL_ISUPPORTS + + NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, bool aAnonymize) override + { + int64_t amount; + if (NS_SUCCEEDED(VsizeDistinguishedAmount(&amount))) { + 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."); + } + return NS_OK; + } +}; +NS_IMPL_ISUPPORTS(VsizeReporter, nsIMemoryReporter) + +class ResidentReporter final : public nsIMemoryReporter +{ + ~ResidentReporter() {} + +public: + NS_DECL_ISUPPORTS + + NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, bool aAnonymize) override + { + int64_t amount; + if (NS_SUCCEEDED(ResidentDistinguishedAmount(&amount))) { + 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."); + } + 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() {} + +public: + NS_DECL_ISUPPORTS + + NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, bool aAnonymize) override + { + int64_t amount = 0; + 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."); + } + return NS_OK; + } +}; +NS_IMPL_ISUPPORTS(ResidentUniqueReporter, nsIMemoryReporter) + +#endif // HAVE_RESIDENT_UNIQUE_REPORTER + +#ifdef HAVE_SYSTEM_HEAP_REPORTER + +class SystemHeapReporter final : public nsIMemoryReporter +{ + ~SystemHeapReporter() {} + +public: + NS_DECL_ISUPPORTS + + NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, bool aAnonymize) override + { + int64_t amount; + if (NS_SUCCEEDED(SystemHeapSize(&amount))) { + 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."); + } + return NS_OK; + } +}; +NS_IMPL_ISUPPORTS(SystemHeapReporter, nsIMemoryReporter) + +#endif // HAVE_SYSTEM_HEAP_REPORTER + +#ifdef XP_UNIX + +#include + +#define HAVE_RESIDENT_PEAK_REPORTER 1 + +static MOZ_MUST_USE 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() {} + +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() {} + +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; + 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."); + } + return NS_OK; + } +}; +NS_IMPL_ISUPPORTS(PageFaultsSoftReporter, nsIMemoryReporter) + +static MOZ_MUST_USE 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() {} + +public: + NS_DECL_ISUPPORTS + + NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, bool aAnonymize) override + { + int64_t amount = 0; + if (NS_SUCCEEDED(PageFaultsHardDistinguishedAmount(&amount))) { + 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."); + } + 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(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 +HeapOverheadFraction(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() {} + +public: + NS_DECL_ISUPPORTS + + NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, bool aAnonymize) override + { + jemalloc_stats_t stats; + jemalloc_stats(&stats); + + 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. + MOZ_COLLECT_REPORT( + "explicit/heap-overhead/bin-unused", KIND_NONHEAP, UNITS_BYTES, + stats.bin_unused, +"Unused bytes due to fragmentation in the bins used for 'small' (<= 2 KiB) " +"allocations. These bytes will be used if additional allocations occur."); + + 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."); + + 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() {} + +public: + NS_DECL_ISUPPORTS + + NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, bool aAnonymize) override + { + size_t Main, Static; + NS_SizeOfAtomTablesIncludingThis(MallocSizeOf, &Main, &Static); + + MOZ_COLLECT_REPORT( + "explicit/atom-tables/main", KIND_HEAP, UNITS_BYTES, Main, + "Memory used by the main atoms table."); + + MOZ_COLLECT_REPORT( + "explicit/atom-tables/static", KIND_HEAP, UNITS_BYTES, Static, + "Memory used by the static atoms table."); + + return NS_OK; + } +}; +NS_IMPL_ISUPPORTS(AtomTablesReporter, 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() {} + +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() {} +}; +NS_IMPL_ISUPPORTS(DMDReporter, nsIMemoryReporter) + +} // namespace dmd +} // namespace mozilla + +#endif // MOZ_DMD + +/** + ** nsMemoryReporterManager implementation + **/ + +NS_IMPL_ISUPPORTS(nsMemoryReporterManager, nsIMemoryReporterManager) + +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; + +#if defined(HAVE_JEMALLOC_STATS) && defined(MOZ_GLUE_IN_PROGRAM) + if (!jemalloc_stats) { + return NS_ERROR_FAILURE; + } +#endif + +#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()); + +#ifdef DEBUG + RegisterStrongReporter(new DeadlockDetectorReporter()); +#endif + +#ifdef MOZ_DMD + RegisterStrongReporter(new mozilla::dmd::DMDReporter()); +#endif + +#ifdef XP_WIN + RegisterStrongReporter(new WindowsAddressSpaceReporter()); +#endif + +#ifdef XP_UNIX + nsMemoryInfoDumper::Initialize(); +#endif + + 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) +{ +} + +nsMemoryReporterManager::~nsMemoryReporterManager() +{ + delete mStrongReporters; + delete mWeakReporters; + NS_ASSERTION(!mSavedStrongReporters, "failed to restore strong reporters"); + NS_ASSERTION(!mSavedWeakReporters, "failed to restore weak reporters"); +} + +#ifdef MOZ_WIDGET_GONK +#define DEBUG_CHILD_PROCESS_MEMORY_REPORTING 1 +#endif + +#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 = */ EmptyString()); +} + +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(this, &nsMemoryReporterManager::StartGettingReports); + rv = MinimizeMemoryUsage(callback); + } else { + rv = StartGettingReports(); + } + return rv; +} + +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; + 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]); + } + + nsCOMPtr timer = do_CreateInstance(NS_TIMER_CONTRACTID); + // Don't use NS_ENSURE_* here; can't return until the report is finished. + if (NS_WARN_IF(!timer)) { + FinishReporting(); + return NS_ERROR_FAILURE; + } + rv = timer->InitWithFuncCallback(TimeoutCallback, + this, kTimeoutLengthMS, + nsITimer::TYPE_ONE_SHOT); + 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( + [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 (auto iter = mStrongReporters->Iter(); !iter.Done(); iter.Next()) { + DispatchReporter(iter.Key(), iter.Data(), + aHandleReport, aHandleReportData, aAnonymize); + } + + for (auto iter = mWeakReporters->Iter(); !iter.Done(); iter.Next()) { + nsCOMPtr reporter = iter.Key(); + DispatchReporter(reporter, iter.Data(), + aHandleReport, aHandleReportData, aAnonymize); + } + } + + return NS_OK; +} + +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::dom::ContentParent* 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; + } + + mozilla::dom::MaybeFileDesc dmdFileDesc = void_t(); +#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 = mozilla::ipc::FILEToFileDescriptor(dmdFile); + fclose(dmdFile); + } + } +#endif + return aChild->SendPMemoryReportRequestConstructor( + 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 + RefPtr nextChild; + nextChild.swap(s->mChildrenPending.LastElement()); + s->mChildrenPending.TruncateLength(s->mChildrenPending.Length() - 1); + // 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->Put(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->Put(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 +} + +/*static*/ int64_t +nsMemoryReporterManager::ResidentUnique() +{ +#ifdef HAVE_RESIDENT_UNIQUE_REPORTER + int64_t amount = 0; + nsresult rv = ResidentUniqueDistinguishedAmount(&amount); + NS_ENSURE_SUCCESS(rv, 0); + return amount; +#else + return 0; +#endif +} + +NS_IMETHODIMP +nsMemoryReporterManager::GetHeapAllocated(int64_t* aAmount) +{ +#ifdef HAVE_JEMALLOC_STATS + jemalloc_stats_t stats; + jemalloc_stats(&stats); + *aAmount = stats.allocated; + 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 +} + +static MOZ_MUST_USE 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::GetImagesContentUsedUncompressed(int64_t* aAmount) +{ + return GetInfallibleAmount(mAmountFns.mImagesContentUsedUncompressed, + aAmount); +} + +NS_IMETHODIMP +nsMemoryReporterManager::GetStorageSQLite(int64_t* aAmount) +{ + return GetInfallibleAmount(mAmountFns.mStorageSQLite, aAmount); +} + +NS_IMETHODIMP +nsMemoryReporterManager::GetLowMemoryEventsVirtual(int64_t* aAmount) +{ + return GetInfallibleAmount(mAmountFns.mLowMemoryEventsVirtual, 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) + : 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, ImagesContentUsedUncompressed) +DEFINE_UNREGISTER_DISTINGUISHED_AMOUNT(ImagesContentUsedUncompressed) + +DEFINE_REGISTER_DISTINGUISHED_AMOUNT(Infallible, StorageSQLite) +DEFINE_UNREGISTER_DISTINGUISHED_AMOUNT(StorageSQLite) + +DEFINE_REGISTER_DISTINGUISHED_AMOUNT(Infallible, LowMemoryEventsVirtual) +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 000000000..8240cbe34 --- /dev/null +++ b/xpcom/base/nsMemoryReporterManager.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 nsMemoryReporterManager_h__ +#define nsMemoryReporterManager_h__ + +#include "mozilla/Mutex.h" +#include "nsHashKeys.h" +#include "nsIMemoryReporter.h" +#include "nsITimer.h" +#include "nsServiceManagerUtils.h" +#include "nsTHashtable.h" + +namespace mozilla { +namespace dom { +class ContentParent; +class MemoryReport; +} // namespace dom +} // namespace mozilla + +class nsITimer; + +class nsMemoryReporterManager final : public nsIMemoryReporterManager +{ + virtual ~nsMemoryReporterManager(); + +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIMEMORYREPORTERMANAGER + + nsMemoryReporterManager(); + + // Gets the memory reporter manager service. + static nsMemoryReporterManager* GetOrCreate() + { + nsCOMPtr imgr = + do_GetService("@mozilla.org/memory-reporter-manager;1"); + return static_cast(imgr.get()); + } + + typedef nsDataHashtable, bool> StrongReportersTable; + typedef nsDataHashtable, 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; + mozilla::InfallibleAmountFn mJSMainRuntimeTemporaryPeak; + mozilla::InfallibleAmountFn mJSMainRuntimeCompartmentsSystem; + mozilla::InfallibleAmountFn mJSMainRuntimeCompartmentsUser; + + mozilla::InfallibleAmountFn mImagesContentUsedUncompressed; + + mozilla::InfallibleAmountFn mStorageSQLite; + + mozilla::InfallibleAmountFn mLowMemoryEventsVirtual; + mozilla::InfallibleAmountFn mLowMemoryEventsPhysical; + + mozilla::InfallibleAmountFn mGhostWindows; + + AmountFns() + { + mozilla::PodZero(this); + } + }; + 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. + static int64_t ResidentUnique(); + + // Functions that measure per-tab memory consumption. + struct SizeOfTabFns + { + mozilla::JSSizeOfTabFn mJS; + mozilla::NonJSSizeOfTabFn mNonJS; + + SizeOfTabFns() + { + mozilla::PodZero(this); + } + }; + SizeOfTabFns mSizeOfTabFns; + +private: + MOZ_MUST_USE nsresult + RegisterReporterHelper(nsIMemoryReporter* aReporter, + bool aForce, bool aStrongRef, bool aIsAsync); + + MOZ_MUST_USE nsresult StartGettingReports(); + // No MOZ_MUST_USE 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 = 50000; + + mozilla::Mutex mMutex; + bool mIsRegistrationBlocked; + + StrongReportersTable* mStrongReporters; + WeakReportersTable* mWeakReporters; + + // These two are only used for testing purposes. + StrongReportersTable* mSavedStrongReporters; + WeakReportersTable* mSavedWeakReporters; + + uint32_t mNextGeneration; + + // 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; + + // This is reinitialized each time a call to GetReports is initiated. + PendingReportersState* mPendingReportersState; + + PendingProcessesState* GetStateForGeneration(uint32_t aGeneration); + static MOZ_MUST_USE bool + StartChildReport(mozilla::dom::ContentParent* 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 000000000..fabf6b9f5 --- /dev/null +++ b/xpcom/base/nsMessageLoop.cpp @@ -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/. */ + +#include "nsMessageLoop.h" +#include "mozilla/WeakPtr.h" +#include "base/message_loop.h" +#include "base/task.h" +#include "nsIRunnable.h" +#include "nsITimer.h" +#include "nsCOMPtr.h" +#include "nsAutoPtr.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: + MOZ_DECLARE_WEAKREFERENCE_TYPENAME(MessageLoopIdleTask) + MessageLoopIdleTask(nsIRunnable* aTask, uint32_t aEnsureRunsAfterMS); + NS_IMETHOD Run() override; + +private: + nsresult Init(uint32_t aEnsureRunsAfterMS); + + nsCOMPtr mTask; + nsCOMPtr mTimer; + + virtual ~MessageLoopIdleTask() {} +}; + +/** + * 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: + explicit MessageLoopTimerCallback(MessageLoopIdleTask* aTask); + + NS_DECL_ISUPPORTS + NS_DECL_NSITIMERCALLBACK + +private: + WeakPtr mTask; + + virtual ~MessageLoopTimerCallback() {} +}; + +MessageLoopIdleTask::MessageLoopIdleTask(nsIRunnable* aTask, + uint32_t aEnsureRunsAfterMS) + : 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) +{ + mTimer = do_CreateInstance("@mozilla.org/timer;1"); + if (NS_WARN_IF(!mTimer)) { + return NS_ERROR_UNEXPECTED; + } + + RefPtr callback = + new MessageLoopTimerCallback(this); + + return mTimer->InitWithCallback(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) + +} // 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(nsISupports* aOuter, + const nsIID& aIID, + void** aInstancePtr) +{ + if (NS_WARN_IF(aOuter)) { + return NS_ERROR_NO_AGGREGATION; + } + 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 000000000..e7b146221 --- /dev/null +++ b/xpcom/base/nsMessageLoop.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/. */ + +#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() + { + } +}; + +#define NS_MESSAGE_LOOP_CID \ +{0x67b3ac0c, 0xd806, 0x4d48, \ +{0x93, 0x9e, 0x6a, 0x81, 0x9e, 0x6c, 0x24, 0x8f}} + +extern nsresult +nsMessageLoopConstructor(nsISupports* aOuter, + const nsIID& aIID, + void** aInstancePtr); diff --git a/xpcom/base/nsObjCExceptions.h b/xpcom/base/nsObjCExceptions.h new file mode 100644 index 000000000..e63c92af5 --- /dev/null +++ b/xpcom/base/nsObjCExceptions.h @@ -0,0 +1,230 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// Undo the damage that exception_defines.h does. +#undef try +#undef catch + +#ifndef nsObjCExceptions_h_ +#define nsObjCExceptions_h_ + +#import + +#ifdef DEBUG +#import +#endif + +#if defined(MOZ_CRASHREPORTER) && defined(__cplusplus) +#include "nsICrashReporter.h" +#include "nsCOMPtr.h" +#include "nsServiceManagerUtils.h" +#endif + +#include +#include +#include "nsError.h" + +// Undo the damage that exception_defines.h does. +#undef try +#undef catch + +/* NOTE: Macros that claim to abort no longer abort, see bug 486574. + * If you actually want to log and abort, call "nsObjCExceptionLogAbort" + * from an exception handler. At some point we will fix this by replacing + * all macros in the tree with appropriately-named macros. + */ + +// See Mozilla bug 163260. +// This file can only be included in an Objective-C context. + +__attribute__((unused)) +static void +nsObjCExceptionLog(NSException* aException) +{ + NSLog(@"Mozilla has caught an Obj-C exception [%@: %@]", + [aException name], [aException reason]); + +#if defined(MOZ_CRASHREPORTER) && defined(__cplusplus) + // Attach exception info to the crash report. + nsCOMPtr crashReporter = + do_GetService("@mozilla.org/toolkit/crash-reporter;1"); + if (crashReporter) { + crashReporter->AppendObjCExceptionInfoToAppNotes(static_cast(aException)); + } +#endif + +#ifdef DEBUG + @try { + // Try to get stack information out of the exception. 10.5 returns the stack + // info with the callStackReturnAddresses selector. + NSArray* stackTrace = nil; + if ([aException respondsToSelector:@selector(callStackReturnAddresses)]) { + NSArray* addresses = (NSArray*) + [aException performSelector:@selector(callStackReturnAddresses)]; + if ([addresses count]) { + stackTrace = addresses; + } + } + + // 10.4 doesn't respond to callStackReturnAddresses so we'll try to pull the + // stack info out of the userInfo. It might not be there, sadly :( + if (!stackTrace) { + stackTrace = [[aException userInfo] objectForKey:NSStackTraceKey]; + } + + if (stackTrace) { + // The command line should look like this: + // /usr/bin/atos -p -printHeader + NSMutableArray* args = + [NSMutableArray arrayWithCapacity:[stackTrace count] + 3]; + + [args addObject:@"-p"]; + int pid = [[NSProcessInfo processInfo] processIdentifier]; + [args addObject:[NSString stringWithFormat:@"%d", pid]]; + + [args addObject:@"-printHeader"]; + + unsigned int stackCount = [stackTrace count]; + unsigned int stackIndex = 0; + for (; stackIndex < stackCount; stackIndex++) { + unsigned long address = + [[stackTrace objectAtIndex:stackIndex] unsignedLongValue]; + [args addObject:[NSString stringWithFormat:@"0x%lx", address]]; + } + + NSPipe* outPipe = [NSPipe pipe]; + + NSTask* task = [[NSTask alloc] init]; + [task setLaunchPath:@"/usr/bin/atos"]; + [task setArguments:args]; + [task setStandardOutput:outPipe]; + [task setStandardError:outPipe]; + + NSLog(@"Generating stack trace for Obj-C exception..."); + + // This will throw an exception if the atos tool cannot be found, and in + // that case we'll just hit our @catch block below. + [task launch]; + + [task waitUntilExit]; + [task release]; + + NSData* outData = + [[outPipe fileHandleForReading] readDataToEndOfFile]; + NSString* outString = + [[NSString alloc] initWithData:outData encoding:NSUTF8StringEncoding]; + + NSLog(@"Stack trace:\n%@", outString); + + [outString release]; + } else { + NSLog(@""); + } + } + @catch (NSException* exn) { + NSLog(@"Failed to generate stack trace for Obj-C exception [%@: %@]", + [exn name], [exn reason]); + } +#endif +} + +__attribute__((unused)) +static void +nsObjCExceptionAbort() +{ + // We need to raise a mach-o signal here, the Mozilla crash reporter on + // Mac OS X does not respond to POSIX signals. Raising mach-o signals directly + // is tricky so we do it by just derefing a null pointer. + int* foo = nullptr; + *foo = 1; +} + +__attribute__((unused)) +static void +nsObjCExceptionLogAbort(NSException* aException) +{ + nsObjCExceptionLog(aException); + nsObjCExceptionAbort(); +} + +#define NS_OBJC_TRY(_e, _fail) \ +@try { _e; } \ +@catch(NSException *_exn) { \ + nsObjCExceptionLog(_exn); \ + _fail; \ +} + +#define NS_OBJC_TRY_EXPR(_e, _fail) \ +({ \ + typeof(_e) _tmp; \ + @try { _tmp = (_e); } \ + @catch(NSException *_exn) { \ + nsObjCExceptionLog(_exn); \ + _fail; \ + } \ + _tmp; \ +}) + +#define NS_OBJC_TRY_EXPR_NULL(_e) \ +NS_OBJC_TRY_EXPR(_e, 0) + +#define NS_OBJC_TRY_IGNORE(_e) \ +NS_OBJC_TRY(_e, ) + +// To reduce code size the abort versions do not reuse above macros. This allows +// catch blocks to only contain one call. + +#define NS_OBJC_TRY_ABORT(_e) \ +@try { _e; } \ +@catch(NSException *_exn) { \ + nsObjCExceptionLog(_exn); \ +} + +#define NS_OBJC_TRY_EXPR_ABORT(_e) \ +({ \ + typeof(_e) _tmp; \ + @try { _tmp = (_e); } \ + @catch(NSException *_exn) { \ + nsObjCExceptionLog(_exn); \ + } \ + _tmp; \ +}) + +// For wrapping blocks of Obj-C calls. Does not actually terminate. +#define NS_OBJC_BEGIN_TRY_ABORT_BLOCK @try { +#define NS_OBJC_END_TRY_ABORT_BLOCK } @catch(NSException *_exn) { \ + nsObjCExceptionLog(_exn); \ + } + +// Same as above ABORT_BLOCK but returns a value after the try/catch block to +// suppress compiler warnings. This allows us to avoid having to refactor code +// to get scoping right when wrapping an entire method. + +#define NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL @try { +#define NS_OBJC_END_TRY_ABORT_BLOCK_NIL } @catch(NSException *_exn) { \ + nsObjCExceptionLog(_exn); \ + } \ + return nil; + +#define NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSNULL @try { +#define NS_OBJC_END_TRY_ABORT_BLOCK_NSNULL } @catch(NSException *_exn) { \ + nsObjCExceptionLog(_exn); \ + } \ + return nullptr; + +#define NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT @try { +#define NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT } @catch(NSException *_exn) { \ + nsObjCExceptionLog(_exn); \ + } \ + return NS_ERROR_FAILURE; + +#define NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN @try { +#define NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(_rv) } @catch(NSException *_exn) { \ + nsObjCExceptionLog(_exn);\ + } \ + return _rv; + +#endif // nsObjCExceptions_h_ diff --git a/xpcom/base/nsQueryObject.h b/xpcom/base/nsQueryObject.h new file mode 100644 index 000000000..a52546324 --- /dev/null +++ b/xpcom/base/nsQueryObject.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 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 + { + 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 + { + 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(nsCOMPtr& aRawPtr) +{ + return nsQueryObject(aRawPtr); +} + +template +inline nsQueryObject +do_QueryObject(RefPtr& aRawPtr) +{ + return nsQueryObject(aRawPtr); +} + +template +inline nsQueryObjectWithError +do_QueryObject(T* aRawPtr, nsresult* aErrorPtr) +{ + return nsQueryObjectWithError(aRawPtr, aErrorPtr); +} + +template +inline nsQueryObjectWithError +do_QueryObject(nsCOMPtr& aRawPtr, nsresult* aErrorPtr) +{ + return nsQueryObjectWithError(aRawPtr, aErrorPtr); +} + +template +inline nsQueryObjectWithError +do_QueryObject(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 000000000..3e6409cf3 --- /dev/null +++ b/xpcom/base/nsSecurityConsoleMessage.cpp @@ -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/. */ + +#include "nsSecurityConsoleMessage.h" + +NS_IMPL_ISUPPORTS(nsSecurityConsoleMessage, nsISecurityConsoleMessage) + +nsSecurityConsoleMessage::nsSecurityConsoleMessage() +{ +} + +nsSecurityConsoleMessage::~nsSecurityConsoleMessage() +{ +} + +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 000000000..c93c13613 --- /dev/null +++ b/xpcom/base/nsSecurityConsoleMessage.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 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/nsSetDllDirectory.h b/xpcom/base/nsSetDllDirectory.h new file mode 100644 index 000000000..0920d5936 --- /dev/null +++ b/xpcom/base/nsSetDllDirectory.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 nsSetDllDirectory_h +#define nsSetDllDirectory_h + +#ifndef XP_WIN +#error This file only makes sense on Windows. +#endif + +#include +#include +#include + +namespace mozilla { + +static void +SanitizeEnvironmentVariables() +{ + DWORD bufferSize = GetEnvironmentVariableW(L"PATH", nullptr, 0); + if (bufferSize) { + wchar_t* originalPath = new wchar_t[bufferSize]; + if (bufferSize - 1 == GetEnvironmentVariableW(L"PATH", originalPath, + bufferSize)) { + bufferSize = ExpandEnvironmentStringsW(originalPath, nullptr, 0); + if (bufferSize) { + wchar_t* newPath = new wchar_t[bufferSize]; + if (ExpandEnvironmentStringsW(originalPath, + newPath, + bufferSize)) { + SetEnvironmentVariableW(L"PATH", newPath); + } + delete[] newPath; + } + } + delete[] originalPath; + } +} + +} + +#endif diff --git a/xpcom/base/nsStatusReporterManager.cpp b/xpcom/base/nsStatusReporterManager.cpp new file mode 100644 index 000000000..491e7d458 --- /dev/null +++ b/xpcom/base/nsStatusReporterManager.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 "nsStatusReporterManager.h" +#include "nsCOMPtr.h" +#include "nsDirectoryServiceDefs.h" +#include "nsArrayEnumerator.h" +#include "nsISimpleEnumerator.h" +#include "nsIFile.h" +#include "nsDumpUtils.h" +#include "nsIFileStreams.h" +#include "nsPrintfCString.h" + +#ifdef XP_WIN +#include +#define getpid _getpid +#else +#include +#endif + +#ifdef XP_UNIX +#define DO_STATUS_REPORT 1 +#endif + +#ifdef DO_STATUS_REPORT // { +namespace { + +class DumpStatusInfoToTempDirRunnable : public mozilla::Runnable +{ +public: + DumpStatusInfoToTempDirRunnable() + { + } + + NS_IMETHOD Run() override + { + nsCOMPtr mgr = + do_GetService("@mozilla.org/status-reporter-manager;1"); + mgr->DumpReports(); + return NS_OK; + } +}; + +void +doStatusReport(const nsCString& aInputStr) +{ + LOG("FifoWatcher(%s) dispatching status report runnable.", aInputStr.get()); + RefPtr runnable = + new DumpStatusInfoToTempDirRunnable(); + NS_DispatchToMainThread(runnable); +} + +} //anonymous namespace +#endif // DO_STATUS_REPORT } + +static bool gStatusReportProgress = 0; +static int gNumReporters = 0; + +nsresult +getStatus(nsACString& aDesc) +{ + if (!gStatusReportProgress) { + aDesc.AssignLiteral("Init"); + } else { + aDesc.AssignLiteral("Running: There are "); + aDesc.AppendInt(gNumReporters); + aDesc.AppendLiteral(" reporters"); + } + return NS_OK; +} + +NS_STATUS_REPORTER_IMPLEMENT(StatusReporter, "StatusReporter State", getStatus) + +#define DUMP(o, s) \ + do { \ + const char* s2 = (s); \ + uint32_t dummy; \ + nsresult rvDump = (o)->Write((s2), strlen(s2), &dummy); \ + if (NS_WARN_IF(NS_FAILED(rvDump))) \ + return rvDump; \ + } while (0) + +static nsresult +DumpReport(nsIFileOutputStream* aOStream, const nsCString& aProcess, + const nsCString& aName, const nsCString& aDescription) +{ + if (aProcess.IsEmpty()) { + int pid = getpid(); + nsPrintfCString pidStr("PID %u", pid); + DUMP(aOStream, "\n {\n \"Process\": \""); + DUMP(aOStream, pidStr.get()); + } else { + DUMP(aOStream, "\n { \"Unknown Process\": \""); + } + + DUMP(aOStream, "\",\n \"Reporter name\": \""); + DUMP(aOStream, aName.get()); + + DUMP(aOStream, "\",\n \"Status Description\": [\""); + nsCString desc = aDescription; + desc.ReplaceSubstring("|", "\",\""); + DUMP(aOStream, desc.get()); + + DUMP(aOStream, "\"]\n }"); + + return NS_OK; +} + +/** + ** nsStatusReporterManager implementation + **/ + +NS_IMPL_ISUPPORTS(nsStatusReporterManager, nsIStatusReporterManager) + +nsStatusReporterManager::nsStatusReporterManager() +{ +} + +nsStatusReporterManager::~nsStatusReporterManager() +{ +} + +NS_IMETHODIMP +nsStatusReporterManager::Init() +{ + RegisterReporter(new NS_STATUS_REPORTER_NAME(StatusReporter)); + gStatusReportProgress = 1; + +#ifdef DO_STATUS_REPORT + if (FifoWatcher::MaybeCreate()) { + FifoWatcher* fw = FifoWatcher::GetSingleton(); + fw->RegisterCallback(NS_LITERAL_CSTRING("status report"), doStatusReport); + } +#endif + + return NS_OK; +} + +NS_IMETHODIMP +nsStatusReporterManager::DumpReports() +{ + static unsigned number = 1; + nsresult rv; + + nsCString filename("status-reports-"); + filename.AppendInt(getpid()); + filename.Append('-'); + filename.AppendInt(number++); + filename.AppendLiteral(".json"); + + // Open a file in NS_OS_TEMP_DIR for writing. + // The file is initialized as "incomplete-status-reports-pid-number.json" in the + // begining, it will be rename as "status-reports-pid-number.json" in the end. + nsCOMPtr tmpFile; + rv = nsDumpUtils::OpenTempFile(NS_LITERAL_CSTRING("incomplete-") + + filename, + getter_AddRefs(tmpFile), + NS_LITERAL_CSTRING("status-reports")); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsCOMPtr ostream = + do_CreateInstance("@mozilla.org/network/file-output-stream;1"); + rv = ostream->Init(tmpFile, -1, -1, 0); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + //Write the reports to the file + + DUMP(ostream, "{\n\"subject\":\"about:service reports\",\n"); + DUMP(ostream, "\"reporters\": [ "); + + nsCOMPtr e; + bool more, first = true; + EnumerateReporters(getter_AddRefs(e)); + while (NS_SUCCEEDED(e->HasMoreElements(&more)) && more) { + nsCOMPtr supports; + e->GetNext(getter_AddRefs(supports)); + nsCOMPtr r = do_QueryInterface(supports); + + nsCString process; + rv = r->GetProcess(process); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsCString name; + rv = r->GetName(name); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsCString description; + rv = r->GetDescription(description); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (first) { + first = false; + } else { + DUMP(ostream, ","); + } + + rv = DumpReport(ostream, process, name, description); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + DUMP(ostream, "\n]\n}\n"); + + rv = ostream->Close(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // Rename the status reports file + nsCOMPtr srFinalFile; + rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(srFinalFile)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + +#ifdef ANDROID + rv = srFinalFile->AppendNative(NS_LITERAL_CSTRING("status-reports")); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } +#endif + + rv = srFinalFile->AppendNative(filename); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = srFinalFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsAutoString srActualFinalFilename; + rv = srFinalFile->GetLeafName(srActualFinalFilename); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = tmpFile->MoveTo(/* directory */ nullptr, srActualFinalFilename); + + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsStatusReporterManager::EnumerateReporters(nsISimpleEnumerator** aResult) +{ + return NS_NewArrayEnumerator(aResult, mReporters); +} + +NS_IMETHODIMP +nsStatusReporterManager::RegisterReporter(nsIStatusReporter* aReporter) +{ + if (mReporters.IndexOf(aReporter) != -1) { + return NS_ERROR_FAILURE; + } + + mReporters.AppendObject(aReporter); + gNumReporters++; + return NS_OK; +} + +NS_IMETHODIMP +nsStatusReporterManager::UnregisterReporter(nsIStatusReporter* aReporter) +{ + if (!mReporters.RemoveObject(aReporter)) { + return NS_ERROR_FAILURE; + } + gNumReporters--; + return NS_OK; +} + +nsresult +NS_RegisterStatusReporter(nsIStatusReporter* aReporter) +{ + nsCOMPtr mgr = + do_GetService("@mozilla.org/status-reporter-manager;1"); + if (!mgr) { + return NS_ERROR_FAILURE; + } + return mgr->RegisterReporter(aReporter); +} + +nsresult +NS_UnregisterStatusReporter(nsIStatusReporter* aReporter) +{ + nsCOMPtr mgr = + do_GetService("@mozilla.org/status-reporter-manager;1"); + if (!mgr) { + return NS_ERROR_FAILURE; + } + return mgr->UnregisterReporter(aReporter); +} + +nsresult +NS_DumpStatusReporter() +{ + nsCOMPtr mgr = + do_GetService("@mozilla.org/status-reporter-manager;1"); + if (!mgr) { + return NS_ERROR_FAILURE; + } + return mgr->DumpReports(); +} diff --git a/xpcom/base/nsStatusReporterManager.h b/xpcom/base/nsStatusReporterManager.h new file mode 100644 index 000000000..84427cfcf --- /dev/null +++ b/xpcom/base/nsStatusReporterManager.h @@ -0,0 +1,41 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + + +#include "nsIStatusReporter.h" +#include "nsCOMArray.h" +#include "nsString.h" + +class nsStatusReporter final : public nsIStatusReporter +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSISTATUSREPORTER + + nsStatusReporter(nsACString& aProcess, nsACString& aDesc); + +private: + nsCString sProcess; + nsCString sName; + nsCString sDesc; + + virtual ~nsStatusReporter(); +}; + + +class nsStatusReporterManager : public nsIStatusReporterManager +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSISTATUSREPORTERMANAGER + + nsStatusReporterManager(); + +private: + nsCOMArray mReporters; + + virtual ~nsStatusReporterManager(); +}; diff --git a/xpcom/base/nsSystemInfo.cpp b/xpcom/base/nsSystemInfo.cpp new file mode 100644 index 000000000..f6d9fd5ad --- /dev/null +++ b/xpcom/base/nsSystemInfo.cpp @@ -0,0 +1,985 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. 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 "nsSystemInfo.h" +#include "prsystem.h" +#include "prio.h" +#include "prprf.h" +#include "mozilla/SSE.h" +#include "mozilla/arm.h" +#include "mozilla/Sprintf.h" + +#ifdef XP_WIN +#include +#include +#include +#include "base/scoped_handle_win.h" +#include "nsAppDirectoryServiceDefs.h" +#include "nsDirectoryServiceDefs.h" +#include "nsDirectoryServiceUtils.h" +#include "nsIObserverService.h" +#include "nsWindowsHelpers.h" +#endif + +#ifdef XP_MACOSX +#include "MacHelpers.h" +#endif + +#ifdef MOZ_WIDGET_GTK +#include +#include +#endif + +#if defined (XP_LINUX) && !defined (ANDROID) +#include +#include +#include "mozilla/Tokenizer.h" +#include "nsCharSeparatedTokenizer.h" + +#include +#include +#endif + +#ifdef MOZ_WIDGET_ANDROID +#include "AndroidBridge.h" +#include "mozilla/dom/ContentChild.h" +#endif + +#ifdef MOZ_WIDGET_GONK +#include +#include "mozilla/Preferences.h" +#include "nsPrintfCString.h" +#endif + +#ifdef ANDROID +extern "C" { +NS_EXPORT int android_sdk_version; +} +#endif + +#ifdef XP_MACOSX +#include +#endif + +#if defined(XP_LINUX) && defined(MOZ_SANDBOX) +#include "mozilla/SandboxInfo.h" +#endif + +// Slot for NS_InitXPCOM2 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; + +#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 + +#if defined(XP_WIN) +namespace { +nsresult +GetHDDInfo(const char* aSpecialDirName, nsAutoCString& aModel, + nsAutoCString& aRevision) +{ + aModel.Truncate(); + aRevision.Truncate(); + + nsCOMPtr profDir; + nsresult rv = NS_GetSpecialDirectory(aSpecialDirName, + getter_AddRefs(profDir)); + NS_ENSURE_SUCCESS(rv, rv); + nsAutoString profDirPath; + rv = profDir->GetPath(profDirPath); + NS_ENSURE_SUCCESS(rv, rv); + wchar_t volumeMountPoint[MAX_PATH] = {L'\\', L'\\', L'.', L'\\'}; + const size_t PREFIX_LEN = 4; + if (!::GetVolumePathNameW(profDirPath.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; + } + // 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) { + aModel = reinterpret_cast(deviceOutput) + + deviceOutput->VendorIdOffset; + } + if (deviceOutput->ProductIdOffset) { + aModel += reinterpret_cast(deviceOutput) + + deviceOutput->ProductIdOffset; + } + aModel.CompressWhitespace(); + if (deviceOutput->ProductRevisionOffset) { + aRevision = reinterpret_cast(deviceOutput) + + deviceOutput->ProductRevisionOffset; + aRevision.CompressWhitespace(); + } + free(deviceOutput); + return NS_OK; +} + +nsresult GetInstallYear(uint32_t& aYear) +{ + HKEY hKey; + LONG status = RegOpenKeyExW(HKEY_LOCAL_MACHINE, + NS_LITERAL_STRING( + "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion" + ).get(), + 0, KEY_READ | KEY_WOW64_64KEY, &hKey); + + if (status != ERROR_SUCCESS) { + return NS_ERROR_UNEXPECTED; + } + + nsAutoRegKey key(hKey); + + DWORD type = 0; + time_t raw_time = 0; + DWORD time_size = sizeof(time_t); + + status = RegQueryValueExW(hKey, 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; + } + + aYear = 1900UL + time.tm_year; + return NS_OK; +} + +nsresult GetCountryCode(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, wwc(aCountryCode.BeginWriting()), + aCountryCode.Length(), 0); + if (!numChars) { + return NS_ERROR_FAILURE; + } + + // numChars includes null terminator + aCountryCode.Truncate(numChars - 1); + return NS_OK; +} + +} // namespace +#endif // defined(XP_WIN) + +using namespace mozilla; + +nsSystemInfo::nsSystemInfo() +{ +} + +nsSystemInfo::~nsSystemInfo() +{ +} + +// 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 }, + // ARM-specific bits. + { "hasEDSP", mozilla::supports_edsp }, + { "hasARMv6", mozilla::supports_armv6 }, + { "hasARMv7", mozilla::supports_armv7 }, + { "hasNEON", mozilla::supports_neon } +}; + +#ifdef XP_WIN +// Lifted from media/webrtc/trunk/webrtc/base/systeminfo.cc, +// so keeping the _ instead of switching to camel case for now. +typedef BOOL (WINAPI *LPFN_GLPI)( + PSYSTEM_LOGICAL_PROCESSOR_INFORMATION, + PDWORD); +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 + + // GetLogicalProcessorInformation() is available on Windows XP SP3 and beyond. + LPFN_GLPI glpi = reinterpret_cast(GetProcAddress( + GetModuleHandle(L"kernel32"), + "GetLogicalProcessorInformation")); + if (nullptr == glpi) { + return; + } + // 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 (!glpi(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 + +nsresult +nsSystemInfo::Init() +{ + nsresult rv; + + static const struct + { + PRSysInfo cmd; + const char* name; + } items[] = { + { PR_SI_SYSNAME, "name" }, + { PR_SI_HOSTNAME, "host" }, + { PR_SI_ARCHITECTURE, "arch" }, + { PR_SI_RELEASE, "version" } + }; + + 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"); + } + } + + rv = SetPropertyAsBool(NS_ConvertASCIItoUTF16("hasWindowsTouchInterface"), + false); + NS_ENSURE_SUCCESS(rv, rv); + + // Additional informations not available through PR_GetSystemInfo. + SetInt32Property(NS_LITERAL_STRING("pagesize"), PR_GetPageSize()); + SetInt32Property(NS_LITERAL_STRING("pageshift"), PR_GetPageShift()); + SetInt32Property(NS_LITERAL_STRING("memmapalign"), PR_GetMemMapAlignment()); + SetUint64Property(NS_LITERAL_STRING("memsize"), PR_GetPhysicalMemorySize()); + SetUint32Property(NS_LITERAL_STRING("umask"), nsSystemInfo::gUserUmask); + + uint64_t virtualMem = 0; + nsAutoCString cpuVendor; + 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) + // Virtual memory: + MEMORYSTATUSEX memStat; + memStat.dwLength = sizeof(memStat); + if (GlobalMemoryStatusEx(&memStat)) { + virtualMem = memStat.ullTotalVirtual; + } + + // 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); + } + + 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; + } + + 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, L3 cache size + // from /proc/cpuinfo file + { + std::map keyValuePairs; + SimpleParseKeyValuePairs("/proc/cpuinfo", keyValuePairs); + + // cpuVendor from "vendor_id" + cpuVendor.Assign(keyValuePairs[NS_LITERAL_CSTRING("vendor_id")]); + + { + // cpuFamily from "cpu family" + Tokenizer::Token t; + Tokenizer p(keyValuePairs[NS_LITERAL_CSTRING("cpu family")]); + 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[NS_LITERAL_CSTRING("model")]); + 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[NS_LITERAL_CSTRING("stepping")]); + 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[NS_LITERAL_CSTRING("cpu cores")]); + if (p.Next(t) && t.Type() == Tokenizer::TOKEN_INTEGER && + t.AsInteger() <= INT32_MAX) { + physicalCPUs = static_cast(t.AsInteger()); + } + } + + { + // cacheSizeL3 from "cache size" + Tokenizer::Token t; + Tokenizer p(keyValuePairs[NS_LITERAL_CSTRING("cache size")]); + if (p.Next(t) && t.Type() == Tokenizer::TOKEN_INTEGER && + t.AsInteger() <= INT32_MAX) { + cacheSizeL3 = static_cast(t.AsInteger()); + if (p.Next(t) && t.Type() == Tokenizer::TOKEN_WORD && + t.AsString() != NS_LITERAL_CSTRING("KB")) { + // If we get here, there was some text after the cache size value + // and that text was not KB. For now, just don't report the + // L3 cache. + cacheSizeL3 = -1; + } + } + } + } + + { + // 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()); + } + } + } + + SetInt32Property(NS_LITERAL_STRING("cpucount"), PR_GetNumberOfProcessors()); +#else + SetInt32Property(NS_LITERAL_STRING("cpucount"), PR_GetNumberOfProcessors()); +#endif + + if (virtualMem) SetUint64Property(NS_LITERAL_STRING("virtualmemsize"), virtualMem); + if (cpuSpeed >= 0) SetInt32Property(NS_LITERAL_STRING("cpuspeed"), cpuSpeed); + if (!cpuVendor.IsEmpty()) SetPropertyAsACString(NS_LITERAL_STRING("cpuvendor"), cpuVendor); + if (cpuFamily >= 0) SetInt32Property(NS_LITERAL_STRING("cpufamily"), cpuFamily); + if (cpuModel >= 0) SetInt32Property(NS_LITERAL_STRING("cpumodel"), cpuModel); + if (cpuStepping >= 0) SetInt32Property(NS_LITERAL_STRING("cpustepping"), cpuStepping); + + if (logicalCPUs >= 0) SetInt32Property(NS_LITERAL_STRING("cpucount"), logicalCPUs); + if (physicalCPUs >= 0) SetInt32Property(NS_LITERAL_STRING("cpucores"), physicalCPUs); + + if (cacheSizeL2 >= 0) SetInt32Property(NS_LITERAL_STRING("cpucachel2"), cacheSizeL2); + if (cacheSizeL3 >= 0) SetInt32Property(NS_LITERAL_STRING("cpucachel3"), cacheSizeL3); + + 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 isWow64; + BOOL gotWow64Value = IsWow64Process(GetCurrentProcess(), &isWow64); + NS_WARNING_ASSERTION(gotWow64Value, "IsWow64Process failed"); + if (gotWow64Value) { + rv = SetPropertyAsBool(NS_LITERAL_STRING("isWow64"), !!isWow64); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + if (NS_FAILED(GetProfileHDDInfo())) { + // We might have been called before profile-do-change. We'll observe that + // event so that we can fill this in later. + nsCOMPtr obsService = + do_GetService(NS_OBSERVERSERVICE_CONTRACTID, &rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + rv = obsService->AddObserver(this, "profile-do-change", false); + if (NS_FAILED(rv)) { + return rv; + } + } + nsAutoCString hddModel, hddRevision; + if (NS_SUCCEEDED(GetHDDInfo(NS_GRE_DIR, hddModel, hddRevision))) { + rv = SetPropertyAsACString(NS_LITERAL_STRING("binHDDModel"), hddModel); + NS_ENSURE_SUCCESS(rv, rv); + rv = SetPropertyAsACString(NS_LITERAL_STRING("binHDDRevision"), + hddRevision); + NS_ENSURE_SUCCESS(rv, rv); + } + if (NS_SUCCEEDED(GetHDDInfo(NS_WIN_WINDOWS_DIR, hddModel, hddRevision))) { + rv = SetPropertyAsACString(NS_LITERAL_STRING("winHDDModel"), hddModel); + NS_ENSURE_SUCCESS(rv, rv); + rv = SetPropertyAsACString(NS_LITERAL_STRING("winHDDRevision"), + hddRevision); + NS_ENSURE_SUCCESS(rv, rv); + } + + nsAutoString countryCode; + if (NS_SUCCEEDED(GetCountryCode(countryCode))) { + rv = SetPropertyAsAString(NS_LITERAL_STRING("countryCode"), countryCode); + NS_ENSURE_SUCCESS(rv, rv); + } + + uint32_t installYear = 0; + if (NS_SUCCEEDED(GetInstallYear(installYear))) { + rv = SetPropertyAsUint32(NS_LITERAL_STRING("installYear"), installYear); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } +#endif + +#if defined(XP_MACOSX) + nsAutoString countryCode; + if (NS_SUCCEEDED(GetSelectedCityInfo(countryCode))) { + rv = SetPropertyAsAString(NS_LITERAL_STRING("countryCode"), countryCode); + NS_ENSURE_SUCCESS(rv, rv); + } +#endif + +#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 MOZ_WIDGET_GTK == 2 + extern int gtk_read_end_of_the_pipe; + + if (gtk_read_end_of_the_pipe != -1) { + do { + gtkver_len = read(gtk_read_end_of_the_pipe, >kver, sizeof(gtkver)); + } while (gtkver_len < 0 && errno == EINTR); + close(gtk_read_end_of_the_pipe); + } +#endif + + 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)); + } + + 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); + } + + rv = SetPropertyAsACString(NS_LITERAL_STRING("secondaryLibrary"), + secondaryLibrary); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } +#endif + +#ifdef MOZ_WIDGET_ANDROID + AndroidSystemInfo info; + if (XRE_IsContentProcess()) { + dom::ContentChild* child = dom::ContentChild::GetSingleton(); + if (child) { + child->SendGetAndroidSystemInfo(&info); + SetupAndroidInfo(info); + } + } else { + GetAndroidSystemInfo(&info); + SetupAndroidInfo(info); + } +#endif + +#ifdef MOZ_WIDGET_GONK + char sdk[PROP_VALUE_MAX]; + if (__system_property_get("ro.build.version.sdk", sdk)) { + android_sdk_version = atoi(sdk); + SetPropertyAsInt32(NS_LITERAL_STRING("sdk_version"), android_sdk_version); + + SetPropertyAsACString(NS_LITERAL_STRING("secondaryLibrary"), + nsPrintfCString("SDK %u", android_sdk_version)); + } + + char characteristics[PROP_VALUE_MAX]; + if (__system_property_get("ro.build.characteristics", characteristics)) { + if (!strcmp(characteristics, "tablet")) { + SetPropertyAsBool(NS_LITERAL_STRING("tablet"), true); + } else if (!strcmp(characteristics, "tv")) { + SetPropertyAsBool(NS_LITERAL_STRING("tv"), true); + } + } + + nsAutoString str; + rv = GetPropertyAsAString(NS_LITERAL_STRING("version"), str); + if (NS_SUCCEEDED(rv)) { + SetPropertyAsAString(NS_LITERAL_STRING("kernel_version"), str); + } + + const nsAdoptingString& b2g_os_name = + mozilla::Preferences::GetString("b2g.osName"); + if (b2g_os_name) { + SetPropertyAsAString(NS_LITERAL_STRING("name"), b2g_os_name); + } + + const nsAdoptingString& b2g_version = + mozilla::Preferences::GetString("b2g.version"); + if (b2g_version) { + SetPropertyAsAString(NS_LITERAL_STRING("version"), b2g_version); + } +#endif + +#if defined(XP_LINUX) && defined(MOZ_SANDBOX) + SandboxInfo sandInfo = SandboxInfo::Get(); + + SetPropertyAsBool(NS_LITERAL_STRING("hasSeccompBPF"), + sandInfo.Test(SandboxInfo::kHasSeccompBPF)); + SetPropertyAsBool(NS_LITERAL_STRING("hasSeccompTSync"), + sandInfo.Test(SandboxInfo::kHasSeccompTSync)); + SetPropertyAsBool(NS_LITERAL_STRING("hasUserNamespaces"), + sandInfo.Test(SandboxInfo::kHasUserNamespaces)); + SetPropertyAsBool(NS_LITERAL_STRING("hasPrivilegedUserNamespaces"), + sandInfo.Test(SandboxInfo::kHasPrivilegedUserNamespaces)); + + if (sandInfo.Test(SandboxInfo::kEnabledForContent)) { + SetPropertyAsBool(NS_LITERAL_STRING("canSandboxContent"), + sandInfo.CanSandboxContent()); + } + + if (sandInfo.Test(SandboxInfo::kEnabledForMedia)) { + SetPropertyAsBool(NS_LITERAL_STRING("canSandboxMedia"), + 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. +// See: https://cs.chromium.org/chromium/src/base/sys_info_android.cc?l=61 +#define DEFAULT_ANDROID_VERSION "6.0.99" + +/* static */ +void +nsSystemInfo::GetAndroidSystemInfo(AndroidSystemInfo* aInfo) +{ + MOZ_ASSERT(XRE_IsParentProcess()); + + if (!mozilla::AndroidBridge::Bridge()) { + aInfo->sdk_version() = 0; + return; + } + + nsAutoString str; + if (mozilla::AndroidBridge::Bridge()->GetStaticStringField( + "android/os/Build", "MODEL", str)) { + aInfo->device() = str; + } + if (mozilla::AndroidBridge::Bridge()->GetStaticStringField( + "android/os/Build", "MANUFACTURER", str)) { + aInfo->manufacturer() = str; + } + if (mozilla::AndroidBridge::Bridge()->GetStaticStringField( + "android/os/Build$VERSION", "RELEASE", str)) { + 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() = NS_LITERAL_STRING(DEFAULT_ANDROID_VERSION); + } else { + aInfo->release_version() = str; + } + } + if (mozilla::AndroidBridge::Bridge()->GetStaticStringField( + "android/os/Build", "HARDWARE", str)) { + aInfo->hardware() = str; + } + int32_t sdk_version; + if (!mozilla::AndroidBridge::Bridge()->GetStaticIntField( + "android/os/Build$VERSION", "SDK_INT", &sdk_version)) { + sdk_version = 0; + } + aInfo->sdk_version() = sdk_version; + aInfo->isTablet() = java::GeckoAppShell::IsTablet(); +} + +void +nsSystemInfo::SetupAndroidInfo(const AndroidSystemInfo& aInfo) +{ + if (!aInfo.device().IsEmpty()) { + SetPropertyAsAString(NS_LITERAL_STRING("device"), aInfo.device()); + } + if (!aInfo.manufacturer().IsEmpty()) { + SetPropertyAsAString(NS_LITERAL_STRING("manufacturer"), aInfo.manufacturer()); + } + if (!aInfo.release_version().IsEmpty()) { + SetPropertyAsAString(NS_LITERAL_STRING("release_version"), aInfo.release_version()); + } + SetPropertyAsBool(NS_LITERAL_STRING("tablet"), 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(NS_LITERAL_STRING("version"), str); + if (NS_SUCCEEDED(rv)) { + SetPropertyAsAString(NS_LITERAL_STRING("kernel_version"), str); + } + // When AndroidBridge is not available (eg. in xpcshell tests), sdk_version is 0. + if (aInfo.sdk_version() != 0) { + android_sdk_version = aInfo.sdk_version(); + if (android_sdk_version >= 8 && !aInfo.hardware().IsEmpty()) { + SetPropertyAsAString(NS_LITERAL_STRING("hardware"), aInfo.hardware()); + } + SetPropertyAsInt32(NS_LITERAL_STRING("version"), android_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"); + } +} + +#if defined(XP_WIN) +NS_IMETHODIMP +nsSystemInfo::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) +{ + if (!strcmp(aTopic, "profile-do-change")) { + nsresult rv; + nsCOMPtr obsService = + do_GetService(NS_OBSERVERSERVICE_CONTRACTID, &rv); + if (NS_FAILED(rv)) { + return rv; + } + rv = obsService->RemoveObserver(this, "profile-do-change"); + if (NS_FAILED(rv)) { + return rv; + } + return GetProfileHDDInfo(); + } + return NS_OK; +} + +nsresult +nsSystemInfo::GetProfileHDDInfo() +{ + nsAutoCString hddModel, hddRevision; + nsresult rv = GetHDDInfo(NS_APP_USER_PROFILE_50_DIR, hddModel, hddRevision); + if (NS_FAILED(rv)) { + return rv; + } + rv = SetPropertyAsACString(NS_LITERAL_STRING("profileHDDModel"), hddModel); + if (NS_FAILED(rv)) { + return rv; + } + rv = SetPropertyAsACString(NS_LITERAL_STRING("profileHDDRevision"), + hddRevision); + return rv; +} + +NS_IMPL_ISUPPORTS_INHERITED(nsSystemInfo, nsHashPropertyBag, nsIObserver) +#endif // defined(XP_WIN) + diff --git a/xpcom/base/nsSystemInfo.h b/xpcom/base/nsSystemInfo.h new file mode 100644 index 000000000..5a7ef4424 --- /dev/null +++ b/xpcom/base/nsSystemInfo.h @@ -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/. */ + +#ifndef _NSSYSTEMINFO_H_ +#define _NSSYSTEMINFO_H_ + +#include "nsHashPropertyBag.h" +#if defined(XP_WIN) +#include "nsIObserver.h" +#endif // defined(XP_WIN) + +#ifdef MOZ_WIDGET_ANDROID +#include "mozilla/dom/PContent.h" +#endif // MOZ_WIDGET_ANDROID + +class nsSystemInfo final + : public nsHashPropertyBag +#if defined(XP_WIN) + , public nsIObserver +#endif // defined(XP_WIN) +{ +public: +#if defined(XP_WIN) + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIOBSERVER +#endif // defined(XP_WIN) + + nsSystemInfo(); + + nsresult Init(); + + // Slot for NS_InitXPCOM2 to pass information to nsSystemInfo::Init. + // See comments above the variable definition and in NS_InitXPCOM2. + 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(); + +#if defined(XP_WIN) + nsresult GetProfileHDDInfo(); +#endif // defined(XP_WIN) +}; + +#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 000000000..3b415174b --- /dev/null +++ b/xpcom/base/nsTraceRefcnt.cpp @@ -0,0 +1,1319 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. 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 "mozilla/IntegerPrintfMacros.h" +#include "mozilla/StaticPtr.h" +#include "nsXPCOMPrivate.h" +#include "nscore.h" +#include "nsISupports.h" +#include "nsTArray.h" +#include "nsTHashtable.h" +#include "prenv.h" +#include "plstr.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 +#define getpid _getpid +#else +#include +#endif + +#include "mozilla/Atomics.h" +#include "mozilla/AutoRestore.h" +#include "mozilla/BlockingResourceBase.h" +#include "mozilla/PoisonIOInterposer.h" + +#include +#include + +#ifdef HAVE_DLOPEN +#include +#endif + +#ifdef MOZ_DMD +#include "base/process_util.h" +#include "nsMemoryInfoDumper.h" +#endif + +//////////////////////////////////////////////////////////////////////////////// + +#include "plhash.h" +#include "prmem.h" + +#include "prthread.h" + +// We use a spin lock instead of a regular mutex because this lock is usually +// only held for a very short time, and gets grabbed at a very high frequency +// (~100000 times per second). On Mac, the overhead of using a regular lock +// is very high, see bug 1137963. +static mozilla::Atomic gTraceLogLocked; + +struct MOZ_STACK_CLASS AutoTraceLogLock final +{ + bool doRelease; + AutoTraceLogLock() + : doRelease(true) + { + uintptr_t currentThread = reinterpret_cast(PR_GetCurrentThread()); + if (gTraceLogLocked == currentThread) { + doRelease = false; + } else { + while (!gTraceLogLocked.compareExchange(0, currentThread)) { + PR_Sleep(PR_INTERVAL_NO_WAIT); /* yield */ + } + } + } + ~AutoTraceLogLock() { if (doRelease) gTraceLogLocked = 0; } +}; + +static PLHashTable* gBloatView; +static PLHashTable* gTypesToLog; +static PLHashTable* gObjectsToLog; +static PLHashTable* gSerialNumbers; +static intptr_t gNextSerialNumber; +static bool gDumpedStatistics = 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); + +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; +}; + +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 const char kStaticCtorDtorWarning[] = + "XPCOM objects created/destroyed from static ctor/dtor"; + +static void +AssertActivityIsLegal() +{ + if (gActivityTLS == BAD_TLS_INDEX || PR_GetThreadPrivate(gActivityTLS)) { + if (PR_GetEnv("MOZ_FATAL_STATIC_XPCOM_CTORS_DTORS")) { + NS_RUNTIMEABORT(kStaticCtorDtorWarning); + } else { + NS_WARNING(kStaticCtorDtorWarning); + } + } +} +# define ASSERT_ACTIVITY_IS_LEGAL \ + PR_BEGIN_MACRO \ + AssertActivityIsLegal(); \ + PR_END_MACRO +#else +# define ASSERT_ACTIVITY_IS_LEGAL PR_BEGIN_MACRO PR_END_MACRO +#endif // DEBUG + +// These functions are copied from nsprpub/lib/ds/plhash.c, with changes +// to the functions not called Default* to free the SerialNumberRecord or +// the BloatEntry. + +static void* +DefaultAllocTable(void* aPool, size_t aSize) +{ + return PR_MALLOC(aSize); +} + +static void +DefaultFreeTable(void* aPool, void* aItem) +{ + PR_Free(aItem); +} + +static PLHashEntry* +DefaultAllocEntry(void* aPool, const void* aKey) +{ + return PR_NEW(PLHashEntry); +} + +static void +SerialNumberFreeEntry(void* aPool, PLHashEntry* aHashEntry, unsigned aFlag) +{ + if (aFlag == HT_FREE_ENTRY) { + delete static_cast(aHashEntry->value); + PR_Free(aHashEntry); + } +} + +static void +TypesToLogFreeEntry(void* aPool, PLHashEntry* aHashEntry, unsigned aFlag) +{ + if (aFlag == HT_FREE_ENTRY) { + free(const_cast(static_cast(aHashEntry->key))); + PR_Free(aHashEntry); + } +} + +static const PLHashAllocOps serialNumberHashAllocOps = { + DefaultAllocTable, DefaultFreeTable, + DefaultAllocEntry, SerialNumberFreeEntry +}; + +static const PLHashAllocOps typesToLogHashAllocOps = { + DefaultAllocTable, DefaultFreeTable, + DefaultAllocEntry, TypesToLogFreeEntry +}; + +//////////////////////////////////////////////////////////////////////////////// + +class CodeAddressServiceStringTable final +{ +public: + CodeAddressServiceStringTable() : mSet(32) {} + + const char* Intern(const char* aString) + { + nsCharPtrHashKey* e = mSet.PutEntry(aString); + return e->GetKey(); + } + + size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const + { + return mSet.SizeOfExcludingThis(aMallocSizeOf); + } + +private: + typedef nsTHashtable StringSet; + StringSet mSet; +}; + +struct CodeAddressServiceStringAlloc final +{ + static char* copy(const char* aStr) { return strdup(aStr); } + static void free(char* aPtr) { ::free(aPtr); } +}; + +// WalkTheStack does not hold any locks needed by MozDescribeCodeAddress, so +// this class does not need to do anything. +struct CodeAddressServiceLock final +{ + static void Unlock() {} + static void Lock() {} + static bool IsLocked() { return true; } +}; + +typedef mozilla::CodeAddressService WalkTheStackCodeAddressService; + +mozilla::StaticAutoPtr gCodeAddressService; + +//////////////////////////////////////////////////////////////////////////////// + +class BloatEntry +{ +public: + BloatEntry(const char* aClassName, uint32_t aClassSize) + : mClassSize(aClassSize) + { + MOZ_ASSERT(strlen(aClassName) > 0, "BloatEntry name must be non-empty"); + mClassName = PL_strdup(aClassName); + mStats.Clear(); + mTotalLeaked = 0; + } + + ~BloatEntry() + { + PL_strfree(mClassName); + } + + uint32_t GetClassSize() + { + return (uint32_t)mClassSize; + } + const char* GetClassName() + { + return mClassName; + } + + void Ctor() + { + mStats.mCreates++; + } + + void Dtor() + { + mStats.mDestroys++; + } + + static int DumpEntry(PLHashEntry* aHashEntry, int aIndex, void* aArg) + { + BloatEntry* entry = (BloatEntry*)aHashEntry->value; + if (entry) { + static_cast*>(aArg)->AppendElement(entry); + } + return HT_ENUMERATE_NEXT; + } + + static int TotalEntries(PLHashEntry* aHashEntry, int aIndex, void* aArg) + { + BloatEntry* entry = (BloatEntry*)aHashEntry->value; + if (entry && nsCRT::strcmp(entry->mClassName, "TOTAL") != 0) { + entry->Total((BloatEntry*)aArg); + } + return HT_ENUMERATE_NEXT; + } + + 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_ChildProcessTypeToString(XRE_GetProcessType()), getpid()); + if (gLogLeaksOnly && !mStats.HaveLeaks()) { + return false; + } + + fprintf(aOut, + "\n" \ + " |<----------------Class--------------->|<-----Bytes------>|<----Objects---->|\n" \ + " | | Per-Inst Leaked| Total Rem|\n"); + + 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: + char* mClassName; + double mClassSize; // This is stored as a double because of the way we compute the avg class size for total bloat. + int64_t mTotalLeaked; // Used only for TOTAL entry. + nsTraceRefcntStats mStats; +}; + +static void +BloatViewFreeEntry(void* aPool, PLHashEntry* aHashEntry, unsigned aFlag) +{ + if (aFlag == HT_FREE_ENTRY) { + BloatEntry* entry = static_cast(aHashEntry->value); + delete entry; + PR_Free(aHashEntry); + } +} + +const static PLHashAllocOps bloatViewHashAllocOps = { + DefaultAllocTable, DefaultFreeTable, + DefaultAllocEntry, BloatViewFreeEntry +}; + +static void +RecreateBloatView() +{ + gBloatView = PL_NewHashTable(256, + PL_HashString, + PL_CompareStrings, + PL_CompareValues, + &bloatViewHashAllocOps, nullptr); +} + +static BloatEntry* +GetBloatEntry(const char* aTypeName, uint32_t aInstanceSize) +{ + if (!gBloatView) { + RecreateBloatView(); + } + BloatEntry* entry = nullptr; + if (gBloatView) { + entry = (BloatEntry*)PL_HashTableLookup(gBloatView, aTypeName); + if (!entry && aInstanceSize > 0) { + + entry = new BloatEntry(aTypeName, aInstanceSize); + PLHashEntry* e = PL_HashTableAdd(gBloatView, aTypeName, entry); + if (!e) { + delete entry; + entry = nullptr; + } + } 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."); + } + } + return entry; +} + +static int +DumpSerialNumbers(PLHashEntry* aHashEntry, int aIndex, void* aClosure) +{ + SerialNumberRecord* record = + static_cast(aHashEntry->value); + auto* outputFile = static_cast(aClosure); +#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 (!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); + } + } + return HT_ENUMERATE_NEXT; +} + + +template<> +class nsDefaultComparator +{ +public: + bool Equals(BloatEntry* const& aEntry1, BloatEntry* const& aEntry2) const + { + return PL_strcmp(aEntry1->GetClassName(), aEntry2->GetClassName()) == 0; + } + bool LessThan(BloatEntry* const& aEntry1, BloatEntry* const& aEntry2) const + { + return PL_strcmp(aEntry1->GetClassName(), aEntry2->GetClassName()) < 0; + } +}; + + +nsresult +nsTraceRefcnt::DumpStatistics() +{ + if (!gBloatLog || !gBloatView) { + return NS_ERROR_FAILURE; + } + + AutoTraceLogLock lock; + + 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); + PL_HashTableEnumerateEntries(gBloatView, BloatEntry::TotalEntries, &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; + PL_HashTableEnumerateEntries(gBloatView, BloatEntry::DumpEntry, &entries); + 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) { + fprintf(gBloatLog, "\nSerial Numbers of Leaked Objects:\n"); + PL_HashTableEnumerateEntries(gSerialNumbers, DumpSerialNumbers, gBloatLog); + } + + return NS_OK; +} + +void +nsTraceRefcnt::ResetStatistics() +{ + AutoTraceLogLock lock; + if (gBloatView) { + PL_HashTableDestroy(gBloatView); + gBloatView = nullptr; + } +} + +static bool +LogThisType(const char* aTypeName) +{ + void* he = PL_HashTableLookup(gTypesToLog, aTypeName); + return he != nullptr; +} + +static PLHashNumber +HashNumber(const void* aKey) +{ + return PLHashNumber(NS_PTR_TO_INT32(aKey)); +} + +static intptr_t +GetSerialNumber(void* aPtr, bool aCreate) +{ + PLHashEntry** hep = PL_HashTableRawLookup(gSerialNumbers, + HashNumber(aPtr), + aPtr); + if (hep && *hep) { + MOZ_RELEASE_ASSERT(!aCreate, "If an object already has a serial number, we should be destroying it."); + return static_cast((*hep)->value)->serialNumber; + } + + if (!aCreate) { + return 0; + } + + SerialNumberRecord* record = new SerialNumberRecord(); + WalkTheStackSavingLocations(record->allocationStack); + PL_HashTableRawAdd(gSerialNumbers, hep, HashNumber(aPtr), + aPtr, static_cast(record)); + return gNextSerialNumber; +} + +static int32_t* +GetRefCount(void* aPtr) +{ + PLHashEntry** hep = PL_HashTableRawLookup(gSerialNumbers, + HashNumber(aPtr), + aPtr); + if (hep && *hep) { + return &(static_cast((*hep)->value)->refCount); + } else { + return nullptr; + } +} + +#ifdef HAVE_CPP_DYNAMIC_CAST_TO_VOID_PTR +static int32_t* +GetCOMPtrCount(void* aPtr) +{ + PLHashEntry** hep = PL_HashTableRawLookup(gSerialNumbers, + HashNumber(aPtr), + aPtr); + if (hep && *hep) { + return &(static_cast((*hep)->value)->COMPtrCount); + } + return nullptr; +} +#endif // HAVE_CPP_DYNAMIC_CAST_TO_VOID_PTR + +static void +RecycleSerialNumberPtr(void* aPtr) +{ + PL_HashTableRemove(gSerialNumbers, aPtr); +} + +static bool +LogThisObj(intptr_t aSerialNumber) +{ + return (bool)PL_HashTableLookup(gObjectsToLog, (const void*)aSerialNumber); +} + +#ifdef XP_WIN +#define FOPEN_NO_INHERIT "N" +#else +#define FOPEN_NO_INHERIT +#endif + +static bool +InitLog(const char* aEnvVar, const char* aMsg, FILE** aResult) +{ + const char* value = getenv(aEnvVar); + if (value) { + if (nsCRT::strcmp(value, "1") == 0) { + *aResult = stdout; + fprintf(stdout, "### %s defined -- logging %s to stdout\n", + aEnvVar, aMsg); + return true; + } else if (nsCRT::strcmp(value, "2") == 0) { + *aResult = stderr; + fprintf(stdout, "### %s defined -- logging %s to stderr\n", + aEnvVar, aMsg); + return true; + } else { + FILE* stream; + nsAutoCString fname(value); + if (!XRE_IsParentProcess()) { + bool hasLogExtension = + fname.RFind(".log", true, -1, 4) == kNotFound ? false : true; + if (hasLogExtension) { + fname.Cut(fname.Length() - 4, 4); + } + fname.Append('_'); + fname.Append((char*)XRE_ChildProcessTypeToString(XRE_GetProcessType())); + fname.AppendLiteral("_pid"); + fname.AppendInt((uint32_t)getpid()); + if (hasLogExtension) { + fname.AppendLiteral(".log"); + } + } + stream = ::fopen(fname.get(), "w" FOPEN_NO_INHERIT); + if (stream) { + MozillaRegisterDebugFD(fileno(stream)); + *aResult = stream; + fprintf(stdout, "### %s defined -- logging %s to %s\n", + aEnvVar, aMsg, fname.get()); + } else { + fprintf(stdout, "### %s defined -- unable to log %s to %s\n", + aEnvVar, aMsg, fname.get()); + MOZ_ASSERT(false, "Tried and failed to create an XPCOM log"); + } + return stream != nullptr; + } + } + return false; +} + + +static void +maybeUnregisterAndCloseFile(FILE*& aFile) +{ + if (!aFile) { + return; + } + + MozillaUnRegisterDebugFILE(aFile); + fclose(aFile); + aFile = nullptr; +} + + +static void +InitTraceLog() +{ + if (gInitialized) { + return; + } + gInitialized = true; + + bool defined = InitLog("XPCOM_MEM_BLOAT_LOG", "bloat/leaks", &gBloatLog); + if (!defined) { + gLogLeaksOnly = InitLog("XPCOM_MEM_LEAK_LOG", "leaks", &gBloatLog); + } + if (defined || gLogLeaksOnly) { + RecreateBloatView(); + if (!gBloatView) { + NS_WARNING("out of memory"); + maybeUnregisterAndCloseFile(gBloatLog); + gLogLeaksOnly = false; + } + } + + InitLog("XPCOM_MEM_REFCNT_LOG", "refcounts", &gRefcntsLog); + + InitLog("XPCOM_MEM_ALLOC_LOG", "new/delete", &gAllocLog); + + const char* classes = getenv("XPCOM_MEM_LOG_CLASSES"); + +#ifdef HAVE_CPP_DYNAMIC_CAST_TO_VOID_PTR + if (classes) { + InitLog("XPCOM_MEM_COMPTR_LOG", "nsCOMPtr", &gCOMPtrLog); + } 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 + + if (classes) { + // if XPCOM_MEM_LOG_CLASSES was set to some value, the value is interpreted + // as a list of class names to track + gTypesToLog = PL_NewHashTable(256, + PL_HashString, + PL_CompareStrings, + PL_CompareValues, + &typesToLogHashAllocOps, nullptr); + if (!gTypesToLog) { + NS_WARNING("out of memory"); + fprintf(stdout, "### XPCOM_MEM_LOG_CLASSES defined -- unable to log specific classes\n"); + } else { + 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'; + } + PL_HashTableAdd(gTypesToLog, strdup(cp), (void*)1); + fprintf(stdout, "%s ", cp); + if (!cm) { + break; + } + *cm = ','; + cp = cm + 1; + } + fprintf(stdout, "\n"); + } + + gSerialNumbers = PL_NewHashTable(256, + HashNumber, + PL_CompareValues, + PL_CompareValues, + &serialNumberHashAllocOps, nullptr); + + + } + + const char* objects = getenv("XPCOM_MEM_LOG_OBJECTS"); + if (objects) { + gObjectsToLog = PL_NewHashTable(256, + HashNumber, + PL_CompareValues, + PL_CompareValues, + nullptr, nullptr); + + if (!gObjectsToLog) { + NS_WARNING("out of memory"); + fprintf(stdout, "### XPCOM_MEM_LOG_OBJECTS defined -- unable to log specific objects\n"); + } else 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++) { + PL_HashTableAdd(gObjectsToLog, (const void*)serialno, (void*)1); + fprintf(stdout, "%" PRIdPTR " ", serialno); + } + if (!cm) { + break; + } + *cm = ','; + cp = cm + 1; + } + fprintf(stdout, "\n"); + } + } + + + if (gBloatLog) { + gLogging = OnlyBloatLogging; + } + + if (gRefcntsLog || gAllocLog || gCOMPtrLog) { + gLogging = FullLogging; + } +} + + +extern "C" { + +static void +PrintStackFrame(uint32_t aFrameNumber, void* aPC, void* aSP, void* aClosure) +{ + FILE* stream = (FILE*)aClosure; + MozCodeAddressDetails details; + char buf[1024]; + + MozDescribeCodeAddress(aPC, &details); + MozFormatCodeAddressDetails(buf, sizeof(buf), aFrameNumber, aPC, &details); + fprintf(stream, "%s\n", buf); + fflush(stream); +} + +static void +PrintStackFrameCached(uint32_t aFrameNumber, void* aPC, void* aSP, + void* aClosure) +{ + auto stream = static_cast(aClosure); + static const size_t buflen = 1024; + char buf[buflen]; + gCodeAddressService->GetLocation(aFrameNumber, aPC, buf, buflen); + fprintf(stream, " %s\n", buf); + fflush(stream); +} + +static void +RecordStackFrame(uint32_t /*aFrameNumber*/, void* aPC, void* /*aSP*/, + void* aClosure) +{ + auto locations = static_cast*>(aClosure); + locations->push_back(aPC); +} + +} + +void +nsTraceRefcnt::WalkTheStack(FILE* aStream) +{ + MozStackWalk(PrintStackFrame, /* skipFrames */ 2, /* maxFrames */ 0, aStream, + 0, nullptr); +} + +/** + * This is a variant of |WalkTheStack| 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 + * |WalkTheStack|. 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) +{ + if (!gCodeAddressService) { + gCodeAddressService = new WalkTheStackCodeAddressService(); + } + MozStackWalk(PrintStackFrameCached, /* skipFrames */ 2, /* maxFrames */ 0, + aStream, 0, nullptr); +} + +static void +WalkTheStackSavingLocations(std::vector& aLocations) +{ + if (!gCodeAddressService) { + gCodeAddressService = new WalkTheStackCodeAddressService(); + } + static const int kFramesToSkip = + 0 + // this frame gets inlined + 1 + // GetSerialNumber + 1; // NS_LogCtor + MozStackWalk(RecordStackFrame, kFramesToSkip, /* maxFrames */ 0, + &aLocations, 0, nullptr); +} + +//---------------------------------------------------------------------- + +EXPORT_XPCOM_API(void) +NS_LogInit() +{ + NS_SetMainThread(); + + // FIXME: This is called multiple times, we should probably not allow that. + StackWalkInitCriticalAddress(); + 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_ChildProcessTypeToString(XRE_GetProcessType()))) { + return; + } + + nsPrintfCString fileName("%sdmd-%d.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(void) +NS_LogAddRef(void* aPtr, nsrefcnt aRefcnt, + const char* aClass, uint32_t aClassSize) +{ + ASSERT_ACTIVITY_IS_LEGAL; + if (!gInitialized) { + InitTraceLog(); + } + if (gLogging == NoLogging) { + return; + } + if (aRefcnt == 1 || gLogging == FullLogging) { + AutoTraceLogLock lock; + + 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 || LogThisType(aClass)); + intptr_t serialno = 0; + if (gSerialNumbers && loggingThisType) { + serialno = GetSerialNumber(aPtr, aRefcnt == 1); + MOZ_ASSERT(serialno != 0, + "Serial number requested for unrecognized pointer! " + "Are you memmoving a refcounted object?"); + int32_t* count = GetRefCount(aPtr); + if (count) { + (*count)++; + } + + } + + 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); + } + + 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); + fflush(gRefcntsLog); + } + } +} + +EXPORT_XPCOM_API(void) +NS_LogRelease(void* aPtr, nsrefcnt aRefcnt, const char* aClass) +{ + ASSERT_ACTIVITY_IS_LEGAL; + if (!gInitialized) { + InitTraceLog(); + } + if (gLogging == NoLogging) { + return; + } + if (aRefcnt == 0 || gLogging == FullLogging) { + AutoTraceLogLock lock; + + if (aRefcnt == 0 && gBloatLog) { + BloatEntry* entry = GetBloatEntry(aClass, 0); + if (entry) { + entry->Dtor(); + } + } + + bool loggingThisType = (!gTypesToLog || LogThisType(aClass)); + intptr_t serialno = 0; + if (gSerialNumbers && loggingThisType) { + serialno = GetSerialNumber(aPtr, false); + MOZ_ASSERT(serialno != 0, + "Serial number requested for unrecognized pointer! " + "Are you memmoving a refcounted object?"); + int32_t* count = GetRefCount(aPtr); + if (count) { + (*count)--; + } + + } + + 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); + 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); + } + + if (aRefcnt == 0 && gSerialNumbers && loggingThisType) { + RecycleSerialNumberPtr(aPtr); + } + } +} + +EXPORT_XPCOM_API(void) +NS_LogCtor(void* aPtr, const char* aType, uint32_t aInstanceSize) +{ + ASSERT_ACTIVITY_IS_LEGAL; + if (!gInitialized) { + InitTraceLog(); + } + + if (gLogging == NoLogging) { + return; + } + + AutoTraceLogLock lock; + + if (gBloatLog) { + BloatEntry* entry = GetBloatEntry(aType, aInstanceSize); + if (entry) { + entry->Ctor(); + } + } + + bool loggingThisType = (!gTypesToLog || LogThisType(aType)); + intptr_t serialno = 0; + if (gSerialNumbers && loggingThisType) { + serialno = GetSerialNumber(aPtr, true); + 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); + } +} + + +EXPORT_XPCOM_API(void) +NS_LogDtor(void* aPtr, const char* aType, uint32_t aInstanceSize) +{ + ASSERT_ACTIVITY_IS_LEGAL; + if (!gInitialized) { + InitTraceLog(); + } + + if (gLogging == NoLogging) { + return; + } + + AutoTraceLogLock lock; + + if (gBloatLog) { + BloatEntry* entry = GetBloatEntry(aType, aInstanceSize); + if (entry) { + entry->Dtor(); + } + } + + bool loggingThisType = (!gTypesToLog || LogThisType(aType)); + intptr_t serialno = 0; + if (gSerialNumbers && loggingThisType) { + serialno = GetSerialNumber(aPtr, false); + 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); + } +} + + +EXPORT_XPCOM_API(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; + + intptr_t serialno = GetSerialNumber(object, false); + if (serialno == 0) { + return; + } + + int32_t* count = GetCOMPtrCount(object); + if (count) { + (*count)++; + } + + bool loggingThisObject = (!gObjectsToLog || LogThisObj(serialno)); + + if (gCOMPtrLog && loggingThisObject) { + fprintf(gCOMPtrLog, "\n %p %" PRIdPTR " nsCOMPtrAddRef %d %p\n", + object, serialno, count ? (*count) : -1, aCOMPtr); + WalkTheStackCached(gCOMPtrLog); + } + } +#endif // HAVE_CPP_DYNAMIC_CAST_TO_VOID_PTR +} + + +EXPORT_XPCOM_API(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; + + intptr_t serialno = GetSerialNumber(object, false); + if (serialno == 0) { + return; + } + + int32_t* count = GetCOMPtrCount(object); + if (count) { + (*count)--; + } + + bool loggingThisObject = (!gObjectsToLog || LogThisObj(serialno)); + + if (gCOMPtrLog && loggingThisObject) { + fprintf(gCOMPtrLog, "\n %p %" PRIdPTR " nsCOMPtrRelease %d %p\n", + object, serialno, count ? (*count) : -1, aCOMPtr); + WalkTheStackCached(gCOMPtrLog); + } + } +#endif // HAVE_CPP_DYNAMIC_CAST_TO_VOID_PTR +} + +void +nsTraceRefcnt::Shutdown() +{ + gCodeAddressService = nullptr; + if (gBloatView) { + PL_HashTableDestroy(gBloatView); + gBloatView = nullptr; + } + if (gTypesToLog) { + PL_HashTableDestroy(gTypesToLog); + gTypesToLog = nullptr; + } + if (gObjectsToLog) { + PL_HashTableDestroy(gObjectsToLog); + gObjectsToLog = nullptr; + } + if (gSerialNumbers) { + PL_HashTableDestroy(gSerialNumbers); + gSerialNumbers = nullptr; + } + maybeUnregisterAndCloseFile(gBloatLog); + maybeUnregisterAndCloseFile(gRefcntsLog); + maybeUnregisterAndCloseFile(gAllocLog); + maybeUnregisterAndCloseFile(gCOMPtrLog); +} + +void +nsTraceRefcnt::SetActivityIsLegal(bool aLegal) +{ + if (gActivityTLS == BAD_TLS_INDEX) { + PR_NewThreadPrivateIndex(&gActivityTLS, nullptr); + } + + PR_SetThreadPrivate(gActivityTLS, reinterpret_cast(!aLegal)); +} diff --git a/xpcom/base/nsTraceRefcnt.h b/xpcom/base/nsTraceRefcnt.h new file mode 100644 index 000000000..73e123a3f --- /dev/null +++ b/xpcom/base/nsTraceRefcnt.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 nsTraceRefcnt_h___ +#define nsTraceRefcnt_h___ + +#include // for FILE +#include "nscore.h" + +class nsTraceRefcnt +{ +public: + static void Shutdown(); + + static nsresult DumpStatistics(); + + static void ResetStatistics(); + + static void WalkTheStack(FILE* aStream); + + /** + * 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); +}; + +//////////////////////////////////////////////////////////////////////////////// +// And now for that utility that you've all been asking for... + +extern "C" void +NS_MeanAndStdDev(double aNumberOfValues, + double aSumOfValues, double aSumOfSquaredValues, + double* aMeanResult, double* aStdDevResult); + +//////////////////////////////////////////////////////////////////////////////// +#endif diff --git a/xpcom/base/nsUUIDGenerator.cpp b/xpcom/base/nsUUIDGenerator.cpp new file mode 100644 index 000000000..254a01322 --- /dev/null +++ b/xpcom/base/nsUUIDGenerator.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/. */ + +#if defined(XP_WIN) +#include +#include +#elif defined(XP_MACOSX) +#include +#else +#include +#include "prrng.h" +#endif + +#include "nsUUIDGenerator.h" + +#ifdef ANDROID +extern "C" NS_EXPORT void arc4random_buf(void*, size_t); +#endif + +using namespace mozilla; + +NS_IMPL_ISUPPORTS(nsUUIDGenerator, nsIUUIDGenerator) + +nsUUIDGenerator::nsUUIDGenerator() + : mLock("nsUUIDGenerator.mLock") +{ +} + +nsUUIDGenerator::~nsUUIDGenerator() +{ +} + +nsresult +nsUUIDGenerator::Init() +{ + // We're a service, so we're guaranteed that Init() is not going + // to be reentered while we're inside Init(). + +#if !defined(XP_WIN) && !defined(XP_MACOSX) && !defined(HAVE_ARC4RANDOM) + /* initialize random number generator using NSPR random noise */ + unsigned int seed; + + size_t bytes = 0; + while (bytes < sizeof(seed)) { + size_t nbytes = PR_GetRandomNoise(((unsigned char*)&seed) + bytes, + sizeof(seed) - bytes); + if (nbytes == 0) { + return NS_ERROR_FAILURE; + } + bytes += nbytes; + } + + /* Initialize a new RNG state, and immediately switch + * back to the previous one -- we want to use mState + * only for our own calls to random(). + */ + mSavedState = initstate(seed, mState, sizeof(mState)); + setstate(mSavedState); + + mRBytes = 4; +#ifdef RAND_MAX + if ((unsigned long)RAND_MAX < 0xffffffffUL) { + mRBytes = 3; + } + if ((unsigned long)RAND_MAX < 0x00ffffffUL) { + mRBytes = 2; + } + if ((unsigned long)RAND_MAX < 0x0000ffffUL) { + mRBytes = 1; + } + if ((unsigned long)RAND_MAX < 0x000000ffUL) { + return NS_ERROR_FAILURE; + } +#endif + +#endif /* non XP_WIN and non XP_MACOSX and non ARC4RANDOM */ + + return NS_OK; +} + +NS_IMETHODIMP +nsUUIDGenerator::GenerateUUID(nsID** aRet) +{ + nsID* id = static_cast(moz_xmalloc(sizeof(nsID))); + if (!id) { + return NS_ERROR_OUT_OF_MEMORY; + } + + nsresult rv = GenerateUUIDInPlace(id); + if (NS_FAILED(rv)) { + free(id); + return rv; + } + + *aRet = id; + return rv; +} + +NS_IMETHODIMP +nsUUIDGenerator::GenerateUUIDInPlace(nsID* aId) +{ + // The various code in this method is probably not threadsafe, so lock + // across the whole method. + MutexAutoLock lock(mLock); + +#if defined(XP_WIN) + HRESULT hr = CoCreateGuid((GUID*)aId); + if (FAILED(hr)) { + return NS_ERROR_FAILURE; + } +#elif defined(XP_MACOSX) + CFUUIDRef uuid = CFUUIDCreate(kCFAllocatorDefault); + if (!uuid) { + return NS_ERROR_FAILURE; + } + + CFUUIDBytes bytes = CFUUIDGetUUIDBytes(uuid); + memcpy(aId, &bytes, sizeof(nsID)); + + CFRelease(uuid); +#else /* not windows or OS X; generate randomness using random(). */ + /* XXX we should be saving the return of setstate here and switching + * back to it; instead, we use the value returned when we called + * initstate, since older glibc's have broken setstate() return values + */ +#ifndef HAVE_ARC4RANDOM + setstate(mState); +#endif + +#ifdef HAVE_ARC4RANDOM_BUF + arc4random_buf(aId, sizeof(nsID)); +#else /* HAVE_ARC4RANDOM_BUF */ + size_t bytesLeft = sizeof(nsID); + while (bytesLeft > 0) { +#ifdef HAVE_ARC4RANDOM + long rval = arc4random(); + const size_t mRBytes = 4; +#else + long rval = random(); +#endif + + + uint8_t* src = (uint8_t*)&rval; + // We want to grab the mRBytes least significant bytes of rval, since + // mRBytes less than sizeof(rval) means the high bytes are 0. +#ifdef IS_BIG_ENDIAN + src += sizeof(rval) - mRBytes; +#endif + uint8_t* dst = ((uint8_t*)aId) + (sizeof(nsID) - bytesLeft); + size_t toWrite = (bytesLeft < mRBytes ? bytesLeft : mRBytes); + for (size_t i = 0; i < toWrite; i++) { + dst[i] = src[i]; + } + + bytesLeft -= toWrite; + } +#endif /* HAVE_ARC4RANDOM_BUF */ + + /* Put in the version */ + aId->m2 &= 0x0fff; + aId->m2 |= 0x4000; + + /* Put in the variant */ + aId->m3[0] &= 0x3f; + aId->m3[0] |= 0x80; + +#ifndef HAVE_ARC4RANDOM + /* Restore the previous RNG state */ + setstate(mSavedState); +#endif +#endif + + return NS_OK; +} diff --git a/xpcom/base/nsUUIDGenerator.h b/xpcom/base/nsUUIDGenerator.h new file mode 100644 index 000000000..dd86093f8 --- /dev/null +++ b/xpcom/base/nsUUIDGenerator.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 _NSUUIDGENERATOR_H_ +#define _NSUUIDGENERATOR_H_ + +#include "mozilla/Attributes.h" +#include "mozilla/Mutex.h" + +#include "nsIUUIDGenerator.h" + +class nsUUIDGenerator final : public nsIUUIDGenerator +{ +public: + nsUUIDGenerator(); + + NS_DECL_THREADSAFE_ISUPPORTS + + NS_DECL_NSIUUIDGENERATOR + + nsresult Init(); + +private: + ~nsUUIDGenerator(); + +protected: + + mozilla::Mutex mLock; +#if !defined(XP_WIN) && !defined(XP_MACOSX) && !defined(HAVE_ARC4RANDOM) + char mState[128]; + char* mSavedState; + uint8_t mRBytes; +#endif +}; + +#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/nsVersionComparatorImpl.cpp b/xpcom/base/nsVersionComparatorImpl.cpp new file mode 100644 index 000000000..8a37d8b1a --- /dev/null +++ b/xpcom/base/nsVersionComparatorImpl.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 "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 000000000..84a76d1bb --- /dev/null +++ b/xpcom/base/nsVersionComparatorImpl.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/. */ + +#include "mozilla/Attributes.h" + +#include "nsIVersionComparator.h" + +class nsVersionComparatorImpl final : public nsIVersionComparator +{ + ~nsVersionComparatorImpl() {} + +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/nsWeakPtr.h b/xpcom/base/nsWeakPtr.h new file mode 100644 index 000000000..e2f7c37f1 --- /dev/null +++ b/xpcom/base/nsWeakPtr.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 nsWeakPtr_h__ +#define nsWeakPtr_h__ + +#include "nsIWeakReference.h" +#include "nsCOMPtr.h" + +// typedef nsCOMPtr nsWeakPtr; + +#endif diff --git a/xpcom/base/nsWindowsHelpers.h b/xpcom/base/nsWindowsHelpers.h new file mode 100644 index 000000000..66505b345 --- /dev/null +++ b/xpcom/base/nsWindowsHelpers.h @@ -0,0 +1,371 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 nsWindowsHelpers_h +#define nsWindowsHelpers_h + +#include +#include "nsAutoRef.h" +#include "nscore.h" +#include "mozilla/Assertions.h" + +// ---------------------------------------------------------------------------- +// Critical Section helper class +// ---------------------------------------------------------------------------- + +class AutoCriticalSection +{ +public: + 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 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) + { + } + + 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); + } + } +}; + + +// 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: + 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: + 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 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; + +namespace { + +// 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) +{ + WCHAR systemPath[MAX_PATH + 1]; + if (!ConstructSystem32Path(aModule, systemPath, MAX_PATH + 1)) { + return NULL; + } + return LoadLibraryW(systemPath); +} + +} + +#endif diff --git a/xpcom/base/nscore.h b/xpcom/base/nscore.h new file mode 100644 index 000000000..f6e73c6bc --- /dev/null +++ b/xpcom/base/nscore.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 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" +#endif + +/* Definitions of functions and operators that allocate memory. */ +#if !defined(XPCOM_GLUE) && !defined(NS_NO_XPCOM) && !defined(MOZ_NO_MOZALLOC) +# include "mozilla/mozalloc.h" +#endif + +/** + * Incorporate the integer data types which XPCOM uses. + */ +#include +#include + +#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 + +#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 + +/** + * Macro for creating typedefs for pointer-to-member types which are + * declared with stdcall. It is important to use this for any type which is + * declared as stdcall (i.e. NS_IMETHOD). For example, instead of writing: + * + * typedef nsresult (nsIFoo::*someType)(nsISupports* arg); + * + * you should write: + * + * typedef + * NS_STDCALL_FUNCPROTO(nsresult, someType, nsIFoo, typeFunc, (nsISupports*)); + * + * where nsIFoo::typeFunc is any method declared as + * NS_IMETHOD typeFunc(nsISupports*); + * + * XXX this can be simplified to always use the non-typeof implementation + * when http://gcc.gnu.org/bugzilla/show_bug.cgi?id=11893 is fixed. + */ + +#ifdef __GNUC__ +#define NS_STDCALL_FUNCPROTO(ret, name, class, func, args) \ + typeof(&class::func) name +#else +#define NS_STDCALL_FUNCPROTO(ret, name, class, func, args) \ + ret (NS_STDCALL class::*name) args +#endif + +/** + * Deprecated declarations. + */ +#ifdef __GNUC__ +# define MOZ_DEPRECATED __attribute__((deprecated)) +#elif defined(_MSC_VER) +# define MOZ_DEPRECATED __declspec(deprecated) +#else +# define MOZ_DEPRECATED +#endif + +/** + * Generic API modifiers which return the standard XPCOM nsresult type + * + * - NS_IMETHOD: use for in-class declarations and definitions. + * - NS_IMETHODIMP: use for out-of-class definitions. + * - NS_METHOD: usually used in conjunction with NS_CALLBACK. + * - NS_CALLBACK: used in some legacy situations. Best avoided. + */ +#define NS_IMETHOD NS_IMETHOD_(nsresult) +#define NS_IMETHODIMP NS_IMETHODIMP_(nsresult) +#define NS_METHOD NS_METHOD_(nsresult) +#define NS_CALLBACK(_name) NS_CALLBACK_(nsresult, _name) + +/** + * Import/Export macros for XPCOM APIs + */ + +#ifdef __cplusplus +#define NS_EXTERN_C extern "C" +#else +#define NS_EXTERN_C +#endif + +#define EXPORT_XPCOM_API(type) NS_EXTERN_C NS_EXPORT type NS_FROZENCALL +#define IMPORT_XPCOM_API(type) NS_EXTERN_C NS_IMPORT type NS_FROZENCALL +#define GLUE_XPCOM_API(type) NS_EXTERN_C NS_HIDDEN_(type) NS_FROZENCALL + +#ifdef IMPL_LIBXUL +#define XPCOM_API(type) EXPORT_XPCOM_API(type) +#elif defined(XPCOM_GLUE) +#define XPCOM_API(type) GLUE_XPCOM_API(type) +#else +#define XPCOM_API(type) IMPORT_XPCOM_API(type) +#endif + +#ifdef MOZILLA_INTERNAL_API + /* + The frozen string API has different definitions of nsAC?String + classes than the internal API. On systems that explicitly declare + dllexport symbols this is not a problem, but on ELF systems + internal symbols can accidentally "shine through"; we rename the + internal classes to avoid symbol conflicts. + */ +# define nsAString nsAString_internal +# define nsACString nsACString_internal +#endif + +#if (defined(DEBUG) || defined(FORCE_BUILD_REFCNT_LOGGING)) +/* Make refcnt logging part of the build. This doesn't mean that + * actual logging will occur (that requires a separate enable; see + * nsTraceRefcnt and nsISupportsImpl.h for more information). */ +#define NS_BUILD_REFCNT_LOGGING +#endif + +/* If NO_BUILD_REFCNT_LOGGING is defined then disable refcnt logging + * in the build. This overrides FORCE_BUILD_REFCNT_LOGGING. */ +#if defined(NO_BUILD_REFCNT_LOGGING) +#undef NS_BUILD_REFCNT_LOGGING +#endif + +/* 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. + */ +#if defined(NS_BUILD_REFCNT_LOGGING) || defined(MOZ_VALGRIND) || defined(MOZ_ASAN) +#define NS_FREE_PERMANENT_DATA +#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" + +typedef MozRefCountType nsrefcnt; + +/* + * 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)) + +/* + * Use NS_STRINGIFY to form a string literal from the value of a macro. + */ +#define NS_STRINGIFY_HELPER(x_) #x_ +#define NS_STRINGIFY(x_) NS_STRINGIFY_HELPER(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 000000000..55795b7bc --- /dev/null +++ b/xpcom/base/nsrootidl.idl @@ -0,0 +1,97 @@ +/* -*- 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" +typedef int64_t PRTime; + +/* + * Forward declarations for new string types + */ +class nsAString; +class nsACString; + +/* + * 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 nsrefcnt ; +typedef unsigned long nsresult ; + +// XXX need this built into xpidl compiler so that it's really size_t or size_t +// and it's scriptable: +typedef unsigned long 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, domstring] native DOMString(ignored); +[ref, domstring] native DOMStringRef(ignored); +[ptr, domstring] native DOMStringPtr(ignored); + +[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); + +%{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 000000000..374763c79 --- /dev/null +++ b/xpcom/build/BinaryPath.h @@ -0,0 +1,188 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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_MACOSX) +#include +#elif defined(XP_UNIX) +#include +#include +#endif + +namespace mozilla { + +class BinaryPath +{ +public: +#ifdef XP_WIN + static nsresult Get(const char* argv0, char aResult[MAXPATHLEN]) + { + wchar_t wide_path[MAXPATHLEN]; + nsresult rv = GetW(argv0, wide_path); + if (NS_FAILED(rv)) { + return rv; + } + WideCharToMultiByte(CP_UTF8, 0, wide_path, -1, + aResult, MAXPATHLEN, nullptr, nullptr); + return NS_OK; + } + +private: + static nsresult GetW(const char* argv0, wchar_t aResult[MAXPATHLEN]) + { + if (::GetModuleFileNameW(0, aResult, MAXPATHLEN)) { + return NS_OK; + } + return NS_ERROR_FAILURE; + } + +#elif defined(XP_MACOSX) + static nsresult Get(const char* argv0, 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(const char* argv0, char aResult[MAXPATHLEN]) + { + // On Android, we use the GRE_HOME variable that is set by the Java + // bootstrap code. + const char* greHome = getenv("GRE_HOME"); +#if defined(MOZ_WIDGET_GONK) + if (!greHome) { + greHome = "/system/b2g"; + } +#endif + + if (!greHome) { + return NS_ERROR_FAILURE; + } + + snprintf(aResult, MAXPATHLEN, "%s/%s", greHome, "dummy"); + aResult[MAXPATHLEN - 1] = '\0'; + return NS_OK; + } + +#elif defined(XP_UNIX) + static nsresult Get(const char* aArgv0, char aResult[MAXPATHLEN]) + { + struct stat fileStat; + // on unix, there is no official way to get the path of the current binary. + // instead of using the MOZILLA_FIVE_HOME hack, which doesn't scale to + // multiple applications, we will try a series of techniques: + // + // 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 nsresult GetFile(const char* aArgv0, nsIFile** aResult) + { + nsCOMPtr lf; +#ifdef XP_WIN + wchar_t exePath[MAXPATHLEN]; + nsresult rv = GetW(aArgv0, exePath); +#else + char exePath[MAXPATHLEN]; + nsresult rv = Get(aArgv0, 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; + } +}; + +} // namespace mozilla + +#endif /* mozilla_BinaryPath_h */ diff --git a/xpcom/build/FileLocation.cpp b/xpcom/build/FileLocation.cpp new file mode 100644 index 000000000..03c1dc027 --- /dev/null +++ b/xpcom/build/FileLocation.cpp @@ -0,0 +1,224 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. 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() +{ +} + +FileLocation::~FileLocation() +{ +} + +FileLocation::FileLocation(nsIFile* aFile) +{ + Init(aFile); +} + +FileLocation::FileLocation(nsIFile* aFile, const char* aPath) +{ + Init(aFile, aPath); +} + +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.Insert("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) { + aData.mZip = new nsZipArchive(); + aData.mZip->OpenArchive(mBaseFile); + } + 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; + } + else 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; + } + else if (mItem) { + nsZipCursor cursor(mItem, mZip, reinterpret_cast(aBuf), + aLen, true); + uint32_t readLen; + cursor.Copy(&readLen); + if (readLen != aLen) { + nsZipArchive::sFileCorruptedReason = "FileLocation::Data: insufficient data"; + 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 000000000..e9a3fc5d0 --- /dev/null +++ b/xpcom/build/FileLocation.h @@ -0,0 +1,134 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 "nsAutoPtr.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(); + + /** + * 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 = nullptr); + + /** + * 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(); + + /** + * 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/FrozenFunctions.cpp b/xpcom/build/FrozenFunctions.cpp new file mode 100644 index 000000000..8a957ca45 --- /dev/null +++ b/xpcom/build/FrozenFunctions.cpp @@ -0,0 +1,138 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsXPCOM.h" +#include "nsXPCOMPrivate.h" +#include "nsXPCOMStrings.h" +#include "xptcall.h" + +#include + +/** + * Private Method to register an exit routine. This method + * used to allow you to setup a callback that will be called from + * the NS_ShutdownXPCOM function after all services and + * components have gone away. It was fatally flawed in that the component + * DLL could be released before the exit function was called; it is now a + * stub implementation that does nothing. + */ +XPCOM_API(nsresult) +NS_RegisterXPCOMExitRoutine(XPCOMExitRoutine aExitRoutine, uint32_t aPriority); + +XPCOM_API(nsresult) +NS_UnregisterXPCOMExitRoutine(XPCOMExitRoutine aExitRoutine); + +static const XPCOMFunctions kFrozenFunctions = { + XPCOM_GLUE_VERSION, + sizeof(XPCOMFunctions), + &NS_InitXPCOM2, + &NS_ShutdownXPCOM, + &NS_GetServiceManager, + &NS_GetComponentManager, + &NS_GetComponentRegistrar, + &NS_GetMemoryManager, + &NS_NewLocalFile, + &NS_NewNativeLocalFile, + &NS_RegisterXPCOMExitRoutine, + &NS_UnregisterXPCOMExitRoutine, + + // these functions were added post 1.4 + &NS_GetDebug, + nullptr, + + // these functions were added post 1.6 + &NS_StringContainerInit, + &NS_StringContainerFinish, + &NS_StringGetData, + &NS_StringSetData, + &NS_StringSetDataRange, + &NS_StringCopy, + &NS_CStringContainerInit, + &NS_CStringContainerFinish, + &NS_CStringGetData, + &NS_CStringSetData, + &NS_CStringSetDataRange, + &NS_CStringCopy, + &NS_CStringToUTF16, + &NS_UTF16ToCString, + &NS_StringCloneData, + &NS_CStringCloneData, + + // these functions were added post 1.7 (post Firefox 1.0) + &moz_xmalloc, + &moz_xrealloc, + &free, + &NS_StringContainerInit2, + &NS_CStringContainerInit2, + &NS_StringGetMutableData, + &NS_CStringGetMutableData, + nullptr, + + // these functions were added post 1.8 + &NS_DebugBreak, + &NS_LogInit, + &NS_LogTerm, + &NS_LogAddRef, + &NS_LogRelease, + &NS_LogCtor, + &NS_LogDtor, + &NS_LogCOMPtrAddRef, + &NS_LogCOMPtrRelease, + &NS_GetXPTCallStub, + &NS_DestroyXPTCallStub, + &NS_InvokeByIndex, + nullptr, + nullptr, + &NS_StringSetIsVoid, + &NS_StringGetIsVoid, + &NS_CStringSetIsVoid, + &NS_CStringGetIsVoid, + + // these functions were added post 1.9, but then made obsolete + nullptr, + nullptr, + + &NS_CycleCollectorSuspect3, +}; + +EXPORT_XPCOM_API(nsresult) +NS_GetFrozenFunctions(XPCOMFunctions* aFunctions, const char* /* aLibraryPath */) +{ + if (!aFunctions) { + return NS_ERROR_OUT_OF_MEMORY; + } + + if (aFunctions->version != XPCOM_GLUE_VERSION) { + return NS_ERROR_FAILURE; + } + + uint32_t size = aFunctions->size; + if (size > sizeof(XPCOMFunctions)) { + size = sizeof(XPCOMFunctions); + } + + size -= offsetof(XPCOMFunctions, init); + + memcpy(&aFunctions->init, &kFrozenFunctions.init, size); + + return NS_OK; +} + +/* + * Stubs for nsXPCOMPrivate.h + */ + +EXPORT_XPCOM_API(nsresult) +NS_RegisterXPCOMExitRoutine(XPCOMExitRoutine aExitRoutine, uint32_t aPriority) +{ + return NS_OK; +} + +EXPORT_XPCOM_API(nsresult) +NS_UnregisterXPCOMExitRoutine(XPCOMExitRoutine aExitRoutine) +{ + return NS_OK; +} diff --git a/xpcom/build/IOInterposer.cpp b/xpcom/build/IOInterposer.cpp new file mode 100644 index 000000000..1c3ff54d5 --- /dev/null +++ b/xpcom/build/IOInterposer.cpp @@ -0,0 +1,582 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. 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" + +using namespace mozilla; + +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() {} + +public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ObserverLists) + + ObserverLists() {} + + 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); + } + + ~PerThreadData() + { + MOZ_COUNT_DTOR(PerThreadData); + } + + void CallObservers(IOInterposeObserver::Observation& aObservation) + { + // Prevent recursive reporting. + if (mIsHandlingObservation) { + return; + } + + mIsHandlingObservation = true; + // Decide which list of observers to inform + std::vector* observers = nullptr; + switch (aObservation.ObservedOperation()) { + case IOInterposeObserver::OpCreateOrOpen: + observers = &mObserverLists->mCreateObservers; + break; + case IOInterposeObserver::OpRead: + observers = &mObserverLists->mReadObservers; + break; + case IOInterposeObserver::OpWrite: + observers = &mObserverLists->mWriteObservers; + break; + case IOInterposeObserver::OpFSync: + observers = &mObserverLists->mFSyncObservers; + break; + case IOInterposeObserver::OpStat: + observers = &mObserverLists->mStatObservers; + break; + case IOInterposeObserver::OpClose: + observers = &mObserverLists->mCloseObservers; + break; + case 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; +}; + +class MasterList +{ +public: + MasterList() + : mObservedOperations(IOInterposeObserver::OpNone) + , mIsEnabled(true) + { + MOZ_COUNT_CTOR(MasterList); + } + + ~MasterList() + { + MOZ_COUNT_DTOR(MasterList); + } + + inline void Disable() { mIsEnabled = false; } + inline void Enable() { mIsEnabled = true; } + + void Register(IOInterposeObserver::Operation aOp, + IOInterposeObserver* aObserver) + { + 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 & IOInterposeObserver::OpCreateOrOpen && + !VectorContains(newLists->mCreateObservers, aObserver)) { + newLists->mCreateObservers.push_back(aObserver); + } + if (aOp & IOInterposeObserver::OpRead && + !VectorContains(newLists->mReadObservers, aObserver)) { + newLists->mReadObservers.push_back(aObserver); + } + if (aOp & IOInterposeObserver::OpWrite && + !VectorContains(newLists->mWriteObservers, aObserver)) { + newLists->mWriteObservers.push_back(aObserver); + } + if (aOp & IOInterposeObserver::OpFSync && + !VectorContains(newLists->mFSyncObservers, aObserver)) { + newLists->mFSyncObservers.push_back(aObserver); + } + if (aOp & IOInterposeObserver::OpStat && + !VectorContains(newLists->mStatObservers, aObserver)) { + newLists->mStatObservers.push_back(aObserver); + } + if (aOp & IOInterposeObserver::OpClose && + !VectorContains(newLists->mCloseObservers, aObserver)) { + newLists->mCloseObservers.push_back(aObserver); + } + if (aOp & IOInterposeObserver::OpNextStage && + !VectorContains(newLists->mStageObservers, aObserver)) { + newLists->mStageObservers.push_back(aObserver); + } + mObserverLists = newLists; + mObservedOperations = + (IOInterposeObserver::Operation)(mObservedOperations | aOp); + + mCurrentGeneration++; + } + + void Unregister(IOInterposeObserver::Operation aOp, + IOInterposeObserver* aObserver) + { + IOInterposer::AutoLock lock(mLock); + + ObserverLists* newLists = nullptr; + if (mObserverLists) { + newLists = new ObserverLists(*mObserverLists); + } else { + newLists = new ObserverLists(); + } + + if (aOp & IOInterposeObserver::OpCreateOrOpen) { + VectorRemove(newLists->mCreateObservers, aObserver); + if (newLists->mCreateObservers.empty()) { + mObservedOperations = + (IOInterposeObserver::Operation)(mObservedOperations & + ~IOInterposeObserver::OpCreateOrOpen); + } + } + if (aOp & IOInterposeObserver::OpRead) { + VectorRemove(newLists->mReadObservers, aObserver); + if (newLists->mReadObservers.empty()) { + mObservedOperations = + (IOInterposeObserver::Operation)(mObservedOperations & + ~IOInterposeObserver::OpRead); + } + } + if (aOp & IOInterposeObserver::OpWrite) { + VectorRemove(newLists->mWriteObservers, aObserver); + if (newLists->mWriteObservers.empty()) { + mObservedOperations = + (IOInterposeObserver::Operation)(mObservedOperations & + ~IOInterposeObserver::OpWrite); + } + } + if (aOp & IOInterposeObserver::OpFSync) { + VectorRemove(newLists->mFSyncObservers, aObserver); + if (newLists->mFSyncObservers.empty()) { + mObservedOperations = + (IOInterposeObserver::Operation)(mObservedOperations & + ~IOInterposeObserver::OpFSync); + } + } + if (aOp & IOInterposeObserver::OpStat) { + VectorRemove(newLists->mStatObservers, aObserver); + if (newLists->mStatObservers.empty()) { + mObservedOperations = + (IOInterposeObserver::Operation)(mObservedOperations & + ~IOInterposeObserver::OpStat); + } + } + if (aOp & IOInterposeObserver::OpClose) { + VectorRemove(newLists->mCloseObservers, aObserver); + if (newLists->mCloseObservers.empty()) { + mObservedOperations = + (IOInterposeObserver::Operation)(mObservedOperations & + ~IOInterposeObserver::OpClose); + } + } + if (aOp & IOInterposeObserver::OpNextStage) { + VectorRemove(newLists->mStageObservers, aObserver); + if (newLists->mStageObservers.empty()) { + mObservedOperations = + (IOInterposeObserver::Operation)(mObservedOperations & + ~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 master list. + IOInterposer::AutoLock lock(mLock); + aPtd.SetObserverLists(mCurrentGeneration, mObserverLists); + } + + inline bool IsObservedOperation(IOInterposeObserver::Operation aOp) + { + // The quick reader may observe that no locks are being employed here, + // hence the result of the operations is truly undefined. However, most + // computers will usually return either true or false, which is good enough. + // It is not a problem if we occasionally report more or less IO than is + // actually occurring. + return mIsEnabled && !!(mObservedOperations & aOp); + } + +private: + RefPtr mObserverLists; + // 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. + IOInterposer::Mutex mLock; + // Flags tracking which operations are being observed + IOInterposeObserver::Operation mObservedOperations; + // Used for quickly disabling everything by IOInterposer::Disable() + Atomic mIsEnabled; + // Used to inform threads that the master observer list has changed + Atomic mCurrentGeneration; +}; + +// Special observation used by IOInterposer::EnteringNextStage() +class NextStageObservation : public IOInterposeObserver::Observation +{ +public: + NextStageObservation() + : IOInterposeObserver::Observation(IOInterposeObserver::OpNextStage, + "IOInterposer", false) + { + mStart = TimeStamp::Now(); + mEnd = mStart; + } +}; + +// List of observers registered +static StaticAutoPtr sMasterList; +static MOZ_THREAD_LOCAL(PerThreadData*) sThreadLocalData; +static bool sThreadLocalDataInitialized; +} // namespace + +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 (sMasterList) { + return true; + } + if (!sThreadLocalData.init()) { + return false; + } + sThreadLocalDataInitialized = true; + bool isMainThread = true; + RegisterCurrentThread(isMainThread); + sMasterList = new MasterList(); + + MainThreadIOLogger::Init(); + + // Now we initialize the various interposers depending on platform + 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(); + sMasterList = nullptr; +#endif +} + +void +IOInterposer::Disable() +{ + if (!sMasterList) { + return; + } + sMasterList->Disable(); +} + +void +IOInterposer::Enable() +{ + if (!sMasterList) { + return; + } + sMasterList->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 (!sMasterList) { + // If there is no longer a master list then we should clear the local one. + ptd->ClearObserverLists(); + return; + } + + sMasterList->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 sMasterList && sMasterList->IsObservedOperation(aOp); +} + +void +IOInterposer::Register(IOInterposeObserver::Operation aOp, + IOInterposeObserver* aObserver) +{ + MOZ_ASSERT(aObserver); + if (!sMasterList || !aObserver) { + return; + } + + sMasterList->Register(aOp, aObserver); +} + +void +IOInterposer::Unregister(IOInterposeObserver::Operation aOp, + IOInterposeObserver* aObserver) +{ + if (!sMasterList) { + return; + } + + sMasterList->Unregister(aOp, aObserver); +} + +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; + } + PerThreadData* curThreadData = sThreadLocalData.get(); + MOZ_ASSERT(curThreadData); + sThreadLocalData.set(nullptr); + delete curThreadData; +} + +void +IOInterposer::EnteringNextStage() +{ + if (!sMasterList) { + return; + } + NextStageObservation observation; + Report(observation); +} + diff --git a/xpcom/build/IOInterposer.h b/xpcom/build/IOInterposer.h new file mode 100644 index 000000000..3634572a5 --- /dev/null +++ b/xpcom/build/IOInterposer.h @@ -0,0 +1,295 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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/GuardObjects.h" +#include "mozilla/TimeStamp.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; } + + /** Request filename associated with the I/O operation, null if unknown */ + virtual const char16_t* Filename() { return nullptr; } + + virtual ~Observation() {} + + 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() {} + +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. + * + * Remark: Init() must be called before observers are registered. + */ +void Register(IOInterposeObserver::Operation aOp, + IOInterposeObserver* aObserver); + +/** + * 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) + * + * Remark: Init() must be called before observers are unregistered. + */ +void Unregister(IOInterposeObserver::Operation aOp, + IOInterposeObserver* aObserver); + +/** + * 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(RELEASE_OR_BETA) + IOInterposer::Init(); +#endif + } + + ~IOInterposerInit() + { +#if !defined(RELEASE_OR_BETA) + IOInterposer::Clear(); +#endif + } +}; + +class MOZ_RAII AutoIOInterposerDisable final +{ +public: + explicit AutoIOInterposerDisable(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM) + { + MOZ_GUARD_OBJECT_NOTIFIER_INIT; + IOInterposer::Disable(); + } + ~AutoIOInterposerDisable() + { + IOInterposer::Enable(); + } + +private: + MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER +}; + +} // namespace mozilla + +#endif // mozilla_IOInterposer_h diff --git a/xpcom/build/IOInterposerPrivate.h b/xpcom/build/IOInterposerPrivate.h new file mode 100644 index 000000000..549321062 --- /dev/null +++ b/xpcom/build/IOInterposerPrivate.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 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 + +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 Monitor +{ +public: + Monitor() + : mLock(PR_NewLock()) + , mCondVar(PR_NewCondVar(mLock)) + { + } + + ~Monitor() + { + PR_DestroyCondVar(mCondVar); + mCondVar = nullptr; + PR_DestroyLock(mLock); + mLock = nullptr; + } + + void Lock() + { + PR_Lock(mLock); + } + + void Unlock() + { + PR_Unlock(mLock); + } + + bool Wait(PRIntervalTime aTimeout = PR_INTERVAL_NO_TIMEOUT) + { + return PR_WaitCondVar(mCondVar, aTimeout) == PR_SUCCESS; + } + + bool Notify() + { + return PR_NotifyCondVar(mCondVar) == PR_SUCCESS; + } + +private: + PRLock* mLock; + PRCondVar* mCondVar; +}; + +class MonitorAutoLock +{ +public: + explicit MonitorAutoLock(Monitor& aMonitor) + : mMonitor(aMonitor) + { + mMonitor.Lock(); + } + + ~MonitorAutoLock() + { + mMonitor.Unlock(); + } + + bool Wait(PRIntervalTime aTimeout = PR_INTERVAL_NO_TIMEOUT) + { + return mMonitor.Wait(aTimeout); + } + + bool Notify() + { + return mMonitor.Notify(); + } + +private: + Monitor& mMonitor; +}; + +class MonitorAutoUnlock +{ +public: + explicit MonitorAutoUnlock(Monitor& aMonitor) + : mMonitor(aMonitor) + { + mMonitor.Unlock(); + } + + ~MonitorAutoUnlock() + { + mMonitor.Lock(); + } + +private: + Monitor& mMonitor; +}; + +class Mutex +{ +public: + Mutex() + : mPRLock(PR_NewLock()) + { + } + + ~Mutex() + { + PR_DestroyLock(mPRLock); + mPRLock = nullptr; + } + + void Lock() + { + PR_Lock(mPRLock); + } + + void Unlock() + { + PR_Unlock(mPRLock); + } + +private: + PRLock* mPRLock; +}; + +class AutoLock +{ +public: + explicit AutoLock(Mutex& aLock) + : mLock(aLock) + { + mLock.Lock(); + } + + ~AutoLock() + { + 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 000000000..69dbedc0f --- /dev/null +++ b/xpcom/build/LateWriteChecks.cpp @@ -0,0 +1,258 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. 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 "nsAppDirectoryServiceDefs.h" +#include "nsDirectoryServiceUtils.h" +#include "nsPrintfCString.h" +#include "mozilla/StackWalk.h" +#include "plstr.h" +#include "prio.h" + +#ifdef XP_WIN +#define NS_T(str) L ## str +#define NS_SLASH "\\" +#include +#include +#include +#include +#include +#include +#else +#define NS_SLASH "/" +#endif + +#include "LateWriteChecks.h" + +#define OBSERVE_LATE_WRITES + +using namespace mozilla; + +/*************************** Auxiliary Declarations ***************************/ + +// 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_ASSERT(mFile); + va_list list; + va_start(list, aFormat); + nsAutoCString str; + str.AppendPrintf(aFormat, list); + va_end(list); + mSHA1.update(str.get(), str.Length()); + fwrite(str.get(), 1, str.Length(), mFile); + } + void Finish(SHA1Sum::Hash& aHash) + { + int fd = fileno(mFile); + fflush(mFile); + MozillaUnRegisterDebugFD(fd); + fclose(mFile); + mSHA1.finish(aHash); + mFile = nullptr; + } +private: + FILE* mFile; + 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 IOInterposeObserver +{ +public: + explicit LateWriteObserver(const char* aProfileDirectory) + : mProfileDirectory(PL_strdup(aProfileDirectory)) + { + } + ~LateWriteObserver() + { + PL_strfree(mProfileDirectory); + mProfileDirectory = nullptr; + } + + void Observe(IOInterposeObserver::Observation& aObservation); +private: + char* mProfileDirectory; +}; + +void +LateWriteObserver::Observe(IOInterposeObserver::Observation& aOb) +{ +#ifdef OBSERVE_LATE_WRITES + // Crash if that is the shutdown check mode + if (gShutdownChecks == SCM_CRASH) { + MOZ_CRASH(); + } + + // If we have shutdown mode SCM_NOTHING or we can't record then abort + if (gShutdownChecks == SCM_NOTHING || !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, /* skipFrames */ 0, /* maxFrames */ 0, + reinterpret_cast(&rawStack), 0, nullptr); + Telemetry::ProcessedStack stack = Telemetry::GetStackAndModules(rawStack); + + nsPrintfCString nameAux("%s%s%s", mProfileDirectory, + NS_SLASH, "Telemetry.LateWriteTmpXXXXXX"); + char* name; + nameAux.GetMutableData(&name); + + // 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 + int result = _mktemp_s(name, strlen(name) + 1); + hFile = CreateFileA(name, GENERIC_WRITE, 0, nullptr, CREATE_NEW, + FILE_ATTRIBUTE_NORMAL, nullptr); + } while (GetLastError() == ERROR_FILE_EXISTS); + + if (hFile == INVALID_HANDLE_VALUE) { + NS_RUNTIMEABORT("Um, how did we get here?"); + } + + // http://support.microsoft.com/kb/139640 + int fd = _open_osfhandle((intptr_t)hFile, _O_APPEND); + if (fd == -1) { + NS_RUNTIMEABORT("Um, how did we get here?"); + } + + stream = _fdopen(fd, "w"); +#else + int fd = mkstemp(name); + 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) { + Telemetry::ProcessedStack::Module module = stack.GetModule(i); + sha1Stream.Printf("%s %s\n", module.mBreakpadId.c_str(), + module.mName.c_str()); + } + + size_t numFrames = stack.GetStackSize(); + sha1Stream.Printf("%u\n", (unsigned)numFrames); + for (size_t i = 0; i < numFrames; ++i) { + const 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); + } + + 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. + nsPrintfCString finalName("%s%s", mProfileDirectory, + "/Telemetry.LateWriteFinal-"); + for (int i = 0; i < 20; ++i) { + finalName.AppendPrintf("%02x", sha1[i]); + } + PR_Delete(finalName.get()); + PR_Rename(name, finalName.get()); +#endif +} + +/******************************* Setup/Teardown *******************************/ + +static StaticAutoPtr sLateWriteObserver; + +namespace mozilla { + +void +InitLateWriteChecks() +{ + nsCOMPtr mozFile; + NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(mozFile)); + if (mozFile) { + nsAutoCString nativePath; + nsresult rv = mozFile->GetNativePath(nativePath); + if (NS_SUCCEEDED(rv) && 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; + } +} + +} // namespace mozilla diff --git a/xpcom/build/LateWriteChecks.h b/xpcom/build/LateWriteChecks.h new file mode 100644 index 000000000..96d981cc1 --- /dev/null +++ b/xpcom/build/LateWriteChecks.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 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(); + +} // namespace mozilla + +#endif // mozilla_LateWriteChecks_h diff --git a/xpcom/build/MainThreadIOLogger.cpp b/xpcom/build/MainThreadIOLogger.cpp new file mode 100644 index 000000000..0ca942cb9 --- /dev/null +++ b/xpcom/build/MainThreadIOLogger.cpp @@ -0,0 +1,225 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. 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 "nsAutoPtr.h" +#include "nsNativeCharsetUtils.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 +{ + ObservationWithStack(mozilla::IOInterposeObserver::Observation& aObs, + ProfilerBacktrace* aStack) + : mObservation(aObs) + , mStack(aStack) + { + const char16_t* filename = aObs.Filename(); + if (filename) { + mFilename = filename; + } + } + + mozilla::IOInterposeObserver::Observation mObservation; + ProfilerBacktrace* mStack; + nsString mFilename; +}; + +class MainThreadIOLoggerImpl final : public mozilla::IOInterposeObserver +{ +public: + MainThreadIOLoggerImpl(); + ~MainThreadIOLoggerImpl(); + + bool Init(); + + void Observe(Observation& aObservation); + +private: + static void sIOThreadFunc(void* aArg); + void IOThreadFunc(); + + TimeStamp mLogStartTime; + const char* mFileName; + PRThread* mIOThread; + IOInterposer::Monitor mMonitor; + bool mShutdownRequired; + std::vector mObservations; +}; + +static StaticAutoPtr sImpl; + +MainThreadIOLoggerImpl::MainThreadIOLoggerImpl() + : mFileName(nullptr) + , mIOThread(nullptr) + , mShutdownRequired(false) +{ +} + +MainThreadIOLoggerImpl::~MainThreadIOLoggerImpl() +{ + if (!mIOThread) { + return; + } + { + // Scope for lock + IOInterposer::MonitorAutoLock lock(mMonitor); + mShutdownRequired = true; + lock.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) +{ + PR_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) { + IOInterposer::MonitorAutoLock lock(mMonitor); + mShutdownRequired = true; + std::vector().swap(mObservations); + return; + } + mLogStartTime = TimeStamp::Now(); + { + // Scope for lock + IOInterposer::MonitorAutoLock lock(mMonitor); + while (true) { + while (!mShutdownRequired && mObservations.empty()) { + lock.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 + 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", + (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)"); + } + } + /** + * Format: + * Start Timestamp (Milliseconds), Operation, Duration (Milliseconds), Event Source, Filename + */ + 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) { + ProfilerBacktrace* stack = i->mStack; + if (stack) { + // TODO: Write out the callstack + // (This will be added in a later bug) + profiler_free_backtrace(stack); + } + } + } + } + } + PR_Close(fd); +} + +void +MainThreadIOLoggerImpl::Observe(Observation& aObservation) +{ + if (!mFileName || !IsMainThread()) { + return; + } + 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)); + lock.Notify(); +} + +} // namespace + +namespace mozilla { + +namespace MainThreadIOLogger { + +bool +Init() +{ + nsAutoPtr impl(new MainThreadIOLoggerImpl()); + if (!impl->Init()) { + return false; + } + sImpl = impl.forget(); + 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 000000000..df9e30b53 --- /dev/null +++ b/xpcom/build/MainThreadIOLogger.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_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 000000000..a7c53a97a --- /dev/null +++ b/xpcom/build/NSPRInterposer.cpp @@ -0,0 +1,185 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. 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" + +namespace { + +using namespace mozilla; + +/* Original IO methods */ +PRCloseFN sCloseFn = nullptr; +PRReadFN sReadFn = nullptr; +PRWriteFN sWriteFn = nullptr; +PRFsyncFN sFSyncFn = nullptr; +PRFileInfoFN sFileInfoFn = nullptr; +PRFileInfo64FN sFileInfo64Fn = nullptr; + +/** + * RAII class for timing the duration of an NSPR I/O call and reporting the + * result to the IOInterposeObserver API. + */ +class NSPRIOAutoObservation : public IOInterposeObserver::Observation +{ +public: + explicit NSPRIOAutoObservation(IOInterposeObserver::Operation aOp) + : IOInterposeObserver::Observation(aOp, "NSPRIOInterposer") + { + } + + ~NSPRIOAutoObservation() + { + Report(); + } +}; + +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(IOInterposeObserver::OpClose); + 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(IOInterposeObserver::OpRead); + 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(IOInterposeObserver::OpWrite); + 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(IOInterposeObserver::OpFSync); + 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(IOInterposeObserver::OpStat); + 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(IOInterposeObserver::OpStat); + 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 000000000..2b8715077 --- /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 000000000..e1a8a1e5b --- /dev/null +++ b/xpcom/build/Omnijar.cpp @@ -0,0 +1,188 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. 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]->CloseArchive(); + sReader[aType] = nullptr; + } + if (sOuterReader[aType]) { + sOuterReader[aType]->CloseArchive(); + 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)); + NS_NAMED_LITERAL_CSTRING(kOmnijarName, NS_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 = new nsZipArchive(); + if (NS_FAILED(zipReader->OpenArchive(file))) { + return; + } + + RefPtr outerReader; + RefPtr handle; + if (NS_SUCCEEDED(nsZipHandle::Init(zipReader, NS_STRINGIFY(OMNIJAR_NAME), + getter_AddRefs(handle)))) { + outerReader = zipReader; + zipReader = new nsZipArchive(); + if (NS_FAILED(zipReader->OpenArchive(handle))) { + 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; +} + +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 += "/" NS_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 000000000..35a208bae --- /dev/null +++ b/xpcom/build/Omnijar.h @@ -0,0 +1,160 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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); + + /** + * 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 */ + +} /* namespace mozilla */ + +#endif /* mozilla_Omnijar_h */ diff --git a/xpcom/build/PoisonIOInterposer.h b/xpcom/build/PoisonIOInterposer.h new file mode 100644 index 000000000..4a4d1426d --- /dev/null +++ b/xpcom/build/PoisonIOInterposer.h @@ -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/. */ + +#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_WIN) || defined(XP_MACOSX) + +#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. + */ +void ClearPoisonIOInterposer(); + +} // namespace mozilla +#endif /* __cplusplus */ + +#else /* XP_WIN || XP_MACOSX */ + +#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 000000000..0e5754392 --- /dev/null +++ b/xpcom/build/PoisonIOInterposerBase.cpp @@ -0,0 +1,291 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. 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/Mutex.h" +#include "mozilla/Scoped.h" +#include "mozilla/UniquePtr.h" + +#include + +#include "PoisonIOInterposer.h" + +#ifdef MOZ_REPLACE_MALLOC +#include "replace_malloc_bridge.h" +#endif + +// Auxiliary method to convert file descriptors to ids +#if defined(XP_WIN32) +#include +inline intptr_t +FileDescriptorToHandle(int aFd) +{ + return _get_osfhandle(aFd); +} +#else +inline intptr_t +FileDescriptorToHandle(int aFd) +{ + return aFd; +} +#endif /* if not XP_WIN32 */ + +using namespace mozilla; + +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 Scoped +{ + static PRLock* Lock; +public: + static void Clear(); + 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() + : Scoped(getDebugFileIDsLock()) + { + PR_Lock(get()); + } +}; + +PRLock* DebugFilesAutoLock::Lock; +void +DebugFilesAutoLock::Clear() +{ + MOZ_ASSERT(Lock != nullptr); + Lock = nullptr; +} + +// 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) +{ + MozillaRegisterDebugHandle(FileDescriptorToHandle(aFd)); +} + +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) +{ + MozillaUnRegisterDebugHandle(FileDescriptorToHandle(aFd)); +} + +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 +DebugFdRegistry::RegisterHandle(intptr_t aHandle) +{ + MozillaRegisterDebugHandle(aHandle); +} + +void +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 000000000..966269495 --- /dev/null +++ b/xpcom/build/PoisonIOInterposerMac.cpp @@ -0,0 +1,386 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. 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 "mach_override.h" + +#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 "plstr.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 { + +using namespace mozilla; + +// 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 IOInterposeObserver API. + */ +class MacIOAutoObservation : public IOInterposeObserver::Observation +{ +public: + MacIOAutoObservation(IOInterposeObserver::Operation aOp, int aFd) + : IOInterposeObserver::Observation(aOp, sReference, sIsEnabled && + !IsDebugFile(aFd)) + , mFd(aFd) + , mHasQueriedFilename(false) + , mFilename(nullptr) + { + } + + MacIOAutoObservation(IOInterposeObserver::Operation aOp, int aFd, + const void* aBuf, size_t aCount) + : IOInterposeObserver::Observation(aOp, sReference, sIsEnabled && + !IsDebugFile(aFd) && + IsValidWrite(aFd, aBuf, aCount)) + , mFd(aFd) + , mHasQueriedFilename(false) + , mFilename(nullptr) + { + } + + // Custom implementation of IOInterposeObserver::Observation::Filename + const char16_t* Filename() override; + + ~MacIOAutoObservation() + { + Report(); + if (mFilename) { + free(mFilename); + mFilename = nullptr; + } + } + +private: + int mFd; + bool mHasQueriedFilename; + char16_t* mFilename; + static const char* sReference; +}; + +const char* MacIOAutoObservation::sReference = "PoisonIOInterposer"; + +// Get filename for this observation +const char16_t* +MacIOAutoObservation::Filename() +{ + // If mHasQueriedFilename is true, then we already have it + if (mHasQueriedFilename) { + return mFilename; + } + char filename[MAXPATHLEN]; + if (fcntl(mFd, F_GETPATH, filename) != -1) { + mFilename = UTF8ToNewUnicode(nsDependentCString(filename)); + } else { + mFilename = nullptr; + } + mHasQueriedFilename = true; + + // Return filename + return 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 = 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(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(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(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(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 = 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; + } + DebugOnly t = mach_override_ptr(d->Function, d->Wrapper, + &d->Buffer); + MOZ_ASSERT(t == err_none); + } +} + +void +OnlyReportDirtyWrites() +{ + sOnlyReportDirtyWrites = true; +} + +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 000000000..1f3daa353 --- /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 000000000..752743b34 --- /dev/null +++ b/xpcom/build/PoisonIOInterposerWin.cpp @@ -0,0 +1,501 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. 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/FileUtilsWin.h" +#include "mozilla/IOInterposer.h" +#include "mozilla/Mutex.h" +#include "mozilla/TimeStamp.h" +#include "nsTArray.h" +#include "nsWindowsDllInterceptor.h" +#include "plstr.h" + +#ifdef MOZ_REPLACE_MALLOC +#include "replace_malloc_bridge.h" +#endif + +using namespace mozilla; + +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 ***************************/ + +/** + * RAII class for timing the duration of an I/O call and reporting the result + * to the IOInterposeObserver API. + */ +class WinIOAutoObservation : public IOInterposeObserver::Observation +{ +public: + WinIOAutoObservation(IOInterposeObserver::Operation aOp, + HANDLE aFileHandle, const LARGE_INTEGER* aOffset) + : IOInterposeObserver::Observation( + aOp, sReference, !IsDebugFile(reinterpret_cast(aFileHandle))) + , mFileHandle(aFileHandle) + , mHasQueriedFilename(false) + , mFilename(nullptr) + { + if (mShouldReport) { + mOffset.QuadPart = aOffset ? aOffset->QuadPart : 0; + } + } + + WinIOAutoObservation(IOInterposeObserver::Operation aOp, nsAString& aFilename) + : IOInterposeObserver::Observation(aOp, sReference) + , mFileHandle(nullptr) + , mHasQueriedFilename(false) + , mFilename(nullptr) + { + if (mShouldReport) { + nsAutoString dosPath; + if (NtPathToDosPath(aFilename, dosPath)) { + mFilename = ToNewUnicode(dosPath); + mHasQueriedFilename = true; + } + mOffset.QuadPart = 0; + } + } + + // Custom implementation of IOInterposeObserver::Observation::Filename + const char16_t* Filename() override; + + ~WinIOAutoObservation() + { + Report(); + if (mFilename) { + MOZ_ASSERT(mHasQueriedFilename); + free(mFilename); + mFilename = nullptr; + } + } + +private: + HANDLE mFileHandle; + LARGE_INTEGER mOffset; + bool mHasQueriedFilename; + char16_t* mFilename; + static const char* sReference; +}; + +const char* WinIOAutoObservation::sReference = "PoisonIOInterposer"; + +// Get filename for this observation +const char16_t* +WinIOAutoObservation::Filename() +{ + // If mHasQueriedFilename is true, then filename is already stored in mFilename + if (mHasQueriedFilename) { + return mFilename; + } + + nsAutoString utf16Filename; + if (HandleToFilename(mFileHandle, mOffset, utf16Filename)) { + // Heap allocate with leakable memory + mFilename = ToNewUnicode(utf16Filename); + } + mHasQueriedFilename = true; + + // Return filename + return mFilename; +} + +/*************************** IO Interposing Methods ***************************/ + +// Function pointers to original functions +static NtCreateFileFn gOriginalNtCreateFile; +static NtReadFileFn gOriginalNtReadFile; +static NtReadFileScatterFn gOriginalNtReadFileScatter; +static NtWriteFileFn gOriginalNtWriteFile; +static NtWriteFileGatherFn gOriginalNtWriteFileGather; +static NtFlushBuffersFileFn gOriginalNtFlushBuffersFile; +static NtQueryFullAttributesFileFn 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) +{ + // 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(IOInterposeObserver::OpCreateOrOpen, filename); + + // Something is badly wrong if this function is undefined + MOZ_ASSERT(gOriginalNtCreateFile); + + // Execute original function + return gOriginalNtCreateFile(aFileHandle, + aDesiredAccess, + aObjectAttributes, + aIoStatusBlock, + aAllocationSize, + aFileAttributes, + aShareAccess, + aCreateDisposition, + aCreateOptions, + aEaBuffer, + aEaLength); +} + +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) +{ + // Report IO + WinIOAutoObservation timer(IOInterposeObserver::OpRead, aFileHandle, aOffset); + + // Something is badly wrong if this function is undefined + MOZ_ASSERT(gOriginalNtReadFile); + + // 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) +{ + // Report IO + WinIOAutoObservation timer(IOInterposeObserver::OpRead, aFileHandle, aOffset); + + // Something is badly wrong if this function is undefined + MOZ_ASSERT(gOriginalNtReadFileScatter); + + // 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) +{ + // Report IO + WinIOAutoObservation timer(IOInterposeObserver::OpWrite, aFileHandle, + aOffset); + + // Something is badly wrong if this function is undefined + MOZ_ASSERT(gOriginalNtWriteFile); + + // 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) +{ + // Report IO + WinIOAutoObservation timer(IOInterposeObserver::OpWrite, aFileHandle, + aOffset); + + // Something is badly wrong if this function is undefined + MOZ_ASSERT(gOriginalNtWriteFileGather); + + // Execute original function + return gOriginalNtWriteFileGather(aFileHandle, + aEvent, + aApc, + aApcCtx, + aIoStatus, + aSegments, + aLength, + aOffset, + aKey); +} + +static NTSTATUS NTAPI +InterposedNtFlushBuffersFile(HANDLE aFileHandle, + PIO_STATUS_BLOCK aIoStatusBlock) +{ + // Report IO + WinIOAutoObservation timer(IOInterposeObserver::OpFSync, aFileHandle, + nullptr); + + // Something is badly wrong if this function is undefined + MOZ_ASSERT(gOriginalNtFlushBuffersFile); + + // Execute original function + return gOriginalNtFlushBuffersFile(aFileHandle, + aIoStatusBlock); +} + +static NTSTATUS NTAPI +InterposedNtQueryFullAttributesFile( + POBJECT_ATTRIBUTES aObjectAttributes, + PFILE_NETWORK_OPEN_INFORMATION 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(IOInterposeObserver::OpStat, filename); + + // Something is badly wrong if this function is undefined + MOZ_ASSERT(gOriginalNtQueryFullAttributesFile); + + // Execute original function + return gOriginalNtQueryFullAttributesFile(aObjectAttributes, + aFileInformation); +} + +} // namespace + +/******************************** IO Poisoning ********************************/ + +// Windows DLL interceptor +static WindowsDllInterceptor sNtDllInterceptor; + +namespace mozilla { + +void +InitPoisonIOInterposer() +{ + // 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; + + // 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 + + // Initialize dll interceptor and add hooks + sNtDllInterceptor.Init("ntdll.dll"); + sNtDllInterceptor.AddHook( + "NtCreateFile", + reinterpret_cast(InterposedNtCreateFile), + reinterpret_cast(&gOriginalNtCreateFile)); + sNtDllInterceptor.AddHook( + "NtReadFile", + reinterpret_cast(InterposedNtReadFile), + reinterpret_cast(&gOriginalNtReadFile)); + sNtDllInterceptor.AddHook( + "NtReadFileScatter", + reinterpret_cast(InterposedNtReadFileScatter), + reinterpret_cast(&gOriginalNtReadFileScatter)); + sNtDllInterceptor.AddHook( + "NtWriteFile", + reinterpret_cast(InterposedNtWriteFile), + reinterpret_cast(&gOriginalNtWriteFile)); + sNtDllInterceptor.AddHook( + "NtWriteFileGather", + reinterpret_cast(InterposedNtWriteFileGather), + reinterpret_cast(&gOriginalNtWriteFileGather)); + sNtDllInterceptor.AddHook( + "NtFlushBuffersFile", + reinterpret_cast(InterposedNtFlushBuffersFile), + reinterpret_cast(&gOriginalNtFlushBuffersFile)); + sNtDllInterceptor.AddHook( + "NtQueryFullAttributesFile", + reinterpret_cast(InterposedNtQueryFullAttributesFile), + reinterpret_cast(&gOriginalNtQueryFullAttributesFile)); +} + +void +ClearPoisonIOInterposer() +{ + MOZ_ASSERT(false); + if (sIOPoisoned) { + // Destroy the DLL interceptor + sIOPoisoned = false; + sNtDllInterceptor = WindowsDllInterceptor(); + } +} + +} // namespace mozilla diff --git a/xpcom/build/ServiceList.h b/xpcom/build/ServiceList.h new file mode 100644 index 000000000..3bb8308dd --- /dev/null +++ b/xpcom/build/ServiceList.h @@ -0,0 +1,46 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 "mozilla/Services.h" + +MOZ_SERVICE(ChromeRegistryService, nsIChromeRegistry, + "@mozilla.org/chrome/chrome-registry;1") +MOZ_SERVICE(ToolkitChromeRegistryService, nsIToolkitChromeRegistry, + "@mozilla.org/chrome/chrome-registry;1") +MOZ_SERVICE(XULChromeRegistryService, nsIXULChromeRegistry, + "@mozilla.org/chrome/chrome-registry;1") +MOZ_SERVICE(XULOverlayProviderService, nsIXULOverlayProvider, + "@mozilla.org/chrome/chrome-registry;1") +MOZ_SERVICE(IOService, nsIIOService, + "@mozilla.org/network/io-service;1") +MOZ_SERVICE(ObserverService, nsIObserverService, + "@mozilla.org/observer-service;1") +MOZ_SERVICE(StringBundleService, nsIStringBundleService, + "@mozilla.org/intl/stringbundle;1") +MOZ_SERVICE(XPConnect, nsIXPConnect, + "@mozilla.org/js/xpc/XPConnect;1") +MOZ_SERVICE(InDOMUtils, inIDOMUtils, + "@mozilla.org/inspector/dom-utils;1") +MOZ_SERVICE(PermissionManager, nsIPermissionManager, + "@mozilla.org/permissionmanager;1"); +MOZ_SERVICE(ServiceWorkerManager, nsIServiceWorkerManager, + "@mozilla.org/serviceworkers/manager;1"); +MOZ_SERVICE(AsyncShutdown, nsIAsyncShutdownService, + "@mozilla.org/async-shutdown-service;1") +MOZ_SERVICE(UUIDGenerator, nsIUUIDGenerator, + "@mozilla.org/uuid-generator;1"); +MOZ_SERVICE(GfxInfo, nsIGfxInfo, + "@mozilla.org/gfx/info;1"); + +#ifdef MOZ_USE_NAMESPACE +namespace mozilla { +#endif + +MOZ_SERVICE(HistoryService, IHistory, + "@mozilla.org/browser/history;1") + +#ifdef MOZ_USE_NAMESPACE +} // namespace mozilla +#endif diff --git a/xpcom/build/Services.cpp b/xpcom/build/Services.cpp new file mode 100644 index 000000000..6c521391e --- /dev/null +++ b/xpcom/build/Services.cpp @@ -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/. */ + +#include "mozilla/Likely.h" +#include "mozilla/Services.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 "nsIXULOverlayProvider.h" +#include "IHistory.h" +#include "nsIXPConnect.h" +#include "inIDOMUtils.h" +#include "nsIPermissionManager.h" +#include "nsIServiceWorkerManager.h" +#include "nsIAsyncShutdown.h" +#include "nsIUUIDGenerator.h" +#include "nsIGfxInfo.h" + +using namespace mozilla; +using namespace mozilla::services; + +/* + * Define a global variable and a getter for every service in ServiceList. + * eg. gIOService and GetIOService() + */ +#define MOZ_SERVICE(NAME, TYPE, CONTRACT_ID) \ + static TYPE* g##NAME = nullptr; \ + \ + already_AddRefed \ + mozilla::services::Get##NAME() \ + { \ + if (MOZ_UNLIKELY(gXPCOMShuttingDown)) { \ + return nullptr; \ + } \ + if (!g##NAME) { \ + nsCOMPtr os = do_GetService(CONTRACT_ID); \ + os.swap(g##NAME); \ + } \ + nsCOMPtr ret = g##NAME; \ + return ret.forget(); \ + } \ + NS_EXPORT_(already_AddRefed) \ + mozilla::services::_external_Get##NAME() \ + { \ + return Get##NAME(); \ + } + +#include "ServiceList.h" +#undef MOZ_SERVICE + +/** + * Clears service cache, sets gXPCOMShuttingDown + */ +void +mozilla::services::Shutdown() +{ + gXPCOMShuttingDown = true; +#define MOZ_SERVICE(NAME, TYPE, CONTRACT_ID) NS_IF_RELEASE(g##NAME); +#include "ServiceList.h" +#undef MOZ_SERVICE +} diff --git a/xpcom/build/Services.h b/xpcom/build/Services.h new file mode 100644 index 000000000..84a355399 --- /dev/null +++ b/xpcom/build/Services.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 mozilla_Services_h +#define mozilla_Services_h + +#include "nscore.h" +#include "nsCOMPtr.h" + +#define MOZ_USE_NAMESPACE +#define MOZ_SERVICE(NAME, TYPE, SERVICE_CID) class TYPE; + +#include "ServiceList.h" +#undef MOZ_SERVICE +#undef MOZ_USE_NAMESPACE + +namespace mozilla { +namespace services { + +#ifdef MOZILLA_INTERNAL_API +#define MOZ_SERVICE(NAME, TYPE, SERVICE_CID) \ + already_AddRefed Get##NAME(); \ + NS_EXPORT_(already_AddRefed) _external_Get##NAME(); + +#include "ServiceList.h" +#undef MOZ_SERVICE +#else +#define MOZ_SERVICE(NAME, TYPE, SERVICE_CID) \ + NS_IMPORT_(already_AddRefed) _external_Get##NAME(); \ + inline already_AddRefed Get##NAME() \ + { \ + return _external_Get##NAME(); \ + } + +#include "ServiceList.h" +#undef MOZ_SERVICE +#endif + +} // namespace services +} // namespace mozilla + +#endif diff --git a/xpcom/build/XPCOM.h b/xpcom/build/XPCOM.h new file mode 100644 index 000000000..f52a87c69 --- /dev/null +++ b/xpcom/build/XPCOM.h @@ -0,0 +1,178 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 + +// core headers required by pretty much everything else + +#include "nscore.h" + +#include "nsXPCOMCID.h" +#include "nsXPCOM.h" + +#include "nsError.h" +#include "nsDebug.h" +#include "nsMemory.h" + +#include "nsID.h" + +#include "nsISupports.h" + +#include "nsTArray.h" +#include "nsTWeakRef.h" + +#include "nsCOMPtr.h" +#include "nsCOMArray.h" + +#ifndef MOZILLA_INTERNAL_API +#include "nsStringAPI.h" +#else +#include "nsString.h" +#include "nsReadableUtils.h" +#include "nsNativeCharsetUtils.h" +#endif + +#include "nsISupportsUtils.h" +#include "nsISupportsImpl.h" + +// core data structures + +#include "nsTHashtable.h" +#include "nsHashKeys.h" +#include "nsBaseHashtable.h" +#include "nsDataHashtable.h" +#include "nsInterfaceHashtable.h" +#include "nsClassHashtable.h" +#include "nsRefPtrHashtable.h" + +// interfaces that inherit directly from nsISupports + +#include "nsIArray.h" +#include "nsIAtom.h" +#include "nsIAtomService.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 "nsIErrorService.h" +#include "nsIEventTarget.h" +#include "nsIException.h" +#include "nsIFactory.h" +#include "nsIFile.h" +#include "nsIHashable.h" +#include "nsIINIParser.h" +#include "nsIInputStream.h" +#include "nsIInterfaceRequestor.h" +#include "nsILineInputStream.h" +#include "nsIMemory.h" +#include "nsIMutable.h" +#include "nsIObserver.h" +#include "nsIObserverService.h" +#include "nsIOutputStream.h" +#include "nsIProcess.h" +#include "nsIProperties.h" +#include "nsIPropertyBag2.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" +#include "nsIMacUtils.h" +#endif + +// xpcom/glue utility headers + +#include "nsComponentManagerUtils.h" +#include "nsServiceManagerUtils.h" + +#include "nsIWeakReferenceUtils.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 "nsAgg.h" +#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 000000000..c72ea48d7 --- /dev/null +++ b/xpcom/build/XPCOMInit.cpp @@ -0,0 +1,1126 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "base/basictypes.h" + +#include "mozilla/AbstractThread.h" +#include "mozilla/Atomics.h" +#include "mozilla/Poison.h" +#include "mozilla/SharedThreadPool.h" +#include "mozilla/XPCOM.h" +#include "nsXULAppAPI.h" + +#include "nsXPCOMPrivate.h" +#include "nsXPCOMCIDInternal.h" + +#include "mozilla/layers/ImageBridgeChild.h" +#include "mozilla/layers/CompositorBridgeParent.h" +#include "mozilla/dom/VideoDecoderManagerChild.h" + +#include "prlink.h" + +#include "nsCycleCollector.h" +#include "nsObserverList.h" +#include "nsObserverService.h" +#include "nsProperties.h" +#include "nsPersistentProperties.h" +#include "nsScriptableInputStream.h" +#include "nsBinaryStream.h" +#include "nsStorageStream.h" +#include "nsPipe.h" +#include "nsScriptableBase64Encoder.h" + +#include "nsMemoryImpl.h" +#include "nsDebugImpl.h" +#include "nsTraceRefcnt.h" +#include "nsErrorService.h" + +// Disable deprecation warnings generated by nsISupportsArray and associated +// classes. +#if defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#elif defined(_MSC_VER) +#pragma warning (push) +#pragma warning (disable : 4996) +#endif + +#include "nsSupportsArray.h" + +#if defined(__GNUC__) +#pragma GCC diagnostic pop +#elif defined(_MSC_VER) +#pragma warning (pop) +#endif + +#include "nsArray.h" +#include "nsINIParserImpl.h" +#include "nsSupportsPrimitives.h" +#include "nsConsoleService.h" + +#include "nsComponentManager.h" +#include "nsCategoryManagerUtils.h" +#include "nsIServiceManager.h" + +#include "nsThreadManager.h" +#include "nsThreadPool.h" + +#include "xptinfo.h" +#include "nsIInterfaceInfoManager.h" +#include "xptiprivate.h" +#include "mozilla/XPTInterfaceInfoManager.h" + +#include "nsTimerImpl.h" +#include "TimerThread.h" + +#include "nsThread.h" +#include "nsProcess.h" +#include "nsEnvironment.h" +#include "nsVersionComparatorImpl.h" + +#include "nsIFile.h" +#include "nsLocalFile.h" +#if defined(XP_UNIX) +#include "nsNativeCharsetUtils.h" +#endif +#include "nsDirectoryService.h" +#include "nsDirectoryServiceDefs.h" +#include "nsCategoryManager.h" +#include "nsICategoryManager.h" +#include "nsMultiplexInputStream.h" + +#include "nsStringStream.h" +extern nsresult nsStringInputStreamConstructor(nsISupports*, REFNSIID, void**); + +#include "nsAtomService.h" +#include "nsAtomTable.h" +#include "nsISupportsImpl.h" + +#include "nsHashPropertyBag.h" + +#include "nsUnicharInputStream.h" +#include "nsVariant.h" + +#include "nsUUIDGenerator.h" + +#include "nsIOUtil.h" + +#include "SpecialSystemDirectory.h" + +#if defined(XP_WIN) +#include "mozilla/WindowsVersion.h" +#include "nsWindowsRegKey.h" +#endif + +#ifdef MOZ_WIDGET_COCOA +#include "nsMacUtilsImpl.h" +#endif + +#include "nsSystemInfo.h" +#include "nsMemoryReporterManager.h" +#include "nsMemoryInfoDumper.h" +#include "nsSecurityConsoleMessage.h" +#include "nsMessageLoop.h" + +#include "nsStatusReporterManager.h" + +#include +#include "mozilla/Services.h" +#include "mozilla/Omnijar.h" +#include "mozilla/HangMonitor.h" +#include "mozilla/Telemetry.h" +#include "mozilla/BackgroundHangMonitor.h" + +#include "nsChromeRegistry.h" +#include "nsChromeProtocolHandler.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/SystemMemoryReporter.h" +#include "mozilla/UniquePtr.h" + +#include "mozilla/ipc/GeckoChildProcessHost.h" + +#include "ogg/ogg.h" +#if defined(MOZ_VPX) && !defined(MOZ_VPX_NO_MEM_REPORTING) +#if defined(HAVE_STDINT_H) +// mozilla-config.h defines HAVE_STDINT_H, and then it's defined *again* in +// vpx_config.h (which we include via vpx_mem.h, below). This redefinition +// triggers a build warning on MSVC, so we have to #undef it first. +#undef HAVE_STDINT_H +#endif +#include "vpx_mem/vpx_mem.h" +#endif + +#include "GeckoProfiler.h" + +#include "jsapi.h" +#include "js/Initialization.h" + +#include "gfxPlatform.h" + +#if EXPOSE_INTL_API +#include "unicode/putil.h" +#endif + +using namespace mozilla; +using base::AtExitManager; +using mozilla::ipc::BrowserProcessSubThread; + +namespace { + +static AtExitManager* sExitManager; +static MessageLoop* sMessageLoop; +static bool sCommandLineWasInitialized; +static BrowserProcessSubThread* sIOThread; +static 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 + +NS_GENERIC_FACTORY_CONSTRUCTOR(nsProcess) + +NS_GENERIC_FACTORY_CONSTRUCTOR(nsSupportsID) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsSupportsString) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsSupportsCString) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsSupportsPRBool) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsSupportsPRUint8) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsSupportsPRUint16) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsSupportsPRUint32) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsSupportsPRUint64) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsSupportsPRTime) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsSupportsChar) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsSupportsPRInt16) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsSupportsPRInt32) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsSupportsPRInt64) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsSupportsFloat) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsSupportsDouble) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsSupportsVoid) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsSupportsInterfacePointer) + +NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsConsoleService, Init) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsAtomService) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsTimer) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsBinaryOutputStream) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsBinaryInputStream) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsStorageStream) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsVersionComparatorImpl) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsScriptableBase64Encoder) + +NS_GENERIC_FACTORY_CONSTRUCTOR(nsVariantCC) + +NS_GENERIC_FACTORY_CONSTRUCTOR(nsHashPropertyBagCC) + +NS_GENERIC_AGGREGATED_CONSTRUCTOR(nsProperties) + +NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsUUIDGenerator, Init) + +#ifdef MOZ_WIDGET_COCOA +NS_GENERIC_FACTORY_CONSTRUCTOR(nsMacUtilsImpl) +#endif + +NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsSystemInfo, Init) + +NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsMemoryReporterManager, Init) + +NS_GENERIC_FACTORY_CONSTRUCTOR(nsMemoryInfoDumper) + +NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsStatusReporterManager, Init) + +NS_GENERIC_FACTORY_CONSTRUCTOR(nsIOUtil) + +NS_GENERIC_FACTORY_CONSTRUCTOR(nsSecurityConsoleMessage) + +static nsresult +nsThreadManagerGetSingleton(nsISupports* aOuter, + const nsIID& aIID, + void** aInstancePtr) +{ + NS_ASSERTION(aInstancePtr, "null outptr"); + if (NS_WARN_IF(aOuter)) { + return NS_ERROR_NO_AGGREGATION; + } + + return nsThreadManager::get().QueryInterface(aIID, aInstancePtr); +} + +NS_GENERIC_FACTORY_CONSTRUCTOR(nsThreadPool) + +static nsresult +nsXPTIInterfaceInfoManagerGetSingleton(nsISupports* aOuter, + const nsIID& aIID, + void** aInstancePtr) +{ + NS_ASSERTION(aInstancePtr, "null outptr"); + if (NS_WARN_IF(aOuter)) { + return NS_ERROR_NO_AGGREGATION; + } + + nsCOMPtr iim(XPTInterfaceInfoManager::GetSingleton()); + if (!iim) { + return NS_ERROR_FAILURE; + } + + return iim->QueryInterface(aIID, aInstancePtr); +} + +nsComponentManagerImpl* nsComponentManagerImpl::gComponentManager = nullptr; +bool gXPCOMShuttingDown = false; +bool gXPCOMThreadsShutDown = false; +char16_t* gGREBinPath = nullptr; + +static NS_DEFINE_CID(kComponentManagerCID, NS_COMPONENTMANAGER_CID); +static NS_DEFINE_CID(kINIParserFactoryCID, NS_INIPARSERFACTORY_CID); + +NS_DEFINE_NAMED_CID(NS_CHROMEREGISTRY_CID); +NS_DEFINE_NAMED_CID(NS_CHROMEPROTOCOLHANDLER_CID); + +NS_DEFINE_NAMED_CID(NS_SECURITY_CONSOLE_MESSAGE_CID); + +NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsChromeRegistry, + nsChromeRegistry::GetSingleton) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsChromeProtocolHandler) + +#define NS_PERSISTENTPROPERTIES_CID NS_IPERSISTENTPROPERTIES_CID /* sigh */ + +static already_AddRefed +CreateINIParserFactory(const mozilla::Module& aModule, + const mozilla::Module::CIDEntry& aEntry) +{ + nsCOMPtr f = new nsINIParserFactory(); + return f.forget(); +} + +#define COMPONENT(NAME, Ctor) static NS_DEFINE_CID(kNS_##NAME##_CID, NS_##NAME##_CID); +#define COMPONENT_M(NAME, Ctor, Selector) static NS_DEFINE_CID(kNS_##NAME##_CID, NS_##NAME##_CID); +#include "XPCOMModule.inc" +#undef COMPONENT +#undef COMPONENT_M + +#define COMPONENT(NAME, Ctor) { &kNS_##NAME##_CID, false, nullptr, Ctor }, +#define COMPONENT_M(NAME, Ctor, Selector) { &kNS_##NAME##_CID, false, nullptr, Ctor, Selector }, +const mozilla::Module::CIDEntry kXPCOMCIDEntries[] = { + { &kComponentManagerCID, true, nullptr, nsComponentManagerImpl::Create, Module::ALLOW_IN_GPU_PROCESS }, + { &kINIParserFactoryCID, false, CreateINIParserFactory }, +#include "XPCOMModule.inc" + { &kNS_CHROMEREGISTRY_CID, false, nullptr, nsChromeRegistryConstructor }, + { &kNS_CHROMEPROTOCOLHANDLER_CID, false, nullptr, nsChromeProtocolHandlerConstructor }, + { &kNS_SECURITY_CONSOLE_MESSAGE_CID, false, nullptr, nsSecurityConsoleMessageConstructor }, + { nullptr } +}; +#undef COMPONENT +#undef COMPONENT_M + +#define COMPONENT(NAME, Ctor) { NS_##NAME##_CONTRACTID, &kNS_##NAME##_CID }, +#define COMPONENT_M(NAME, Ctor, Selector) { NS_##NAME##_CONTRACTID, &kNS_##NAME##_CID, Selector }, +const mozilla::Module::ContractIDEntry kXPCOMContracts[] = { +#include "XPCOMModule.inc" + { NS_CHROMEREGISTRY_CONTRACTID, &kNS_CHROMEREGISTRY_CID }, + { NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "chrome", &kNS_CHROMEPROTOCOLHANDLER_CID }, + { NS_INIPARSERFACTORY_CONTRACTID, &kINIParserFactoryCID }, + { NS_SECURITY_CONSOLE_MESSAGE_CONTRACTID, &kNS_SECURITY_CONSOLE_MESSAGE_CID }, + { nullptr } +}; +#undef COMPONENT +#undef COMPONENT_M + +const mozilla::Module kXPCOMModule = { + mozilla::Module::kVersion, kXPCOMCIDEntries, kXPCOMContracts, + nullptr, + nullptr, + nullptr, + nullptr, + Module::ALLOW_IN_GPU_PROCESS +}; + +// gDebug will be freed during shutdown. +static nsIDebug2* gDebug = nullptr; + +EXPORT_XPCOM_API(nsresult) +NS_GetDebug(nsIDebug2** aResult) +{ + return nsDebugImpl::Create(nullptr, NS_GET_IID(nsIDebug2), (void**)aResult); +} + +EXPORT_XPCOM_API(nsresult) +NS_InitXPCOM(nsIServiceManager** aResult, + nsIFile* aBinDirectory) +{ + return NS_InitXPCOM2(aResult, aBinDirectory, nullptr); +} + +class ICUReporter final + : public nsIMemoryReporter + , public 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() {} +}; + +NS_IMPL_ISUPPORTS(ICUReporter, nsIMemoryReporter) + +/* static */ template<> Atomic +CountingAllocatorBase::sAmount(0); + +class OggReporter final + : public nsIMemoryReporter + , public 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() {} +}; + +NS_IMPL_ISUPPORTS(OggReporter, nsIMemoryReporter) + +/* static */ template<> Atomic +CountingAllocatorBase::sAmount(0); + +#ifdef MOZ_VPX +class VPXReporter final + : public nsIMemoryReporter + , public CountingAllocatorBase +{ +public: + NS_DECL_ISUPPORTS + +private: + NS_IMETHOD + CollectReports(nsIHandleReportCallback* aHandleReport, nsISupports* aData, + bool aAnonymize) override + { + MOZ_COLLECT_REPORT( + "explicit/media/libvpx", KIND_HEAP, UNITS_BYTES, MemoryAllocated(), + "Memory allocated through libvpx for WebM media files."); + + return NS_OK; + } + + ~VPXReporter() {} +}; + +NS_IMPL_ISUPPORTS(VPXReporter, nsIMemoryReporter) + +/* static */ template<> Atomic +CountingAllocatorBase::sAmount(0); +#endif /* MOZ_VPX */ + +static double +TimeSinceProcessCreation() +{ + bool ignore; + return (TimeStamp::Now() - TimeStamp::ProcessCreation(ignore)).ToMilliseconds(); +} + +static bool sInitializedJS = false; + +// Note that on OSX, aBinDirectory will point to .app/Contents/Resources/browser +EXPORT_XPCOM_API(nsresult) +NS_InitXPCOM2(nsIServiceManager** aResult, + nsIFile* aBinDirectory, + nsIDirectoryServiceProvider* aAppFileLocationProvider) +{ + static bool sInitialized = false; + if (sInitialized) { + return NS_ERROR_FAILURE; + } + + sInitialized = true; + + mozPoisonValueInit(); + + NS_LogInit(); + + NS_InitAtomTable(); + + mozilla::LogModule::Init(); + + JS_SetCurrentEmbedderTimeFunction(TimeSinceProcessCreation); + + char aLocal; + profiler_init(&aLocal); + nsresult rv = NS_OK; + + // We are not shutting down + gXPCOMShuttingDown = false; + + // Initialize the available memory tracker before other threads have had a + // chance to start up, because the initialization is not thread-safe. + mozilla::AvailableMemoryTracker::Init(); + +#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)) { + UniquePtr ioThread = 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; + } + + // 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 + +#if defined(XP_UNIX) + NS_StartupNativeCharsetUtils(); +#endif + + NS_StartupLocalFile(); + + StartupSpecialSystemDirectory(); + + nsDirectoryService::RealInit(); + + bool value; + + if (aBinDirectory) { + rv = aBinDirectory->IsDirectory(&value); + + if (NS_SUCCEEDED(rv) && value) { + nsDirectoryService::gService->Set(NS_XPCOM_INIT_CURRENT_PROCESS_DIR, + 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(NS_LITERAL_CSTRING("nonexistent-executable")); + 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); + +#if defined(MOZ_VPX) && !defined(MOZ_VPX_NO_MEM_REPORTING) + // And for VPX. + vpx_mem_set_functions(VPXReporter::CountingMalloc, + VPXReporter::CountingCalloc, + VPXReporter::CountingRealloc, + VPXReporter::CountingFree, + memcpy, + memset, + memmove); +#endif + +#if EXPOSE_INTL_API && defined(MOZ_ICU_DATA_ARCHIVE) + nsCOMPtr greDir; + nsDirectoryService::gService->Get(NS_GRE_DIR, + NS_GET_IID(nsIFile), + getter_AddRefs(greDir)); + MOZ_ASSERT(greDir); + nsAutoCString nativeGREPath; + greDir->GetNativePath(nativeGREPath); + u_setDataDirectory(nativeGREPath.get()); +#endif + + // Initialize the JS engine. + const char* jsInitFailureReason = JS_InitWithFailureDiagnostic(); + if (jsInitFailureReason) { + NS_RUNTIMEABORT(jsInitFailureReason); + } + sInitializedJS = true; + + // Init AbstractThread. + AbstractThread::InitStatics(); + + rv = nsComponentManagerImpl::gComponentManager->Init(); + if (NS_FAILED(rv)) { + NS_RELEASE(nsComponentManagerImpl::gComponentManager); + return rv; + } + + if (aResult) { + NS_ADDREF(*aResult = nsComponentManagerImpl::gComponentManager); + } + + // The iimanager constructor searches and registers XPT files. + // (We trigger the singleton's lazy construction here to make that happen.) + (void)XPTInterfaceInfoManager::GetSingleton(); + + // 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 SharedThreadPool (which needs the service manager). + SharedThreadPool::InitStatics(); + + // Force layout to spin up so that nsContentUtils is available for cx stack + // munging. Note that layout registers a number of static atoms, and also + // seals the static atom table, so NS_RegisterStaticAtom may not be called + // beyond this point. + nsCOMPtr componentLoader = + do_GetService("@mozilla.org/moz/jsloader;1"); + + mozilla::scache::StartupCache::GetSingleton(); + mozilla::AvailableMemoryTracker::Activate(); + + // Notify observers of xpcom autoregistration start + NS_CreateServicesFromCategory(NS_XPCOM_STARTUP_CATEGORY, + nullptr, + NS_XPCOM_STARTUP_OBSERVER_ID); +#ifdef XP_WIN + CreateAnonTempFileRemover(); +#endif + + // We only want the SystemMemoryReporter running in one process, because it + // profiles the entire system. The main process is the obvious place for + // it. + if (XRE_IsParentProcess()) { + mozilla::SystemMemoryReporter::Init(); + } + + // The memory reporter manager is up and running -- register our reporters. + RegisterStrongMemoryReporter(new ICUReporter()); + RegisterStrongMemoryReporter(new OggReporter()); +#ifdef MOZ_VPX + RegisterStrongMemoryReporter(new VPXReporter()); +#endif + + mozilla::Telemetry::Init(); + + mozilla::HangMonitor::Startup(); + 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()); + + return NS_OK; +} + +EXPORT_XPCOM_API(nsresult) +NS_InitMinimalXPCOM() +{ + mozPoisonValueInit(); + NS_SetMainThread(); + mozilla::TimeStamp::Startup(); + NS_LogInit(); + NS_InitAtomTable(); + mozilla::LogModule::Init(); + + char aLocal; + profiler_init(&aLocal); + + 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; + } + + AbstractThread::InitStatics(); + SharedThreadPool::InitStatics(); + mozilla::Telemetry::Init(); + mozilla::HangMonitor::Startup(); + 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)) { + NS_RUNTIMEABORT("JS_SetICUMemoryFunctions failed."); + } + sICUReporterInitialized = true; + } +} + +nsresult +ShutdownXPCOM(nsIServiceManager* aServMgr) +{ + // Make sure the hang monitor is enabled for shutdown. + HangMonitor::NotifyActivity(); + + if (!NS_IsMainThread()) { + NS_RUNTIMEABORT("Shutdown on wrong thread"); + } + + nsresult rv; + nsCOMPtr moduleLoaders; + + // 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; + } + + RefPtr observerService; + CallGetService("@mozilla.org/observer-service;1", + (nsObserverService**)getter_AddRefs(observerService)); + + if (observerService) { + mozilla::KillClearOnShutdown(ShutdownPhase::WillShutdown); + observerService->NotifyObservers(nullptr, + NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID, + nullptr); + + nsCOMPtr mgr; + rv = NS_GetServiceManager(getter_AddRefs(mgr)); + if (NS_SUCCEEDED(rv)) { + mozilla::KillClearOnShutdown(ShutdownPhase::Shutdown); + observerService->NotifyObservers(mgr, NS_XPCOM_SHUTDOWN_OBSERVER_ID, + nullptr); + } + } + + // This must happen after the shutdown of media and widgets, which + // are triggered by the NS_XPCOM_SHUTDOWN_OBSERVER_ID notification. + NS_ProcessPendingEvents(thread); + gfxPlatform::ShutdownLayersIPC(); + mozilla::dom::VideoDecoderManagerChild::Shutdown(); + + mozilla::scache::StartupCache::DeleteSingleton(); + if (observerService) + { + mozilla::KillClearOnShutdown(ShutdownPhase::ShutdownThreads); + observerService->NotifyObservers(nullptr, + NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID, + nullptr); + } + + gXPCOMThreadsShutDown = true; + NS_ProcessPendingEvents(thread); + + // Shutdown the timer thread and all timers that might still be alive before + // shutting down the component manager + nsTimerImpl::Shutdown(); + + 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().Shutdown(); + + NS_ProcessPendingEvents(thread); + + HangMonitor::NotifyActivity(); + + // Late-write checks needs to find the profile directory, so it has to + // be initialized before mozilla::services::Shutdown or (because of + // xpcshell tests replacing the service) modules being unloaded. + mozilla::InitLateWriteChecks(); + + // We save the "xpcom-shutdown-loaders" observers to notify after + // the observerservice is gone. + if (observerService) { + mozilla::KillClearOnShutdown(ShutdownPhase::ShutdownLoaders); + observerService->EnumerateObservers(NS_XPCOM_SHUTDOWN_LOADERS_OBSERVER_ID, + getter_AddRefs(moduleLoaders)); + + observerService->Shutdown(); + } + } + + // Free ClearOnShutdown()'ed smart pointers. This needs to happen *after* + // we've finished notifying observers of XPCOM shutdown, because shutdown + // observers themselves might call ClearOnShutdown(). + mozilla::KillClearOnShutdown(ShutdownPhase::ShutdownFinal); + + // 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(); + } + + // Release the directory service + nsDirectoryService::gService = nullptr; + + free(gGREBinPath); + gGREBinPath = nullptr; + + if (moduleLoaders) { + bool more; + nsCOMPtr el; + while (NS_SUCCEEDED(moduleLoaders->HasMoreElements(&more)) && more) { + moduleLoaders->GetNext(getter_AddRefs(el)); + + // Don't worry about weak-reference observers here: there is + // no reason for weak-ref observers to register for + // xpcom-shutdown-loaders + + // 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. + nsCOMPtr obs(do_QueryInterface(el)); + if (obs) { + obs->Observe(nullptr, NS_XPCOM_SHUTDOWN_LOADERS_OBSERVER_ID, nullptr); + } + } + + moduleLoaders = nullptr; + } + + bool shutdownCollect; +#ifdef NS_FREE_PERMANENT_DATA + shutdownCollect = true; +#else + shutdownCollect = !!PR_GetEnv("MOZ_CC_RUN_DURING_SHUTDOWN"); +#endif + nsCycleCollector_shutdown(shutdownCollect); + + PROFILER_MARKER("Shutdown xpcom"); + // If we are doing any shutdown checks, poison writes. + if (gShutdownChecks != SCM_NOTHING) { +#ifdef XP_MACOSX + mozilla::OnlyReportDirtyWrites(); +#endif /* XP_MACOSX */ + mozilla::BeginLateWriteChecks(); + } + + // Shutdown nsLocalFile string conversion + NS_ShutdownLocalFile(); +#ifdef XP_UNIX + NS_ShutdownNativeCharsetUtils(); +#endif + + // Shutdown xpcom. This will release all loaders and cause others holding + // a refcount to the component manager to release it. + if (nsComponentManagerImpl::gComponentManager) { + rv = (nsComponentManagerImpl::gComponentManager)->Shutdown(); + NS_ASSERTION(NS_SUCCEEDED(rv), "Component Manager shutdown failed."); + } else { + NS_WARNING("Component Manager was never created ..."); + } + +#ifdef MOZ_ENABLE_PROFILER_SPS + // In optimized builds we don't do shutdown collections by default, so + // uncollected (garbage) objects may keep the nsXPConnect singleton alive, + // and its XPCJSContext along with it. However, we still destroy various + // bits of state in JS_ShutDown(), so we need to make sure the profiler + // can't access them when it shuts down. This call nulls out the + // JS pseudo-stack's internal reference to the main thread JSContext, + // duplicating the call in XPCJSContext::~XPCJSContext() in case that + // never fired. + if (PseudoStack* stack = mozilla_get_pseudo_stack()) { + stack->sampleContext(nullptr); + } +#endif + + if (sInitializedJS) { + // Shut down the JS engine. + JS_ShutDown(); + sInitializedJS = false; + } + + // Release our own singletons + // Do this _after_ shutting down the component manager, because the + // JS component loader will use XPConnect to call nsIModule::canUnload, + // and that will spin up the InterfaceInfoManager again -- bad mojo + XPTInterfaceInfoManager::FreeInterfaceInfoManager(); + + // 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(); + + NS_ShutdownAtomTable(); + + NS_IF_RELEASE(gDebug); + + delete sIOThread; + sIOThread = nullptr; + + delete sMessageLoop; + sMessageLoop = nullptr; + + if (sCommandLineWasInitialized) { + CommandLine::Terminate(); + sCommandLineWasInitialized = false; + } + + delete sExitManager; + sExitManager = nullptr; + + Omnijar::CleanUp(); + + HangMonitor::Shutdown(); + + delete sMainHangMonitor; + sMainHangMonitor = nullptr; + + BackgroundHangMonitor::Shutdown(); + + profiler_shutdown(); + + NS_LogTerm(); + +#if defined(MOZ_WIDGET_GONK) + // This _exit(0) call is intended to be temporary, to get shutdown leak + // checking working on non-B2G platforms. + // On debug B2G, the child process crashes very late. Instead, just + // give up so at least we exit cleanly. See bug 1071866. + if (XRE_IsContentProcess()) { + NS_WARNING("Exiting child process early!"); + _exit(0); + } +#endif + + return NS_OK; +} + +} // namespace mozilla diff --git a/xpcom/build/XPCOMModule.inc b/xpcom/build/XPCOMModule.inc new file mode 100644 index 000000000..5ac2885e2 --- /dev/null +++ b/xpcom/build/XPCOMModule.inc @@ -0,0 +1,81 @@ + COMPONENT_M(MEMORY, nsMemoryImpl::Create, Module::ALLOW_IN_GPU_PROCESS) + COMPONENT_M(DEBUG, nsDebugImpl::Create, Module::ALLOW_IN_GPU_PROCESS) + COMPONENT(ERRORSERVICE, nsErrorService::Create) + + COMPONENT_M(CATEGORYMANAGER, nsCategoryManager::Create, Module::ALLOW_IN_GPU_PROCESS) + + COMPONENT(SCRIPTABLEINPUTSTREAM, nsScriptableInputStream::Create) + COMPONENT(BINARYINPUTSTREAM, nsBinaryInputStreamConstructor) + COMPONENT(BINARYOUTPUTSTREAM, nsBinaryOutputStreamConstructor) + COMPONENT(STORAGESTREAM, nsStorageStreamConstructor) + COMPONENT(VERSIONCOMPARATOR, nsVersionComparatorImplConstructor) + COMPONENT(SCRIPTABLEBASE64ENCODER, nsScriptableBase64EncoderConstructor) + COMPONENT(PIPE, nsPipeConstructor) + + COMPONENT(PROPERTIES, nsPropertiesConstructor) + + COMPONENT(PERSISTENTPROPERTIES, nsPersistentProperties::Create) + + COMPONENT(SUPPORTSARRAY, nsSupportsArray::Create) + COMPONENT(ARRAY, nsArrayBase::XPCOMConstructor) + COMPONENT(CONSOLESERVICE, nsConsoleServiceConstructor) + COMPONENT(ATOMSERVICE, nsAtomServiceConstructor) + COMPONENT_M(OBSERVERSERVICE, nsObserverService::Create, Module::ALLOW_IN_GPU_PROCESS) + + COMPONENT_M(TIMER, nsTimerConstructor, Module::ALLOW_IN_GPU_PROCESS) + +#define COMPONENT_SUPPORTS(TYPE, Type) \ + COMPONENT(SUPPORTS_##TYPE, nsSupports##Type##Constructor) + + COMPONENT_SUPPORTS(ID, ID) + COMPONENT_SUPPORTS(STRING, String) + COMPONENT_SUPPORTS(CSTRING, CString) + COMPONENT_SUPPORTS(PRBOOL, PRBool) + COMPONENT_SUPPORTS(PRUINT8, PRUint8) + COMPONENT_SUPPORTS(PRUINT16, PRUint16) + COMPONENT_SUPPORTS(PRUINT32, PRUint32) + COMPONENT_SUPPORTS(PRUINT64, PRUint64) + COMPONENT_SUPPORTS(PRTIME, PRTime) + COMPONENT_SUPPORTS(CHAR, Char) + COMPONENT_SUPPORTS(PRINT16, PRInt16) + COMPONENT_SUPPORTS(PRINT32, PRInt32) + COMPONENT_SUPPORTS(PRINT64, PRInt64) + COMPONENT_SUPPORTS(FLOAT, Float) + COMPONENT_SUPPORTS(DOUBLE, Double) + COMPONENT_SUPPORTS(VOID, Void) + COMPONENT_SUPPORTS(INTERFACE_POINTER, InterfacePointer) + +#undef COMPONENT_SUPPORTS + COMPONENT(LOCAL_FILE, nsLocalFile::nsLocalFileConstructor) + COMPONENT(DIRECTORY_SERVICE, nsDirectoryService::Create) + COMPONENT(PROCESS, nsProcessConstructor) + COMPONENT(ENVIRONMENT, nsEnvironment::Create) + + COMPONENT(THREADMANAGER, nsThreadManagerGetSingleton) + COMPONENT_M(THREADPOOL, nsThreadPoolConstructor, Module::ALLOW_IN_GPU_PROCESS) + + COMPONENT(STRINGINPUTSTREAM, nsStringInputStreamConstructor) + COMPONENT(MULTIPLEXINPUTSTREAM, nsMultiplexInputStreamConstructor) + + COMPONENT(VARIANT, nsVariantCCConstructor) + COMPONENT(INTERFACEINFOMANAGER_SERVICE, nsXPTIInterfaceInfoManagerGetSingleton) + + COMPONENT(HASH_PROPERTY_BAG, nsHashPropertyBagCCConstructor) + + COMPONENT(UUID_GENERATOR, nsUUIDGeneratorConstructor) + +#if defined(XP_WIN) + COMPONENT(WINDOWSREGKEY, nsWindowsRegKeyConstructor) +#endif + +#if defined(MOZ_WIDGET_COCOA) + COMPONENT(MACUTILSIMPL, nsMacUtilsImplConstructor) +#endif + + COMPONENT(SYSTEMINFO, nsSystemInfoConstructor) + COMPONENT(MEMORY_REPORTER_MANAGER, nsMemoryReporterManagerConstructor) + COMPONENT(MEMORY_INFO_DUMPER, nsMemoryInfoDumperConstructor) + COMPONENT(IOUTIL, nsIOUtilConstructor) + COMPONENT(CYCLE_COLLECTOR_LOGGER, nsCycleCollectorLoggerConstructor) + COMPONENT(MESSAGE_LOOP, nsMessageLoopConstructor) + COMPONENT(STATUS_REPORTER_MANAGER, nsStatusReporterManagerConstructor) diff --git a/xpcom/build/XREChildData.h b/xpcom/build/XREChildData.h new file mode 100644 index 000000000..487fede94 --- /dev/null +++ b/xpcom/build/XREChildData.h @@ -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/. */ + +#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 TargetServices; +} +#endif + +namespace mozilla { +namespace gmp { +class GMPLoader; +} +} + +/** + * Data needed to start a child process. + */ +struct XREChildData +{ +#if !defined(MOZ_WIDGET_ANDROID) && !defined(MOZ_WIDGET_GONK) + /** + * Used to load the GMP binary. + */ + mozilla::UniquePtr gmpLoader; +#endif + +#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; +#endif +}; + +#endif // XREChildData_h diff --git a/xpcom/build/XREShellData.h b/xpcom/build/XREShellData.h new file mode 100644 index 000000000..11bc162d9 --- /dev/null +++ b/xpcom/build/XREShellData.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 XREShellData_h +#define XREShellData_h + +#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 +}; + +#endif // XREShellData_h diff --git a/xpcom/build/mach_override.c b/xpcom/build/mach_override.c new file mode 100644 index 000000000..9e4940d29 --- /dev/null +++ b/xpcom/build/mach_override.c @@ -0,0 +1,789 @@ +// 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[] = { + { 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 } +}; +#elif defined(__x86_64__) +static AsmInstructionMatch possibleInstructions[] = { + { 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 } +}; +#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 000000000..d9be988a3 --- /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 000000000..68bd001a2 --- /dev/null +++ b/xpcom/build/moz.build @@ -0,0 +1,102 @@ +# -*- 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', + 'nsXREAppData.h', + 'nsXULAppAPI.h', + 'XREChildData.h', + 'xrecore.h', + 'XREShellData.h', +] + +EXPORTS.mozilla += [ + 'FileLocation.h', + 'IOInterposer.h', + 'LateWriteChecks.h', + 'Omnijar.h', + 'PoisonIOInterposer.h', + 'ServiceList.h', + 'Services.h', + 'XPCOM.h', +] + +if CONFIG['OS_ARCH'] == 'WINNT': + EXPORTS += ['nsWindowsDllInterceptor.h'] + EXPORTS.mozilla += ['perfprobe.h'] + SOURCES += [ + 'perfprobe.cpp', + 'PoisonIOInterposerBase.cpp', + 'PoisonIOInterposerWin.cpp', + ] +elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa': + UNIFIED_SOURCES += [ + 'PoisonIOInterposerBase.cpp', + 'PoisonIOInterposerMac.cpp', + ] + SOURCES += ['mach_override.c'] + SOURCES['mach_override.c'].flags += ['-Wno-unused-function'] +else: + SOURCES += ['PoisonIOInterposerStub.cpp'] + +include('../glue/objs.mozbuild') + +UNIFIED_SOURCES += xpcom_gluens_src_cppsrcs +UNIFIED_SOURCES += xpcom_glue_src_cppsrcs + +UNIFIED_SOURCES += [ + 'FrozenFunctions.cpp', + 'IOInterposer.cpp', + 'LateWriteChecks.cpp', + 'MainThreadIOLogger.cpp', + 'nsXPCOMStrings.cpp', + 'Services.cpp', + 'XPCOMInit.cpp', +] + +if CONFIG['OS_ARCH'] != 'WINNT': + SOURCES += [ + 'NSPRInterposer.cpp', + ] + +# FileLocation.cpp and Omnijar.cpp cannot be built in unified mode because they +# use plarena.h. +SOURCES += [ + 'FileLocation.cpp', + 'Omnijar.cpp', +] + +include('/ipc/chromium/chromium-config.mozbuild') + +FINAL_LIBRARY = 'xul' + +DEFINES['_IMPL_NS_STRINGAPI'] = True +DEFINES['OMNIJAR_NAME'] = CONFIG['OMNIJAR_NAME'] +if CONFIG['MOZ_ICU_DATA_ARCHIVE']: + DEFINES['MOZ_ICU_DATA_ARCHIVE'] = True + +LOCAL_INCLUDES += [ + '!..', + '../base', + '../components', + '../ds', + '../glue', + '../io', + '../reflect/xptinfo', + '../threads', + '/chrome', + '/docshell/base', +] + +if CONFIG['MOZ_VPX']: + LOCAL_INCLUDES += [ + '/media/libvpx', + ] + +if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa': + CXXFLAGS += CONFIG['TK_CFLAGS'] diff --git a/xpcom/build/nsWindowsDllInterceptor.h b/xpcom/build/nsWindowsDllInterceptor.h new file mode 100644 index 000000000..f7b7c93fb --- /dev/null +++ b/xpcom/build/nsWindowsDllInterceptor.h @@ -0,0 +1,1127 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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_WINDOWS_DLL_INTERCEPTOR_H_ +#define NS_WINDOWS_DLL_INTERCEPTOR_H_ + +#include "mozilla/Assertions.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/UniquePtr.h" +#include "nsWindowsHelpers.h" + +#include +#include +#include + +/* + * Simple function interception. + * + * We have two separate mechanisms for intercepting a function: We can use the + * built-in nop space, if it exists, or we can create a detour. + * + * Using the built-in nop space works as follows: On x86-32, DLL functions + * begin with a two-byte nop (mov edi, edi) and are preceeded by five bytes of + * NOP instructions. + * + * When we detect a function with this prelude, we do the following: + * + * 1. Write a long jump to our interceptor function into the five bytes of NOPs + * before the function. + * + * 2. Write a short jump -5 into the two-byte nop at the beginning of the function. + * + * This mechanism is nice because it's thread-safe. It's even safe to do if + * another thread is currently running the function we're modifying! + * + * When the WindowsDllNopSpacePatcher is destroyed, we overwrite the short jump + * but not the long jump, so re-intercepting the same function won't work, + * because its prelude won't match. + * + * + * Unfortunately nop space patching doesn't work on functions which don't have + * this magic prelude (and in particular, x86-64 never has the prelude). So + * when we can't use the built-in nop space, we fall back to using a detour, + * which works as follows: + * + * 1. Save first N bytes of OrigFunction to trampoline, where N is a + * number of bytes >= 5 that are instruction aligned. + * + * 2. Replace first 5 bytes of OrigFunction with a jump to the Hook + * function. + * + * 3. After N bytes of the trampoline, add a jump to OrigFunction+N to + * continue original program flow. + * + * 4. Hook function needs to call the trampoline during its execution, + * to invoke the original function (so address of trampoline is + * returned). + * + * When the WindowsDllDetourPatcher object is destructed, OrigFunction is + * patched again to jump directly to the trampoline instead of going through + * the hook function. As such, re-intercepting the same function won't work, as + * jump instructions are not supported. + * + * Note that this is not thread-safe. Sad day. + * + */ + +#include + +namespace mozilla { +namespace internal { + +class AutoVirtualProtect +{ +public: + AutoVirtualProtect(void* aFunc, size_t aSize, DWORD aProtect) + : mFunc(aFunc), mSize(aSize), mNewProtect(aProtect), mOldProtect(0), + mSuccess(false) + {} + + ~AutoVirtualProtect() + { + if (mSuccess) { + VirtualProtectEx(GetCurrentProcess(), mFunc, mSize, mOldProtect, + &mOldProtect); + } + } + + bool Protect() + { + mSuccess = !!VirtualProtectEx(GetCurrentProcess(), mFunc, mSize, + mNewProtect, &mOldProtect); + if (!mSuccess) { + // printf("VirtualProtectEx failed! %d\n", GetLastError()); + } + return mSuccess; + } + +private: + void* const mFunc; + size_t const mSize; + DWORD const mNewProtect; + DWORD mOldProtect; + bool mSuccess; +}; + +class WindowsDllNopSpacePatcher +{ + typedef uint8_t* byteptr_t; + HMODULE mModule; + + // Dumb array for remembering the addresses of functions we've patched. + // (This should be nsTArray, but non-XPCOM code uses this class.) + static const size_t maxPatchedFns = 128; + byteptr_t mPatchedFns[maxPatchedFns]; + int mPatchedFnsLen; + +public: + WindowsDllNopSpacePatcher() + : mModule(0) + , mPatchedFnsLen(0) + {} + +#if defined(_M_IX86) + ~WindowsDllNopSpacePatcher() + { + // Restore the mov edi, edi to the beginning of each function we patched. + + for (int i = 0; i < mPatchedFnsLen; i++) { + byteptr_t fn = mPatchedFns[i]; + + // Ensure we can write to the code. + AutoVirtualProtect protect(fn, 2, PAGE_EXECUTE_READWRITE); + if (!protect.Protect()) { + continue; + } + + // mov edi, edi + *((uint16_t*)fn) = 0xff8b; + + // I don't think this is actually necessary, but it can't hurt. + FlushInstructionCache(GetCurrentProcess(), + /* ignored */ nullptr, + /* ignored */ 0); + } + } + + void Init(const char* aModuleName) + { + if (!IsCompatible()) { +#if defined(MOZILLA_INTERNAL_API) + NS_WARNING("NOP space patching is unavailable for compatibility reasons"); +#endif + return; + } + + mModule = LoadLibraryExA(aModuleName, nullptr, 0); + if (!mModule) { + //printf("LoadLibraryEx for '%s' failed\n", aModuleName); + return; + } + } + + /** + * NVIDIA Optimus drivers utilize Microsoft Detours 2.x to patch functions + * in our address space. There is a bug in Detours 2.x that causes it to + * patch at the wrong address when attempting to detour code that is already + * NOP space patched. This function is an effort to detect the presence of + * this NVIDIA code in our address space and disable NOP space patching if it + * is. We also check AppInit_DLLs since this is the mechanism that the Optimus + * drivers use to inject into our process. + */ + static bool IsCompatible() + { + // These DLLs are known to have bad interactions with this style of patching + const wchar_t* kIncompatibleDLLs[] = { + L"detoured.dll", + L"_etoured.dll", + L"nvd3d9wrap.dll", + L"nvdxgiwrap.dll" + }; + // See if the infringing DLLs are already loaded + for (unsigned int i = 0; i < mozilla::ArrayLength(kIncompatibleDLLs); ++i) { + if (GetModuleHandleW(kIncompatibleDLLs[i])) { + return false; + } + } + if (GetModuleHandleW(L"user32.dll")) { + // user32 is loaded but the infringing DLLs are not, assume we're safe to + // proceed. + return true; + } + // If user32 has not loaded yet, check AppInit_DLLs to ensure that Optimus + // won't be loaded once user32 is initialized. + HKEY hkey = NULL; + if (!RegOpenKeyExW(HKEY_LOCAL_MACHINE, + L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Windows", + 0, KEY_QUERY_VALUE, &hkey)) { + nsAutoRegKey key(hkey); + DWORD numBytes = 0; + const wchar_t kAppInitDLLs[] = L"AppInit_DLLs"; + // Query for required buffer size + LONG status = RegQueryValueExW(hkey, kAppInitDLLs, nullptr, + nullptr, nullptr, &numBytes); + mozilla::UniquePtr data; + if (!status) { + // Allocate the buffer and query for the actual data + data = mozilla::MakeUnique(numBytes / sizeof(wchar_t)); + status = RegQueryValueExW(hkey, kAppInitDLLs, nullptr, + nullptr, (LPBYTE)data.get(), &numBytes); + } + if (!status) { + // For each token, split up the filename components and then check the + // name of the file. + const wchar_t kDelimiters[] = L", "; + wchar_t* tokenContext = nullptr; + wchar_t* token = wcstok_s(data.get(), kDelimiters, &tokenContext); + while (token) { + wchar_t fname[_MAX_FNAME] = {0}; + if (!_wsplitpath_s(token, nullptr, 0, nullptr, 0, + fname, mozilla::ArrayLength(fname), + nullptr, 0)) { + // nvinit.dll is responsible for bootstrapping the DLL injection, so + // that is the library that we check for here + const wchar_t kNvInitName[] = L"nvinit"; + if (!_wcsnicmp(fname, kNvInitName, + mozilla::ArrayLength(kNvInitName))) { + return false; + } + } + token = wcstok_s(nullptr, kDelimiters, &tokenContext); + } + } + } + return true; + } + + bool AddHook(const char* aName, intptr_t aHookDest, void** aOrigFunc) + { + if (!mModule) { + return false; + } + + if (!IsCompatible()) { +#if defined(MOZILLA_INTERNAL_API) + NS_WARNING("NOP space patching is unavailable for compatibility reasons"); +#endif + return false; + } + + if (mPatchedFnsLen == maxPatchedFns) { + // printf ("No space for hook in mPatchedFns.\n"); + return false; + } + + byteptr_t fn = reinterpret_cast(GetProcAddress(mModule, aName)); + if (!fn) { + //printf ("GetProcAddress failed\n"); + return false; + } + + fn = ResolveRedirectedAddress(fn); + + // Ensure we can read and write starting at fn - 5 (for the long jmp we're + // going to write) and ending at fn + 2 (for the short jmp up to the long + // jmp). These bytes may span two pages with different protection. + AutoVirtualProtect protectBefore(fn - 5, 5, PAGE_EXECUTE_READWRITE); + AutoVirtualProtect protectAfter(fn, 2, PAGE_EXECUTE_READWRITE); + if (!protectBefore.Protect() || !protectAfter.Protect()) { + return false; + } + + bool rv = WriteHook(fn, aHookDest, aOrigFunc); + + if (rv) { + mPatchedFns[mPatchedFnsLen] = fn; + mPatchedFnsLen++; + } + + return rv; + } + + bool WriteHook(byteptr_t aFn, intptr_t aHookDest, void** aOrigFunc) + { + // Check that the 5 bytes before aFn are NOP's or INT 3's, + // and that the 2 bytes after aFn are mov(edi, edi). + // + // It's safe to read aFn[-5] because we set it to PAGE_EXECUTE_READWRITE + // before calling WriteHook. + + for (int i = -5; i <= -1; i++) { + if (aFn[i] != 0x90 && aFn[i] != 0xcc) { // nop or int 3 + return false; + } + } + + // mov edi, edi. Yes, there are two ways to encode the same thing: + // + // 0x89ff == mov r/m, r + // 0x8bff == mov r, r/m + // + // where "r" is register and "r/m" is register or memory. Windows seems to + // use 8bff; I include 89ff out of paranoia. + if ((aFn[0] != 0x8b && aFn[0] != 0x89) || aFn[1] != 0xff) { + return false; + } + + // Write a long jump into the space above the function. + aFn[-5] = 0xe9; // jmp + *((intptr_t*)(aFn - 4)) = aHookDest - (uintptr_t)(aFn); // target displacement + + // Set aOrigFunc here, because after this point, aHookDest might be called, + // and aHookDest might use the aOrigFunc pointer. + *aOrigFunc = aFn + 2; + + // Short jump up into our long jump. + *((uint16_t*)(aFn)) = 0xf9eb; // jmp $-5 + + // I think this routine is safe without this, but it can't hurt. + FlushInstructionCache(GetCurrentProcess(), + /* ignored */ nullptr, + /* ignored */ 0); + + return true; + } + +private: + static byteptr_t ResolveRedirectedAddress(const byteptr_t aOriginalFunction) + { + // If function entry is jmp [disp32] such as used by kernel32, + // we resolve redirected address from import table. + if (aOriginalFunction[0] == 0xff && aOriginalFunction[1] == 0x25) { + return (byteptr_t)(**((uint32_t**) (aOriginalFunction + 2))); + } + + return aOriginalFunction; + } +#else + void Init(const char* aModuleName) + { + // Not implemented except on x86-32. + } + + bool AddHook(const char* aName, intptr_t aHookDest, void** aOrigFunc) + { + // Not implemented except on x86-32. + return false; + } +#endif +}; + +class WindowsDllDetourPatcher +{ + typedef unsigned char* byteptr_t; +public: + WindowsDllDetourPatcher() + : mModule(0), mHookPage(0), mMaxHooks(0), mCurHooks(0) + { + } + + ~WindowsDllDetourPatcher() + { + int i; + byteptr_t p; + for (i = 0, p = mHookPage; i < mCurHooks; i++, p += kHookSize) { +#if defined(_M_IX86) + size_t nBytes = 1 + sizeof(intptr_t); +#elif defined(_M_X64) + size_t nBytes = 2 + sizeof(intptr_t); +#else +#error "Unknown processor type" +#endif + byteptr_t origBytes = (byteptr_t)DecodePointer(*((byteptr_t*)p)); + + // ensure we can modify the original code + AutoVirtualProtect protect(origBytes, nBytes, PAGE_EXECUTE_READWRITE); + if (!protect.Protect()) { + continue; + } + + // Remove the hook by making the original function jump directly + // in the trampoline. + intptr_t dest = (intptr_t)(p + sizeof(void*)); +#if defined(_M_IX86) + // Ensure the JMP from CreateTrampoline is where we expect it to be. + if (origBytes[0] != 0xE9) + continue; + *((intptr_t*)(origBytes + 1)) = + dest - (intptr_t)(origBytes + 5); // target displacement +#elif defined(_M_X64) + // Ensure the MOV R11 from CreateTrampoline is where we expect it to be. + if (origBytes[0] != 0x49 || origBytes[1] != 0xBB) + continue; + *((intptr_t*)(origBytes + 2)) = dest; +#else +#error "Unknown processor type" +#endif + } + } + + void Init(const char* aModuleName, int aNumHooks = 0) + { + if (mModule) { + return; + } + + mModule = LoadLibraryExA(aModuleName, nullptr, 0); + if (!mModule) { + //printf("LoadLibraryEx for '%s' failed\n", aModuleName); + return; + } + + int hooksPerPage = 4096 / kHookSize; + if (aNumHooks == 0) { + aNumHooks = hooksPerPage; + } + + mMaxHooks = aNumHooks + (hooksPerPage % aNumHooks); + + mHookPage = (byteptr_t)VirtualAllocEx(GetCurrentProcess(), nullptr, + mMaxHooks * kHookSize, + MEM_COMMIT | MEM_RESERVE, + PAGE_EXECUTE_READ); + if (!mHookPage) { + mModule = 0; + return; + } + } + + bool Initialized() { return !!mModule; } + + bool AddHook(const char* aName, intptr_t aHookDest, void** aOrigFunc) + { + if (!mModule) { + return false; + } + + void* pAddr = (void*)GetProcAddress(mModule, aName); + if (!pAddr) { + //printf ("GetProcAddress failed\n"); + return false; + } + + pAddr = ResolveRedirectedAddress((byteptr_t)pAddr); + + CreateTrampoline(pAddr, aHookDest, aOrigFunc); + if (!*aOrigFunc) { + //printf ("CreateTrampoline failed\n"); + return false; + } + + return true; + } + +protected: + const static int kPageSize = 4096; + const static int kHookSize = 128; + + HMODULE mModule; + byteptr_t mHookPage; + int mMaxHooks; + int mCurHooks; + + // rex bits + static const BYTE kMaskHighNibble = 0xF0; + static const BYTE kRexOpcode = 0x40; + static const BYTE kMaskRexW = 0x08; + static const BYTE kMaskRexR = 0x04; + static const BYTE kMaskRexX = 0x02; + static const BYTE kMaskRexB = 0x01; + + // mod r/m bits + static const BYTE kRegFieldShift = 3; + static const BYTE kMaskMod = 0xC0; + static const BYTE kMaskReg = 0x38; + static const BYTE kMaskRm = 0x07; + static const BYTE kRmNeedSib = 0x04; + static const BYTE kModReg = 0xC0; + static const BYTE kModDisp32 = 0x80; + static const BYTE kModDisp8 = 0x40; + static const BYTE kModNoRegDisp = 0x00; + static const BYTE kRmNoRegDispDisp32 = 0x05; + + // sib bits + static const BYTE kMaskSibScale = 0xC0; + static const BYTE kMaskSibIndex = 0x38; + static const BYTE kMaskSibBase = 0x07; + static const BYTE kSibBaseEbp = 0x05; + + int CountModRmSib(const BYTE *aModRm, BYTE* aSubOpcode = nullptr) + { + if (!aModRm) { + return -1; + } + int numBytes = 1; // Start with 1 for mod r/m byte itself + switch (*aModRm & kMaskMod) { + case kModReg: + return numBytes; + case kModDisp8: + numBytes += 1; + break; + case kModDisp32: + numBytes += 4; + break; + case kModNoRegDisp: + if ((*aModRm & kMaskRm) == kRmNoRegDispDisp32) { +#if defined(_M_X64) + // RIP-relative on AMD64, currently unsupported + return -1; +#else + // On IA-32, all ModR/M instruction modes address memory relative to 0 + numBytes += 4; +#endif + } else if (((*aModRm & kMaskRm) == kRmNeedSib && + (*(aModRm + 1) & kMaskSibBase) == kSibBaseEbp)) { + numBytes += 4; + } + break; + default: + // This should not be reachable + MOZ_ASSERT_UNREACHABLE("Impossible value for modr/m byte mod bits"); + return -1; + } + if ((*aModRm & kMaskRm) == kRmNeedSib) { + // SIB byte + numBytes += 1; + } + if (aSubOpcode) { + *aSubOpcode = (*aModRm & kMaskReg) >> kRegFieldShift; + } + return numBytes; + } + +#if defined(_M_X64) + // To patch for JMP and JE + + enum JumpType { + Je, + Jmp + }; + + struct JumpPatch { + JumpPatch() + : mHookOffset(0), mJumpAddress(0), mType(JumpType::Jmp) + { + } + + JumpPatch(size_t aOffset, intptr_t aAddress, JumpType aType = JumpType::Jmp) + : mHookOffset(aOffset), mJumpAddress(aAddress), mType(aType) + { + } + + void AddJumpPatch(size_t aHookOffset, intptr_t aAbsJumpAddress, + JumpType aType = JumpType::Jmp) + { + mHookOffset = aHookOffset; + mJumpAddress = aAbsJumpAddress; + mType = aType; + } + + size_t GenerateJump(uint8_t* aCode) + { + size_t offset = mHookOffset; + if (mType == JumpType::Je) { + // JNE RIP+14 + aCode[offset] = 0x75; + aCode[offset + 1] = 14; + offset += 2; + } + + // JMP [RIP+0] + aCode[offset] = 0xff; + aCode[offset + 1] = 0x25; + *reinterpret_cast(aCode + offset + 2) = 0; + + // Jump table + *reinterpret_cast(aCode + offset + 2 + 4) = mJumpAddress; + + return offset + 2 + 4 + 8; + } + + bool HasJumpPatch() const + { + return !!mJumpAddress; + } + + size_t mHookOffset; + intptr_t mJumpAddress; + JumpType mType; + }; + +#endif + + enum ePrefixGroupBits + { + eNoPrefixes = 0, + ePrefixGroup1 = (1 << 0), + ePrefixGroup2 = (1 << 1), + ePrefixGroup3 = (1 << 2), + ePrefixGroup4 = (1 << 3) + }; + + int CountPrefixBytes(byteptr_t aBytes, const int aBytesIndex, + unsigned char* aOutGroupBits) + { + unsigned char& groupBits = *aOutGroupBits; + groupBits = eNoPrefixes; + int index = aBytesIndex; + while (true) { + switch (aBytes[index]) { + // Group 1 + case 0xF0: // LOCK + case 0xF2: // REPNZ + case 0xF3: // REP / REPZ + if (groupBits & ePrefixGroup1) { + return -1; + } + groupBits |= ePrefixGroup1; + ++index; + break; + + // Group 2 + case 0x2E: // CS override / branch not taken + case 0x36: // SS override + case 0x3E: // DS override / branch taken + case 0x64: // FS override + case 0x65: // GS override + if (groupBits & ePrefixGroup2) { + return -1; + } + groupBits |= ePrefixGroup2; + ++index; + break; + + // Group 3 + case 0x66: // operand size override + if (groupBits & ePrefixGroup3) { + return -1; + } + groupBits |= ePrefixGroup3; + ++index; + break; + + // Group 4 + case 0x67: // Address size override + if (groupBits & ePrefixGroup4) { + return -1; + } + groupBits |= ePrefixGroup4; + ++index; + break; + + default: + return index - aBytesIndex; + } + } + } + + void CreateTrampoline(void* aOrigFunction, intptr_t aDest, void** aOutTramp) + { + *aOutTramp = nullptr; + + AutoVirtualProtect protectHookPage(mHookPage, mMaxHooks * kHookSize, + PAGE_EXECUTE_READWRITE); + if (!protectHookPage.Protect()) { + return; + } + + byteptr_t tramp = FindTrampolineSpace(); + if (!tramp) { + return; + } + + byteptr_t origBytes = (byteptr_t)aOrigFunction; + + int nBytes = 0; + +#if defined(_M_IX86) + int pJmp32 = -1; + while (nBytes < 5) { + // Understand some simple instructions that might be found in a + // prologue; we might need to extend this as necessary. + // + // Note! If we ever need to understand jump instructions, we'll + // need to rewrite the displacement argument. + unsigned char prefixGroups; + int numPrefixBytes = CountPrefixBytes(origBytes, nBytes, &prefixGroups); + if (numPrefixBytes < 0 || (prefixGroups & (ePrefixGroup3 | ePrefixGroup4))) { + // Either the prefix sequence was bad, or there are prefixes that + // we don't currently support (groups 3 and 4) + return; + } + nBytes += numPrefixBytes; + if (origBytes[nBytes] >= 0x88 && origBytes[nBytes] <= 0x8B) { + // various MOVs + ++nBytes; + int len = CountModRmSib(origBytes + nBytes); + if (len < 0) { + return; + } + nBytes += len; + } else if (origBytes[nBytes] == 0xA1) { + // MOV eax, [seg:offset] + nBytes += 5; + } else if (origBytes[nBytes] == 0xB8) { + // MOV 0xB8: http://ref.x86asm.net/coder32.html#xB8 + nBytes += 5; + } else if (origBytes[nBytes] == 0x83) { + // ADD|ODR|ADC|SBB|AND|SUB|XOR|CMP r/m, imm8 + unsigned char b = origBytes[nBytes + 1]; + if ((b & 0xc0) == 0xc0) { + // ADD|ODR|ADC|SBB|AND|SUB|XOR|CMP r, imm8 + nBytes += 3; + } else { + // bail + return; + } + } else if (origBytes[nBytes] == 0x68) { + // PUSH with 4-byte operand + nBytes += 5; + } else if ((origBytes[nBytes] & 0xf0) == 0x50) { + // 1-byte PUSH/POP + nBytes++; + } else if (origBytes[nBytes] == 0x6A) { + // PUSH imm8 + nBytes += 2; + } else if (origBytes[nBytes] == 0xe9) { + pJmp32 = nBytes; + // jmp 32bit offset + nBytes += 5; + } else if (origBytes[nBytes] == 0xff && origBytes[nBytes + 1] == 0x25) { + // jmp [disp32] + nBytes += 6; + } else { + //printf ("Unknown x86 instruction byte 0x%02x, aborting trampoline\n", origBytes[nBytes]); + return; + } + } +#elif defined(_M_X64) + JumpPatch jump; + + while (nBytes < 13) { + + // if found JMP 32bit offset, next bytes must be NOP or INT3 + if (jump.HasJumpPatch()) { + if (origBytes[nBytes] == 0x90 || origBytes[nBytes] == 0xcc) { + nBytes++; + continue; + } + return; + } + if (origBytes[nBytes] == 0x0f) { + nBytes++; + if (origBytes[nBytes] == 0x1f) { + // nop (multibyte) + nBytes++; + if ((origBytes[nBytes] & 0xc0) == 0x40 && + (origBytes[nBytes] & 0x7) == 0x04) { + nBytes += 3; + } else { + return; + } + } else if (origBytes[nBytes] == 0x05) { + // syscall + nBytes++; + } else if (origBytes[nBytes] == 0x84) { + // je rel32 + jump.AddJumpPatch(nBytes - 1, + (intptr_t) + origBytes + nBytes + 5 + + *(reinterpret_cast(origBytes + + nBytes + 1)), + JumpType::Je); + nBytes += 5; + } else { + return; + } + } else if (origBytes[nBytes] == 0x40 || + origBytes[nBytes] == 0x41) { + // Plain REX or REX.B + nBytes++; + + if ((origBytes[nBytes] & 0xf0) == 0x50) { + // push/pop with Rx register + nBytes++; + } else if (origBytes[nBytes] >= 0xb8 && origBytes[nBytes] <= 0xbf) { + // mov r32, imm32 + nBytes += 5; + } else { + return; + } + } else if (origBytes[nBytes] == 0x45) { + // REX.R & REX.B + nBytes++; + + if (origBytes[nBytes] == 0x33) { + // xor r32, r32 + nBytes += 2; + } else { + return; + } + } else if ((origBytes[nBytes] & 0xfb) == 0x48) { + // REX.W | REX.WR + nBytes++; + + if (origBytes[nBytes] == 0x81 && + (origBytes[nBytes + 1] & 0xf8) == 0xe8) { + // sub r, dword + nBytes += 6; + } else if (origBytes[nBytes] == 0x83 && + (origBytes[nBytes + 1] & 0xf8) == 0xe8) { + // sub r, byte + nBytes += 3; + } else if (origBytes[nBytes] == 0x83 && + (origBytes[nBytes + 1] & 0xf8) == 0x60) { + // and [r+d], imm8 + nBytes += 5; + } else if (origBytes[nBytes] == 0x85) { + // 85 /r => TEST r/m32, r32 + if ((origBytes[nBytes + 1] & 0xc0) == 0xc0) { + nBytes += 2; + } else { + return; + } + } else if ((origBytes[nBytes] & 0xfd) == 0x89) { + ++nBytes; + // MOV r/m64, r64 | MOV r64, r/m64 + int len = CountModRmSib(origBytes + nBytes); + if (len < 0) { + return; + } + nBytes += len; + } else if (origBytes[nBytes] == 0xc7) { + // MOV r/m64, imm32 + if (origBytes[nBytes + 1] == 0x44) { + // MOV [r64+disp8], imm32 + // ModR/W + SIB + disp8 + imm32 + nBytes += 8; + } else { + return; + } + } else if (origBytes[nBytes] == 0xff) { + // JMP /4 + if ((origBytes[nBytes + 1] & 0xc0) == 0x0 && + (origBytes[nBytes + 1] & 0x07) == 0x5) { + // [rip+disp32] + // convert JMP 32bit offset to JMP 64bit direct + jump.AddJumpPatch(nBytes - 1, + *reinterpret_cast( + origBytes + nBytes + 6 + + *reinterpret_cast(origBytes + nBytes + + 2))); + nBytes += 6; + } else { + // not support yet! + return; + } + } else { + // not support yet! + return; + } + } else if (origBytes[nBytes] == 0x66) { + // operand override prefix + nBytes += 1; + // This is the same as the x86 version + if (origBytes[nBytes] >= 0x88 && origBytes[nBytes] <= 0x8B) { + // various MOVs + unsigned char b = origBytes[nBytes + 1]; + if (((b & 0xc0) == 0xc0) || + (((b & 0xc0) == 0x00) && + ((b & 0x07) != 0x04) && ((b & 0x07) != 0x05))) { + // REG=r, R/M=r or REG=r, R/M=[r] + nBytes += 2; + } else if ((b & 0xc0) == 0x40) { + if ((b & 0x07) == 0x04) { + // REG=r, R/M=[SIB + disp8] + nBytes += 4; + } else { + // REG=r, R/M=[r + disp8] + nBytes += 3; + } + } else { + // complex MOV, bail + return; + } + } + } else if ((origBytes[nBytes] & 0xf0) == 0x50) { + // 1-byte push/pop + nBytes++; + } else if (origBytes[nBytes] == 0x65) { + // GS prefix + // + // The entry of GetKeyState on Windows 10 has the following code. + // 65 48 8b 04 25 30 00 00 00 mov rax,qword ptr gs:[30h] + // (GS prefix + REX + MOV (0x8b) ...) + if (origBytes[nBytes + 1] == 0x48 && + (origBytes[nBytes + 2] >= 0x88 && origBytes[nBytes + 2] <= 0x8b)) { + nBytes += 3; + int len = CountModRmSib(origBytes + nBytes); + if (len < 0) { + // no way to support this yet. + return; + } + nBytes += len; + } else { + return; + } + } else if (origBytes[nBytes] == 0x90) { + // nop + nBytes++; + } else if (origBytes[nBytes] == 0xb8) { + // MOV 0xB8: http://ref.x86asm.net/coder32.html#xB8 + nBytes += 5; + } else if (origBytes[nBytes] == 0x33) { + // xor r32, r/m32 + nBytes += 2; + } else if (origBytes[nBytes] == 0xf6) { + // test r/m8, imm8 (used by ntdll on Windows 10 x64) + // (no flags are affected by near jmp since there is no task switch, + // so it is ok for a jmp to be written immediately after a test) + BYTE subOpcode = 0; + int nModRmSibBytes = CountModRmSib(&origBytes[nBytes + 1], &subOpcode); + if (nModRmSibBytes < 0 || subOpcode != 0) { + // Unsupported + return; + } + nBytes += 2 + nModRmSibBytes; + } else if (origBytes[nBytes] == 0xc3) { + // ret + nBytes++; + } else if (origBytes[nBytes] == 0xcc) { + // int 3 + nBytes++; + } else if (origBytes[nBytes] == 0xe9) { + // jmp 32bit offset + jump.AddJumpPatch(nBytes, + // convert JMP 32bit offset to JMP 64bit direct + (intptr_t) + origBytes + nBytes + 5 + + *(reinterpret_cast(origBytes + nBytes + 1))); + nBytes += 5; + } else if (origBytes[nBytes] == 0xff) { + nBytes++; + if ((origBytes[nBytes] & 0xf8) == 0xf0) { + // push r64 + nBytes++; + } else { + return; + } + } else { + return; + } + } +#else +#error "Unknown processor type" +#endif + + if (nBytes > 100) { + //printf ("Too big!"); + return; + } + + // We keep the address of the original function in the first bytes of + // the trampoline buffer + *((void**)tramp) = EncodePointer(aOrigFunction); + tramp += sizeof(void*); + + memcpy(tramp, aOrigFunction, nBytes); + + // OrigFunction+N, the target of the trampoline + byteptr_t trampDest = origBytes + nBytes; + +#if defined(_M_IX86) + if (pJmp32 >= 0) { + // Jump directly to the original target of the jump instead of jumping to the + // original function. + // Adjust jump target displacement to jump location in the trampoline. + *((intptr_t*)(tramp + pJmp32 + 1)) += origBytes - tramp; + } else { + tramp[nBytes] = 0xE9; // jmp + *((intptr_t*)(tramp + nBytes + 1)) = + (intptr_t)trampDest - (intptr_t)(tramp + nBytes + 5); // target displacement + } +#elif defined(_M_X64) + // If JMP/JE opcode found, we don't insert to trampoline jump + if (jump.HasJumpPatch()) { + size_t offset = jump.GenerateJump(tramp); + if (jump.mType != JumpType::Jmp) { + JumpPatch patch(offset, reinterpret_cast(trampDest)); + patch.GenerateJump(tramp); + } + } else { + JumpPatch patch(nBytes, reinterpret_cast(trampDest)); + patch.GenerateJump(tramp); + } +#endif + + // The trampoline is now valid. + *aOutTramp = tramp; + + // ensure we can modify the original code + AutoVirtualProtect protect(aOrigFunction, nBytes, PAGE_EXECUTE_READWRITE); + if (!protect.Protect()) { + return; + } + +#if defined(_M_IX86) + // now modify the original bytes + origBytes[0] = 0xE9; // jmp + *((intptr_t*)(origBytes + 1)) = + aDest - (intptr_t)(origBytes + 5); // target displacement +#elif defined(_M_X64) + // mov r11, address + origBytes[0] = 0x49; + origBytes[1] = 0xbb; + + *((intptr_t*)(origBytes + 2)) = aDest; + + // jmp r11 + origBytes[10] = 0x41; + origBytes[11] = 0xff; + origBytes[12] = 0xe3; +#endif + } + + byteptr_t FindTrampolineSpace() + { + if (mCurHooks >= mMaxHooks) { + return 0; + } + + byteptr_t p = mHookPage + mCurHooks * kHookSize; + + mCurHooks++; + + return p; + } + + static void* ResolveRedirectedAddress(const byteptr_t aOriginalFunction) + { +#if defined(_M_IX86) + // If function entry is jmp [disp32] such as used by kernel32, + // we resolve redirected address from import table. + if (aOriginalFunction[0] == 0xff && aOriginalFunction[1] == 0x25) { + return (void*)(**((uint32_t**) (aOriginalFunction + 2))); + } +#elif defined(_M_X64) + if (aOriginalFunction[0] == 0xe9) { + // require for TestDllInterceptor with --disable-optimize + int32_t offset = *((int32_t*)(aOriginalFunction + 1)); + return aOriginalFunction + 5 + offset; + } +#endif + + return aOriginalFunction; + } +}; + +} // namespace internal + +class WindowsDllInterceptor +{ + internal::WindowsDllNopSpacePatcher mNopSpacePatcher; + internal::WindowsDllDetourPatcher mDetourPatcher; + + const char* mModuleName; + int mNHooks; + +public: + WindowsDllInterceptor() + : mModuleName(nullptr) + , mNHooks(0) + {} + + void Init(const char* aModuleName, int aNumHooks = 0) + { + if (mModuleName) { + return; + } + + mModuleName = aModuleName; + mNHooks = aNumHooks; + mNopSpacePatcher.Init(aModuleName); + + // Lazily initialize mDetourPatcher, since it allocates memory and we might + // not need it. + } + + bool AddHook(const char* aName, intptr_t aHookDest, void** aOrigFunc) + { + // Use a nop space patch if possible, otherwise fall back to a detour. + // This should be the preferred method for adding hooks. + + if (!mModuleName) { + return false; + } + + if (mNopSpacePatcher.AddHook(aName, aHookDest, aOrigFunc)) { + return true; + } + + return AddDetour(aName, aHookDest, aOrigFunc); + } + + bool AddDetour(const char* aName, intptr_t aHookDest, void** aOrigFunc) + { + // Generally, code should not call this method directly. Use AddHook unless + // there is a specific need to avoid nop space patches. + + if (!mModuleName) { + return false; + } + + if (!mDetourPatcher.Initialized()) { + mDetourPatcher.Init(mModuleName, mNHooks); + } + + return mDetourPatcher.AddHook(aName, aHookDest, aOrigFunc); + } +}; + +} // namespace mozilla + +#endif /* NS_WINDOWS_DLL_INTERCEPTOR_H_ */ diff --git a/xpcom/build/nsXPCOM.h b/xpcom/build/nsXPCOM.h new file mode 100644 index 000000000..925ca6901 --- /dev/null +++ b/xpcom/build/nsXPCOM.h @@ -0,0 +1,433 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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" + +#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(nsAString); +DECL_CLASS(nsACString); + +DECL_CLASS(nsISupports); +DECL_CLASS(nsIModule); +DECL_CLASS(nsIComponentManager); +DECL_CLASS(nsIComponentRegistrar); +DECL_CLASS(nsIServiceManager); +DECL_CLASS(nsIFile); +DECL_CLASS(nsILocalFile); +DECL_CLASS(nsIDirectoryServiceProvider); +DECL_CLASS(nsIMemory); +DECL_CLASS(nsIDebug2); + +#ifdef __cplusplus +namespace mozilla { +struct Module; +} // namespace mozilla +#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. + * + * @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_InitXPCOM2(nsIServiceManager** aResult, + nsIFile* aBinDirectory, + nsIDirectoryServiceProvider* aAppFileLocationProvider); + +/** + * 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. + */ +XPCOM_API(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 access to the memory manager. See nsIMemory + * + * @param aResult Interface pointer to the memory manager + * + * @return NS_OK for success; + * other error codes indicate a failure during initialisation. + */ +XPCOM_API(nsresult) NS_GetMemoryManager(nsIMemory** 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); + +#endif + +/** + * Allocator functions for the standalone glue. + * Do not use outside the xpcom glue code. + * Use moz_xmalloc/moz_xrealloc/free, or new/delete instead. + */ +#ifdef XPCOM_GLUE +/** + * Allocates a block of memory of a particular size. If the memory cannot + * be allocated (because of an out-of-memory condition), the process aborts. + * + * @param aSize The size of the block to allocate + * @result The block of memory + * @note This function is thread-safe. + */ +XPCOM_API(void*) NS_Alloc(size_t aSize); + +/** + * Reallocates a block of memory to a new size. + * + * @param aPtr The block of memory to reallocate. This block must originally + have been allocated by NS_Alloc or NS_Realloc + * @param aSize The new size. If 0, frees the block like NS_Free + * @result The reallocated block of memory + * @note This function is thread-safe. + * + * If aPtr is null, this function behaves like NS_Alloc. + * If s is the size of the block to which aPtr points, the first min(s, size) + * bytes of aPtr's block are copied to the new block. If the allocation + * succeeds, aPtr is freed and a pointer to the new block is returned. If the + * allocation fails, the process aborts. + */ +XPCOM_API(void*) NS_Realloc(void* aPtr, size_t aSize); + +/** + * Frees a block of memory. Null is a permissible value, in which case no + * action is taken. + * + * @param aPtr The block of memory to free. This block must originally have + * been allocated by NS_Alloc or NS_Realloc + * @note This function is thread-safe. + */ +XPCOM_API(void) NS_Free(void* aPtr); +#else +#define NS_Alloc moz_xmalloc +#define NS_Realloc moz_xrealloc +#define NS_Free free +#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); + +#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 000000000..7219babe4 --- /dev/null +++ b/xpcom/build/nsXPCOMCID.h @@ -0,0 +1,185 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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" + +/** + * Status reporter service CID + */ +#define NS_STATUS_REPORTER_MANAGER_CONTRACTID "@mozilla.org/status-reporter-manager;1" + +/** + * Cycle collector logger contract id + */ +#define NS_CYCLE_COLLECTOR_LOGGER_CONTRACTID "@mozilla.org/cycle-collector-logger;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_VOID_CID \ +{ 0xaf10f3e0, 0x568d, 0x11d3, \ +{ 0xba, 0xf8, 0x0, 0x80, 0x5f, 0x8a, 0x5d, 0xd7 } } +#define NS_SUPPORTS_VOID_CONTRACTID "@mozilla.org/supports-void;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 000000000..c9f7fb6f3 --- /dev/null +++ b/xpcom/build/nsXPCOMCIDInternal.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 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" + +/** + * A thread pool component. + * @implements nsIThreadPool + */ +#define NS_THREADPOOL_CONTRACTID "@mozilla.org/thread-pool;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 000000000..99a994013 --- /dev/null +++ b/xpcom/build/nsXPCOMPrivate.h @@ -0,0 +1,317 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 "nsXPCOMStrings.h" +#include "xptcall.h" + +class nsStringContainer; +class nsCStringContainer; +class nsPurpleBufferEntry; + +/** + * During this shutdown notification all threads which run XPCOM code must + * be joined. + */ +#define NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID "xpcom-shutdown-threads" + +/** + * During this shutdown notification all module loaders must unload XPCOM + * modules. + */ +#define NS_XPCOM_SHUTDOWN_LOADERS_OBSERVER_ID "xpcom-shutdown-loaders" + +// PUBLIC +typedef nsresult (*InitFunc)(nsIServiceManager** aResult, + nsIFile* aBinDirectory, + nsIDirectoryServiceProvider* aAppFileLocationProvider); +typedef nsresult (*ShutdownFunc)(nsIServiceManager* aServMgr); +typedef nsresult (*GetServiceManagerFunc)(nsIServiceManager** aResult); +typedef nsresult (*GetComponentManagerFunc)(nsIComponentManager** aResult); +typedef nsresult (*GetComponentRegistrarFunc)(nsIComponentRegistrar** aResult); +typedef nsresult (*GetMemoryManagerFunc)(nsIMemory** aResult); +typedef nsresult (*NewLocalFileFunc)(const nsAString& aPath, + bool aFollowLinks, nsIFile** aResult); +typedef nsresult (*NewNativeLocalFileFunc)(const nsACString& aPath, + bool aFollowLinks, + nsIFile** aResult); + +typedef nsresult (*GetDebugFunc)(nsIDebug2** aResult); + +typedef nsresult (*StringContainerInitFunc)(nsStringContainer&); +typedef nsresult (*StringContainerInit2Func)(nsStringContainer&, + const char16_t*, + uint32_t, uint32_t); +typedef void (*StringContainerFinishFunc)(nsStringContainer&); +typedef uint32_t (*StringGetDataFunc)(const nsAString&, const char16_t**, + bool*); +typedef uint32_t (*StringGetMutableDataFunc)(nsAString&, uint32_t, + char16_t**); +typedef char16_t* (*StringCloneDataFunc)(const nsAString&); +typedef nsresult (*StringSetDataFunc)(nsAString&, const char16_t*, uint32_t); +typedef nsresult (*StringSetDataRangeFunc)(nsAString&, uint32_t, uint32_t, + const char16_t*, uint32_t); +typedef nsresult (*StringCopyFunc)(nsAString&, const nsAString&); +typedef void (*StringSetIsVoidFunc)(nsAString&, const bool); +typedef bool (*StringGetIsVoidFunc)(const nsAString&); + +typedef nsresult (*CStringContainerInitFunc)(nsCStringContainer&); +typedef nsresult (*CStringContainerInit2Func)(nsCStringContainer&, + const char*, + uint32_t, uint32_t); +typedef void (*CStringContainerFinishFunc)(nsCStringContainer&); +typedef uint32_t (*CStringGetDataFunc)(const nsACString&, const char**, + bool*); +typedef uint32_t (*CStringGetMutableDataFunc)(nsACString&, uint32_t, char**); +typedef char* (*CStringCloneDataFunc)(const nsACString&); +typedef nsresult (*CStringSetDataFunc)(nsACString&, const char*, uint32_t); +typedef nsresult (*CStringSetDataRangeFunc)(nsACString&, uint32_t, uint32_t, + const char*, uint32_t); +typedef nsresult (*CStringCopyFunc)(nsACString&, const nsACString&); +typedef void (*CStringSetIsVoidFunc)(nsACString&, const bool); +typedef bool (*CStringGetIsVoidFunc)(const nsACString&); + +typedef nsresult (*CStringToUTF16)(const nsACString&, nsCStringEncoding, + nsAString&); +typedef nsresult (*UTF16ToCString)(const nsAString&, nsCStringEncoding, + nsACString&); + +typedef void* (*AllocFunc)(size_t aSize); +typedef void* (*ReallocFunc)(void* aPtr, size_t aSize); +typedef void (*FreeFunc)(void* aPtr); + +typedef void (*DebugBreakFunc)(uint32_t aSeverity, + const char* aStr, const char* aExpr, + const char* aFile, int32_t aLine); + +typedef void (*xpcomVoidFunc)(); +typedef void (*LogAddRefFunc)(void*, nsrefcnt, const char*, uint32_t); +typedef void (*LogReleaseFunc)(void*, nsrefcnt, const char*); +typedef void (*LogCtorFunc)(void*, const char*, uint32_t); +typedef void (*LogCOMPtrFunc)(void*, nsISupports*); + +typedef nsresult (*GetXPTCallStubFunc)(REFNSIID, nsIXPTCProxy*, + nsISomeInterface**); +typedef void (*DestroyXPTCallStubFunc)(nsISomeInterface*); +typedef nsresult (*InvokeByIndexFunc)(nsISupports*, uint32_t, uint32_t, + nsXPTCVariant*); +typedef bool (*CycleCollectorFunc)(nsISupports*); +typedef nsPurpleBufferEntry* + (*CycleCollectorSuspect2Func)(void*, + nsCycleCollectionParticipant*); +typedef bool (*CycleCollectorForget2Func)(nsPurpleBufferEntry*); +typedef void (*CycleCollectorSuspect3Func)(void*, + nsCycleCollectionParticipant*, + nsCycleCollectingAutoRefCnt*, + bool*); +// PRIVATE AND DEPRECATED +typedef NS_CALLBACK(XPCOMExitRoutine)(void); + +typedef nsresult (*RegisterXPCOMExitRoutineFunc)(XPCOMExitRoutine aExitRoutine, + uint32_t aPriority); +typedef nsresult (*UnregisterXPCOMExitRoutineFunc)(XPCOMExitRoutine aExitRoutine); + +typedef struct XPCOMFunctions +{ + uint32_t version; + uint32_t size; + + InitFunc init; + ShutdownFunc shutdown; + GetServiceManagerFunc getServiceManager; + GetComponentManagerFunc getComponentManager; + GetComponentRegistrarFunc getComponentRegistrar; + GetMemoryManagerFunc getMemoryManager; + NewLocalFileFunc newLocalFile; + NewNativeLocalFileFunc newNativeLocalFile; + + RegisterXPCOMExitRoutineFunc registerExitRoutine; + UnregisterXPCOMExitRoutineFunc unregisterExitRoutine; + + // Added for Mozilla 1.5 + GetDebugFunc getDebug; + void* getTraceRefcnt; + + // Added for Mozilla 1.7 + StringContainerInitFunc stringContainerInit; + StringContainerFinishFunc stringContainerFinish; + StringGetDataFunc stringGetData; + StringSetDataFunc stringSetData; + StringSetDataRangeFunc stringSetDataRange; + StringCopyFunc stringCopy; + CStringContainerInitFunc cstringContainerInit; + CStringContainerFinishFunc cstringContainerFinish; + CStringGetDataFunc cstringGetData; + CStringSetDataFunc cstringSetData; + CStringSetDataRangeFunc cstringSetDataRange; + CStringCopyFunc cstringCopy; + CStringToUTF16 cstringToUTF16; + UTF16ToCString utf16ToCString; + StringCloneDataFunc stringCloneData; + CStringCloneDataFunc cstringCloneData; + + // Added for Mozilla 1.8 + AllocFunc allocFunc; + ReallocFunc reallocFunc; + FreeFunc freeFunc; + StringContainerInit2Func stringContainerInit2; + CStringContainerInit2Func cstringContainerInit2; + StringGetMutableDataFunc stringGetMutableData; + CStringGetMutableDataFunc cstringGetMutableData; + void* init3; // obsolete + + // Added for Mozilla 1.9 + DebugBreakFunc debugBreakFunc; + xpcomVoidFunc logInitFunc; + xpcomVoidFunc logTermFunc; + LogAddRefFunc logAddRefFunc; + LogReleaseFunc logReleaseFunc; + LogCtorFunc logCtorFunc; + LogCtorFunc logDtorFunc; + LogCOMPtrFunc logCOMPtrAddRefFunc; + LogCOMPtrFunc logCOMPtrReleaseFunc; + GetXPTCallStubFunc getXPTCallStubFunc; + DestroyXPTCallStubFunc destroyXPTCallStubFunc; + InvokeByIndexFunc invokeByIndexFunc; + CycleCollectorFunc cycleSuspectFunc; // obsolete: use cycleSuspect3Func + CycleCollectorFunc cycleForgetFunc; // obsolete + StringSetIsVoidFunc stringSetIsVoid; + StringGetIsVoidFunc stringGetIsVoid; + CStringSetIsVoidFunc cstringSetIsVoid; + CStringGetIsVoidFunc cstringGetIsVoid; + + // Added for Mozilla 1.9.1 + CycleCollectorSuspect2Func cycleSuspect2Func; // obsolete: use cycleSuspect3Func + CycleCollectorForget2Func cycleForget2Func; // obsolete + + CycleCollectorSuspect3Func cycleSuspect3Func; + +} XPCOMFunctions; + +typedef nsresult (*GetFrozenFunctionsFunc)(XPCOMFunctions* aEntryPoints, + const char* aLibraryPath); +XPCOM_API(nsresult) NS_GetFrozenFunctions(XPCOMFunctions* aEntryPoints, + const char* aLibraryPath); + + +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 + * + */ +nsresult +ShutdownXPCOM(nsIServiceManager* aServMgr); + +void SetICUMemoryFunctions(); + +/** + * C++ namespaced version of NS_LogTerm. + */ +void LogTerm(); + +} // namespace mozilla + + +// think hard before changing this +#define XPCOM_GLUE_VERSION 1 + + +/* 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_WIN32) + +#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 + +extern bool gXPCOMShuttingDown; +extern bool gXPCOMThreadsShutDown; + +// 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/nsXPCOMStrings.cpp b/xpcom/build/nsXPCOMStrings.cpp new file mode 100644 index 000000000..617edbab2 --- /dev/null +++ b/xpcom/build/nsXPCOMStrings.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 "nsString.h" +#include "nsCharTraits.h" + +#include "nsXPCOMStrings.h" +#include "nsNativeCharsetUtils.h" + +/* ------------------------------------------------------------------------- */ + +XPCOM_API(nsresult) +NS_StringContainerInit(nsStringContainer& aContainer) +{ + NS_ASSERTION(sizeof(nsStringContainer_base) >= sizeof(nsString), + "nsStringContainer is not large enough"); + + // use placement new to avoid heap allocating nsString object + new (&aContainer) nsString(); + + return NS_OK; +} + +XPCOM_API(nsresult) +NS_StringContainerInit2(nsStringContainer& aContainer, + const char16_t* aData, + uint32_t aDataLength, + uint32_t aFlags) +{ + NS_ASSERTION(sizeof(nsStringContainer_base) >= sizeof(nsString), + "nsStringContainer is not large enough"); + + if (!aData) { + new (&aContainer) nsString(); + } else { + if (aDataLength == UINT32_MAX) { + if (NS_WARN_IF(aFlags & NS_STRING_CONTAINER_INIT_SUBSTRING)) { + return NS_ERROR_INVALID_ARG; + } + aDataLength = nsCharTraits::length(aData); + } + + if (aFlags & (NS_STRING_CONTAINER_INIT_DEPEND | + NS_STRING_CONTAINER_INIT_ADOPT)) { + uint32_t flags; + if (aFlags & NS_STRING_CONTAINER_INIT_SUBSTRING) { + flags = nsSubstring::F_NONE; + } else { + flags = nsSubstring::F_TERMINATED; + } + + if (aFlags & NS_STRING_CONTAINER_INIT_ADOPT) { + flags |= nsSubstring::F_OWNED; + } + + new (&aContainer) nsSubstring(const_cast(aData), + aDataLength, flags); + } else { + new (&aContainer) nsString(aData, aDataLength); + } + } + + return NS_OK; +} + +XPCOM_API(void) +NS_StringContainerFinish(nsStringContainer& aContainer) +{ + // call the nsString dtor + reinterpret_cast(&aContainer)->~nsString(); +} + +/* ------------------------------------------------------------------------- */ + +XPCOM_API(uint32_t) +NS_StringGetData(const nsAString& aStr, const char16_t** aData, + bool* aTerminated) +{ + if (aTerminated) { + *aTerminated = aStr.IsTerminated(); + } + + *aData = aStr.BeginReading(); + return aStr.Length(); +} + +XPCOM_API(uint32_t) +NS_StringGetMutableData(nsAString& aStr, uint32_t aDataLength, + char16_t** aData) +{ + if (aDataLength != UINT32_MAX) { + aStr.SetLength(aDataLength); + if (aStr.Length() != aDataLength) { + *aData = nullptr; + return 0; + } + } + + *aData = aStr.BeginWriting(); + return aStr.Length(); +} + +XPCOM_API(char16_t*) +NS_StringCloneData(const nsAString& aStr) +{ + return ToNewUnicode(aStr); +} + +XPCOM_API(nsresult) +NS_StringSetData(nsAString& aStr, const char16_t* aData, uint32_t aDataLength) +{ + aStr.Assign(aData, aDataLength); + return NS_OK; // XXX report errors +} + +XPCOM_API(nsresult) +NS_StringSetDataRange(nsAString& aStr, + uint32_t aCutOffset, uint32_t aCutLength, + const char16_t* aData, uint32_t aDataLength) +{ + if (aCutOffset == UINT32_MAX) { + // append case + if (aData) { + aStr.Append(aData, aDataLength); + } + return NS_OK; // XXX report errors + } + + if (aCutLength == UINT32_MAX) { + aCutLength = aStr.Length() - aCutOffset; + } + + if (aData) { + if (aDataLength == UINT32_MAX) { + aStr.Replace(aCutOffset, aCutLength, nsDependentString(aData)); + } else { + aStr.Replace(aCutOffset, aCutLength, Substring(aData, aDataLength)); + } + } else { + aStr.Cut(aCutOffset, aCutLength); + } + + return NS_OK; // XXX report errors +} + +XPCOM_API(nsresult) +NS_StringCopy(nsAString& aDest, const nsAString& aSrc) +{ + aDest.Assign(aSrc); + return NS_OK; // XXX report errors +} + +XPCOM_API(void) +NS_StringSetIsVoid(nsAString& aStr, const bool aIsVoid) +{ + aStr.SetIsVoid(aIsVoid); +} + +XPCOM_API(bool) +NS_StringGetIsVoid(const nsAString& aStr) +{ + return aStr.IsVoid(); +} + +/* ------------------------------------------------------------------------- */ + +XPCOM_API(nsresult) +NS_CStringContainerInit(nsCStringContainer& aContainer) +{ + NS_ASSERTION(sizeof(nsStringContainer_base) >= sizeof(nsCString), + "nsCStringContainer is not large enough"); + + // use placement new to avoid heap allocating nsCString object + new (&aContainer) nsCString(); + + return NS_OK; +} + +XPCOM_API(nsresult) +NS_CStringContainerInit2(nsCStringContainer& aContainer, + const char* aData, + uint32_t aDataLength, + uint32_t aFlags) +{ + NS_ASSERTION(sizeof(nsStringContainer_base) >= sizeof(nsCString), + "nsStringContainer is not large enough"); + + if (!aData) { + new (&aContainer) nsCString(); + } else { + if (aDataLength == UINT32_MAX) { + if (NS_WARN_IF(aFlags & NS_CSTRING_CONTAINER_INIT_SUBSTRING)) { + return NS_ERROR_INVALID_ARG; + } + aDataLength = nsCharTraits::length(aData); + } + + if (aFlags & (NS_CSTRING_CONTAINER_INIT_DEPEND | + NS_CSTRING_CONTAINER_INIT_ADOPT)) { + uint32_t flags; + if (aFlags & NS_CSTRING_CONTAINER_INIT_SUBSTRING) { + flags = nsCSubstring::F_NONE; + } else { + flags = nsCSubstring::F_TERMINATED; + } + + if (aFlags & NS_CSTRING_CONTAINER_INIT_ADOPT) { + flags |= nsCSubstring::F_OWNED; + } + + new (&aContainer) nsCSubstring(const_cast(aData), + aDataLength, flags); + } else { + new (&aContainer) nsCString(aData, aDataLength); + } + } + + return NS_OK; +} + +XPCOM_API(void) +NS_CStringContainerFinish(nsCStringContainer& aContainer) +{ + // call the nsCString dtor + reinterpret_cast(&aContainer)->~nsCString(); +} + +/* ------------------------------------------------------------------------- */ + +XPCOM_API(uint32_t) +NS_CStringGetData(const nsACString& aStr, const char** aData, + bool* aTerminated) +{ + if (aTerminated) { + *aTerminated = aStr.IsTerminated(); + } + + *aData = aStr.BeginReading(); + return aStr.Length(); +} + +XPCOM_API(uint32_t) +NS_CStringGetMutableData(nsACString& aStr, uint32_t aDataLength, char** aData) +{ + if (aDataLength != UINT32_MAX) { + aStr.SetLength(aDataLength); + if (aStr.Length() != aDataLength) { + *aData = nullptr; + return 0; + } + } + + *aData = aStr.BeginWriting(); + return aStr.Length(); +} + +XPCOM_API(char*) +NS_CStringCloneData(const nsACString& aStr) +{ + return ToNewCString(aStr); +} + +XPCOM_API(nsresult) +NS_CStringSetData(nsACString& aStr, const char* aData, uint32_t aDataLength) +{ + aStr.Assign(aData, aDataLength); + return NS_OK; // XXX report errors +} + +XPCOM_API(nsresult) +NS_CStringSetDataRange(nsACString& aStr, + uint32_t aCutOffset, uint32_t aCutLength, + const char* aData, uint32_t aDataLength) +{ + if (aCutOffset == UINT32_MAX) { + // append case + if (aData) { + aStr.Append(aData, aDataLength); + } + return NS_OK; // XXX report errors + } + + if (aCutLength == UINT32_MAX) { + aCutLength = aStr.Length() - aCutOffset; + } + + if (aData) { + if (aDataLength == UINT32_MAX) { + aStr.Replace(aCutOffset, aCutLength, nsDependentCString(aData)); + } else { + aStr.Replace(aCutOffset, aCutLength, Substring(aData, aDataLength)); + } + } else { + aStr.Cut(aCutOffset, aCutLength); + } + + return NS_OK; // XXX report errors +} + +XPCOM_API(nsresult) +NS_CStringCopy(nsACString& aDest, const nsACString& aSrc) +{ + aDest.Assign(aSrc); + return NS_OK; // XXX report errors +} + +XPCOM_API(void) +NS_CStringSetIsVoid(nsACString& aStr, const bool aIsVoid) +{ + aStr.SetIsVoid(aIsVoid); +} + +XPCOM_API(bool) +NS_CStringGetIsVoid(const nsACString& aStr) +{ + return aStr.IsVoid(); +} + +/* ------------------------------------------------------------------------- */ + +XPCOM_API(nsresult) +NS_CStringToUTF16(const nsACString& aSrc, + nsCStringEncoding aSrcEncoding, + nsAString& aDest) +{ + switch (aSrcEncoding) { + case NS_CSTRING_ENCODING_ASCII: + CopyASCIItoUTF16(aSrc, aDest); + break; + case NS_CSTRING_ENCODING_UTF8: + CopyUTF8toUTF16(aSrc, aDest); + break; + case NS_CSTRING_ENCODING_NATIVE_FILESYSTEM: + NS_CopyNativeToUnicode(aSrc, aDest); + break; + default: + return NS_ERROR_NOT_IMPLEMENTED; + } + + return NS_OK; // XXX report errors +} + +XPCOM_API(nsresult) +NS_UTF16ToCString(const nsAString& aSrc, + nsCStringEncoding aDestEncoding, + nsACString& aDest) +{ + switch (aDestEncoding) { + case NS_CSTRING_ENCODING_ASCII: + LossyCopyUTF16toASCII(aSrc, aDest); + break; + case NS_CSTRING_ENCODING_UTF8: + CopyUTF16toUTF8(aSrc, aDest); + break; + case NS_CSTRING_ENCODING_NATIVE_FILESYSTEM: + NS_CopyUnicodeToNative(aSrc, aDest); + break; + default: + return NS_ERROR_NOT_IMPLEMENTED; + } + + return NS_OK; // XXX report errors +} diff --git a/xpcom/build/nsXREAppData.h b/xpcom/build/nsXREAppData.h new file mode 100644 index 000000000..fbc7adb8f --- /dev/null +++ b/xpcom/build/nsXREAppData.h @@ -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/. */ + +#ifndef nsXREAppData_h +#define nsXREAppData_h + +#include +#include "mozilla/Attributes.h" + +class nsIFile; + +#if defined(XP_WIN) && defined(MOZ_SANDBOX) +namespace sandbox { +class BrokerServices; +} +#endif + +/** + * Application-specific data needed to start the apprunner. + * + * @note When this structure is allocated and manipulated by XRE_CreateAppData, + * string fields will be allocated with moz_xmalloc, and interface pointers + * are strong references. + */ +struct nsXREAppData +{ + /** + * This should be set to sizeof(nsXREAppData). This structure may be + * extended in future releases, and this ensures that binary compatibility + * is maintained. + */ + uint32_t size; + + /** + * The directory of the application to be run. May be null if the + * xulrunner and the app are installed into the same directory. + */ + nsIFile* MOZ_NON_OWNING_REF 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. + */ + const char* 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). + */ + const char* 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). + */ + const char* 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. + */ + const char* version; + + /** + * The application's build identifier, e.g. "2004051604" + */ + const char* 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 - . @ _ { } * + */ + const char* ID; + + /** + * The copyright information to print for the -h commandline flag, + * e.g. "Copyright (c) 2003 mozilla.org". + */ + const char* copyright; + + /** + * Combination of NS_XRE_ prefixed flags (defined below). + */ + uint32_t flags; + + /** + * The location of the XRE. XRE_main may not be able to figure this out + * programatically. + */ + nsIFile* MOZ_NON_OWNING_REF xreDirectory; + + /** + * The minimum/maximum compatible XRE version. + */ + const char* minVersion; + const char* maxVersion; + + /** + * The server URL to send crash reports to. + */ + const char* 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 + */ + const char* profile; + + /** + * The application name to use in the User Agent string. + */ + const char* UAName; + +#if defined(XP_WIN) && defined(MOZ_SANDBOX) + /** + * Chromium sandbox BrokerServices. + */ + sandbox::BrokerServices* sandboxBrokerServices; +#endif +}; + +/** + * 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 the Windows DLL Blocklist initialized properly. For testing + * purposes only. Set in nsBrowserApp on startup, automated tests then + * check the result. + */ +#define NS_XRE_DLL_BLOCKLIST_ENABLED (1 << 2) + +/** + * Indicates whether or not to use Breakpad crash reporting. + */ +#define NS_XRE_ENABLE_CRASH_REPORTER (1 << 3) + +#endif // nsXREAppData_h diff --git a/xpcom/build/nsXULAppAPI.h b/xpcom/build/nsXULAppAPI.h new file mode 100644 index 000000000..426a58f06 --- /dev/null +++ b/xpcom/build/nsXULAppAPI.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/. */ + +#ifndef _nsXULAppAPI_h__ +#define _nsXULAppAPI_h__ + +#include "nsID.h" +#include "xrecore.h" +#include "nsXPCOM.h" +#include "nsISupports.h" +#include "mozilla/Logging.h" +#include "nsXREAppData.h" +#include "js/TypeDecls.h" + +#include "mozilla/ArrayUtils.h" +#include "mozilla/Assertions.h" +#include "mozilla/Vector.h" +#include "mozilla/TimeStamp.h" +#include "XREChildData.h" +#include "XREShellData.h" + +/** + * 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 a list of all enabled extension + * directories and files (packed XPIs). The list includes compatible + * platform-specific extension subdirectories. + * + * @note The directory list will have no members when the application is + * launched in safe mode. + */ +#define XRE_EXTENSIONS_DIR_LIST "XREExtDL" + +/** + * 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 host manifests used by the WebExtensions + * native messaging feature are found. + */ +#define XRE_SYS_NATIVE_MESSAGING_MANIFESTS "XRESysNativeMessaging" +#define XRE_USER_NATIVE_MESSAGING_MANIFESTS "XREUserNativeMessaging" +#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 provides the update directory. Callers should + * fall back to appDir. + * 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: + * If app dir is under Program Files: + * Documents and Settings\\Local Settings\Application Data\ + * + * + * If app dir isn’t under Program Files: + * Documents and Settings\\Local Settings\Application Data\ + * + * + * Mac: ~/Library/Caches/Mozilla/updates/ + * + * Gonk: /data/local + * + * All others: Parent directory of XRE_EXECUTABLE_FILE. + */ +#define XRE_UPDATE_ROOT_DIR "UpdRootD" + +/** + * A directory service key which provides an alternate location + * to UpdRootD to to store large files. This key is currently + * only implemented in the Gonk directory service provider. + */ + +#define XRE_UPDATE_ARCHIVE_DIR "UpdArchD" + +/** + * A directory service key which provides the directory where an OS update is +* applied. + * At present this is supported only in Gonk. + */ +#define XRE_OS_UPDATE_APPLY_TO_DIR "OSUpdApplyToD" + +/** + * 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 aAppData Information about the application to be run. + * + * @param aFlags Platform specific flags. + * + * @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. + */ +XRE_API(int, + XRE_main, (int argc, char* argv[], const nsXREAppData* aAppData, + uint32_t aFlags)) + +/** + * 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. + */ +XRE_API(nsresult, + XRE_GetFileFromPath, (const char* aPath, nsIFile** aResult)) + +/** + * Get the path of the running application binary and store it in aResult. + * @param aArgv0 The value passed as argv[0] of main(). This value is only + * used on *nix, and only when other methods of determining + * the binary path have failed. + */ +XRE_API(nsresult, + XRE_GetBinaryPath, (const char* aArgv0, nsIFile** aResult)) + +/** + * Get the static module built in to libxul. + */ +XRE_API(const mozilla::Module*, + XRE_GetStaticModule, ()) + +/** + * Lock a profile directory using platform-specific semantics. + * + * @param aDirectory The profile directory to lock. + * @param aLockObject An opaque lock object. The directory will remain locked + * as long as the XPCOM reference is held. + */ +XRE_API(nsresult, + XRE_LockProfileDirectory, (nsIFile* aDirectory, + nsISupports** aLockObject)) + +/** + * Initialize libXUL for embedding purposes. + * + * @param aLibXULDirectory The directory in which the libXUL shared library + * was found. + * @param aAppDirectory The directory in which the application components + * and resources can be found. This will map to + * the NS_OS_CURRENT_PROCESS_DIR directory service + * key. + * @param aAppDirProvider A directory provider for the application. This + * provider will be aggregated by a libxul provider + * which will provide the base required GRE keys. + * + * @note This function must be called from the "main" thread. + * + * @note At the present time, this function may only be called once in + * a given process. Use XRE_TermEmbedding to clean up and free + * resources allocated by XRE_InitEmbedding. + */ + +XRE_API(nsresult, + XRE_InitEmbedding2, (nsIFile* aLibXULDirectory, + nsIFile* aAppDirectory, + nsIDirectoryServiceProvider* aAppDirProvider)) + +/** + * Register static XPCOM component information. + * This method may be called at any time before or after XRE_main or + * XRE_InitEmbedding. + */ +XRE_API(nsresult, + XRE_AddStaticComponent, (const mozilla::Module* aComponent)) + +/** + * 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 only skin packages and style overlays. + */ +enum NSLocationType +{ + NS_APP_LOCATION, + NS_EXTENSION_LOCATION, + NS_SKIN_LOCATION, + NS_BOOTSTRAPPED_LOCATION +}; + +XRE_API(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 only skin packages and style overlays. + */ +XRE_API(nsresult, + XRE_AddJarManifestLocation, (NSLocationType aType, + nsIFile* aLocation)) + +/** + * Fire notifications to inform the toolkit about a new profile. This + * method should be called after XRE_InitEmbedding if the embedder + * wishes to run with a profile. Normally the embedder should call + * XRE_LockProfileDirectory to lock the directory before calling this + * method. + * + * @note There are two possibilities for selecting a profile: + * + * 1) Select the profile before calling XRE_InitEmbedding. The aAppDirProvider + * object passed to XRE_InitEmbedding should provide the + * NS_APP_USER_PROFILE_50_DIR key, and may also provide the following keys: + * - NS_APP_USER_PROFILE_LOCAL_50_DIR + * - NS_APP_PROFILE_DIR_STARTUP + * - NS_APP_PROFILE_LOCAL_DIR_STARTUP + * In this scenario XRE_NotifyProfile should be called immediately after + * XRE_InitEmbedding. Component registration information will be stored in + * the profile and JS components may be stored in the fastload cache. + * + * 2) Select a profile some time after calling XRE_InitEmbedding. In this case + * the embedder must install a directory service provider which provides + * NS_APP_USER_PROFILE_50_DIR and optionally + * NS_APP_USER_PROFILE_LOCAL_50_DIR. Component registration information + * will be stored in the application directory and JS components will not + * fastload. + */ +XRE_API(void, + XRE_NotifyProfile, ()) + +/** + * Terminate embedding started with XRE_InitEmbedding or XRE_InitEmbedding2 + */ +XRE_API(void, + XRE_TermEmbedding, ()) + +/** + * Create a new nsXREAppData structure from an application.ini file. + * + * @param aINIFile The application.ini file to parse. + * @param aAppData A newly-allocated nsXREAppData structure. The caller is + * responsible for freeing this structure using + * XRE_FreeAppData. + */ +XRE_API(nsresult, + XRE_CreateAppData, (nsIFile* aINIFile, + nsXREAppData** aAppData)) + +/** + * 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. + */ +XRE_API(nsresult, + XRE_ParseAppData, (nsIFile* aINIFile, + nsXREAppData* aAppData)) + +/** + * Free a nsXREAppData structure that was allocated with XRE_CreateAppData. + */ +XRE_API(void, + XRE_FreeAppData, (nsXREAppData* aAppData)) + +enum GeckoProcessType +{ + GeckoProcessType_Default = 0, + + GeckoProcessType_Plugin, + GeckoProcessType_Content, + + GeckoProcessType_IPDLUnitTest, + + GeckoProcessType_GMPlugin, // Gecko Media Plugin + + GeckoProcessType_GPU, // GPU and compositor process + + GeckoProcessType_End, + GeckoProcessType_Invalid = GeckoProcessType_End +}; + +static const char* const kGeckoProcessTypeString[] = { + "default", + "plugin", + "tab", + "ipdlunittest", + "geckomediaplugin", + "gpu" +}; + +static_assert(MOZ_ARRAY_LENGTH(kGeckoProcessTypeString) == + GeckoProcessType_End, + "Array length mismatch"); + +XRE_API(const char*, + XRE_ChildProcessTypeToString, (GeckoProcessType aProcessType)) + +XRE_API(void, + XRE_SetProcessType, (const char* aProcessTypeString)) + +#if defined(MOZ_CRASHREPORTER) +// Used in the "master" parent process hosting the crash server +XRE_API(bool, + XRE_TakeMinidumpForChild, (uint32_t aChildPid, nsIFile** aDump, + uint32_t* aSequence)) + +// Used in child processes. +XRE_API(bool, + XRE_SetRemoteExceptionHandler, (const char* aPipe)) +#endif + +namespace mozilla { +namespace gmp { +class GMPLoader; +} // namespace gmp +} // namespace mozilla + +XRE_API(nsresult, + XRE_InitChildProcess, (int aArgc, + char* aArgv[], + const XREChildData* aChildData)) + +XRE_API(GeckoProcessType, + XRE_GetProcessType, ()) + +XRE_API(bool, + XRE_IsParentProcess, ()) + +XRE_API(bool, + XRE_IsContentProcess, ()) + +XRE_API(bool, + XRE_IsGPUProcess, ()) + +typedef void (*MainFunction)(void* aData); + +XRE_API(nsresult, + XRE_InitParentProcess, (int aArgc, + char* aArgv[], + MainFunction aMainFunction, + void* aMainFunctionExtraData)) + +XRE_API(int, + XRE_RunIPDLTest, (int aArgc, + char* aArgv[])) + +XRE_API(nsresult, + XRE_RunAppShell, ()) + +XRE_API(nsresult, + XRE_InitCommandLine, (int aArgc, char* aArgv[])) + +XRE_API(nsresult, + XRE_DeinitCommandLine, ()) + +class MessageLoop; + +XRE_API(void, + XRE_ShutdownChildProcess, ()) + +XRE_API(MessageLoop*, + XRE_GetIOMessageLoop, ()) + +XRE_API(bool, + XRE_SendTestShellCommand, (JSContext* aCx, + JSString* aCommand, + void* aCallback)) +XRE_API(bool, + XRE_ShutdownTestShell, ()) + +XRE_API(void, + XRE_InstallX11ErrorHandler, ()) + +XRE_API(void, + XRE_TelemetryAccumulate, (int aID, uint32_t aSample)) + +XRE_API(void, + XRE_StartupTimelineRecord, (int aEvent, mozilla::TimeStamp aWhen)) + +XRE_API(void, + XRE_InitOmnijar, (nsIFile* aGreOmni, + nsIFile* aAppOmni)) +XRE_API(void, + XRE_StopLateWriteChecks, (void)) + +XRE_API(void, + XRE_EnableSameExecutableForContentProc, ()) + +XRE_API(int, + XRE_XPCShellMain, (int argc, char** argv, char** envp, + const XREShellData* aShellData)) + +#if MOZ_WIDGET_GTK == 2 +XRE_API(void, + XRE_GlibInit, ()) +#endif + + +#ifdef LIBFUZZER +#include "LibFuzzerRegistry.h" + +XRE_API(void, + XRE_LibFuzzerSetMain, (int, char**, LibFuzzerMain)) + +XRE_API(void, + XRE_LibFuzzerGetFuncs, (const char*, LibFuzzerInitFunc*, + LibFuzzerTestingFunc*)) +#endif // LIBFUZZER + +#endif // _nsXULAppAPI_h__ diff --git a/xpcom/build/perfprobe.cpp b/xpcom/build/perfprobe.cpp new file mode 100644 index 000000000..118e73fc8 --- /dev/null +++ b/xpcom/build/perfprobe.cpp @@ -0,0 +1,242 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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" +#include "nsAutoPtr.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, %ul", 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 000000000..bc3563654 --- /dev/null +++ b/xpcom/build/perfprobe.h @@ -0,0 +1,204 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 "nsAutoPtr.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 000000000..38fedfa17 --- /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/build/xrecore.h b/xpcom/build/xrecore.h new file mode 100644 index 000000000..9749a8e90 --- /dev/null +++ b/xpcom/build/xrecore.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 xrecore_h__ +#define xrecore_h__ + +#include "nscore.h" + +/** + * Import/export macros for libXUL APIs. + */ +#ifdef XPCOM_GLUE +#define XRE_API(type, name, params) \ + typedef type (NS_FROZENCALL * name##Type) params; \ + extern name##Type name NS_HIDDEN; +#elif defined(IMPL_LIBXUL) +#define XRE_API(type, name, params) EXPORT_XPCOM_API(type) name params; +#else +#define XRE_API(type, name, params) IMPORT_XPCOM_API(type) name params; +#endif + +#endif // xrecore_h__ diff --git a/xpcom/components/ManifestParser.cpp b/xpcom/components/ManifestParser.cpp new file mode 100644 index 000000000..4dcfc8402 --- /dev/null +++ b/xpcom/components/ManifestParser.cpp @@ -0,0 +1,792 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. 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 "ManifestParser.h" + +#include + +#include "prio.h" +#include "prprf.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 "AndroidBridge.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; + + // Binary components are only allowed for APP locations. + bool apponly; + + // Some directives should only be delivered for APP or EXTENSION locations. + bool componentonly; + + bool ischrome; + + bool allowbootstrap; + + // The platform/contentaccessible flags only apply to content 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); + void* xptonlyfunc; + + bool isContract; +}; +static const ManifestDirective kParsingTable[] = { + { + "manifest", 1, false, false, true, true, false, + &nsComponentManagerImpl::ManifestManifest, nullptr, nullptr + }, + { + "binary-component", 1, true, true, false, false, false, + &nsComponentManagerImpl::ManifestBinaryComponent, nullptr, nullptr + }, + { + "interfaces", 1, false, true, false, false, false, + &nsComponentManagerImpl::ManifestXPT, nullptr, nullptr + }, + { + "component", 2, false, true, false, false, false, + &nsComponentManagerImpl::ManifestComponent, nullptr, nullptr + }, + { + "contract", 2, false, true, false, false, false, + &nsComponentManagerImpl::ManifestContract, nullptr, nullptr, true + }, + { + "category", 3, false, true, false, false, false, + &nsComponentManagerImpl::ManifestCategory, nullptr, nullptr + }, + { + "content", 2, false, true, true, true, true, + nullptr, &nsChromeRegistry::ManifestContent, nullptr + }, + { + "locale", 3, false, true, true, true, false, + nullptr, &nsChromeRegistry::ManifestLocale, nullptr + }, + { + "skin", 3, false, false, true, true, false, + nullptr, &nsChromeRegistry::ManifestSkin, nullptr + }, + { + "overlay", 2, false, true, true, false, false, + nullptr, &nsChromeRegistry::ManifestOverlay, nullptr + }, + { + "style", 2, false, false, true, false, false, + nullptr, &nsChromeRegistry::ManifestStyle, nullptr + }, + { + // NB: note that while skin manifests can use this, they are only allowed + // to use it for chrome://../skin/ URLs + "override", 2, false, false, true, true, false, + nullptr, &nsChromeRegistry::ManifestOverride, nullptr + }, + { + "resource", 2, false, true, true, true, false, + nullptr, &nsChromeRegistry::ManifestResource, nullptr + } +}; + +static const char kWhitespace[] = "\t "; + +static bool +IsNewline(char aChar) +{ + return aChar == '\n' || aChar == '\r'; +} + +namespace { +struct AutoPR_smprintf_free +{ + explicit AutoPR_smprintf_free(char* aBuf) : mBuf(aBuf) {} + + ~AutoPR_smprintf_free() + { + if (mBuf) { + PR_smprintf_free(mBuf); + } + } + + operator char*() const { return mBuf; } + + char* mBuf; +}; + +} // namespace + +/** + * If we are pre-loading XPTs, this method may do nothing because the + * console service is not initialized. + */ +void +LogMessage(const char* aMsg, ...) +{ + if (!nsComponentManagerImpl::gComponentManager) { + return; + } + + nsCOMPtr console = + do_GetService(NS_CONSOLESERVICE_CONTRACTID); + if (!console) { + return; + } + + va_list args; + va_start(args, aMsg); + AutoPR_smprintf_free formatted(PR_vsmprintf(aMsg, args)); + va_end(args); + + nsCOMPtr error = + new nsConsoleMessage(NS_ConvertUTF8toUTF16(formatted).get()); + console->LogMessage(error); +} + +/** + * If we are pre-loading XPTs, this method may do nothing because the + * console service is not initialized. + */ +void +LogMessageWithContext(FileLocation& aFile, + uint32_t aLineNumber, const char* aMsg, ...) +{ + va_list args; + va_start(args, aMsg); + AutoPR_smprintf_free formatted(PR_vsmprintf(aMsg, args)); + va_end(args); + if (!formatted) { + return; + } + + if (!nsComponentManagerImpl::gComponentManager) { + return; + } + + 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, (char*)formatted); + return; + } + + nsCOMPtr console = + do_GetService(NS_CONSOLESERVICE_CONTRACTID); + if (!console) { + return; + } + + nsresult rv = error->Init(NS_ConvertUTF8toUTF16(formatted), + NS_ConvertUTF8toUTF16(file), EmptyString(), + aLineNumber, 0, nsIScriptError::warningFlag, + "chrome registration"); + 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 nsSubstring& aFlag, const nsSubstring& 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 nsSubstring& aFlag, const nsSubstring& aData, + const nsSubstring& 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; +} + +/** + * 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 + + +/** + * For XPT-Only mode, the parser handles only directives of "manifest" + * and "interfaces", and always call the function given by |xptonlyfunc| + * variable of struct |ManifestDirective|. + * + * This function is safe to be called before the component manager is + * ready if aXPTOnly is true for it don't invoke any component during + * parsing. + */ +void +ParseManifest(NSLocationType aType, FileLocation& aFile, char* aBuf, + bool aChromeOnly, bool aXPTOnly) +{ + nsComponentManagerImpl::ManifestProcessingContext mgrcx(aType, aFile, + aChromeOnly); + nsChromeRegistry::ManifestProcessingContext chromecx(aType, aFile); + nsresult rv; + + NS_NAMED_LITERAL_STRING(kPlatform, "platform"); + NS_NAMED_LITERAL_STRING(kContentAccessible, "contentaccessible"); + NS_NAMED_LITERAL_STRING(kRemoteEnabled, "remoteenabled"); + NS_NAMED_LITERAL_STRING(kRemoteRequired, "remoterequired"); + NS_NAMED_LITERAL_STRING(kApplication, "application"); + NS_NAMED_LITERAL_STRING(kAppVersion, "appversion"); + NS_NAMED_LITERAL_STRING(kGeckoVersion, "platformversion"); + NS_NAMED_LITERAL_STRING(kOs, "os"); + NS_NAMED_LITERAL_STRING(kOsVersion, "osversion"); + NS_NAMED_LITERAL_STRING(kABI, "abi"); + NS_NAMED_LITERAL_STRING(kProcess, "process"); +#if defined(MOZ_WIDGET_ANDROID) + NS_NAMED_LITERAL_STRING(kTablet, "tablet"); +#endif + + NS_NAMED_LITERAL_STRING(kMain, "main"); + NS_NAMED_LITERAL_STRING(kContent, "content"); + + // Obsolete + NS_NAMED_LITERAL_STRING(kXPCNativeWrappers, "xpcnativewrappers"); + + nsAutoString appID; + nsAutoString appVersion; + nsAutoString geckoVersion; + nsAutoString osTarget; + nsAutoString abi; + nsAutoString process; + + nsCOMPtr xapp; + if (!aXPTOnly) { + // Avoid to create any component for XPT only mode. + // No xapp means no ID, version, ..., modifiers checking. + 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::OSXVersionMajor(); + SInt32 minorVersion = nsCocoaFeatures::OSXVersionMinor(); + 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 (mozilla::AndroidBridge::Bridge()) { + mozilla::AndroidBridge::Bridge()->GetStaticStringField("android/os/Build$VERSION", + "RELEASE", + osVersion); + isTablet = java::GeckoAppShell::IsTablet(); + } +#endif + + if (XRE_IsContentProcess()) { + process = kContent; + } else { + process = kMain; + } + + // Because contracts must be registered after CIDs, we save and process them + // at the end. + nsTArray contracts; + + 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) && + (!aXPTOnly || d->xptonlyfunc)) { + directive = d; + break; + } + } + + if (!directive) { + LogMessageWithContext(aFile, line, + "Ignoring unrecognized chrome manifest directive '%s'.", + token); + continue; + } + + if (!directive->allowbootstrap && NS_BOOTSTRAPPED_LOCATION == aType) { + LogMessageWithContext(aFile, line, + "Bootstrapped manifest not allowed to use '%s' directive.", + token); + continue; + } + +#ifndef MOZ_BINARY_EXTENSIONS + if (directive->apponly && NS_APP_LOCATION != aType) { + LogMessageWithContext(aFile, line, + "Only application manifests may use the '%s' directive.", token); + continue; + } +#endif + + if (directive->componentonly && NS_SKIN_LOCATION == aType) { + LogMessageWithContext(aFile, line, + "Skin 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 + int flags = 0; + + while ((token = nsCRT::strtok(whitespace, kWhitespace, &whitespace)) && + ok) { + ToLowerCase(token); + NS_ConvertASCIItoUTF16 wtoken(token); + + if (CheckStringFlag(kApplication, wtoken, appID, stApp) || + CheckStringFlag(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 + + if (directive->contentflags) { + bool flag; + if (CheckFlag(kPlatform, wtoken, flag)) { + if (flag) + flags |= nsChromeRegistry::PLATFORM_PACKAGE; + continue; + } + 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 + stABI == eBad || + stProcess == eBad) { + continue; + } + + if (directive->regfunc) { + if (GeckoProcessType_Default != XRE_GetProcessType()) { + continue; + } + + if (!nsChromeRegistry::gChromeRegistry) { + nsCOMPtr cr = + mozilla::services::GetChromeRegistryService(); + 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) { + if (directive->isContract) { + CachedDirective* cd = contracts.AppendElement(); + cd->lineno = line; + cd->argv[0] = argv[0]; + cd->argv[1] = argv[1]; + } else { + (nsComponentManagerImpl::gComponentManager->*(directive->mgrfunc))( + mgrcx, line, argv); + } + } + } + + for (uint32_t i = 0; i < contracts.Length(); ++i) { + CachedDirective& d = contracts[i]; + nsComponentManagerImpl::gComponentManager->ManifestContract(mgrcx, + d.lineno, + d.argv); + } +} diff --git a/xpcom/components/ManifestParser.h b/xpcom/components/ManifestParser.h new file mode 100644 index 000000000..019ec326c --- /dev/null +++ b/xpcom/components/ManifestParser.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 ManifestParser_h +#define ManifestParser_h + +#include "nsComponentManager.h" +#include "nsChromeRegistry.h" +#include "mozilla/FileLocation.h" + +void ParseManifest(NSLocationType aType, mozilla::FileLocation& aFile, + char* aBuf, bool aChromeOnly, bool aXPTOnly = false); + +void LogMessage(const char* aMsg, ...); + +void LogMessageWithContext(mozilla::FileLocation& aFile, + uint32_t aLineNumber, const char* aMsg, ...); + +#endif // ManifestParser_h diff --git a/xpcom/components/Module.h b/xpcom/components/Module.h new file mode 100644 index 000000000..21d07e82e --- /dev/null +++ b/xpcom/components/Module.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_Module_h +#define mozilla_Module_h + +#include "nscore.h" +#include "nsID.h" +#include "nsIFactory.h" +#include "nsCOMPtr.h" // for already_AddRefed + +namespace mozilla { + +/** + * A module implements one or more XPCOM components. This structure is used + * for both binary and script modules, but the registration members + * (cids/contractids/categoryentries) are unused for modules which are loaded + * via a module loader. + */ +struct Module +{ + static const unsigned int kVersion = 52; + + struct CIDEntry; + + typedef already_AddRefed (*GetFactoryProcPtr)( + const Module& module, const CIDEntry& entry); + + typedef nsresult (*ConstructorProcPtr)(nsISupports* aOuter, + const nsIID& aIID, + void** aResult); + + typedef nsresult (*LoadFuncPtr)(); + typedef void (*UnloadFuncPtr)(); + + /** + * This selector allows CIDEntrys to be marked so that they're only loaded + * into certain kinds of processes. Selectors can be combined. + */ + enum ProcessSelector + { + ANY_PROCESS = 0x0, + MAIN_PROCESS_ONLY = 0x1, + CONTENT_PROCESS_ONLY = 0x2, + + /** + * By default, modules are not loaded in the GPU process, even if + * ANY_PROCESS is specified. This flag enables a module in the + * GPU process. + */ + ALLOW_IN_GPU_PROCESS = 0x4 + }; + + /** + * The constructor callback is an implementation detail of the default binary + * loader and may be null. + */ + struct CIDEntry + { + const nsCID* cid; + bool service; + GetFactoryProcPtr getFactoryProc; + ConstructorProcPtr constructorProc; + ProcessSelector processSelector; + }; + + struct ContractIDEntry + { + const char* contractid; + nsID const* cid; + ProcessSelector processSelector; + }; + + struct CategoryEntry + { + const char* category; + const char* entry; + const char* value; + }; + + /** + * Binary compatibility check, should be kModuleVersion. + */ + unsigned int mVersion; + + /** + * An array of CIDs (class IDs) implemented by this module. The final entry + * should be { nullptr }. + */ + const CIDEntry* mCIDs; + + /** + * An array of mappings from contractid to CID. The final entry should + * be { nullptr }. + */ + const ContractIDEntry* mContractIDs; + + /** + * An array of category manager entries. The final entry should be + * { nullptr }. + */ + const CategoryEntry* mCategoryEntries; + + /** + * When the component manager tries to get the factory for a CID, it first + * checks for this module-level getfactory callback. If this function is + * not implemented, it checks the CIDEntry getfactory callback. If that is + * also nullptr, a generic factory is generated using the CIDEntry + * constructor callback which must be non-nullptr. + */ + GetFactoryProcPtr getFactoryProc; + + /** + * Optional Function which are called when this module is loaded and + * at shutdown. These are not C++ constructor/destructors to avoid + * calling them too early in startup or too late in shutdown. + */ + LoadFuncPtr loadProc; + UnloadFuncPtr unloadProc; + + /** + * Optional flags which control whether the module loads on a process-type + * basis. + */ + ProcessSelector selector; +}; + +} // namespace mozilla + +#if defined(MOZILLA_INTERNAL_API) +# define NSMODULE_NAME(_name) _name##_NSModule +# if defined(_MSC_VER) +# pragma section(".kPStaticModules$M", read) +# pragma comment(linker, "/merge:.kPStaticModules=.rdata") +# define NSMODULE_SECTION __declspec(allocate(".kPStaticModules$M"), dllexport) +# elif defined(__GNUC__) +# if defined(__ELF__) +# define NSMODULE_SECTION __attribute__((section(".kPStaticModules"), visibility("default"))) +# elif defined(__MACH__) +# define NSMODULE_SECTION __attribute__((section("__DATA, .kPStaticModules"), visibility("default"))) +# elif defined (_WIN32) +# define NSMODULE_SECTION __attribute__((section(".kPStaticModules"), dllexport)) +# endif +# endif +# if !defined(NSMODULE_SECTION) +# error Do not know how to define sections. +# endif +# define NSMODULE_DEFN(_name) extern NSMODULE_SECTION mozilla::Module const *const NSMODULE_NAME(_name) +#else +# define NSMODULE_NAME(_name) NSModule +# define NSMODULE_DEFN(_name) extern "C" NS_EXPORT mozilla::Module const *const NSModule +#endif + +#endif // mozilla_Module_h diff --git a/xpcom/components/ModuleLoader.h b/xpcom/components/ModuleLoader.h new file mode 100644 index 000000000..c2c920d4d --- /dev/null +++ b/xpcom/components/ModuleLoader.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 mozilla_ModuleLoader_h +#define mozilla_ModuleLoader_h + +#include "nsISupports.h" +#include "mozilla/Module.h" +#include "mozilla/FileLocation.h" + +#define MOZILLA_MODULELOADER_PSEUDO_IID \ +{ 0xD951A8CE, 0x6E9F, 0x464F, \ + { 0x8A, 0xC8, 0x14, 0x61, 0xC0, 0xD3, 0x63, 0xC8 } } + +namespace mozilla { + +/** + * Module loaders are responsible for loading a component file. The static + * component loader is special and does not use this abstract interface. + * + * @note Implementations of this interface should be threadsafe, + * methods may be called from any thread. + */ +class ModuleLoader : public nsISupports +{ +public: + NS_DECLARE_STATIC_IID_ACCESSOR(MOZILLA_MODULELOADER_PSEUDO_IID) + + /** + * Return the module for a specified file. The caller should cache + * the module: the implementer should not expect for the same file + * to be loaded multiple times. The Module object should either be + * statically or permanently allocated; it will not be freed. + */ + virtual const Module* LoadModule(mozilla::FileLocation& aFile) = 0; +}; +NS_DEFINE_STATIC_IID_ACCESSOR(ModuleLoader, MOZILLA_MODULELOADER_PSEUDO_IID) + +} // namespace mozilla + +#endif // mozilla_ModuleLoader_h diff --git a/xpcom/components/ModuleUtils.h b/xpcom/components/ModuleUtils.h new file mode 100644 index 000000000..9c31ed76b --- /dev/null +++ b/xpcom/components/ModuleUtils.h @@ -0,0 +1,98 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 "mozilla/Attributes.h" +#include "mozilla/Module.h" + +#define NS_GENERIC_FACTORY_CONSTRUCTOR(_InstanceClass) \ +static nsresult \ +_InstanceClass##Constructor(nsISupports *aOuter, REFNSIID aIID, \ + void **aResult) \ +{ \ + RefPtr<_InstanceClass> inst; \ + \ + *aResult = nullptr; \ + if (nullptr != aOuter) { \ + return NS_ERROR_NO_AGGREGATION; \ + } \ + \ + inst = new _InstanceClass(); \ + return inst->QueryInterface(aIID, aResult); \ +} + +#define NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(_InstanceClass, _InitMethod) \ +static nsresult \ +_InstanceClass##Constructor(nsISupports *aOuter, REFNSIID aIID, \ + void **aResult) \ +{ \ + nsresult rv; \ + \ + RefPtr<_InstanceClass> inst; \ + \ + *aResult = nullptr; \ + if (nullptr != aOuter) { \ + return NS_ERROR_NO_AGGREGATION; \ + } \ + \ + inst = new _InstanceClass(); \ + rv = inst->_InitMethod(); \ + if (NS_SUCCEEDED(rv)) { \ + rv = inst->QueryInterface(aIID, aResult); \ + } \ + \ + return rv; \ +} + +// 'Constructor' that uses an existing getter function that gets a singleton. +// NOTE: assumes that getter does an AddRef - so additional AddRef is not done. +#define NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(_InstanceClass, _GetterProc) \ +static nsresult \ +_InstanceClass##Constructor(nsISupports *aOuter, REFNSIID aIID, \ + void **aResult) \ +{ \ + RefPtr<_InstanceClass> inst; \ + \ + *aResult = nullptr; \ + if (nullptr != aOuter) { \ + return NS_ERROR_NO_AGGREGATION; \ + } \ + \ + inst = already_AddRefed<_InstanceClass>(_GetterProc()); \ + if (nullptr == inst) { \ + return NS_ERROR_OUT_OF_MEMORY; \ + } \ + return inst->QueryInterface(aIID, aResult); \ +} + +#ifndef MOZILLA_INTERNAL_API + +#include "nsIModule.h" +#include "nsISupportsUtils.h" + +namespace mozilla { + +class GenericModule final : public nsIModule +{ + ~GenericModule() {} + +public: + explicit GenericModule(const mozilla::Module* aData) : mData(aData) {} + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIMODULE + +private: + const mozilla::Module* mData; +}; + +} // namespace mozilla + +#endif + +#endif // mozilla_GenericModule_h diff --git a/xpcom/components/moz.build b/xpcom/components/moz.build new file mode 100644 index 000000000..73a85cb3b --- /dev/null +++ b/xpcom/components/moz.build @@ -0,0 +1,55 @@ +# -*- 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', + 'nsIModule.idl', + 'nsIServiceManager.idl', +] + +XPIDL_MODULE = 'xpcom_components' + +EXPORTS += [ + 'nsCategoryManagerUtils.h', +] + +EXPORTS.mozilla += [ + 'Module.h', + 'ModuleLoader.h', + 'ModuleUtils.h', +] + +# nsCategoryManager.cpp and nsComponentManager.cpp cannot be built in +# unified mode because they use thea PL_ARENA_CONST_ALIGN_MASK macro +# with plarena.h. +SOURCES += [ + 'nsCategoryManager.cpp', + 'nsComponentManager.cpp', +] + +UNIFIED_SOURCES += [ + 'ManifestParser.cpp', + 'nsNativeModuleLoader.cpp', +] + +FINAL_LIBRARY = 'xul' + +LOCAL_INCLUDES += [ + '!..', + '../base', + '../build', + '../ds', + '../reflect/xptinfo', + '/chrome', + '/modules/libjar', +] + +if 'gtk' in CONFIG['MOZ_WIDGET_TOOLKIT']: + CXXFLAGS += CONFIG['TK_CFLAGS'] diff --git a/xpcom/components/nsCategoryManager.cpp b/xpcom/components/nsCategoryManager.cpp new file mode 100644 index 000000000..527c78719 --- /dev/null +++ b/xpcom/components/nsCategoryManager.cpp @@ -0,0 +1,832 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#define PL_ARENA_CONST_ALIGN_MASK 7 + +#include "nsICategoryManager.h" +#include "nsCategoryManager.h" + +#include "plarena.h" +#include "prio.h" +#include "prprf.h" +#include "prlock.h" +#include "nsCOMPtr.h" +#include "nsTHashtable.h" +#include "nsClassHashtable.h" +#include "nsIFactory.h" +#include "nsIStringEnumerator.h" +#include "nsSupportsPrimitives.h" +#include "nsComponentManagerUtils.h" +#include "nsServiceManagerUtils.h" +#include "nsIObserver.h" +#include "nsIObserverService.h" +#include "nsReadableUtils.h" +#include "nsCRT.h" +#include "nsQuickSort.h" +#include "nsEnumeratorUtils.h" +#include "nsThreadUtils.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/Services.h" + +#include "ManifestParser.h" +#include "nsISimpleEnumerator.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 ;) +*/ + +#define NS_CATEGORYMANAGER_ARENA_SIZE (1024 * 8) + +// pulled in from nsComponentManager.cpp +char* ArenaStrdup(const char* aStr, PLArenaPool* aArena); + +// +// BaseStringEnumerator is subclassed by EntryEnumerator and +// CategoryEnumerator +// +class BaseStringEnumerator + : public nsISimpleEnumerator + , private nsIUTF8StringEnumerator +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSISIMPLEENUMERATOR + NS_DECL_NSIUTF8STRINGENUMERATOR + +protected: + // Callback function for NS_QuickSort to sort mArray + static int SortCallback(const void*, const void*, void*); + + BaseStringEnumerator() + : mArray(nullptr) + , mCount(0) + , mSimpleCurItem(0) + , mStringCurItem(0) + { + } + + // A virtual destructor is needed here because subclasses of + // BaseStringEnumerator do not implement their own Release() method. + + virtual ~BaseStringEnumerator() + { + delete [] mArray; + } + + void Sort(); + + const char** mArray; + uint32_t mCount; + uint32_t mSimpleCurItem; + uint32_t mStringCurItem; +}; + +NS_IMPL_ISUPPORTS(BaseStringEnumerator, nsISimpleEnumerator, + nsIUTF8StringEnumerator) + +NS_IMETHODIMP +BaseStringEnumerator::HasMoreElements(bool* aResult) +{ + *aResult = (mSimpleCurItem < mCount); + + return NS_OK; +} + +NS_IMETHODIMP +BaseStringEnumerator::GetNext(nsISupports** aResult) +{ + if (mSimpleCurItem >= mCount) { + return NS_ERROR_FAILURE; + } + + nsSupportsDependentCString* str = + new nsSupportsDependentCString(mArray[mSimpleCurItem++]); + if (!str) { + return NS_ERROR_OUT_OF_MEMORY; + } + + *aResult = str; + NS_ADDREF(*aResult); + return NS_OK; +} + +NS_IMETHODIMP +BaseStringEnumerator::HasMore(bool* aResult) +{ + *aResult = (mStringCurItem < mCount); + + return NS_OK; +} + +NS_IMETHODIMP +BaseStringEnumerator::GetNext(nsACString& aResult) +{ + if (mStringCurItem >= mCount) { + return NS_ERROR_FAILURE; + } + + aResult = nsDependentCString(mArray[mStringCurItem++]); + return NS_OK; +} + +int +BaseStringEnumerator::SortCallback(const void* aE1, const void* aE2, + void* /*unused*/) +{ + char const* const* s1 = reinterpret_cast(aE1); + char const* const* s2 = reinterpret_cast(aE2); + + return strcmp(*s1, *s2); +} + +void +BaseStringEnumerator::Sort() +{ + NS_QuickSort(mArray, mCount, sizeof(mArray[0]), SortCallback, nullptr); +} + +// +// EntryEnumerator is the wrapper that allows nsICategoryManager::EnumerateCategory +// +class EntryEnumerator + : public BaseStringEnumerator +{ +public: + static EntryEnumerator* Create(nsTHashtable& aTable); +}; + + +EntryEnumerator* +EntryEnumerator::Create(nsTHashtable& aTable) +{ + EntryEnumerator* enumObj = new EntryEnumerator(); + if (!enumObj) { + return nullptr; + } + + enumObj->mArray = new char const* [aTable.Count()]; + if (!enumObj->mArray) { + delete enumObj; + return nullptr; + } + + for (auto iter = aTable.Iter(); !iter.Done(); iter.Next()) { + CategoryLeaf* leaf = iter.Get(); + if (leaf->value) { + enumObj->mArray[enumObj->mCount++] = leaf->GetKey(); + } + } + + enumObj->Sort(); + + return enumObj; +} + + +// +// CategoryNode implementations +// + +CategoryNode* +CategoryNode::Create(PLArenaPool* aArena) +{ + return new (aArena) CategoryNode(); +} + +CategoryNode::~CategoryNode() +{ +} + +void* +CategoryNode::operator new(size_t aSize, PLArenaPool* aArena) +{ + void* p; + PL_ARENA_ALLOCATE(p, aArena, aSize); + return p; +} + +nsresult +CategoryNode::GetLeaf(const char* aEntryName, + char** aResult) +{ + MutexAutoLock lock(mLock); + nsresult rv = NS_ERROR_NOT_AVAILABLE; + CategoryLeaf* ent = mTable.GetEntry(aEntryName); + + if (ent && ent->value) { + *aResult = NS_strdup(ent->value); + if (*aResult) { + rv = NS_OK; + } + } + + return rv; +} + +nsresult +CategoryNode::AddLeaf(const char* aEntryName, + const char* aValue, + bool aReplace, + char** aResult, + PLArenaPool* aArena) +{ + if (aResult) { + *aResult = nullptr; + } + + MutexAutoLock lock(mLock); + CategoryLeaf* leaf = mTable.GetEntry(aEntryName); + + if (!leaf) { + const char* arenaEntryName = ArenaStrdup(aEntryName, aArena); + if (!arenaEntryName) { + return NS_ERROR_OUT_OF_MEMORY; + } + + leaf = mTable.PutEntry(arenaEntryName); + if (!leaf) { + return NS_ERROR_OUT_OF_MEMORY; + } + } + + if (leaf->value && !aReplace) { + return NS_ERROR_INVALID_ARG; + } + + const char* arenaValue = ArenaStrdup(aValue, aArena); + if (!arenaValue) { + return NS_ERROR_OUT_OF_MEMORY; + } + + if (aResult && leaf->value) { + *aResult = ToNewCString(nsDependentCString(leaf->value)); + if (!*aResult) { + return NS_ERROR_OUT_OF_MEMORY; + } + } + + leaf->value = arenaValue; + return NS_OK; +} + +void +CategoryNode::DeleteLeaf(const char* 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(aEntryName); +} + +nsresult +CategoryNode::Enumerate(nsISimpleEnumerator** aResult) +{ + if (NS_WARN_IF(!aResult)) { + return NS_ERROR_INVALID_ARG; + } + + MutexAutoLock lock(mLock); + EntryEnumerator* enumObj = EntryEnumerator::Create(mTable); + + if (!enumObj) { + return NS_ERROR_OUT_OF_MEMORY; + } + + *aResult = enumObj; + NS_ADDREF(*aResult); + return NS_OK; +} + +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); +} + +// +// CategoryEnumerator class +// + +class CategoryEnumerator + : public BaseStringEnumerator +{ +public: + static CategoryEnumerator* Create(nsClassHashtable& aTable); +}; + +CategoryEnumerator* +CategoryEnumerator::Create(nsClassHashtable& + aTable) +{ + CategoryEnumerator* enumObj = new CategoryEnumerator(); + if (!enumObj) { + return nullptr; + } + + enumObj->mArray = new const char* [aTable.Count()]; + if (!enumObj->mArray) { + delete enumObj; + return nullptr; + } + + for (auto iter = aTable.Iter(); !iter.Done(); iter.Next()) { + // if a category has no entries, we pretend it doesn't exist + CategoryNode* aNode = iter.UserData(); + if (aNode->Count()) { + const char* str = iter.Key(); + enumObj->mArray[enumObj->mCount++] = str; + } + } + + return enumObj; +} + + +// +// 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(nsISupports* aOuter, REFNSIID aIID, void** aResult) +{ + if (aOuter) { + return NS_ERROR_NO_AGGREGATION; + } + + return GetSingleton()->QueryInterface(aIID, aResult); +} + +nsCategoryManager::nsCategoryManager() + : mLock("nsCategoryManager") + , mSuppressNotifications(false) +{ + PL_INIT_ARENA_POOL(&mArena, "CategoryManagerArena", + NS_CATEGORYMANAGER_ARENA_SIZE); +} + +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(); + + PL_FinishArenaPool(&mArena); +} + +inline CategoryNode* +nsCategoryManager::get_category(const char* aName) +{ + CategoryNode* node; + if (!mTable.Get(aName, &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 += PL_SizeOfArenaPoolExcludingPool(&mArena, aMallocSizeOf); + + n += mTable.ShallowSizeOfExcludingThis(aMallocSizeOf); + for (auto iter = mTable.ConstIter(); !iter.Done(); iter.Next()) { + // We don't measure the key string because it's a non-owning pointer. + n += iter.Data()->SizeOfExcludingThis(aMallocSizeOf); + } + + return n; +} + +namespace { + +class CategoryNotificationRunnable : public Runnable +{ +public: + CategoryNotificationRunnable(nsISupports* aSubject, + const char* aTopic, + const char* aData) + : 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 char* aCategoryName, + const char* aEntryName) +{ + if (mSuppressNotifications) { + return; + } + + RefPtr r; + + if (aEntryName) { + nsCOMPtr entry = + do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID); + if (!entry) { + return; + } + + nsresult rv = entry->SetData(nsDependentCString(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 char* aCategoryName, + const char* aEntryName, + char** aResult) +{ + if (NS_WARN_IF(!aCategoryName) || + NS_WARN_IF(!aEntryName) || + NS_WARN_IF(!aResult)) { + return NS_ERROR_INVALID_ARG; + } + + 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 char* aCategoryName, + const char* aEntryName, + const char* aValue, + bool aPersist, + bool aReplace, + char** 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 char* aCategoryName, + const char* aEntryName, + const char* aValue, + bool aReplace, + char** aOldValue) +{ + if (aOldValue) { + *aOldValue = nullptr; + } + + // 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 = CategoryNode::Create(&mArena); + + char* categoryName = ArenaStrdup(aCategoryName, &mArena); + mTable.Put(categoryName, category); + } + } + + if (!category) { + return; + } + + // We will need the return value of AddLeaf even if the called doesn't want it + char* oldEntry = nullptr; + + nsresult rv = category->AddLeaf(aEntryName, + aValue, + aReplace, + &oldEntry, + &mArena); + + if (NS_SUCCEEDED(rv)) { + if (oldEntry) { + NotifyObservers(NS_XPCOM_CATEGORY_ENTRY_REMOVED_OBSERVER_ID, + aCategoryName, aEntryName); + } + NotifyObservers(NS_XPCOM_CATEGORY_ENTRY_ADDED_OBSERVER_ID, + aCategoryName, aEntryName); + + if (aOldValue) { + *aOldValue = oldEntry; + } else { + free(oldEntry); + } + } +} + +NS_IMETHODIMP +nsCategoryManager::DeleteCategoryEntry(const char* aCategoryName, + const char* aEntryName, + bool aDontPersist) +{ + if (NS_WARN_IF(!aCategoryName) || + NS_WARN_IF(!aEntryName)) { + return NS_ERROR_INVALID_ARG; + } + + /* + 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 char* aCategoryName) +{ + if (NS_WARN_IF(!aCategoryName)) { + return NS_ERROR_INVALID_ARG; + } + + // 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, nullptr); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsCategoryManager::EnumerateCategory(const char* aCategoryName, + nsISimpleEnumerator** aResult) +{ + if (NS_WARN_IF(!aCategoryName) || + NS_WARN_IF(!aResult)) { + return NS_ERROR_INVALID_ARG; + } + + 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; + } + + nsCOMPtr enumerator; + rv = categoryManager->EnumerateCategory(aCategory, + getter_AddRefs(enumerator)); + if (NS_FAILED(rv)) { + return; + } + + nsCOMPtr senumerator = + do_QueryInterface(enumerator); + if (!senumerator) { + NS_WARNING("Category enumerator doesn't support nsIUTF8StringEnumerator."); + return; + } + + bool hasMore; + while (NS_SUCCEEDED(senumerator->HasMore(&hasMore)) && hasMore) { + // From here on just skip any error we get. + nsAutoCString entryString; + if (NS_FAILED(senumerator->GetNext(entryString))) { + continue; + } + + nsXPIDLCString contractID; + rv = categoryManager->GetCategoryEntry(aCategory, entryString.get(), + getter_Copies(contractID)); + if (NS_FAILED(rv)) { + continue; + } + + nsCOMPtr instance = do_GetService(contractID); + 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) { + 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 000000000..3a4faed72 --- /dev/null +++ b/xpcom/components/nsCategoryManager.h @@ -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/. */ + + +#ifndef NSCATEGORYMANAGER_H +#define NSCATEGORYMANAGER_H + +#include "prio.h" +#include "plarena.h" +#include "nsClassHashtable.h" +#include "nsICategoryManager.h" +#include "nsIMemoryReporter.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/Mutex.h" +#include "mozilla/Attributes.h" + +class nsIMemoryReporter; + +/* 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 char* aEntryName, + char** aResult); + + nsresult AddLeaf(const char* aEntryName, + const char* aValue, + bool aReplace, + char** aResult, + PLArenaPool* aArena); + + void DeleteLeaf(const char* 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(PLArenaPool* aArena); + ~CategoryNode(); + void operator delete(void*) {} + + size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf); + +private: + CategoryNode() : mLock("CategoryLeaf") {} + + void* operator new(size_t aSize, PLArenaPool* aArena); + + nsTHashtable mTable; + mozilla::Mutex mLock; +}; + + +/** + * 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 char* aCategory, + const char* aKey, + const char* aValue, + bool aReplace = true, + char** aOldValue = nullptr); + + static nsresult Create(nsISupports* aOuter, 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 char* aName); + void NotifyObservers(const char* aTopic, + const char* aCategoryName, // must be a static string + const char* aEntryName); + + PLArenaPool mArena; + nsClassHashtable mTable; + mozilla::Mutex mLock; + bool mSuppressNotifications; +}; + +#endif diff --git a/xpcom/components/nsCategoryManagerUtils.h b/xpcom/components/nsCategoryManagerUtils.h new file mode 100644 index 000000000..a83d097c2 --- /dev/null +++ b/xpcom/components/nsCategoryManagerUtils.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 nsCategoryManagerUtils_h__ +#define nsCategoryManagerUtils_h__ + +#include "nsICategoryManager.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 000000000..b9eb8e275 --- /dev/null +++ b/xpcom/components/nsComponentManager.cpp @@ -0,0 +1,2083 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 Original Code has been modified by IBM Corporation. + * Modifications made by IBM described herein are + * Copyright (c) International Business Machines + * Corporation, 2000 + * + * Modifications to Mozilla code or documentation + * identified per MPL Section 3.3 + * + * Date Modified by Description of modification + * 04/20/2000 IBM Corp. Added PR_CALLBACK for Optlink use in OS2 + */ + +#include +#include "nscore.h" +#include "nsISupports.h" +#include "nspr.h" +#include "nsCRT.h" // for atoll + +// Arena used by component manager for storing contractid string, dll +// location strings and small objects +// CAUTION: Arena align mask needs to be defined before including plarena.h +// currently from nsComponentManager.h +#define PL_ARENA_CONST_ALIGN_MASK 7 +#define NS_CM_BLOCK_SIZE (1024 * 8) + +#include "nsCategoryManager.h" +#include "nsCOMPtr.h" +#include "nsComponentManager.h" +#include "nsDirectoryService.h" +#include "nsDirectoryServiceDefs.h" +#include "nsCategoryManager.h" +#include "nsCategoryManagerUtils.h" +#include "xptiprivate.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/XPTInterfaceInfoManager.h" +#include "nsIConsoleService.h" +#include "nsIObserverService.h" +#include "nsISimpleEnumerator.h" +#include "nsIStringEnumerator.h" +#include "nsXPCOM.h" +#include "nsXPCOMPrivate.h" +#include "nsISupportsPrimitives.h" +#include "nsIClassInfo.h" +#include "nsLocalFile.h" +#include "nsReadableUtils.h" +#include "nsString.h" +#include "nsXPIDLString.h" +#include "prcmon.h" +#include "xptinfo.h" // this after nsISupports, to pick up IID so that xpt stuff doesn't try to define it itself... +#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 "nsArrayEnumerator.h" +#include "nsStringEnumerator.h" +#include "mozilla/FileUtils.h" +#include "mozilla/UniquePtr.h" +#include "nsDataHashtable.h" + +#include // for placement new + +#include "mozilla/Omnijar.h" + +#include "mozilla/Logging.h" +#include "LogModulePrefWatcher.h" + +using namespace mozilla; + +static LazyLogModule nsComponentManagerLog("nsComponentManager"); + +#if 0 || defined (DEBUG_timeless) + #define SHOW_DENIED_ON_SHUTDOWN + #define SHOW_CI_ON_EXISTING_SERVICE +#endif + +// Bloated registry buffer size to improve startup performance -- needs to +// be big enough to fit the entire file into memory or it'll thrash. +// 512K is big enough to allow for some future growth in the registry. +#define BIG_REGISTRY_BUFLEN (512*1024) + +// Common Key Names +const char xpcomComponentsKeyName[] = "software/mozilla/XPCOM/components"; +const char xpcomKeyName[] = "software/mozilla/XPCOM"; + +// Common Value Names +const char fileSizeValueName[] = "FileSize"; +const char lastModValueName[] = "LastModTimeStamp"; +const char nativeComponentType[] = "application/x-mozilla-native"; +const char staticComponentType[] = "application/x-mozilla-static"; + +NS_DEFINE_CID(kCategoryManagerCID, NS_CATEGORYMANAGER_CID); + +#define UID_STRING_LENGTH 39 + +nsresult +nsGetServiceFromCategory::operator()(const nsIID& aIID, + void** aInstancePtr) const +{ + nsresult rv; + nsXPIDLCString value; + nsCOMPtr catman; + nsComponentManagerImpl* compMgr = nsComponentManagerImpl::gComponentManager; + if (!compMgr) { + rv = NS_ERROR_NOT_INITIALIZED; + goto error; + } + + if (!mCategory || !mEntry) { + // when categories have defaults, use that for null mEntry + rv = NS_ERROR_NULL_POINTER; + goto error; + } + + rv = compMgr->nsComponentManagerImpl::GetService(kCategoryManagerCID, + NS_GET_IID(nsICategoryManager), + getter_AddRefs(catman)); + if (NS_FAILED(rv)) { + goto error; + } + + /* find the contractID for category.entry */ + rv = catman->GetCategoryEntry(mCategory, mEntry, + getter_Copies(value)); + if (NS_FAILED(rv)) { + goto error; + } + if (!value) { + rv = NS_ERROR_SERVICE_NOT_AVAILABLE; + goto error; + } + + rv = compMgr->nsComponentManagerImpl::GetServiceByContractID(value, + aIID, + aInstancePtr); + if (NS_FAILED(rv)) { +error: + *aInstancePtr = 0; + } + if (mErrorPtr) { + *mErrorPtr = rv; + } + return rv; +} + +//////////////////////////////////////////////////////////////////////////////// +// Arena helper functions +//////////////////////////////////////////////////////////////////////////////// +char* +ArenaStrndup(const char* aStr, uint32_t aLen, PLArenaPool* aArena) +{ + void* mem; + // Include trailing null in the aLen + PL_ARENA_ALLOCATE(mem, aArena, aLen + 1); + if (mem) { + memcpy(mem, aStr, aLen + 1); + } + return static_cast(mem); +} + +char* +ArenaStrdup(const char* aStr, PLArenaPool* aArena) +{ + return ArenaStrndup(aStr, strlen(aStr), aArena); +} + +// GetService and a few other functions need to exit their mutex mid-function +// without reentering it later in the block. This class supports that +// style of early-exit that MutexAutoUnlock doesn't. + +namespace { + +class MOZ_STACK_CLASS MutexLock +{ +public: + explicit MutexLock(SafeMutex& aMutex) + : mMutex(aMutex) + , mLocked(false) + { + Lock(); + } + + ~MutexLock() + { + if (mLocked) { + Unlock(); + } + } + + void Lock() + { + NS_ASSERTION(!mLocked, "Re-entering a mutex"); + mMutex.Lock(); + mLocked = true; + } + + void Unlock() + { + NS_ASSERTION(mLocked, "Exiting a mutex that isn't held!"); + mMutex.Unlock(); + mLocked = false; + } + +private: + SafeMutex& mMutex; + bool mLocked; +}; + +} // namespace + +// this is safe to call during InitXPCOM +static already_AddRefed +GetLocationFromDirectoryService(const char* aProp) +{ + nsCOMPtr directoryService; + nsDirectoryService::Create(nullptr, + 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(nsISupports* aOuter, REFNSIID aIID, + void** aResult) +{ + if (aOuter) { + return NS_ERROR_NO_AGGREGATION; + } + + if (!gComponentManager) { + return NS_ERROR_FAILURE; + } + + return gComponentManager->QueryInterface(aIID, aResult); +} + +static const int CONTRACTID_HASHTABLE_INITIAL_LENGTH = 1024; + +nsComponentManagerImpl::nsComponentManagerImpl() + : mFactories(CONTRACTID_HASHTABLE_INITIAL_LENGTH) + , mContractIDs(CONTRACTID_HASHTABLE_INITIAL_LENGTH) + , mLock("nsComponentManagerImpl.mLock") + , mStatus(NOT_INITIALIZED) +{ +} + +nsTArray* nsComponentManagerImpl::sStaticModules; + +NSMODULE_DEFN(start_kPStaticModules); +NSMODULE_DEFN(end_kPStaticModules); + +/* The content between start_kPStaticModules and end_kPStaticModules is gathered + * by the linker from various objects containing symbols in a specific section. + * ASAN considers (rightfully) the use of this content as a global buffer + * overflow. But this is a deliberate and well-considered choice, with no proper + * way to make ASAN happy. */ +MOZ_ASAN_BLACKLIST +/* static */ void +nsComponentManagerImpl::InitializeStaticModules() +{ + if (sStaticModules) { + return; + } + + sStaticModules = new nsTArray; + for (const mozilla::Module * const* staticModules = + &NSMODULE_NAME(start_kPStaticModules) + 1; + staticModules < &NSMODULE_NAME(end_kPStaticModules); ++staticModules) + if (*staticModules) { // ASAN adds padding + sStaticModules->AppendElement(*staticModules); + } +} + +nsTArray* +nsComponentManagerImpl::sModuleLocations; + +/* static */ void +nsComponentManagerImpl::InitializeModuleLocations() +{ + if (sModuleLocations) { + return; + } + + sModuleLocations = new nsTArray; +} + +nsresult +nsComponentManagerImpl::Init() +{ + MOZ_ASSERT(NOT_INITIALIZED == mStatus); + + // Initialize our arena + PL_INIT_ARENA_POOL(&mArena, "ComponentManagerArena", NS_CM_BLOCK_SIZE); + + nsCOMPtr greDir = + GetLocationFromDirectoryService(NS_GRE_DIR); + nsCOMPtr appDir = + GetLocationFromDirectoryService(NS_XPCOM_CURRENT_PROCESS_DIR); + + InitializeStaticModules(); + + nsresult rv = mNativeModuleLoader.Init(); + if (NS_FAILED(rv)) { + return rv; + } + + nsCategoryManager::GetSingleton()->SuppressNotifications(true); + + RegisterModule(&kXPCOMModule, nullptr); + + for (uint32_t i = 0; i < sStaticModules->Length(); ++i) { + RegisterModule((*sStaticModules)[i], nullptr); + } + + bool loadChromeManifests = (XRE_GetProcessType() != GeckoProcessType_GPU); + if (loadChromeManifests) { + // The overall order in which chrome.manifests are expected to be treated + // is the following: + // - greDir + // - greDir's omni.ja + // - appDir + // - appDir's omni.ja + + InitializeModuleLocations(); + ComponentLocation* cl = sModuleLocations->AppendElement(); + nsCOMPtr lf = CloneAndAppend(greDir, + NS_LITERAL_CSTRING("chrome.manifest")); + cl->type = NS_APP_LOCATION; + cl->location.Init(lf); + + RefPtr greOmnijar = + mozilla::Omnijar::GetReader(mozilla::Omnijar::GRE); + if (greOmnijar) { + cl = sModuleLocations->AppendElement(); + cl->type = NS_APP_LOCATION; + cl->location.Init(greOmnijar, "chrome.manifest"); + } + + bool equals = false; + appDir->Equals(greDir, &equals); + if (!equals) { + cl = sModuleLocations->AppendElement(); + cl->type = NS_APP_LOCATION; + lf = CloneAndAppend(appDir, NS_LITERAL_CSTRING("chrome.manifest")); + 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"); + } + + 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; + + return NS_OK; +} + +static bool +ProcessSelectorMatches(Module::ProcessSelector aSelector) +{ + GeckoProcessType type = XRE_GetProcessType(); + if (type == GeckoProcessType_GPU) { + return !!(aSelector & Module::ALLOW_IN_GPU_PROCESS); + } + + if (aSelector & Module::MAIN_PROCESS_ONLY) { + return type == GeckoProcessType_Default; + } + if (aSelector & Module::CONTENT_PROCESS_ONLY) { + return type == GeckoProcessType_Content; + } + return true; +} + +static const int kModuleVersionWithSelector = 51; + +void +nsComponentManagerImpl::RegisterModule(const mozilla::Module* aModule, + FileLocation* aFile) +{ + mLock.AssertNotCurrentThreadOwns(); + + if (aModule->mVersion >= kModuleVersionWithSelector && + !ProcessSelectorMatches(aModule->selector)) + { + return; + } + + { + // Scope the monitor so that we don't hold it while calling into the + // category manager. + MutexLock lock(mLock); + + KnownModule* m; + if (aFile) { + nsCString uri; + aFile->GetURIString(uri); + NS_ASSERTION(!mKnownModules.Get(uri), + "Must not register a binary module twice."); + + m = new KnownModule(aModule, *aFile); + mKnownModules.Put(uri, m); + } else { + m = new KnownModule(aModule); + mKnownStaticModules.AppendElement(m); + } + + if (aModule->mCIDs) { + const mozilla::Module::CIDEntry* entry; + for (entry = aModule->mCIDs; entry->cid; ++entry) { + RegisterCIDEntryLocked(entry, m); + } + } + + if (aModule->mContractIDs) { + const mozilla::Module::ContractIDEntry* entry; + for (entry = aModule->mContractIDs; entry->contractid; ++entry) { + RegisterContractIDLocked(entry); + } + MOZ_ASSERT(!entry->cid, "Incorrectly terminated contract list"); + } + } + + if (aModule->mCategoryEntries) { + const mozilla::Module::CategoryEntry* entry; + for (entry = aModule->mCategoryEntries; entry->category; ++entry) + nsCategoryManager::GetSingleton()->AddCategoryEntry(entry->category, + entry->entry, + entry->value); + } +} + +void +nsComponentManagerImpl::RegisterCIDEntryLocked( + const mozilla::Module::CIDEntry* aEntry, + KnownModule* aModule) +{ + mLock.AssertCurrentThreadOwns(); + + if (!ProcessSelectorMatches(aEntry->processSelector)) { + return; + } + + nsFactoryEntry* f = mFactories.Get(*aEntry->cid); + if (f) { + NS_WARNING("Re-registering a CID?"); + + char idstr[NSID_LENGTH]; + aEntry->cid->ToProvidedString(idstr); + + nsCString existing; + if (f->mModule) { + existing = f->mModule->Description(); + } else { + existing = ""; + } + SafeMutexAutoUnlock unlock(mLock); + LogMessage("While registering XPCOM module %s, trying to re-register CID '%s' already registered by %s.", + aModule->Description().get(), + idstr, + existing.get()); + return; + } + + f = new nsFactoryEntry(aEntry, aModule); + mFactories.Put(*aEntry->cid, f); +} + +void +nsComponentManagerImpl::RegisterContractIDLocked( + const mozilla::Module::ContractIDEntry* aEntry) +{ + mLock.AssertCurrentThreadOwns(); + + if (!ProcessSelectorMatches(aEntry->processSelector)) { + return; + } + + nsFactoryEntry* f = mFactories.Get(*aEntry->cid); + if (!f) { + NS_WARNING("No CID found when attempting to map contract ID"); + + char idstr[NSID_LENGTH]; + aEntry->cid->ToProvidedString(idstr); + + SafeMutexAutoUnlock unlock(mLock); + LogMessage("Could not map contract ID '%s' to CID %s because no implementation of the CID is registered.", + aEntry->contractid, + idstr); + + return; + } + + mContractIDs.Put(nsDependentCString(aEntry->contractid), f); +} + +static void +CutExtension(nsCString& aPath) +{ + int32_t dotPos = aPath.RFindChar('.'); + if (kNotFound == dotPos) { + aPath.Truncate(); + } else { + aPath.Cut(0, dotPos + 1); + } +} + +static void +DoRegisterManifest(NSLocationType aType, + FileLocation& aFile, + bool aChromeOnly, + bool aXPTOnly) +{ + MOZ_ASSERT(!aXPTOnly || !nsComponentManagerImpl::gComponentManager); + uint32_t len; + FileLocation::Data data; + UniquePtr buf; + nsresult rv = aFile.GetData(data); + if (NS_SUCCEEDED(rv)) { + rv = data.GetSize(&len); + } + if (NS_SUCCEEDED(rv)) { + buf = MakeUnique(len + 1); + rv = data.Copy(buf.get(), len); + } + if (NS_SUCCEEDED(rv)) { + buf[len] = '\0'; + ParseManifest(aType, aFile, buf.get(), aChromeOnly, aXPTOnly); + } 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, false); +} + +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::ManifestBinaryComponent(ManifestProcessingContext& aCx, + int aLineNo, + char* const* aArgv) +{ + if (aCx.mFile.IsZip()) { + NS_WARNING("Cannot load binary components from a jar."); + LogMessageWithContext(aCx.mFile, aLineNo, + "Cannot load binary components from a jar."); + return; + } + + FileLocation f(aCx.mFile, aArgv[0]); + nsCString uri; + f.GetURIString(uri); + + if (mKnownModules.Get(uri)) { + NS_WARNING("Attempting to register a binary component twice."); + LogMessageWithContext(aCx.mFile, aLineNo, + "Attempting to register a binary component twice."); + return; + } + + const mozilla::Module* m = mNativeModuleLoader.LoadModule(f); + // The native module loader should report an error here, we don't have to + if (!m) { + return; + } + + RegisterModule(m, &f); +} + +static void +DoRegisterXPT(FileLocation& aFile) +{ + uint32_t len; + FileLocation::Data data; + UniquePtr buf; + nsresult rv = aFile.GetData(data); + if (NS_SUCCEEDED(rv)) { + rv = data.GetSize(&len); + } + if (NS_SUCCEEDED(rv)) { + buf = MakeUnique(len); + rv = data.Copy(buf.get(), len); + } + if (NS_SUCCEEDED(rv)) { + XPTInterfaceInfoManager::GetSingleton()->RegisterBuffer(buf.get(), len); + } else { + nsCString uri; + aFile.GetURIString(uri); + LogMessage("Could not read '%s'.", uri.get()); + } +} + +void +nsComponentManagerImpl::ManifestXPT(ManifestProcessingContext& aCx, + int aLineNo, char* const* aArgv) +{ + FileLocation f(aCx.mFile, aArgv[0]); + DoRegisterXPT(f); +} + +void +nsComponentManagerImpl::ManifestComponent(ManifestProcessingContext& aCx, + int aLineNo, char* const* aArgv) +{ + mLock.AssertNotCurrentThreadOwns(); + + char* id = aArgv[0]; + char* file = aArgv[1]; + + nsID cid; + if (!cid.Parse(id)) { + LogMessageWithContext(aCx.mFile, aLineNo, + "Malformed CID: '%s'.", id); + return; + } + + // Precompute the hash/file data outside of the lock + FileLocation fl(aCx.mFile, file); + nsCString hash; + fl.GetURIString(hash); + + MutexLock lock(mLock); + nsFactoryEntry* f = mFactories.Get(cid); + if (f) { + char idstr[NSID_LENGTH]; + cid.ToProvidedString(idstr); + + nsCString existing; + if (f->mModule) { + existing = f->mModule->Description(); + } else { + existing = ""; + } + + lock.Unlock(); + + LogMessageWithContext(aCx.mFile, aLineNo, + "Trying to re-register CID '%s' already registered by %s.", + idstr, + existing.get()); + return; + } + + KnownModule* km; + + km = mKnownModules.Get(hash); + if (!km) { + km = new KnownModule(fl); + mKnownModules.Put(hash, km); + } + + void* place; + + PL_ARENA_ALLOCATE(place, &mArena, sizeof(nsCID)); + nsID* permanentCID = static_cast(place); + *permanentCID = cid; + + PL_ARENA_ALLOCATE(place, &mArena, sizeof(mozilla::Module::CIDEntry)); + mozilla::Module::CIDEntry* e = new (place) mozilla::Module::CIDEntry(); + e->cid = permanentCID; + + f = new nsFactoryEntry(e, km); + mFactories.Put(cid, f); +} + +void +nsComponentManagerImpl::ManifestContract(ManifestProcessingContext& aCx, + int aLineNo, char* const* aArgv) +{ + mLock.AssertNotCurrentThreadOwns(); + + char* contract = aArgv[0]; + char* id = aArgv[1]; + + nsID cid; + if (!cid.Parse(id)) { + LogMessageWithContext(aCx.mFile, aLineNo, + "Malformed CID: '%s'.", id); + return; + } + + MutexLock lock(mLock); + nsFactoryEntry* f = mFactories.Get(cid); + if (!f) { + lock.Unlock(); + LogMessageWithContext(aCx.mFile, aLineNo, + "Could not map contract ID '%s' to CID %s because no implementation of the CID is registered.", + contract, id); + return; + } + + mContractIDs.Put(nsDependentCString(contract), f); +} + +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(category, key, 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); + } +} + +bool +nsComponentManagerImpl::KnownModule::EnsureLoader() +{ + if (!mLoader) { + nsCString extension; + mFile.GetURIString(extension); + CutExtension(extension); + mLoader = + nsComponentManagerImpl::gComponentManager->LoaderForExtension(extension); + } + return !!mLoader; +} + +bool +nsComponentManagerImpl::KnownModule::Load() +{ + if (mFailed) { + return false; + } + if (!mModule) { + if (!EnsureLoader()) { + return false; + } + + mModule = mLoader->LoadModule(mFile); + + if (!mModule) { + mFailed = true; + return false; + } + } + if (!mLoaded) { + if (mModule->loadProc) { + nsresult rv = mModule->loadProc(); + if (NS_FAILED(rv)) { + mFailed = true; + return false; + } + } + mLoaded = true; + } + return true; +} + +nsCString +nsComponentManagerImpl::KnownModule::Description() const +{ + nsCString s; + if (mFile) { + mFile.GetURIString(s); + } else { + s = ""; + } + return s; +} + +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 + mLoaderMap.Clear(); + mKnownModules.Clear(); + mKnownStaticModules.Clear(); + + delete sStaticModules; + delete sModuleLocations; + + // Unload libraries + mNativeModuleLoader.UnloadLibraries(); + + // delete arena for strings and small objects + PL_FinishArenaPool(&mArena); + + 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); +} + +nsFactoryEntry* +nsComponentManagerImpl::GetFactoryEntry(const char* aContractID, + uint32_t aContractIDLen) +{ + SafeMutexAutoLock lock(mLock); + return mContractIDs.Get(nsDependentCString(aContractID, aContractIDLen)); +} + + +nsFactoryEntry* +nsComponentManagerImpl::GetFactoryEntry(const nsCID& aClass) +{ + SafeMutexAutoLock lock(mLock); + return mFactories.Get(aClass); +} + +already_AddRefed +nsComponentManagerImpl::FindFactory(const nsCID& aClass) +{ + nsFactoryEntry* e = GetFactoryEntry(aClass); + if (!e) { + return nullptr; + } + + return e->GetFactory(); +} + +already_AddRefed +nsComponentManagerImpl::FindFactory(const char* aContractID, + uint32_t aContractIDLen) +{ + nsFactoryEntry* entry = GetFactoryEntry(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 = aClass.ToString(); + PR_LogPrint("nsComponentManager: GetClassObject(%s)", buf); + if (buf) { + free(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: GetClassObject(%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\tGetClassObject() %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, + nsISupports* aDelegate, + 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 + nsXPIDLCString cid, iid; + cid.Adopt(aClass.ToString()); + iid.Adopt(aIID.ToString()); + fprintf(stderr, "Creating new instance on shutdown. Denied.\n" + " CID: %s\n IID: %s\n", cid.get(), iid.get()); +#endif /* SHOW_DENIED_ON_SHUTDOWN */ + return NS_ERROR_UNEXPECTED; + } + + if (!aResult) { + return NS_ERROR_NULL_POINTER; + } + *aResult = nullptr; + + nsFactoryEntry* entry = GetFactoryEntry(aClass); + + if (!entry) { + return NS_ERROR_FACTORY_NOT_REGISTERED; + } + +#ifdef SHOW_CI_ON_EXISTING_SERVICE + if (entry->mServiceObject) { + nsXPIDLCString cid; + cid.Adopt(aClass.ToString()); + nsAutoCString message; + message = NS_LITERAL_CSTRING("You are calling CreateInstance \"") + + cid + + NS_LITERAL_CSTRING("\" when a service for this CID already exists!"); + NS_ERROR(message.get()); + } +#endif + + nsresult rv; + nsCOMPtr factory = entry->GetFactory(); + if (factory) { + rv = factory->CreateInstance(aDelegate, aIID, aResult); + if (NS_SUCCEEDED(rv) && !*aResult) { + NS_ERROR("Factory did not return an object but returned success!"); + rv = NS_ERROR_SERVICE_NOT_FOUND; + } + } else { + // Translate error values + rv = NS_ERROR_FACTORY_NOT_REGISTERED; + } + + if (MOZ_LOG_TEST(nsComponentManagerLog, LogLevel::Warning)) { + char* buf = aClass.ToString(); + MOZ_LOG(nsComponentManagerLog, LogLevel::Warning, + ("nsComponentManager: CreateInstance(%s) %s", buf, + NS_SUCCEEDED(rv) ? "succeeded" : "FAILED")); + if (buf) { + free(buf); + } + } + + 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, + nsISupports* aDelegate, + 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 + nsXPIDLCString iid; + iid.Adopt(aIID.ToString()); + fprintf(stderr, "Creating new instance on shutdown. Denied.\n" + " ContractID: %s\n IID: %s\n", aContractID, iid.get()); +#endif /* SHOW_DENIED_ON_SHUTDOWN */ + return NS_ERROR_UNEXPECTED; + } + + if (!aResult) { + return NS_ERROR_NULL_POINTER; + } + *aResult = nullptr; + + nsFactoryEntry* entry = GetFactoryEntry(aContractID, strlen(aContractID)); + + if (!entry) { + return NS_ERROR_FACTORY_NOT_REGISTERED; + } + +#ifdef SHOW_CI_ON_EXISTING_SERVICE + if (entry->mServiceObject) { + nsAutoCString message; + message = + NS_LITERAL_CSTRING("You are calling CreateInstance \"") + + nsDependentCString(aContractID) + + NS_LITERAL_CSTRING("\" 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(aDelegate, aIID, aResult); + if (NS_SUCCEEDED(rv) && !*aResult) { + NS_ERROR("Factory did not return an object but returned success!"); + rv = NS_ERROR_SERVICE_NOT_FOUND; + } + } 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 (auto iter = mFactories.Iter(); !iter.Done(); iter.Next()) { + nsFactoryEntry* entry = iter.UserData(); + entry->mFactory = nullptr; + entry->mServiceObject = 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(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); + 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; +} + +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 + nsXPIDLCString cid, iid; + cid.Adopt(aClass.ToString()); + iid.Adopt(aIID.ToString()); + fprintf(stderr, "Getting service on shutdown. Denied.\n" + " CID: %s\n IID: %s\n", cid.get(), iid.get()); +#endif /* SHOW_DENIED_ON_SHUTDOWN */ + return NS_ERROR_UNEXPECTED; + } + + // `service` must be released after the lock is released, so it must be + // declared before the lock in this C++ block. + nsCOMPtr service; + MutexLock lock(mLock); + + nsFactoryEntry* entry = mFactories.Get(aClass); + if (!entry) { + return NS_ERROR_FACTORY_NOT_REGISTERED; + } + + if (entry->mServiceObject) { + lock.Unlock(); + return entry->mServiceObject->QueryInterface(aIID, aResult); + } + + PRThread* currentPRThread = PR_GetCurrentThread(); + MOZ_ASSERT(currentPRThread, "This should never be null!"); + + // Needed to optimize the event loop below. + nsIThread* currentThread = nullptr; + + PRThread* pendingPRThread; + while ((pendingPRThread = GetPendingServiceThread(aClass))) { + if (pendingPRThread == currentPRThread) { + NS_ERROR("Recursive GetService!"); + return NS_ERROR_NOT_AVAILABLE; + } + + + SafeMutexAutoUnlock unlockPending(mLock); + + if (!currentThread) { + currentThread = NS_GetCurrentThread(); + MOZ_ASSERT(currentThread, "This should never be null!"); + } + + // This will process a single event or yield the thread if no event is + // pending. + if (!NS_ProcessNextEvent(currentThread, false)) { + PR_Sleep(PR_INTERVAL_NO_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 (entry->mServiceObject) { + lock.Unlock(); + return entry->mServiceObject->QueryInterface(aIID, aResult); + } + +#ifdef DEBUG + PendingServiceInfo* newInfo = +#endif + AddPendingService(aClass, 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: + + nsresult rv; + { + SafeMutexAutoUnlock unlock(mLock); + rv = CreateInstance(aClass, nullptr, 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_FOUND; + } + +#ifdef DEBUG + pendingPRThread = GetPendingServiceThread(aClass); + MOZ_ASSERT(pendingPRThread == currentPRThread, + "Pending service array has been changed!"); +#endif + RemovePendingService(aClass); + + if (NS_FAILED(rv)) { + return rv; + } + + NS_ASSERTION(!entry->mServiceObject, "Created two instances of a service!"); + + entry->mServiceObject = service.forget(); + + lock.Unlock(); + nsISupports** sresult = reinterpret_cast(aResult); + *sresult = entry->mServiceObject; + (*sresult)->AddRef(); + + return NS_OK; +} + +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 + nsXPIDLCString cid, iid; + cid.Adopt(aClass.ToString()); + iid.Adopt(aIID.ToString()); + fprintf(stderr, "Checking for service on shutdown. Denied.\n" + " CID: %s\n IID: %s\n", cid.get(), iid.get()); +#endif /* SHOW_DENIED_ON_SHUTDOWN */ + return NS_ERROR_UNEXPECTED; + } + + nsresult rv = NS_ERROR_SERVICE_NOT_AVAILABLE; + nsFactoryEntry* entry; + + { + SafeMutexAutoLock lock(mLock); + entry = mFactories.Get(aClass); + } + + if (entry && entry->mServiceObject) { + nsCOMPtr service; + rv = entry->mServiceObject->QueryInterface(aIID, getter_AddRefs(service)); + *aResult = (service != nullptr); + } + + return rv; +} + +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 + nsXPIDLCString iid; + iid.Adopt(aIID.ToString()); + fprintf(stderr, "Checking for service on shutdown. Denied.\n" + " ContractID: %s\n IID: %s\n", aContractID, iid.get()); +#endif /* SHOW_DENIED_ON_SHUTDOWN */ + return NS_ERROR_UNEXPECTED; + } + + nsresult rv = NS_ERROR_SERVICE_NOT_AVAILABLE; + nsFactoryEntry* entry; + { + SafeMutexAutoLock lock(mLock); + entry = mContractIDs.Get(nsDependentCString(aContractID)); + } + + if (entry && entry->mServiceObject) { + nsCOMPtr service; + rv = entry->mServiceObject->QueryInterface(aIID, getter_AddRefs(service)); + *aResult = (service != nullptr); + } + return rv; +} + + +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 + nsXPIDLCString iid; + iid.Adopt(aIID.ToString()); + fprintf(stderr, "Getting service on shutdown. Denied.\n" + " ContractID: %s\n IID: %s\n", aContractID, iid.get()); +#endif /* SHOW_DENIED_ON_SHUTDOWN */ + return NS_ERROR_UNEXPECTED; + } + + // `service` must be released after the lock is released, so it must be + // declared before the lock in this C++ block. + nsCOMPtr service; + MutexLock lock(mLock); + + nsFactoryEntry* entry = mContractIDs.Get(nsDependentCString(aContractID)); + if (!entry) { + return NS_ERROR_FACTORY_NOT_REGISTERED; + } + + if (entry->mServiceObject) { + // We need to not be holding the service manager's monitor while calling + // QueryInterface, because it invokes user code which could try to re-enter + // the service manager, or try to grab some other lock/monitor/condvar + // and deadlock, e.g. bug 282743. + // `entry` is valid until XPCOM shutdown, so we can safely use it after + // exiting the lock. + lock.Unlock(); + return entry->mServiceObject->QueryInterface(aIID, aResult); + } + + PRThread* currentPRThread = PR_GetCurrentThread(); + MOZ_ASSERT(currentPRThread, "This should never be null!"); + + // Needed to optimize the event loop below. + nsIThread* currentThread = nullptr; + + PRThread* pendingPRThread; + while ((pendingPRThread = GetPendingServiceThread(*entry->mCIDEntry->cid))) { + if (pendingPRThread == currentPRThread) { + NS_ERROR("Recursive GetService!"); + return NS_ERROR_NOT_AVAILABLE; + } + + SafeMutexAutoUnlock unlockPending(mLock); + + if (!currentThread) { + currentThread = NS_GetCurrentThread(); + MOZ_ASSERT(currentThread, "This should never be null!"); + } + + // This will process a single event or yield the thread if no event is + // pending. + if (!NS_ProcessNextEvent(currentThread, false)) { + PR_Sleep(PR_INTERVAL_NO_WAIT); + } + } + + if (currentThread && entry->mServiceObject) { + // If we have a currentThread then we must have waited on another thread + // to create the service. Grab it now if that succeeded. + lock.Unlock(); + return entry->mServiceObject->QueryInterface(aIID, aResult); + } + +#ifdef DEBUG + PendingServiceInfo* newInfo = +#endif + AddPendingService(*entry->mCIDEntry->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: + + nsresult rv; + { + SafeMutexAutoUnlock unlock(mLock); + rv = CreateInstanceByContractID(aContractID, nullptr, 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_FOUND; + } + +#ifdef DEBUG + pendingPRThread = GetPendingServiceThread(*entry->mCIDEntry->cid); + MOZ_ASSERT(pendingPRThread == currentPRThread, + "Pending service array has been changed!"); +#endif + RemovePendingService(*entry->mCIDEntry->cid); + + if (NS_FAILED(rv)) { + return rv; + } + + NS_ASSERTION(!entry->mServiceObject, "Created two instances of a service!"); + + entry->mServiceObject = service.forget(); + + lock.Unlock(); + + nsISupports** sresult = reinterpret_cast(aResult); + *sresult = entry->mServiceObject; + (*sresult)->AddRef(); + + return NS_OK; +} + +already_AddRefed +nsComponentManagerImpl::LoaderForExtension(const nsACString& aExt) +{ + nsCOMPtr loader = mLoaderMap.Get(aExt); + if (!loader) { + loader = do_GetServiceFromCategory("module-loader", + PromiseFlatCString(aExt).get()); + if (!loader) { + return nullptr; + } + + mLoaderMap.Put(aExt, loader); + } + + return loader.forget(); +} + +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; + } + + SafeMutexAutoLock lock(mLock); + nsFactoryEntry* oldf = mFactories.Get(aClass); + if (!oldf) { + return NS_ERROR_FACTORY_NOT_REGISTERED; + } + + mContractIDs.Put(nsDependentCString(aContractID), oldf); + return NS_OK; + } + + nsAutoPtr f(new nsFactoryEntry(aClass, aFactory)); + + SafeMutexAutoLock lock(mLock); + nsFactoryEntry* oldf = mFactories.Get(aClass); + if (oldf) { + return NS_ERROR_FACTORY_EXISTS; + } + + if (aContractID) { + mContractIDs.Put(nsDependentCString(aContractID), f); + } + + mFactories.Put(aClass, f.forget()); + + 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; + + { + SafeMutexAutoLock lock(mLock); + nsFactoryEntry* f = mFactories.Get(aClass); + if (!f || f->mFactory != aFactory) { + return NS_ERROR_FACTORY_NOT_REGISTERED; + } + + mFactories.Remove(aClass); + + // 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::AutoUnregister(nsIFile* aLocation) +{ + NS_ERROR("AutoUnregister not implemented."); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsComponentManagerImpl::RegisterFactoryLocation(const nsCID& aCID, + const char* aClassName, + const char* aContractID, + nsIFile* aFile, + const char* aLoaderStr, + const char* aType) +{ + NS_ERROR("RegisterFactoryLocation not implemented."); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsComponentManagerImpl::UnregisterFactoryLocation(const nsCID& aCID, + nsIFile* aFile) +{ + NS_ERROR("UnregisterFactoryLocation not implemented."); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsComponentManagerImpl::IsCIDRegistered(const nsCID& aClass, + bool* aResult) +{ + *aResult = (nullptr != GetFactoryEntry(aClass)); + return NS_OK; +} + +NS_IMETHODIMP +nsComponentManagerImpl::IsContractIDRegistered(const char* aClass, + bool* aResult) +{ + if (NS_WARN_IF(!aClass)) { + return NS_ERROR_INVALID_ARG; + } + + nsFactoryEntry* entry = GetFactoryEntry(aClass, strlen(aClass)); + + if (entry) { + // UnregisterFactory might have left a stale nsFactoryEntry in + // mContractIDs, so we should check to see whether this entry has + // anything useful. + *aResult = (bool(entry->mModule) || + bool(entry->mFactory) || + bool(entry->mServiceObject)); + } else { + *aResult = false; + } + return NS_OK; +} + +NS_IMETHODIMP +nsComponentManagerImpl::EnumerateCIDs(nsISimpleEnumerator** aEnumerator) +{ + nsCOMArray array; + for (auto iter = mFactories.Iter(); !iter.Done(); iter.Next()) { + const nsID& id = iter.Key(); + nsCOMPtr wrapper = new nsSupportsID(); + wrapper->SetData(&id); + array.AppendObject(wrapper); + } + return NS_NewArrayEnumerator(aEnumerator, array); +} + +NS_IMETHODIMP +nsComponentManagerImpl::EnumerateContractIDs(nsISimpleEnumerator** aEnumerator) +{ + nsTArray* array = new nsTArray; + for (auto iter = mContractIDs.Iter(); !iter.Done(); iter.Next()) { + const nsACString& contract = iter.Key(); + array->AppendElement(contract); + } + + nsCOMPtr e; + nsresult rv = NS_NewAdoptingUTF8StringEnumerator(getter_AddRefs(e), array); + if (NS_FAILED(rv)) { + return rv; + } + + return CallQueryInterface(e, aEnumerator); +} + +NS_IMETHODIMP +nsComponentManagerImpl::CIDToContractID(const nsCID& aClass, + char** aResult) +{ + NS_ERROR("CIDTOContractID not implemented"); + return NS_ERROR_FACTORY_NOT_REGISTERED; +} + +NS_IMETHODIMP +nsComponentManagerImpl::ContractIDToCID(const char* aContractID, + nsCID** aResult) +{ + { + SafeMutexAutoLock lock(mLock); + nsFactoryEntry* entry = mContractIDs.Get(nsDependentCString(aContractID)); + if (entry) { + *aResult = (nsCID*)moz_xmalloc(sizeof(nsCID)); + **aResult = *entry->mCIDEntry->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 += mLoaderMap.ShallowSizeOfExcludingThis(aMallocSizeOf); + + n += mFactories.ShallowSizeOfExcludingThis(aMallocSizeOf); + for (auto iter = mFactories.ConstIter(); !iter.Done(); iter.Next()) { + n += iter.Data()->SizeOfIncludingThis(aMallocSizeOf); + } + + n += mContractIDs.ShallowSizeOfExcludingThis(aMallocSizeOf); + for (auto iter = mContractIDs.ConstIter(); !iter.Done(); iter.Next()) { + // We don't measure the nsFactoryEntry data because it's owned by + // mFactories (which is measured above). + n += iter.Key().SizeOfExcludingThisIfUnshared(aMallocSizeOf); + } + + n += sStaticModules->ShallowSizeOfIncludingThis(aMallocSizeOf); + n += sModuleLocations->ShallowSizeOfIncludingThis(aMallocSizeOf); + + n += mKnownStaticModules.ShallowSizeOfExcludingThis(aMallocSizeOf); + n += mKnownModules.ShallowSizeOfExcludingThis(aMallocSizeOf); + + n += PL_SizeOfArenaPoolExcludingPool(&mArena, aMallocSizeOf); + + n += mPendingServices.ShallowSizeOfExcludingThis(aMallocSizeOf); + + // Measurement of the following members may be added later if DMD finds it is + // worthwhile: + // - mLoaderMap's keys and values + // - mMon + // - sStaticModules' entries + // - sModuleLocations' entries + // - mNativeModuleLoader + // - mKnownStaticModules' entries? + // - mKnownModules' keys and values? + + return n; +} + +//////////////////////////////////////////////////////////////////////////////// +// nsFactoryEntry +//////////////////////////////////////////////////////////////////////////////// + +nsFactoryEntry::nsFactoryEntry(const mozilla::Module::CIDEntry* aEntry, + nsComponentManagerImpl::KnownModule* aModule) + : mCIDEntry(aEntry) + , mModule(aModule) +{ +} + +nsFactoryEntry::nsFactoryEntry(const nsCID& aCID, nsIFactory* aFactory) + : mCIDEntry(nullptr) + , mModule(nullptr) + , mFactory(aFactory) +{ + mozilla::Module::CIDEntry* e = new mozilla::Module::CIDEntry(); + nsCID* cid = new nsCID; + *cid = aCID; + e->cid = cid; + mCIDEntry = e; +} + +nsFactoryEntry::~nsFactoryEntry() +{ + // If this was a RegisterFactory entry, we own the CIDEntry/CID + if (!mModule) { + delete mCIDEntry->cid; + delete mCIDEntry; + } +} + +already_AddRefed +nsFactoryEntry::GetFactory() +{ + nsComponentManagerImpl::gComponentManager->mLock.AssertNotCurrentThreadOwns(); + + if (!mFactory) { + // RegisterFactory then UnregisterFactory can leave an entry in mContractIDs + // pointing to an unusable nsFactoryEntry. + if (!mModule) { + return nullptr; + } + + if (!mModule->Load()) { + return nullptr; + } + + // Don't set mFactory directly, it needs to be locked + nsCOMPtr factory; + + if (mModule->Module()->getFactoryProc) { + factory = mModule->Module()->getFactoryProc(*mModule->Module(), + *mCIDEntry); + } else if (mCIDEntry->getFactoryProc) { + factory = mCIDEntry->getFactoryProc(*mModule->Module(), *mCIDEntry); + } else { + NS_ASSERTION(mCIDEntry->constructorProc, "no getfactory or constructor"); + factory = new mozilla::GenericFactory(mCIDEntry->constructorProc); + } + if (!factory) { + return nullptr; + } + + SafeMutexAutoLock lock(nsComponentManagerImpl::gComponentManager->mLock); + // Threads can race to set mFactory + if (!mFactory) { + factory.swap(mFactory); + } + } + nsCOMPtr factory = mFactory; + return factory.forget(); +} + +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: + // - mCIDEntry; + // - mModule; + // - 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; +} + +EXPORT_XPCOM_API(nsresult) +XRE_AddStaticComponent(const mozilla::Module* aComponent) +{ + nsComponentManagerImpl::InitializeStaticModules(); + nsComponentManagerImpl::sStaticModules->AppendElement(aComponent); + + if (nsComponentManagerImpl::gComponentManager && + nsComponentManagerImpl::NORMAL == + nsComponentManagerImpl::gComponentManager->mStatus) { + nsComponentManagerImpl::gComponentManager->RegisterModule(aComponent, + nullptr); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsComponentManagerImpl::AddBootstrappedManifestLocation(nsIFile* 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, NS_LITERAL_CSTRING("chrome.manifest")); + return XRE_AddManifestLocation(NS_BOOTSTRAPPED_LOCATION, manifest); +} + +NS_IMETHODIMP +nsComponentManagerImpl::RemoveBootstrappedManifestLocation(nsIFile* aLocation) +{ + nsCOMPtr cr = mozilla::services::GetChromeRegistryService(); + 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, NS_LITERAL_CSTRING("chrome.manifest")); + elem.location.Init(lf); + } + + // Remove reference. + nsComponentManagerImpl::sModuleLocations->RemoveElement(elem, + ComponentLocationComparator()); + + rv = cr->CheckForNewChrome(); + return rv; +} + +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, false); + } + } + + 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; +} + diff --git a/xpcom/components/nsComponentManager.h b/xpcom/components/nsComponentManager.h new file mode 100644 index 000000000..f0e0f684a --- /dev/null +++ b/xpcom/components/nsComponentManager.h @@ -0,0 +1,363 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 "xpcom-private.h" +#include "nsIComponentManager.h" +#include "nsIComponentRegistrar.h" +#include "nsIMemoryReporter.h" +#include "nsIServiceManager.h" +#include "nsIFile.h" +#include "mozilla/Atomics.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/Module.h" +#include "mozilla/ModuleLoader.h" +#include "mozilla/Mutex.h" +#include "nsXULAppAPI.h" +#include "nsNativeModuleLoader.h" +#include "nsIFactory.h" +#include "nsIInterfaceRequestor.h" +#include "nsIInterfaceRequestorUtils.h" +#include "PLDHashTable.h" +#include "prtime.h" +#include "nsCOMPtr.h" +#include "nsAutoPtr.h" +#include "nsWeakReference.h" +#include "plarena.h" +#include "nsCOMArray.h" +#include "nsDataHashtable.h" +#include "nsInterfaceHashtable.h" +#include "nsClassHashtable.h" +#include "nsTArray.h" + +#include "mozilla/Omnijar.h" +#include "mozilla/Attributes.h" + +struct nsFactoryEntry; +class nsIServiceManager; +struct PRThread; + +#define NS_COMPONENTMANAGER_CID \ +{ /* 91775d60-d5dc-11d2-92fb-00e09805570f */ \ + 0x91775d60, \ + 0xd5dc, \ + 0x11d2, \ + {0x92, 0xfb, 0x00, 0xe0, 0x98, 0x05, 0x57, 0x0f} \ +} + +/* keys for registry use */ +extern const char xpcomKeyName[]; +extern const char xpcomComponentsKeyName[]; +extern const char lastModValueName[]; +extern const char fileSizeValueName[]; +extern const char nativeComponentType[]; +extern const char staticComponentType[]; + +#ifdef DEBUG +#define XPCOM_CHECK_PENDING_CIDS +#endif +//////////////////////////////////////////////////////////////////////////////// + +extern const mozilla::Module kXPCOMModule; + +/** + * This is a wrapper around mozilla::Mutex which provides runtime + * checking for a deadlock where the same thread tries to lock a mutex while + * it is already locked. This checking is present in both debug and release + * builds. + */ +class SafeMutex +{ +public: + explicit SafeMutex(const char* aName) + : mMutex(aName) + , mOwnerThread(nullptr) + { + } + + ~SafeMutex() {} + + void Lock() + { + AssertNotCurrentThreadOwns(); + mMutex.Lock(); + MOZ_ASSERT(mOwnerThread == nullptr); + mOwnerThread = PR_GetCurrentThread(); + } + + void Unlock() + { + MOZ_ASSERT(mOwnerThread == PR_GetCurrentThread()); + mOwnerThread = nullptr; + mMutex.Unlock(); + } + + void AssertCurrentThreadOwns() const + { + // This method is a debug-only check + MOZ_ASSERT(mOwnerThread == PR_GetCurrentThread()); + } + + MOZ_NEVER_INLINE void AssertNotCurrentThreadOwns() const + { + // This method is a release-mode check + if (PR_GetCurrentThread() == mOwnerThread) { + MOZ_CRASH(); + } + } + +private: + mozilla::Mutex mMutex; + mozilla::Atomic mOwnerThread; +}; + +typedef mozilla::BaseAutoLock SafeMutexAutoLock; +typedef mozilla::BaseAutoUnlock SafeMutexAutoUnlock; + +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(nsISupports* aOuter, 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 LoaderForExtension(const nsACString& aExt); + nsInterfaceHashtable mLoaderMap; + + already_AddRefed FindFactory(const nsCID& aClass); + already_AddRefed FindFactory(const char* aContractID, + uint32_t aContractIDLen); + + already_AddRefed LoadFactory(nsFactoryEntry* aEntry); + + nsFactoryEntry* GetFactoryEntry(const char* aContractID, + uint32_t aContractIDLen); + nsFactoryEntry* GetFactoryEntry(const nsCID& aClass); + + nsDataHashtable mFactories; + nsDataHashtable mContractIDs; + + SafeMutex mLock; + + 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* sStaticModules; + static nsTArray* sModuleLocations; + + nsNativeModuleLoader mNativeModuleLoader; + + class KnownModule + { + public: + /** + * Static or binary module. + */ + KnownModule(const mozilla::Module* aModule, mozilla::FileLocation& aFile) + : mModule(aModule) + , mFile(aFile) + , mLoaded(false) + , mFailed(false) + { + } + + explicit KnownModule(const mozilla::Module* aModule) + : mModule(aModule) + , mLoaded(false) + , mFailed(false) + { + } + + explicit KnownModule(mozilla::FileLocation& aFile) + : mModule(nullptr) + , mFile(aFile) + , mLoader(nullptr) + , mLoaded(false) + , mFailed(false) + { + } + + ~KnownModule() + { + if (mLoaded && mModule->unloadProc) { + mModule->unloadProc(); + } + } + + bool EnsureLoader(); + bool Load(); + + const mozilla::Module* Module() const { return mModule; } + + /** + * For error logging, get a description of this module, either the + * file path, or . + */ + nsCString Description() const; + + private: + const mozilla::Module* mModule; + mozilla::FileLocation mFile; + nsCOMPtr mLoader; + bool mLoaded; + bool mFailed; + }; + + // The KnownModule is kept alive by these members, it is + // referenced by pointer from the factory entries. + nsTArray> mKnownStaticModules; + // The key is the URI string of the module + nsClassHashtable mKnownModules; + + // Mutex not held + void RegisterModule(const mozilla::Module* aModule, + mozilla::FileLocation* aFile); + + + // Mutex held + void RegisterCIDEntryLocked(const mozilla::Module::CIDEntry* aEntry, + KnownModule* aModule); + void RegisterContractIDLocked(const mozilla::Module::ContractIDEntry* aEntry); + + // 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() {} + + NSLocationType mType; + mozilla::FileLocation mFile; + bool mChromeOnly; + }; + + void ManifestManifest(ManifestProcessingContext& aCx, int aLineNo, + char* const* aArgv); + void ManifestBinaryComponent(ManifestProcessingContext& aCx, int aLineNo, + char* const* aArgv); + void ManifestXPT(ManifestProcessingContext& aCx, int aLineNo, + char* const* aArgv); + void ManifestComponent(ManifestProcessingContext& aCx, int aLineNo, + char* const* aArgv); + void ManifestContract(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; + + PLArenaPool mArena; + + struct PendingServiceInfo + { + const nsCID* cid; + PRThread* thread; + }; + + inline PendingServiceInfo* AddPendingService(const nsCID& aServiceCID, + PRThread* aThread); + inline void RemovePendingService(const nsCID& aServiceCID); + inline PRThread* GetPendingServiceThread(const nsCID& aServiceCID) const; + + nsTArray mPendingServices; + + size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const; + +private: + ~nsComponentManagerImpl(); +}; + + +#define NS_MAX_FILENAME_LEN 1024 + +#define NS_ERROR_IS_DIR NS_ERROR_GENERATE_FAILURE(NS_ERROR_MODULE_XPCOM, 24) + +struct nsFactoryEntry +{ + nsFactoryEntry(const mozilla::Module::CIDEntry* aEntry, + nsComponentManagerImpl::KnownModule* aModule); + + // nsIComponentRegistrar.registerFactory support + nsFactoryEntry(const nsCID& aClass, nsIFactory* aFactory); + + ~nsFactoryEntry(); + + already_AddRefed GetFactory(); + + size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf); + + const mozilla::Module::CIDEntry* mCIDEntry; + nsComponentManagerImpl::KnownModule* mModule; + + nsCOMPtr mFactory; + nsCOMPtr mServiceObject; +}; + +#endif // nsComponentManager_h__ diff --git a/xpcom/components/nsICategoryManager.idl b/xpcom/components/nsICategoryManager.idl new file mode 100644 index 000000000..e33d3ed8b --- /dev/null +++ b/xpcom/components/nsICategoryManager.idl @@ -0,0 +1,69 @@ +/* -*- 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; + +/* + * nsICategoryManager + */ + +[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. + */ + string getCategoryEntry(in string aCategory, in string 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 + */ + string addCategoryEntry(in string aCategory, in string aEntry, + in string 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 string aCategory, in string aEntry, + in boolean aPersist); + + /** + * Delete a category and all entries. + * @param aCategory The category to be deleted. + */ + void deleteCategory(in string aCategory); + + /** + * Enumerate the entries in a category. + * @param aCategory The category to be enumerated. + * @return a simple enumerator, each result QIs to + * nsISupportsCString. + */ + nsISimpleEnumerator enumerateCategory(in string aCategory); + + /** + * Enumerate all existing categories + * @param aCategory The category to be enumerated. + * @return a simple enumerator, each result QIs to + * nsISupportsCString. + */ + nsISimpleEnumerator enumerateCategories(); +}; + diff --git a/xpcom/components/nsIClassInfo.idl b/xpcom/components/nsIClassInfo.idl new file mode 100644 index 000000000..639d12aa8 --- /dev/null +++ b/xpcom/components/nsIClassInfo.idl @@ -0,0 +1,87 @@ +/* -*- 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 +{ + /** + * Get an ordered list of the interface ids that instances of the class + * promise to implement. Note that nsISupports is an implicit member + * of any such list and need not be included. + * + * Should set *count = 0 and *array = null and return NS_OK if getting the + * list is not supported. + */ + void getInterfaces(out uint32_t count, + [array, size_is(count), retval] out nsIIDPtr array); + + /** + * 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. + */ + readonly attribute string contractID; + + /** + * A human readable string naming the class, or null. + */ + readonly attribute string 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 MAIN_THREAD_ONLY = 1 << 2; + const uint32_t DOM_OBJECT = 1 << 3; + const uint32_t PLUGIN_OBJECT = 1 << 4; + const uint32_t SINGLETON_CLASSINFO = 1 << 5; + + /** + * 'flags' attribute bitflag: whether objects of this type implement + * nsIContent. + */ + const uint32_t CONTENT_NODE = 1 << 6; + + // 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 000000000..1e5693a28 --- /dev/null +++ b/xpcom/components/nsIComponentManager.idl @@ -0,0 +1,106 @@ +/* -*- 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; + +[scriptable, 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 aDelegate : Used for aggregation + * @param aIID : IID of interface requested + */ + void createInstance(in nsCIDRef aClass, + in nsISupports aDelegate, + 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 aDelegate : Used for aggregation + * @param aIID : IID of interface requested + */ + void createInstanceByContractID(in string aContractID, + in nsISupports aDelegate, + 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(); +}; + + +%{ 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 000000000..e12acdf25 --- /dev/null +++ b/xpcom/components/nsIComponentRegistrar.idl @@ -0,0 +1,163 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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; +interface nsISimpleEnumerator; + +[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); + + /** + * autoUnregister + * @status OBSOLETE: This method is no longer implemented, but preserved + * in this interface for binary compatibility with + * Mozilla 1.9.2. + */ + void autoUnregister(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); + + /** + * registerFactoryLocation + * @status OBSOLETE: This method is no longer implemented, but preserved + * in this interface for binary compatibility with + * Mozilla 1.9.2. + */ + void registerFactoryLocation(in nsCIDRef aClass, + in string aClassName, + in string aContractID, + in nsIFile aFile, + in string aLoaderStr, + in string aType); + + /** + * unregisterFactoryLocation + * @status OBSOLETE: This method is no longer implemented, but preserved + * in this interface for binary compatibility with + * Mozilla 1.9.2. + */ + void unregisterFactoryLocation(in nsCIDRef aClass, + in nsIFile aFile); + + /** + * 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); + + /** + * enumerateCIDs + * + * Enumerate the list of all registered CIDs. + * + * @return : enumerator for CIDs. Elements of the enumeration can be QI'ed + * for the nsISupportsID interface. From the nsISupportsID, you + * can obtain the actual CID. + */ + nsISimpleEnumerator enumerateCIDs(); + + /** + * enumerateContractIDs + * + * Enumerate the list of all registered ContractIDs. + * + * @return : enumerator for ContractIDs. Elements of the enumeration can be + * QI'ed for the nsISupportsCString interface. From the + * nsISupportsCString interface, you can obtain the actual + * Contract ID string. + */ + nsISimpleEnumerator enumerateContractIDs(); + + /** + * CIDToContractID + * @status OBSOLETE: This method is no longer implemented, but preserved + * in this interface for binary compatibility with + * Mozilla 1.9.2. + */ + string CIDToContractID(in nsCIDRef aClass); + + /** + * 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 000000000..54152976f --- /dev/null +++ b/xpcom/components/nsIFactory.idl @@ -0,0 +1,42 @@ +/* -*- 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(00000001-0000-0000-c000-000000000046)] +interface nsIFactory : nsISupports { + /** + * Creates an instance of a component. + * + * @param aOuter Pointer to a component that wishes to be aggregated + * in the resulting instance. This will be nullptr if no + * aggregation is requested. + * @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. + * @throws NS_ERROR_NO_AGGREGATION - if an 'outer' object is supplied, but the + * component is not aggregatable. + * NS_ERROR* - Method failure. + */ + void createInstance(in nsISupports aOuter, in nsIIDRef iid, + [retval, iid_is(iid)] out nsQIResult result); + + /** + * LockFactory provides the client a way to keep the component + * in memory until it is finished with it. The client can call + * LockFactory(PR_TRUE) to lock the factory and LockFactory(PR_FALSE) + * to release the factory. + * + * @param lock - Must be PR_TRUE or PR_FALSE + * @throws NS_ERROR* - Method failure. + */ + void lockFactory(in boolean lock); +}; diff --git a/xpcom/components/nsIModule.idl b/xpcom/components/nsIModule.idl new file mode 100644 index 000000000..4570a0199 --- /dev/null +++ b/xpcom/components/nsIModule.idl @@ -0,0 +1,82 @@ +/* -*- 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 nsIFile; +interface nsIComponentManager; + +/** + * The nsIModule interface. + */ + +[scriptable, uuid(7392D032-5371-11d3-994E-00805FD26FEE)] +interface nsIModule : nsISupports +{ + /** + * Object Instance Creation + * + * Obtains a Class Object from a nsIModule for a given CID and IID pair. + * This class object can either be query to a nsIFactory or a may be + * query to a nsIClassInfo. + * + * @param aCompMgr : The global component manager + * @param aClass : ClassID of object instance requested + * @param aIID : IID of interface requested + * + */ + void getClassObject(in nsIComponentManager aCompMgr, + in nsCIDRef aClass, + in nsIIDRef aIID, + [retval, iid_is(aIID)] out nsQIResult aResult); + + + /** + * One time registration callback + * + * When the nsIModule is discovered, this method will be + * called so that any setup registration can be preformed. + * + * @param aCompMgr : The global component manager + * @param aLocation : The location of the nsIModule on disk + * @param aLoaderStr: Opaque loader specific string + * @param aType : Loader Type being used to load this module + */ + void registerSelf(in nsIComponentManager aCompMgr, + in nsIFile aLocation, + in string aLoaderStr, + in string aType); + /** + * One time unregistration callback + * + * When the nsIModule is being unregistered, this method will be + * called so that any unregistration can be preformed + * + * @param aCompMgr : The global component manager + * @param aLocation : The location of the nsIModule on disk + * @param aLoaderStr : Opaque loader specific string + * + */ + void unregisterSelf(in nsIComponentManager aCompMgr, + in nsIFile aLocation, + in string aLoaderStr); + + /** + * Module load management + * + * @param aCompMgr : The global component manager + * + * @return indicates to the caller if the module can be unloaded. + * Returning PR_TRUE isn't a guarantee that the module will be + * unloaded. It constitues only willingness of the module to be + * unloaded. It is very important to ensure that no outstanding + * references to the module's code/data exist before returning + * PR_TRUE. + * Returning PR_FALSE guaratees that the module won't be unloaded. + */ + boolean canUnload(in nsIComponentManager aCompMgr); +}; + + diff --git a/xpcom/components/nsIServiceManager.idl b/xpcom/components/nsIServiceManager.idl new file mode 100644 index 000000000..45c1c3cc2 --- /dev/null +++ b/xpcom/components/nsIServiceManager.idl @@ -0,0 +1,72 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. 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. + */ + +[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 throw otherwise + * + * @param aClass or aContractID : aClass or aContractID of object + * instance requested + * @param aIID : IID of interface requested + * @throws NS_ERROR_SERVICE_NOT_AVAILABLE if the service hasn't been + * instantiated + * @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/nsNativeModuleLoader.cpp b/xpcom/components/nsNativeModuleLoader.cpp new file mode 100644 index 000000000..6452f5d1b --- /dev/null +++ b/xpcom/components/nsNativeModuleLoader.cpp @@ -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/. + * This Original Code has been modified by IBM Corporation. + * Modifications made by IBM described herein are + * Copyright (c) International Business Machines + * Corporation, 2000 + * + * Modifications to Mozilla code or documentation + * identified per MPL Section 3.3 + * + * Date Modified by Description of modification + * 04/20/2000 IBM Corp. Added PR_CALLBACK for Optlink use in OS2 + */ + +#include "nsNativeModuleLoader.h" + +#include "mozilla/Logging.h" +#include "prinit.h" +#include "prerror.h" + +#include "nsComponentManager.h" +#include "ManifestParser.h" // for LogMessage +#include "nsCRTGlue.h" +#include "nsThreadUtils.h" +#include "nsTraceRefcnt.h" + +#include "nsIFile.h" +#include "mozilla/WindowsDllBlocklist.h" + +#ifdef XP_WIN +#include +#endif + +#ifdef XP_MACOSX +#include +#endif + +#ifdef DEBUG +#define IMPLEMENT_BREAK_AFTER_LOAD +#endif + +using namespace mozilla; + +static LazyLogModule sNativeModuleLoaderLog("nsNativeModuleLoader"); +#define LOG(level, args) MOZ_LOG(sNativeModuleLoaderLog, level, args) + +nsresult +nsNativeModuleLoader::Init() +{ + MOZ_ASSERT(NS_IsMainThread(), "Startup not on main thread?"); + LOG(LogLevel::Debug, ("nsNativeModuleLoader::Init()")); + return NS_OK; +} + +class LoadModuleMainThreadRunnable : public Runnable +{ +public: + LoadModuleMainThreadRunnable(nsNativeModuleLoader* aLoader, + FileLocation& aFile) + : mManager(nsComponentManagerImpl::gComponentManager) + , mLoader(aLoader) + , mFile(aFile) + , mResult(nullptr) + { + } + + NS_IMETHOD Run() override + { + mResult = mLoader->LoadModule(mFile); + return NS_OK; + } + + RefPtr mManager; + nsNativeModuleLoader* mLoader; + FileLocation mFile; + const mozilla::Module* mResult; +}; + +const mozilla::Module* +nsNativeModuleLoader::LoadModule(FileLocation& aFile) +{ + if (aFile.IsZip()) { + NS_ERROR("Binary components cannot be loaded from JARs"); + return nullptr; + } + nsCOMPtr file = aFile.GetBaseFile(); + nsresult rv; + + if (!NS_IsMainThread()) { + // If this call is off the main thread, synchronously proxy it + // to the main thread. + RefPtr r = + new LoadModuleMainThreadRunnable(this, aFile); + NS_DispatchToMainThread(r, NS_DISPATCH_SYNC); + return r->mResult; + } + + nsCOMPtr hashedFile(do_QueryInterface(file)); + if (!hashedFile) { + NS_ERROR("nsIFile is not nsIHashable"); + return nullptr; + } + + nsAutoCString filePath; + file->GetNativePath(filePath); + + NativeLoadData data; + + if (mLibraries.Get(hashedFile, &data)) { + NS_ASSERTION(data.mModule, "Corrupt mLibraries hash"); + LOG(LogLevel::Debug, + ("nsNativeModuleLoader::LoadModule(\"%s\") - found in cache", + filePath.get())); + return data.mModule; + } + + // We haven't loaded this module before + { +#ifdef HAS_DLL_BLOCKLIST + AutoSetXPCOMLoadOnMainThread guard; +#endif + rv = file->Load(&data.mLibrary); + } + + if (NS_FAILED(rv)) { + char errorMsg[1024] = ""; + + if (PR_GetErrorTextLength() < (int)sizeof(errorMsg)) { + PR_GetErrorText(errorMsg); + } + + LogMessage("Failed to load native module at path '%s': (%lx) %s", + filePath.get(), rv, errorMsg); + + return nullptr; + } + +#ifdef IMPLEMENT_BREAK_AFTER_LOAD + nsAutoCString leafName; + file->GetNativeLeafName(leafName); + + char* env = getenv("XPCOM_BREAK_ON_LOAD"); + char* blist; + if (env && *env && (blist = strdup(env))) { + char* nextTok = blist; + while (char* token = NS_strtok(":", &nextTok)) { + if (leafName.Find(token, true) != kNotFound) { + NS_BREAK(); + } + } + + free(blist); + } +#endif + + void* module = PR_FindSymbol(data.mLibrary, "NSModule"); + if (!module) { + LogMessage("Native module at path '%s' doesn't export symbol `NSModule`.", + filePath.get()); + PR_UnloadLibrary(data.mLibrary); + return nullptr; + } + + data.mModule = *(mozilla::Module const* const*)module; + if (mozilla::Module::kVersion != data.mModule->mVersion) { + LogMessage("Native module at path '%s' is incompatible with this version of Firefox, has version %i, expected %i.", + filePath.get(), data.mModule->mVersion, + mozilla::Module::kVersion); + PR_UnloadLibrary(data.mLibrary); + return nullptr; + } + + mLibraries.Put(hashedFile, data); // infallible + return data.mModule; +} + +void +nsNativeModuleLoader::UnloadLibraries() +{ + MOZ_ASSERT(NS_IsMainThread(), "Shutdown not on main thread?"); + + for (auto iter = mLibraries.Iter(); !iter.Done(); iter.Next()) { + NativeLoadData& loadData = iter.Data(); + loadData.mModule = nullptr; + } + + for (auto iter = mLibraries.Iter(); !iter.Done(); iter.Next()) { + if (MOZ_LOG_TEST(sNativeModuleLoaderLog, LogLevel::Debug)) { + nsIHashable* hashedFile = iter.Key(); + nsCOMPtr file(do_QueryInterface(hashedFile)); + + nsAutoCString filePath; + file->GetNativePath(filePath); + + LOG(LogLevel::Debug, + ("nsNativeModuleLoader::UnloaderFunc(\"%s\")", filePath.get())); + } + +#ifdef NS_BUILD_REFCNT_LOGGING + nsTraceRefcnt::SetActivityIsLegal(false); +#endif + +#if 0 + // XXXbsmedberg: do this as soon as the static-destructor crash(es) + // are fixed + NativeLoadData& loadData = iter.Data(); + PRStatus ret = PR_UnloadLibrary(loadData.mLibrary); + NS_ASSERTION(ret == PR_SUCCESS, "Failed to unload library"); +#endif + +#ifdef NS_BUILD_REFCNT_LOGGING + nsTraceRefcnt::SetActivityIsLegal(true); +#endif + + iter.Remove(); + } +} diff --git a/xpcom/components/nsNativeModuleLoader.h b/xpcom/components/nsNativeModuleLoader.h new file mode 100644 index 000000000..b05a4a7a2 --- /dev/null +++ b/xpcom/components/nsNativeModuleLoader.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 nsNativeModuleLoader_h__ +#define nsNativeModuleLoader_h__ + +#include "nsDataHashtable.h" +#include "nsHashKeys.h" +#include "prlink.h" + +namespace mozilla { +class FileLocation; +} // namespace mozilla + +class nsNativeModuleLoader final +{ +public: + const mozilla::Module* LoadModule(mozilla::FileLocation& aFile); + + nsresult Init(); + + void UnloadLibraries(); + +private: + struct NativeLoadData + { + NativeLoadData() : mModule(nullptr), mLibrary(nullptr) {} + + const mozilla::Module* mModule; + PRLibrary* mLibrary; + }; + + nsDataHashtable mLibraries; +}; + +#endif /* nsNativeModuleLoader_h__ */ diff --git a/xpcom/doc/README b/xpcom/doc/README new file mode 100644 index 000000000..6817f789f --- /dev/null +++ b/xpcom/doc/README @@ -0,0 +1,11 @@ + + + + READ ME + + +

+ XPCOM documentation can be found at https://developer.mozilla.org/en-US/docs/XPCOM +

+ + diff --git a/xpcom/ds/IncrementalTokenizer.cpp b/xpcom/ds/IncrementalTokenizer.cpp new file mode 100644 index 000000000..429428516 --- /dev/null +++ b/xpcom/ds/IncrementalTokenizer.cpp @@ -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/. */ + +#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(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; + } + + nsCString::char_iterator 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; +} + +} // mozilla diff --git a/xpcom/ds/IncrementalTokenizer.h b/xpcom/ds/IncrementalTokenizer.h new file mode 100644 index 000000000..f93668e63 --- /dev/null +++ b/xpcom/ds/IncrementalTokenizer.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 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. + */ + MOZ_MUST_USE + 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; +}; + +} // mozilla + +#endif diff --git a/xpcom/ds/StickyTimeDuration.h b/xpcom/ds/StickyTimeDuration.h new file mode 100644 index 000000000..39a887dbc --- /dev/null +++ b/xpcom/ds/StickyTimeDuration.h @@ -0,0 +1,267 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 "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 || (!IsInfinite(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 || IsInfinite(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(IsInfinite(aB) == IsInfinite(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 000000000..66cc1ebb7 --- /dev/null +++ b/xpcom/ds/Tokenizer.cpp @@ -0,0 +1,738 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. 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 { + +static const char sWhitespaces[] = " \t"; + +Tokenizer::Tokenizer(const nsACString& aSource, + const char* aWhitespaces, + const char* aAdditionalWordChars) + : TokenizerBase(aWhitespaces, aAdditionalWordChars) +{ + mInputFinished = true; + aSource.BeginReading(mCursor); + mRecord = mRollback = mCursor; + aSource.EndReading(mEnd); +} + +Tokenizer::Tokenizer(const char* aSource, + const char* aWhitespaces, + const char* aAdditionalWordChars) + : Tokenizer(nsDependentCString(aSource), aWhitespaces, aAdditionalWordChars) +{ +} + +bool +Tokenizer::Next(Token& aToken) +{ + if (!HasInput()) { + mHasFailed = true; + return false; + } + + mRollback = mCursor; + mCursor = Parse(aToken); + + AssignFragment(aToken, mRollback, mCursor); + + mPastEof = aToken.Type() == TOKEN_EOF; + mHasFailed = false; + return true; +} + +bool +Tokenizer::Check(const TokenType aTokenType, Token& aResult) +{ + if (!HasInput()) { + mHasFailed = true; + return false; + } + + nsACString::const_char_iterator next = Parse(aResult); + if (aTokenType != aResult.Type()) { + mHasFailed = true; + return false; + } + + mRollback = mCursor; + mCursor = next; + + AssignFragment(aResult, mRollback, mCursor); + + mPastEof = aResult.Type() == TOKEN_EOF; + mHasFailed = false; + return true; +} + +bool +Tokenizer::Check(const Token& aToken) +{ + if (!HasInput()) { + mHasFailed = true; + return false; + } + + Token parsed; + nsACString::const_char_iterator next = Parse(parsed); + if (!aToken.Equals(parsed)) { + mHasFailed = true; + return false; + } + + mRollback = mCursor; + mCursor = next; + mPastEof = parsed.Type() == TOKEN_EOF; + mHasFailed = false; + return true; +} + +void +Tokenizer::SkipWhites(WhiteSkipping aIncludeNewLines) +{ + if (!CheckWhite() && (aIncludeNewLines == DONT_INCLUDE_NEW_LINE || !CheckEOL())) { + return; + } + + nsACString::const_char_iterator rollback = mRollback; + while (CheckWhite() || (aIncludeNewLines == INCLUDE_NEW_LINE && CheckEOL())) { + } + + mHasFailed = false; + mRollback = rollback; +} + +void +Tokenizer::SkipUntil(Token const& aToken) +{ + nsACString::const_char_iterator rollback = mCursor; + const Token eof = Token::EndOfFile(); + + Token t; + while (Next(t)) { + if (aToken.Equals(t) || eof.Equals(t)) { + Rollback(); + break; + } + } + + mRollback = rollback; +} + +bool +Tokenizer::CheckChar(bool (*aClassifier)(const char aChar)) +{ + if (!aClassifier) { + MOZ_ASSERT(false); + return false; + } + + if (!HasInput() || mCursor == mEnd) { + mHasFailed = true; + return false; + } + + if (!aClassifier(*mCursor)) { + mHasFailed = true; + return false; + } + + mRollback = mCursor; + ++mCursor; + mHasFailed = false; + return true; +} + +bool +Tokenizer::ReadChar(char* aValue) +{ + MOZ_RELEASE_ASSERT(aValue); + + Token t; + if (!Check(TOKEN_CHAR, t)) { + return false; + } + + *aValue = t.AsChar(); + return true; +} + +bool +Tokenizer::ReadChar(bool (*aClassifier)(const char aChar), char* aValue) +{ + MOZ_RELEASE_ASSERT(aValue); + + if (!CheckChar(aClassifier)) { + return false; + } + + *aValue = *mRollback; + return true; +} + +bool +Tokenizer::ReadWord(nsACString& aValue) +{ + Token t; + if (!Check(TOKEN_WORD, t)) { + return false; + } + + aValue.Assign(t.AsString()); + return true; +} + +bool +Tokenizer::ReadWord(nsDependentCSubstring& aValue) +{ + Token t; + if (!Check(TOKEN_WORD, t)) { + return false; + } + + aValue.Rebind(t.AsString().BeginReading(), t.AsString().Length()); + return true; +} + +bool +Tokenizer::ReadUntil(Token const& aToken, nsACString& aResult, ClaimInclusion aInclude) +{ + nsDependentCSubstring substring; + bool rv = ReadUntil(aToken, substring, aInclude); + aResult.Assign(substring); + return rv; +} + +bool +Tokenizer::ReadUntil(Token const& aToken, nsDependentCSubstring& aResult, ClaimInclusion aInclude) +{ + Record(); + nsACString::const_char_iterator rollback = mCursor; + + bool found = false; + Token t; + while (Next(t)) { + if (aToken.Equals(t)) { + found = true; + break; + } + } + + Claim(aResult, aInclude); + mRollback = rollback; + return found; +} + +void +Tokenizer::Rollback() +{ + MOZ_ASSERT(mCursor > mRollback || mPastEof, + "Tokenizer::Rollback() cannot use twice or before any parsing"); + + mPastEof = false; + mHasFailed = false; + mCursor = mRollback; +} + +void +Tokenizer::Record(ClaimInclusion aInclude) +{ + mRecord = aInclude == INCLUDE_LAST + ? mRollback + : mCursor; +} + +void +Tokenizer::Claim(nsACString& aResult, ClaimInclusion aInclusion) +{ + nsACString::const_char_iterator close = aInclusion == EXCLUDE_LAST + ? mRollback + : mCursor; + aResult.Assign(Substring(mRecord, close)); +} + +void +Tokenizer::Claim(nsDependentCSubstring& aResult, ClaimInclusion aInclusion) +{ + nsACString::const_char_iterator close = aInclusion == EXCLUDE_LAST + ? mRollback + : mCursor; + aResult.Rebind(mRecord, close - mRecord); +} + +// TokenizerBase + +TokenizerBase::TokenizerBase(const char* aWhitespaces, + const char* 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) +{ +} + +TokenizerBase::Token +TokenizerBase::AddCustomToken(const nsACString & aValue, + ECaseSensitivity aCaseInsensitivity, bool aEnabled) +{ + 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; +} + +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"); +} + +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"); +} + +void +TokenizerBase::SetTokenizingMode(Mode aMode) +{ + mMode = aMode; +} + +bool +TokenizerBase::HasFailed() const +{ + return mHasFailed; +} + +bool +TokenizerBase::HasInput() const +{ + return !mPastEof; +} + +nsACString::const_char_iterator +TokenizerBase::Parse(Token& aToken) const +{ + if (mCursor == mEnd) { + if (!mInputFinished) { + return mCursor; + } + + aToken = Token::EndOfFile(); + return mEnd; + } + + nsACString::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; + } + + nsACString::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 (strchr(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; +} + +bool +TokenizerBase::IsEnd(const nsACString::const_char_iterator& caret) const +{ + return caret == mEnd; +} + +bool +TokenizerBase::IsPending(const nsACString::const_char_iterator& caret) const +{ + return IsEnd(caret) && !mInputFinished; +} + +bool +TokenizerBase::IsWordFirst(const char aInput) const +{ + // TODO: make this fully work with unicode + return (ToLowerCase(static_cast(aInput)) != + ToUpperCase(static_cast(aInput))) || + '_' == aInput || + (mAdditionalWordChars ? !!strchr(mAdditionalWordChars, aInput) : false); +} + +bool +TokenizerBase::IsWord(const char aInput) const +{ + return IsWordFirst(aInput) || IsNumber(aInput); +} + +bool +TokenizerBase::IsNumber(const char aInput) const +{ + // TODO: are there unicode numbers? + return aInput >= '0' && aInput <= '9'; +} + +bool +TokenizerBase::IsCustom(const nsACString::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()); + } + + uint32_t inputLength = mEnd - caret; + if (aCustomToken.mCustom.Length() > inputLength) { + return false; + } + + nsDependentCSubstring inputFragment(caret, aCustomToken.mCustom.Length()); + if (aCustomToken.mCustomCaseInsensitivity == CASE_INSENSITIVE) { + return inputFragment.Equals(aCustomToken.mCustom, nsCaseInsensitiveUTF8StringComparator()); + } + return inputFragment.Equals(aCustomToken.mCustom); +} + +void TokenizerBase::AssignFragment(Token& aToken, + nsACString::const_char_iterator begin, + nsACString::const_char_iterator end) +{ + aToken.AssignFragment(begin, end); +} + +// TokenizerBase::Token + +TokenizerBase::Token::Token() + : mType(TOKEN_UNKNOWN) + , mChar(0) + , mInteger(0) + , mCustomCaseInsensitivity(CASE_SENSITIVE) + , mCustomEnabled(false) +{ +} + +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()); + } +} + +TokenizerBase::Token& +TokenizerBase::Token::operator=(const Token& aOther) +{ + 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; +} + +void +TokenizerBase::Token::AssignFragment(nsACString::const_char_iterator begin, + nsACString::const_char_iterator end) +{ + mFragment.Rebind(begin, end - begin); +} + +// static +TokenizerBase::Token +TokenizerBase::Token::Raw() +{ + Token t; + t.mType = TOKEN_RAW; + return t; +} + +// static +TokenizerBase::Token +TokenizerBase::Token::Word(const nsACString& aValue) +{ + Token t; + t.mType = TOKEN_WORD; + t.mWord.Rebind(aValue.BeginReading(), aValue.Length()); + return t; +} + +// static +TokenizerBase::Token +TokenizerBase::Token::Char(const char aValue) +{ + Token t; + t.mType = TOKEN_CHAR; + t.mChar = aValue; + return t; +} + +// static +TokenizerBase::Token +TokenizerBase::Token::Number(const uint64_t aValue) +{ + Token t; + t.mType = TOKEN_INTEGER; + t.mInteger = aValue; + return t; +} + +// static +TokenizerBase::Token +TokenizerBase::Token::Whitespace() +{ + Token t; + t.mType = TOKEN_WS; + t.mChar = '\0'; + return t; +} + +// static +TokenizerBase::Token +TokenizerBase::Token::NewLine() +{ + Token t; + t.mType = TOKEN_EOL; + return t; +} + +// static +TokenizerBase::Token +TokenizerBase::Token::EndOfFile() +{ + Token t; + t.mType = TOKEN_EOF; + return t; +} + +// static +TokenizerBase::Token +TokenizerBase::Token::Error() +{ + Token t; + t.mType = TOKEN_ERROR; + return t; +} + +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; + } +} + +char +TokenizerBase::Token::AsChar() const +{ + MOZ_ASSERT(mType == TOKEN_CHAR || mType == TOKEN_WS); + return mChar; +} + +nsDependentCSubstring +TokenizerBase::Token::AsString() const +{ + MOZ_ASSERT(mType == TOKEN_WORD); + return mWord; +} + +uint64_t +TokenizerBase::Token::AsInteger() const +{ + MOZ_ASSERT(mType == TOKEN_INTEGER); + return mInteger; +} + +} // mozilla diff --git a/xpcom/ds/Tokenizer.h b/xpcom/ds/Tokenizer.h new file mode 100644 index 000000000..b4aad9ed9 --- /dev/null +++ b/xpcom/ds/Tokenizer.h @@ -0,0 +1,446 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 "nsString.h" +#include "mozilla/CheckedInt.h" +#include "mozilla/UniquePtr.h" +#include "nsTArray.h" + +namespace mozilla { + +class TokenizerBase +{ +public: + /** + * 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 Tokenizer or are results of some of + * the Tokenizer's methods. + */ + class Token + { + TokenType mType; + nsDependentCSubstring mWord; + nsCString mCustom; + char 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. + nsDependentCSubstring mFragment; + + friend class TokenizerBase; + void AssignFragment(nsACString::const_char_iterator begin, + nsACString::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(const nsACString& aWord); + static Token Char(const char aChar); + static Token Number(const uint64_t 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; } + char AsChar() const; + nsDependentCSubstring AsString() const; + uint64_t AsInteger() const; + + nsDependentCSubstring 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 nsACString& aValue, ECaseSensitivity aCaseInsensitivity, bool aEnabled = true); + template + Token AddCustomToken(const char(&aValue)[N], ECaseSensitivity aCaseInsensitivity, bool aEnabled = true) + { + return AddCustomToken(nsDependentCSubstring(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. + */ + MOZ_MUST_USE bool HasFailed() const; + +protected: + explicit TokenizerBase(const char* aWhitespaces = nullptr, + const char* 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. + nsACString::const_char_iterator Parse(Token& aToken) const; + // Is read cursor at the end? + bool IsEnd(const nsACString::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-char token. + bool IsPending(const nsACString::const_char_iterator & caret) const; + // Is read cursor on a character that is a word start? + bool IsWordFirst(const char aInput) const; + // Is read cursor on a character that is an in-word letter? + bool IsWord(const char aInput) const; + // Is read cursor on a character that is a valid number? + // TODO - support multiple radix + bool IsNumber(const char aInput) const; + // Is equal to the given custom token? + bool IsCustom(const nsACString::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, + nsACString::const_char_iterator begin, + nsACString::const_char_iterator end); + + // 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 char* mWhitespaces; + // Additinal custom word characters + const char* mAdditionalWordChars; + + // All these point to the original buffer passed to the constructor or to the incremental + // buffer after FeedInput. + nsACString::const_char_iterator mCursor; // Position of the current (actually next to read) token start + nsACString::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. It doesn't allow any user dictionaries or + * user define token types. + * + * It is limited only to ASCII input for now. UTF-8 or any other input + * encoding must yet be implemented. + */ +class Tokenizer : public TokenizerBase +{ +public: + /** + * @param aSource + * The string to parse. + * IMPORTANT NOTE: Tokenizer doesn't ensure the input string buffer lifetime. + * It's up to the consumer to make sure the string's buffer outlives the Tokenizer! + * @param aWhitespaces + * If non-null Tokenizer 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 Tokenizer(const nsACString& aSource, + const char* aWhitespaces = nullptr, + const char* aAdditionalWordChars = nullptr); + explicit Tokenizer(const char* aSource, + const char* aWhitespaces = nullptr, + const char* 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. + */ + MOZ_MUST_USE + bool Next(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. + */ + MOZ_MUST_USE + bool Check(const TokenType aTokenType, 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. + */ + MOZ_MUST_USE + bool Check(const 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(Token const& aToken); + + // These are mostly shortcuts for the Check() methods above. + + /** + * Check whitespace character is present. + */ + MOZ_MUST_USE + bool CheckWhite() { return Check(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. + */ + MOZ_MUST_USE + bool CheckChar(const char aChar) { return Check(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. + */ + MOZ_MUST_USE + bool CheckChar(bool (*aClassifier)(const char aChar)); + /** + * Check for a whole expected word. + */ + MOZ_MUST_USE + bool CheckWord(const nsACString& aWord) { return Check(Token::Word(aWord)); } + /** + * Shortcut for literal const word check with compile time length calculation. + */ + template + MOZ_MUST_USE + bool CheckWord(const char (&aWord)[N]) { return Check(Token::Word(nsDependentCString(aWord, N - 1))); } + /** + * Checks \r, \n or \r\n. + */ + MOZ_MUST_USE + bool CheckEOL() { return Check(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. + */ + MOZ_MUST_USE + bool CheckEOF() { return Check(Token::EndOfFile()); } + + /** + * These are shortcuts to obtain the value immediately when the token type matches. + */ + MOZ_MUST_USE bool ReadChar(char* aValue); + MOZ_MUST_USE bool ReadChar(bool (*aClassifier)(const char aChar), + char* aValue); + MOZ_MUST_USE bool ReadWord(nsACString& aValue); + MOZ_MUST_USE bool ReadWord(nsDependentCSubstring& 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 + MOZ_MUST_USE bool ReadInteger(T* aValue) + { + MOZ_RELEASE_ASSERT(aValue); + + nsACString::const_char_iterator rollback = mRollback; + nsACString::const_char_iterator cursor = mCursor; + Token t; + if (!Check(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; + mCursor = cursor; + mHasFailed = true; + return false; + } + + *aValue = checked.value(); + return true; + } + + /** + * Returns the read cursor position back as it was before the last call of any parsing + * method of Tokenizer (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 Tokenizer. + */ + 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 Tokenizer 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(nsACString& aResult, ClaimInclusion aInclude = EXCLUDE_LAST); + void Claim(nsDependentCSubstring& 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. + */ + MOZ_MUST_USE bool ReadUntil(Token const& aToken, nsDependentCSubstring& aResult, + ClaimInclusion aInclude = EXCLUDE_LAST); + MOZ_MUST_USE bool ReadUntil(Token const& aToken, nsACString& aResult, + ClaimInclusion aInclude = EXCLUDE_LAST); + +protected: + // All these point to the original buffer passed to the Tokenizer's constructor + nsACString::const_char_iterator mRecord; // Position where the recorded sub-string for Claim() is + nsACString::const_char_iterator mRollback; // Position of the previous token start + +private: + Tokenizer() = delete; + Tokenizer(const Tokenizer&) = delete; + Tokenizer(Tokenizer&&) = delete; + Tokenizer(const Tokenizer&&) = delete; + Tokenizer &operator=(const Tokenizer&) = delete; +}; + +} // mozilla + +#endif // Tokenizer_h__ diff --git a/xpcom/ds/moz.build b/xpcom/ds/moz.build new file mode 100644 index 000000000..e12f1c3dd --- /dev/null +++ b/xpcom/ds/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/. + +XPIDL_SOURCES += [ + 'nsIArray.idl', + 'nsIArrayExtensions.idl', + 'nsIAtom.idl', + 'nsIAtomService.idl', + 'nsICollection.idl', + 'nsIEnumerator.idl', + 'nsIHashable.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', + 'nsISupportsArray.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' + +EXPORTS += [ + 'nsArray.h', + 'nsAtomService.h', + 'nsCharSeparatedTokenizer.h', + 'nsCheapSets.h', + 'nsCRT.h', + 'nsExpirationTracker.h', + 'nsHashPropertyBag.h', + 'nsMathUtils.h', + 'nsStaticAtom.h', + 'nsStaticNameTable.h', + 'nsStringEnumerator.h', + 'nsSupportsArray.h', + 'nsSupportsPrimitives.h', + 'nsVariant.h', + 'nsWhitespaceTokenizer.h', +] + +EXPORTS.mozilla += [ + 'IncrementalTokenizer.h', + 'StickyTimeDuration.h', + 'Tokenizer.h', +] + +UNIFIED_SOURCES += [ + 'IncrementalTokenizer.cpp', + 'nsArray.cpp', + 'nsAtomService.cpp', + 'nsAtomTable.cpp', + 'nsCRT.cpp', + 'nsHashPropertyBag.cpp', + 'nsINIParserImpl.cpp', + 'nsObserverList.cpp', + 'nsObserverService.cpp', + 'nsProperties.cpp', + 'nsStringEnumerator.cpp', + 'nsSupportsArray.cpp', + 'nsSupportsArrayEnumerator.cpp', + 'nsSupportsPrimitives.cpp', + 'nsVariant.cpp', + 'Tokenizer.cpp', +] + +# These two files cannot be built in unified mode because they use the +# PL_ARENA_CONST_ALIGN_MASK macro with plarena.h. +SOURCES += [ + 'nsPersistentProperties.cpp', + 'nsStaticNameTable.cpp', +] + +EXTRA_COMPONENTS += [ + 'nsINIProcessor.js', + 'nsINIProcessor.manifest', +] + +LOCAL_INCLUDES += [ + '../io', +] + +FINAL_LIBRARY = 'xul' diff --git a/xpcom/ds/nsArray.cpp b/xpcom/ds/nsArray.cpp new file mode 100644 index 000000000..d137a14a5 --- /dev/null +++ b/xpcom/ds/nsArray.cpp @@ -0,0 +1,242 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. 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 "nsIWeakReference.h" +#include "nsIWeakReferenceUtils.h" +#include "nsThreadUtils.h" + +// used by IndexOf() +struct MOZ_STACK_CLASS findIndexOfClosure +{ + // This is only used for pointer comparison, so we can just use a void*. + void* targetElement; + uint32_t startIndex; + uint32_t resultIndex; +}; + +static bool FindElementCallback(void* aElement, void* aClosure); + +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) +{ + // optimize for the common case by forwarding to mArray + if (aStartIndex == 0) { + uint32_t idx = mArray.IndexOf(aElement); + if (idx == UINT32_MAX) { + return NS_ERROR_FAILURE; + } + + *aResult = idx; + return NS_OK; + } + + findIndexOfClosure closure = { aElement, aStartIndex, 0 }; + bool notFound = mArray.EnumerateForwards(FindElementCallback, &closure); + if (notFound) { + return NS_ERROR_FAILURE; + } + + *aResult = closure.resultIndex; + return NS_OK; +} + +NS_IMETHODIMP +nsArrayBase::Enumerate(nsISimpleEnumerator** aResult) +{ + return NS_NewArrayEnumerator(aResult, static_cast(this)); +} + +// nsIMutableArray implementation + +NS_IMETHODIMP +nsArrayBase::AppendElement(nsISupports* aElement, bool aWeak) +{ + bool result; + if (aWeak) { + nsCOMPtr elementRef = do_GetWeakReference(aElement); + NS_ASSERTION(elementRef, + "AppendElement: Trying to use weak references on an object that doesn't support it"); + if (!elementRef) { + return NS_ERROR_FAILURE; + } + result = mArray.AppendObject(elementRef); + } + + else { + // add the object directly + 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 aWeak) +{ + nsCOMPtr elementRef; + if (aWeak) { + elementRef = do_GetWeakReference(aElement); + NS_ASSERTION(elementRef, + "InsertElementAt: Trying to use weak references on an object that doesn't support it"); + if (!elementRef) { + return NS_ERROR_FAILURE; + } + } else { + elementRef = aElement; + } + bool result = mArray.InsertObjectAt(elementRef, aIndex); + return result ? NS_OK : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsArrayBase::ReplaceElementAt(nsISupports* aElement, uint32_t aIndex, bool aWeak) +{ + nsCOMPtr elementRef; + if (aWeak) { + elementRef = do_GetWeakReference(aElement); + NS_ASSERTION(elementRef, + "ReplaceElementAt: Trying to use weak references on an object that doesn't support it"); + if (!elementRef) { + return NS_ERROR_FAILURE; + } + } else { + elementRef = aElement; + } + mArray.ReplaceObjectAt(elementRef, 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; +} + +// +// static helper routines +// +bool +FindElementCallback(void* aElement, void* aClosure) +{ + findIndexOfClosure* closure = static_cast(aClosure); + nsISupports* element = static_cast(aElement); + + // don't start searching until we're past the startIndex + if (closure->resultIndex >= closure->startIndex && + element == closure->targetElement) { + return false; // stop! We found it + } + closure->resultIndex++; + + return true; +} + +nsresult +nsArrayBase::XPCOMConstructor(nsISupports* aOuter, const nsIID& aIID, + void** aResult) +{ + if (aOuter) { + return NS_ERROR_NO_AGGREGATION; + } + + 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 000000000..0cda23487 --- /dev/null +++ b/xpcom/ds/nsArray.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 nsArray_h__ +#define nsArray_h__ + +#include "nsIArrayExtensions.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(nsISupports* aOuter, const nsIID& aIID, + void** aResult); +protected: + nsArrayBase() {} + 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() {} +}; + +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() {} +}; + +#endif diff --git a/xpcom/ds/nsAtomService.cpp b/xpcom/ds/nsAtomService.cpp new file mode 100644 index 000000000..7785ca2e4 --- /dev/null +++ b/xpcom/ds/nsAtomService.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 "nsAtomService.h" +#include "nsIAtom.h" + +NS_IMPL_ISUPPORTS(nsAtomService, nsIAtomService) + +nsAtomService::nsAtomService() +{ +} + +nsresult +nsAtomService::GetAtom(const nsAString& aString, nsIAtom** aResult) +{ + *aResult = NS_Atomize(aString).take(); + if (!*aResult) { + return NS_ERROR_OUT_OF_MEMORY; + } + + return NS_OK; +} diff --git a/xpcom/ds/nsAtomService.h b/xpcom/ds/nsAtomService.h new file mode 100644 index 000000000..7a1bcf2cd --- /dev/null +++ b/xpcom/ds/nsAtomService.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 __nsAtomService_h +#define __nsAtomService_h + +#include "nsIAtomService.h" +#include "mozilla/Attributes.h" + +class nsAtomService final : public nsIAtomService +{ +public: + nsAtomService(); + NS_DECL_THREADSAFE_ISUPPORTS + + NS_DECL_NSIATOMSERVICE + +private: + ~nsAtomService() {} +}; + +#endif diff --git a/xpcom/ds/nsAtomTable.cpp b/xpcom/ds/nsAtomTable.cpp new file mode 100644 index 000000000..3dd3bd36c --- /dev/null +++ b/xpcom/ds/nsAtomTable.cpp @@ -0,0 +1,736 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. 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/Mutex.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/Sprintf.h" +#include "mozilla/Unused.h" + +#include "nsAtomTable.h" +#include "nsStaticAtom.h" +#include "nsString.h" +#include "nsCRT.h" +#include "PLDHashTable.h" +#include "prenv.h" +#include "nsThreadUtils.h" +#include "nsDataHashtable.h" +#include "nsHashKeys.h" +#include "nsAutoPtr.h" +#include "nsUnicharUtils.h" +#include "nsPrintfCString.h" + +// There are two kinds of atoms handled by this module. +// +// - DynamicAtom: the atom itself is heap allocated, as is the nsStringBuffer it +// points to. |gAtomTable| holds weak references to them DynamicAtoms. When +// the refcount of a DynamicAtom drops to zero, we increment a static counter. +// When that counter reaches a certain threshold, we iterate over the atom +// table, removing and deleting DynamicAtoms with refcount zero. This allows +// us to avoid acquiring the atom table lock during normal refcounting. +// +// - StaticAtom: the atom itself is heap allocated, but it points to a static +// nsStringBuffer. |gAtomTable| effectively owns StaticAtoms, because such +// atoms ignore all AddRef/Release calls, which ensures they stay alive until +// |gAtomTable| itself is destroyed whereupon they are explicitly deleted. +// +// Note that gAtomTable is used on multiple threads, and callers must +// acquire gAtomTableLock before touching it. + +using namespace mozilla; + +//---------------------------------------------------------------------- + +class CheckStaticAtomSizes +{ + CheckStaticAtomSizes() + { + static_assert((sizeof(nsFakeStringBuffer<1>().mRefCnt) == + sizeof(nsStringBuffer().mRefCount)) && + (sizeof(nsFakeStringBuffer<1>().mSize) == + sizeof(nsStringBuffer().mStorageSize)) && + (offsetof(nsFakeStringBuffer<1>, mRefCnt) == + offsetof(nsStringBuffer, mRefCount)) && + (offsetof(nsFakeStringBuffer<1>, mSize) == + offsetof(nsStringBuffer, mStorageSize)) && + (offsetof(nsFakeStringBuffer<1>, mStringData) == + sizeof(nsStringBuffer)), + "mocked-up strings' representations should be compatible"); + } +}; + +//---------------------------------------------------------------------- + +static Atomic gUnusedAtomCount(0); + +class DynamicAtom final : public nsIAtom +{ +public: + static already_AddRefed Create(const nsAString& aString, uint32_t aHash) + { + // The refcount is appropriately initialized in the constructor. + return dont_AddRef(new DynamicAtom(aString, aHash)); + } + + static void GCAtomTable(); + + enum class GCKind { + RegularOperation, + Shutdown, + }; + + static void GCAtomTableLocked(const MutexAutoLock& aProofOfLock, + GCKind aKind); + +private: + DynamicAtom(const nsAString& aString, uint32_t aHash) + : mRefCnt(1) + { + mLength = aString.Length(); + mIsStatic = false; + RefPtr buf = nsStringBuffer::FromString(aString); + if (buf) { + mString = static_cast(buf->Data()); + } else { + const size_t size = (mLength + 1) * sizeof(char16_t); + buf = nsStringBuffer::Alloc(size); + if (MOZ_UNLIKELY(!buf)) { + // We OOM because atom allocations should be small and it's hard to + // handle them more gracefully in a constructor. + NS_ABORT_OOM(size); + } + mString = static_cast(buf->Data()); + CopyUnicodeTo(aString, 0, mString, mLength); + mString[mLength] = char16_t(0); + } + + mHash = aHash; + MOZ_ASSERT(mHash == HashString(mString, mLength)); + + NS_ASSERTION(mString[mLength] == char16_t(0), "null terminated"); + NS_ASSERTION(buf && buf->StorageSize() >= (mLength + 1) * sizeof(char16_t), + "enough storage"); + NS_ASSERTION(Equals(aString), "correct data"); + + // Take ownership of buffer + mozilla::Unused << buf.forget(); + } + +private: + // We don't need a virtual destructor because we always delete via a + // DynamicAtom* pointer (in GCAtomTable()), not an nsIAtom* pointer. + ~DynamicAtom(); + +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIATOM +}; + +class StaticAtom final : public nsIAtom +{ +public: + StaticAtom(nsStringBuffer* aStringBuffer, uint32_t aLength, uint32_t aHash) + { + mLength = aLength; + mIsStatic = true; + mString = static_cast(aStringBuffer->Data()); + // Technically we could currently avoid doing this addref by instead making + // the static atom buffers have an initial refcount of 2. + aStringBuffer->AddRef(); + + mHash = aHash; + MOZ_ASSERT(mHash == HashString(mString, mLength)); + + MOZ_ASSERT(mString[mLength] == char16_t(0), "null terminated"); + MOZ_ASSERT(aStringBuffer && + aStringBuffer->StorageSize() == (mLength + 1) * sizeof(char16_t), + "correct storage"); + } + + // We don't need a virtual destructor because we always delete via a + // StaticAtom* pointer (in AtomTableClearEntry()), not an nsIAtom* pointer. + ~StaticAtom() {} + + NS_DECL_ISUPPORTS + NS_DECL_NSIATOM +}; + +NS_IMPL_QUERY_INTERFACE(StaticAtom, nsIAtom) + +NS_IMETHODIMP_(MozExternalRefCountType) +StaticAtom::AddRef() +{ + return 2; +} + +NS_IMETHODIMP_(MozExternalRefCountType) +StaticAtom::Release() +{ + return 1; +} + +NS_IMETHODIMP +DynamicAtom::ScriptableToString(nsAString& aBuf) +{ + nsStringBuffer::FromData(mString)->ToString(mLength, aBuf); + return NS_OK; +} + +NS_IMETHODIMP +StaticAtom::ScriptableToString(nsAString& aBuf) +{ + nsStringBuffer::FromData(mString)->ToString(mLength, aBuf); + return NS_OK; +} + +NS_IMETHODIMP +DynamicAtom::ToUTF8String(nsACString& aBuf) +{ + CopyUTF16toUTF8(nsDependentString(mString, mLength), aBuf); + return NS_OK; +} + +NS_IMETHODIMP +StaticAtom::ToUTF8String(nsACString& aBuf) +{ + CopyUTF16toUTF8(nsDependentString(mString, mLength), aBuf); + return NS_OK; +} + +NS_IMETHODIMP +DynamicAtom::ScriptableEquals(const nsAString& aString, bool* aResult) +{ + *aResult = aString.Equals(nsDependentString(mString, mLength)); + return NS_OK; +} + +NS_IMETHODIMP +StaticAtom::ScriptableEquals(const nsAString& aString, bool* aResult) +{ + *aResult = aString.Equals(nsDependentString(mString, mLength)); + return NS_OK; +} + +NS_IMETHODIMP_(size_t) +DynamicAtom::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) +{ + size_t n = aMallocSizeOf(this); + n += nsStringBuffer::FromData(mString)->SizeOfIncludingThisIfUnshared( + aMallocSizeOf); + return n; +} + +NS_IMETHODIMP_(size_t) +StaticAtom::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) +{ + size_t n = aMallocSizeOf(this); + // Don't measure the string buffer pointed to by the StaticAtom because it's + // in static memory. + return n; +} + +//---------------------------------------------------------------------- + +/** + * The shared hash table for atom lookups. + * + * Callers must hold gAtomTableLock before manipulating the table. + */ +static PLDHashTable* gAtomTable; +static Mutex* gAtomTableLock; + +struct AtomTableKey +{ + AtomTableKey(const char16_t* aUTF16String, uint32_t aLength, uint32_t aHash) + : mUTF16String(aUTF16String) + , mUTF8String(nullptr) + , mLength(aLength) + , mHash(aHash) + { + MOZ_ASSERT(mHash == HashString(mUTF16String, mLength)); + } + + AtomTableKey(const char* aUTF8String, uint32_t aLength, uint32_t aHash) + : mUTF16String(nullptr) + , mUTF8String(aUTF8String) + , mLength(aLength) + , mHash(aHash) + { + mozilla::DebugOnly err; + MOZ_ASSERT(aHash == HashUTF8AsUTF16(mUTF8String, mLength, &err)); + } + + AtomTableKey(const char16_t* aUTF16String, uint32_t aLength, + uint32_t* aHashOut) + : mUTF16String(aUTF16String) + , mUTF8String(nullptr) + , mLength(aLength) + { + mHash = HashString(mUTF16String, mLength); + *aHashOut = mHash; + } + + AtomTableKey(const char* aUTF8String, uint32_t aLength, uint32_t* aHashOut) + : mUTF16String(nullptr) + , mUTF8String(aUTF8String) + , mLength(aLength) + { + bool err; + mHash = HashUTF8AsUTF16(mUTF8String, mLength, &err); + if (err) { + mUTF8String = nullptr; + mLength = 0; + mHash = 0; + } + *aHashOut = mHash; + } + + const char16_t* mUTF16String; + const char* mUTF8String; + uint32_t mLength; + uint32_t mHash; +}; + +struct AtomTableEntry : public PLDHashEntryHdr +{ + // These references are either to DynamicAtoms, in which case they are + // non-owning, or they are to StaticAtoms, which aren't really refcounted. + // See the comment at the top of this file for more details. + nsIAtom* MOZ_NON_OWNING_REF mAtom; +}; + +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) { + return + CompareUTF8toUTF16(nsDependentCSubstring(k->mUTF8String, + k->mUTF8String + k->mLength), + nsDependentAtomString(he->mAtom)) == 0; + } + + uint32_t length = he->mAtom->GetLength(); + if (length != k->mLength) { + return false; + } + + return memcmp(he->mAtom->GetUTF16String(), + k->mUTF16String, length * sizeof(char16_t)) == 0; +} + +static void +AtomTableClearEntry(PLDHashTable* aTable, PLDHashEntryHdr* aEntry) +{ + auto entry = static_cast(aEntry); + nsIAtom* atom = entry->mAtom; + if (atom->IsStaticAtom()) { + // This case -- when the entry being cleared holds a StaticAtom -- only + // occurs when gAtomTable is destroyed, whereupon all StaticAtoms within it + // must be explicitly deleted. The cast is required because StaticAtom + // doesn't have a virtual destructor. + delete static_cast(atom); + } +} + +static void +AtomTableInitEntry(PLDHashEntryHdr* aEntry, const void* aKey) +{ + static_cast(aEntry)->mAtom = nullptr; +} + +static const PLDHashTableOps AtomTableOps = { + AtomTableGetHash, + AtomTableMatchKey, + PLDHashTable::MoveEntryStub, + AtomTableClearEntry, + AtomTableInitEntry +}; + +//---------------------------------------------------------------------- + +void +DynamicAtom::GCAtomTable() +{ + MutexAutoLock lock(*gAtomTableLock); + GCAtomTableLocked(lock, GCKind::RegularOperation); +} + +void +DynamicAtom::GCAtomTableLocked(const MutexAutoLock& aProofOfLock, + GCKind aKind) +{ + uint32_t removedCount = 0; // Use a non-atomic temporary for cheaper increments. + nsAutoCString nonZeroRefcountAtoms; + uint32_t nonZeroRefcountAtomsCount = 0; + for (auto i = gAtomTable->Iter(); !i.Done(); i.Next()) { + auto entry = static_cast(i.Get()); + if (entry->mAtom->IsStaticAtom()) { + continue; + } + + auto atom = static_cast(entry->mAtom); + if (atom->mRefCnt == 0) { + i.Remove(); + delete atom; + ++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_LITERAL_CSTRING(",") + name; + } else if (nonZeroRefcountAtomsCount == 20) { + nonZeroRefcountAtoms += NS_LITERAL_CSTRING(",..."); + } + nonZeroRefcountAtomsCount++; + } +#endif + + } + if (nonZeroRefcountAtomsCount) { + nsPrintfCString msg("%d dynamic atom(s) with non-zero refcount: %s", + nonZeroRefcountAtomsCount, nonZeroRefcountAtoms.get()); + NS_ASSERTION(nonZeroRefcountAtomsCount == 0, msg.get()); + } + + // During the course of this function, the atom table is locked. This means + // that, barring refcounting bugs in consumers, an atom can never go from + // refcount == 0 to refcount != 0 during a GC. However, an atom _can_ go from + // refcount != 0 to refcount == 0 if a Release() occurs in parallel with GC. + // This means that we cannot assert that gUnusedAtomCount == removedCount, and + // thus that there are no unused atoms at the end of a GC. We can and do, + // however, assert this after the last GC at shutdown. + if (aKind == GCKind::RegularOperation) { + MOZ_ASSERT(removedCount <= gUnusedAtomCount); + } else { + // Complain if somebody adds new GCKind enums. + MOZ_ASSERT(aKind == GCKind::Shutdown); + // Our unused atom count should be accurate. + MOZ_ASSERT(removedCount == gUnusedAtomCount); + } + + gUnusedAtomCount -= removedCount; +} + +NS_IMPL_QUERY_INTERFACE(DynamicAtom, nsIAtom) + +NS_IMETHODIMP_(MozExternalRefCountType) +DynamicAtom::AddRef(void) +{ + nsrefcnt count = ++mRefCnt; + if (count == 1) { + MOZ_ASSERT(gUnusedAtomCount > 0); + gUnusedAtomCount--; + } + return count; +} + +#ifdef DEBUG +// We set a lower GC threshold for atoms in debug builds so that we exercise +// the GC machinery more often. +static const uint32_t kAtomGCThreshold = 20; +#else +static const uint32_t kAtomGCThreshold = 10000; +#endif + +NS_IMETHODIMP_(MozExternalRefCountType) +DynamicAtom::Release(void) +{ + MOZ_ASSERT(mRefCnt > 0); + nsrefcnt count = --mRefCnt; + if (count == 0) { + if (++gUnusedAtomCount >= kAtomGCThreshold) { + GCAtomTable(); + } + } + + return count; +} + +DynamicAtom::~DynamicAtom() +{ + nsStringBuffer::FromData(mString)->Release(); +} + +//---------------------------------------------------------------------- + +class StaticAtomEntry : public PLDHashEntryHdr +{ +public: + typedef const nsAString& KeyType; + typedef const nsAString* KeyTypePointer; + + explicit StaticAtomEntry(KeyTypePointer aKey) {} + StaticAtomEntry(const StaticAtomEntry& aOther) : mAtom(aOther.mAtom) {} + + // We do not delete the atom because that's done when gAtomTable is + // destroyed -- which happens immediately after gStaticAtomTable is destroyed + // -- in NS_PurgeAtomTable(). + ~StaticAtomEntry() {} + + bool KeyEquals(KeyTypePointer aKey) const + { + return mAtom->Equals(*aKey); + } + + static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; } + static PLDHashNumber HashKey(KeyTypePointer aKey) + { + return HashString(*aKey); + } + + enum { ALLOW_MEMMOVE = true }; + + // StaticAtoms aren't really refcounted. Because these entries live in a + // global hashtable, this reference is essentially owning. + StaticAtom* MOZ_OWNING_REF mAtom; +}; + +/** + * A hashtable of static atoms that existed at app startup. This hashtable + * helps nsHtml5AtomTable. + */ +typedef nsTHashtable StaticAtomTable; +static StaticAtomTable* gStaticAtomTable = nullptr; + +/** + * Whether it is still OK to add atoms to gStaticAtomTable. + */ +static bool gStaticAtomTableSealed = false; + +// The atom table very quickly gets 10,000+ entries in it (or even 100,000+). +// But choosing the best initial length has some subtleties: we add ~2700 +// static atoms to the table at start-up, and then we start adding and removing +// dynamic atoms. If we make the table too big to start with, when the first +// dynamic atom gets removed the load factor will be < 25% and so we will +// shrink it to 4096 entries. +// +// By choosing an initial length of 4096, we get an initial capacity of 8192. +// That's the biggest initial capacity that will let us be > 25% full when the +// first dynamic atom is removed (when the count is ~2700), thus avoiding any +// shrinking. +#define ATOM_HASHTABLE_INITIAL_LENGTH 4096 + +void +NS_InitAtomTable() +{ + MOZ_ASSERT(!gAtomTable); + gAtomTable = new PLDHashTable(&AtomTableOps, sizeof(AtomTableEntry), + ATOM_HASHTABLE_INITIAL_LENGTH); + gAtomTableLock = new Mutex("Atom Table Lock"); +} + +void +NS_ShutdownAtomTable() +{ + delete gStaticAtomTable; + gStaticAtomTable = nullptr; + +#ifdef NS_FREE_PERMANENT_DATA + // Do a final GC to satisfy leak checking. We skip this step in release + // builds. + { + MutexAutoLock lock(*gAtomTableLock); + DynamicAtom::GCAtomTableLocked(lock, DynamicAtom::GCKind::Shutdown); + } +#endif + + delete gAtomTable; + gAtomTable = nullptr; + delete gAtomTableLock; + gAtomTableLock = nullptr; +} + +void +NS_SizeOfAtomTablesIncludingThis(MallocSizeOf aMallocSizeOf, + size_t* aMain, size_t* aStatic) +{ + MutexAutoLock lock(*gAtomTableLock); + *aMain = gAtomTable->ShallowSizeOfIncludingThis(aMallocSizeOf); + for (auto iter = gAtomTable->Iter(); !iter.Done(); iter.Next()) { + auto entry = static_cast(iter.Get()); + *aMain += entry->mAtom->SizeOfIncludingThis(aMallocSizeOf); + } + + // The atoms pointed to by gStaticAtomTable are also pointed to by gAtomTable, + // and they're measured by the loop above. So no need to measure them here. + *aStatic = gStaticAtomTable + ? gStaticAtomTable->ShallowSizeOfIncludingThis(aMallocSizeOf) + : 0; +} + +static inline AtomTableEntry* +GetAtomHashEntry(const char* aString, uint32_t aLength, uint32_t* aHashOut) +{ + gAtomTableLock->AssertCurrentThreadOwns(); + AtomTableKey key(aString, aLength, aHashOut); + // This is an infallible add. + return static_cast(gAtomTable->Add(&key)); +} + +static inline AtomTableEntry* +GetAtomHashEntry(const char16_t* aString, uint32_t aLength, uint32_t* aHashOut) +{ + gAtomTableLock->AssertCurrentThreadOwns(); + AtomTableKey key(aString, aLength, aHashOut); + // This is an infallible add. + return static_cast(gAtomTable->Add(&key)); +} + +void +RegisterStaticAtoms(const nsStaticAtom* aAtoms, uint32_t aAtomCount) +{ + MutexAutoLock lock(*gAtomTableLock); + + MOZ_RELEASE_ASSERT(!gStaticAtomTableSealed, + "Atom table has already been sealed!"); + + if (!gStaticAtomTable) { + gStaticAtomTable = new StaticAtomTable(); + } + + for (uint32_t i = 0; i < aAtomCount; ++i) { + nsStringBuffer* stringBuffer = aAtoms[i].mStringBuffer; + nsIAtom** atomp = aAtoms[i].mAtom; + + MOZ_ASSERT(nsCRT::IsAscii(static_cast(stringBuffer->Data()))); + + uint32_t stringLen = stringBuffer->StorageSize() / sizeof(char16_t) - 1; + + uint32_t hash; + AtomTableEntry* he = + GetAtomHashEntry(static_cast(stringBuffer->Data()), + stringLen, &hash); + + nsIAtom* atom = he->mAtom; + if (atom) { + // Disallow creating a dynamic atom, and then later, while the + // dynamic atom is still alive, registering that same atom as a + // static atom. It causes subtle bugs, and we're programming in + // C++ here, not Smalltalk. + if (!atom->IsStaticAtom()) { + nsAutoCString name; + atom->ToUTF8String(name); + MOZ_CRASH_UNSAFE_PRINTF( + "Static atom registration for %s should be pushed back", name.get()); + } + } else { + atom = new StaticAtom(stringBuffer, stringLen, hash); + he->mAtom = atom; + } + *atomp = atom; + + if (!gStaticAtomTableSealed) { + StaticAtomEntry* entry = + gStaticAtomTable->PutEntry(nsDependentAtomString(atom)); + MOZ_ASSERT(atom->IsStaticAtom()); + entry->mAtom = static_cast(atom); + } + } +} + +already_AddRefed +NS_Atomize(const char* aUTF8String) +{ + return NS_Atomize(nsDependentCString(aUTF8String)); +} + +already_AddRefed +NS_Atomize(const nsACString& aUTF8String) +{ + MutexAutoLock lock(*gAtomTableLock); + uint32_t hash; + AtomTableEntry* he = GetAtomHashEntry(aUTF8String.Data(), + aUTF8String.Length(), + &hash); + + if (he->mAtom) { + nsCOMPtr atom = he->mAtom; + + return atom.forget(); + } + + // This results in an extra addref/release of the nsStringBuffer. + // Unfortunately there doesn't seem to be any APIs to avoid that. + // Actually, now there is, sort of: ForgetSharedBuffer. + nsString str; + CopyUTF8toUTF16(aUTF8String, str); + RefPtr atom = DynamicAtom::Create(str, hash); + + he->mAtom = atom; + + return atom.forget(); +} + +already_AddRefed +NS_Atomize(const char16_t* aUTF16String) +{ + return NS_Atomize(nsDependentString(aUTF16String)); +} + +already_AddRefed +NS_Atomize(const nsAString& aUTF16String) +{ + MutexAutoLock lock(*gAtomTableLock); + uint32_t hash; + AtomTableEntry* he = GetAtomHashEntry(aUTF16String.Data(), + aUTF16String.Length(), + &hash); + + if (he->mAtom) { + nsCOMPtr atom = he->mAtom; + + return atom.forget(); + } + + RefPtr atom = DynamicAtom::Create(aUTF16String, hash); + he->mAtom = atom; + + return atom.forget(); +} + +nsrefcnt +NS_GetNumberOfAtoms(void) +{ + DynamicAtom::GCAtomTable(); // Trigger a GC so that we return a deterministic result. + MutexAutoLock lock(*gAtomTableLock); + return gAtomTable->EntryCount(); +} + +nsIAtom* +NS_GetStaticAtom(const nsAString& aUTF16String) +{ + NS_PRECONDITION(gStaticAtomTable, "Static atom table not created yet."); + NS_PRECONDITION(gStaticAtomTableSealed, "Static atom table not sealed yet."); + StaticAtomEntry* entry = gStaticAtomTable->GetEntry(aUTF16String); + return entry ? entry->mAtom : nullptr; +} + +void +NS_SealStaticAtomTable() +{ + gStaticAtomTableSealed = true; +} diff --git a/xpcom/ds/nsAtomTable.h b/xpcom/ds/nsAtomTable.h new file mode 100644 index 000000000..89e5792f7 --- /dev/null +++ b/xpcom/ds/nsAtomTable.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 nsAtomTable_h__ +#define nsAtomTable_h__ + +#include "mozilla/MemoryReporting.h" +#include + +void NS_InitAtomTable(); +void NS_ShutdownAtomTable(); + +void NS_SizeOfAtomTablesIncludingThis(mozilla::MallocSizeOf aMallocSizeOf, + size_t* aMain, size_t* aStatic); + +#endif // nsAtomTable_h__ diff --git a/xpcom/ds/nsCRT.cpp b/xpcom/ds/nsCRT.cpp new file mode 100644 index 000000000..0d11a8c26 --- /dev/null +++ b/xpcom/ds/nsCRT.cpp @@ -0,0 +1,187 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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; +} + +/** + * Compare unichar string ptrs, stopping at the 1st null or nth char. + * NOTE: If either is null, we return 0. + * NOTE: We DO NOT terminate the search upon encountering nullptr's before N + * + * @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::strncmp(const char16_t* aStr1, const char16_t* aStr2, uint32_t aNum) +{ + if (aStr1 && aStr2) { + if (aNum != 0) { + do { + char16_t c1 = *aStr1++; + char16_t c2 = *aStr2++; + if (c1 != c2) { + if (c1 < c2) { + return -1; + } + return 1; + } + } while (--aNum != 0); + } + } + return 0; +} + +const char* +nsCRT::memmem(const char* aHaystack, uint32_t aHaystackLen, + const char* aNeedle, uint32_t aNeedleLen) +{ + // Sanity checking + if (!(aHaystack && aNeedle && aHaystackLen && aNeedleLen && + aNeedleLen <= aHaystackLen)) { + return nullptr; + } + +#ifdef HAVE_MEMMEM + return (const char*)::memmem(aHaystack, aHaystackLen, aNeedle, aNeedleLen); +#else + // No memmem means we need to roll our own. This isn't really optimized + // for performance ... if that becomes an issue we can take some inspiration + // from the js string compare code in jsstr.cpp + for (uint32_t i = 0; i < aHaystackLen - aNeedleLen; i++) { + if (!memcmp(aHaystack + i, aNeedle, aNeedleLen)) { + return aHaystack + i; + } + } +#endif + return nullptr; +} + +// 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 000000000..6fad83d6d --- /dev/null +++ b/xpcom/ds/nsCRT.h @@ -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/. */ +#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_LINEBREAK_LEN 2 +#else +# ifdef XP_UNIX +# define NS_LINEBREAK "\012" +# define NS_LINEBREAK_LEN 1 +# endif /* XP_UNIX */ +#endif /* XP_WIN */ + +extern const char16_t kIsoLatin1ToUCS2[256]; + +// This macro can be used in a class declaration for classes that want +// to ensure that their instance memory is zeroed. +#define NS_DECL_AND_IMPL_ZEROING_OPERATOR_NEW \ + void* operator new(size_t sz) CPP_THROW_NEW { \ + void* rv = ::operator new(sz); \ + if (rv) { \ + memset(rv, 0, sz); \ + } \ + return rv; \ + } \ + void operator delete(void* ptr) { \ + ::operator delete(ptr); \ + } + +// This macro works with the next macro to declare a non-inlined +// version of the above. +#define NS_DECL_ZEROING_OPERATOR_NEW \ + void* operator new(size_t sz) CPP_THROW_NEW; \ + void operator delete(void* ptr); + +#define NS_IMPL_ZEROING_OPERATOR_NEW(_class) \ + void* _class::operator new(size_t sz) CPP_THROW_NEW { \ + void* rv = ::operator new(sz); \ + if (rv) { \ + memset(rv, 0, sz); \ + } \ + return rv; \ + } \ + void _class::operator delete(void* ptr) { \ + ::operator delete(ptr); \ + } + +// Freeing helper +#define CRTFREEIF(x) if (x) { nsCRT::free(x); x = 0; } + +/// 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)); + } + + static int32_t strncmp(const char* aStr1, const char* aStr2, + uint32_t aMaxLen) + { + return int32_t(PL_strncmp(aStr1, aStr2, aMaxLen)); + } + + /// Case-insensitive string comparison. + static int32_t strcasecmp(const char* aStr1, const char* aStr2) + { + return int32_t(PL_strcasecmp(aStr1, aStr2)); + } + + /// Case-insensitive string comparison with length + static int32_t strncasecmp(const char* aStr1, const char* aStr2, + uint32_t aMaxLen) + { + int32_t result = int32_t(PL_strncasecmp(aStr1, aStr2, aMaxLen)); + //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; + } + + static int32_t strncmp(const char* aStr1, const char* aStr2, int32_t aMaxLen) + { + // inline the first test (assumes strings are not null): + int32_t diff = + ((const unsigned char*)aStr1)[0] - ((const unsigned char*)aStr2)[0]; + if (diff != 0) { + return diff; + } + return int32_t(PL_strncmp(aStr1, aStr2, unsigned(aMaxLen))); + } + + /** + + 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); + /// Like strcmp except for ucs2 strings + static int32_t strncmp(const char16_t* aStr1, const char16_t* aStr2, + uint32_t aMaxLen); + + // The GNU libc has memmem, which is strstr except for binary data + // This is our own implementation that uses memmem on platforms + // where it's available. + static const char* memmem(const char* aHaystack, uint32_t aHaystackLen, + const char* aNeedle, uint32_t aNeedleLen); + + // 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 IsUpper(char aChar) { return NS_IsUpper(aChar); } + static bool IsLower(char aChar) { return NS_IsLower(aChar); } + + static bool IsAscii(char16_t aChar) { return NS_IsAscii(aChar); } + static bool IsAscii(const char16_t* aString) { return NS_IsAscii(aString); } + static bool IsAsciiAlpha(char16_t aChar) { return NS_IsAsciiAlpha(aChar); } + static bool IsAsciiDigit(char16_t aChar) { return NS_IsAsciiDigit(aChar); } + static bool IsAsciiSpace(char16_t aChar) { return NS_IsAsciiWhitespace(aChar); } + static bool IsAscii(const char* aString) { return NS_IsAscii(aString); } + static bool IsAscii(const char* aString, uint32_t aLength) + { + return NS_IsAscii(aString, aLength); + } +}; + + +inline bool +NS_IS_SPACE(char16_t aChar) +{ + return ((int(aChar) & 0x7f) == int(aChar)) && isspace(int(aChar)); +} + +#define NS_IS_CNTRL(i) ((((unsigned int) (i)) > 0x7f) ? (int) 0 : iscntrl(i)) +#define NS_IS_DIGIT(i) ((((unsigned int) (i)) > 0x7f) ? (int) 0 : isdigit(i)) +#if defined(XP_WIN) +#define NS_IS_ALPHA(VAL) (isascii((int)(VAL)) && isalpha((int)(VAL))) +#else +#define NS_IS_ALPHA(VAL) ((((unsigned int) (VAL)) > 0x7f) ? (int) 0 : isalpha((int)(VAL))) +#endif + + +#endif /* nsCRT_h___ */ diff --git a/xpcom/ds/nsCharSeparatedTokenizer.h b/xpcom/ds/nsCharSeparatedTokenizer.h new file mode 100644 index 000000000..0e24d9d3e --- /dev/null +++ b/xpcom/ds/nsCharSeparatedTokenizer.h @@ -0,0 +1,200 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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/RangedPtr.h" + +#include "nsDependentSubstring.h" +#include "nsCRT.h" + +/** + * 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. + * + * 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 +{ + typedef typename DependentSubstringType::char_type CharType; + typedef typename DependentSubstringType::substring_type SubstringType; + +public: + // 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 + { + SEPARATOR_OPTIONAL = 1 + }; + + nsTCharSeparatedTokenizer(const SubstringType& aSource, + CharType aSeparatorChar, + uint32_t aFlags = 0) + : mIter(aSource.Data(), aSource.Length()) + , mEnd(aSource.Data() + aSource.Length(), aSource.Data(), + aSource.Length()) + , mSeparatorChar(aSeparatorChar) + , mWhitespaceBeforeFirstToken(false) + , mWhitespaceAfterCurrentToken(false) + , mSeparatorAfterCurrentToken(false) + , mSeparatorOptional(aFlags & SEPARATOR_OPTIONAL) + { + // 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"); + + 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 (mSeparatorOptional) { + // 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(mSeparatorOptional || + (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()); + } + +private: + mozilla::RangedPtr mIter; + const mozilla::RangedPtr mEnd; + CharType mSeparatorChar; + bool mWhitespaceBeforeFirstToken; + bool mWhitespaceAfterCurrentToken; + bool mSeparatorAfterCurrentToken; + bool mSeparatorOptional; +}; + +template +class nsCharSeparatedTokenizerTemplate + : public nsTCharSeparatedTokenizer +{ +public: + nsCharSeparatedTokenizerTemplate(const nsSubstring& aSource, + char16_t aSeparatorChar, + uint32_t aFlags = 0) + : nsTCharSeparatedTokenizer(aSource, aSeparatorChar, aFlags) + { + } +}; + +typedef nsCharSeparatedTokenizerTemplate<> nsCharSeparatedTokenizer; + +template +class nsCCharSeparatedTokenizerTemplate + : public nsTCharSeparatedTokenizer +{ +public: + nsCCharSeparatedTokenizerTemplate(const nsCSubstring& aSource, + char aSeparatorChar, + uint32_t aFlags = 0) + : nsTCharSeparatedTokenizer(aSource, aSeparatorChar, aFlags) + { + } +}; + +typedef nsCCharSeparatedTokenizerTemplate<> nsCCharSeparatedTokenizer; + +#endif /* __nsCharSeparatedTokenizer_h */ diff --git a/xpcom/ds/nsCheapSets.h b/xpcom/ds/nsCheapSets.h new file mode 100644 index 000000000..d75e60d20 --- /dev/null +++ b/xpcom/ds/nsCheapSets.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 __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) {} + ~nsCheapSet() { Clear(); } + + /** + * Remove all entries. + */ + void Clear() + { + switch (mState) { + case ZERO: + break; + case ONE: + GetSingleEntry()->~EntryType(); + break; + case MANY: + delete mUnion.table; + break; + default: + NS_NOTREACHED("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: + NS_NOTREACHED("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: + NS_NOTREACHED("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; + } + MOZ_FALLTHROUGH; + + case MANY: + mUnion.table->PutEntry(aVal); + return; + default: + NS_NOTREACHED("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: + NS_NOTREACHED("bogus state"); + break; + } +} + +#endif diff --git a/xpcom/ds/nsExpirationTracker.h b/xpcom/ds/nsExpirationTracker.h new file mode 100644 index 000000000..2e77895db --- /dev/null +++ b/xpcom/ds/nsExpirationTracker.h @@ -0,0 +1,570 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 "mozilla/Logging.h" +#include "nsTArray.h" +#include "nsITimer.h" +#include "nsCOMPtr.h" +#include "nsAutoPtr.h" +#include "nsComponentManagerUtils.h" +#include "nsIEventTarget.h" +#include "nsIObserver.h" +#include "nsIObserverService.h" +#include "nsThreadUtils.h" +#include "mozilla/Attributes.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) {} + 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. + * + */ +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. + */ + ExpirationTrackerImpl(uint32_t aTimerPeriod, const char* aName) + : mTimerPeriod(aTimerPeriod) + , mNewestGeneration(0) + , mInAgeOneGeneration(false) + , mName(aName) + { + static_assert(K >= 2 && K <= nsExpirationState::NOT_TRACKED, + "Unsupported number of generations (must be 2 <= K <= 15)"); + MOZ_ASSERT(NS_IsMainThread()); + 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) + { + nsExpirationState* state = aObj->GetExpirationState(); + NS_ASSERTION(!state->IsTracked(), + "Tried to add an object that's already tracked"); + 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; + } + } + if (!generation.AppendElement(aObj)) { + return NS_ERROR_OUT_OF_MEMORY; + } + 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) + { + nsExpirationState* state = aObj->GetExpirationState(); + NS_ASSERTION(state->IsTracked(), "Tried to remove an object that's not tracked"); + nsTArray& generation = mGenerations[state->mGeneration]; + uint32_t index = state->mIndexInGeneration; + NS_ASSERTION(generation.Length() > index && + generation[index] == aObj, "Object is lying about its index"); + // Move the last object to fill the hole created by removing aObj + uint32_t last = generation.Length() - 1; + T* lastObj = generation[last]; + generation[index] = lastObj; + lastObj->GetExpirationState()->mIndexInGeneration = index; + generation.RemoveElementAt(last); + 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) + { + for (uint32_t i = 0; i < K; ++i) { + if (!mGenerations[i].IsEmpty()) { + return false; + } + } + return true; + } + +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; + + 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. + + /** + * 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); + } + + void HandleTimeout() { + AutoLock lock(GetMutex()); + AgeOneGenerationLocked(lock); + // Cancel the timer if we have no objects to track + if (IsEmptyLocked(lock)) { + mTimer->Cancel(); + mTimer = nullptr; + } + } + + 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; + } + mTimer = do_CreateInstance("@mozilla.org/timer;1"); + if (!mTimer) { + return NS_ERROR_OUT_OF_MEMORY; + } + if (!NS_IsMainThread()) { + // TimerCallback should always be run on the main thread to prevent races + // to the destruction of the tracker. + nsCOMPtr target = do_GetMainThread(); + NS_ENSURE_STATE(target); + mTimer->SetTarget(target); + } + mTimer->InitWithNamedFuncCallback(TimerCallback, this, mTimerPeriod, + nsITimer::TYPE_REPEATING_SLACK, mName); + return NS_OK; + } +}; + +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() { + return AutoLock(mLock); + } + + Lock& GetMutex() override + { + return mLock; + } + + void NotifyExpiredLocked(T* aObject, const AutoLock&) override + { + NotifyExpired(aObject); + } + +protected: + virtual void NotifyExpired(T* aObj) = 0; + +public: + nsExpirationTracker(uint32_t aTimerPeriod, const char* aName) + : ::detail::SingleThreadedExpirationTracker(aTimerPeriod, aName) + { } + + virtual ~nsExpirationTracker() + { } + + 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/nsHashPropertyBag.cpp b/xpcom/ds/nsHashPropertyBag.cpp new file mode 100644 index 000000000..6f9fc8dec --- /dev/null +++ b/xpcom/ds/nsHashPropertyBag.cpp @@ -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/. */ + +#include "nsHashPropertyBag.h" +#include "nsArray.h" +#include "nsArrayEnumerator.h" +#include "nsIVariant.h" +#include "nsIProperty.h" +#include "nsVariant.h" +#include "mozilla/Attributes.h" + +/* + * 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.Put(aName, aValue); + + return NS_OK; +} + +NS_IMETHODIMP +nsHashPropertyBagBase::DeleteProperty(const nsAString& aName) +{ + // is it too much to ask for ns*Hashtable to return + // a boolean indicating whether RemoveEntry succeeded + // or not?!?! + bool isFound = mPropertyHash.Get(aName, nullptr); + if (!isFound) { + return NS_ERROR_FAILURE; + } + + // then from the hash + mPropertyHash.Remove(aName); + + return NS_OK; +} + + +// +// nsSimpleProperty class and impl; used for GetEnumerator +// + +class nsSimpleProperty final : public nsIProperty +{ + ~nsSimpleProperty() {} + +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, false); + } + + return NS_NewArrayEnumerator(aResult, propertyArray); +} + +#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); +} + + +/* + * 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 + + +/* + * 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 000000000..e41c984ba --- /dev/null +++ b/xpcom/ds/nsHashPropertyBag.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 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() {} + + 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() {} + NS_DECL_THREADSAFE_ISUPPORTS + +protected: + virtual ~nsHashPropertyBag() {} +}; + +/* A cycle collected nsHashPropertyBag for main-thread-only use. */ +class nsHashPropertyBagCC final : public nsHashPropertyBagBase +{ +public: + nsHashPropertyBagCC() {} + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(nsHashPropertyBagCC, + nsIWritablePropertyBag) +protected: + virtual ~nsHashPropertyBagCC() {} +}; + +#endif /* nsHashPropertyBag_h___ */ diff --git a/xpcom/ds/nsIArray.idl b/xpcom/ds/nsIArray.idl new file mode 100644 index 000000000..d591253e9 --- /dev/null +++ b/xpcom/ds/nsIArray.idl @@ -0,0 +1,91 @@ +/* -*- 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, 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. + */ + nsISimpleEnumerator enumerate(); +}; diff --git a/xpcom/ds/nsIArrayExtensions.idl b/xpcom/ds/nsIArrayExtensions.idl new file mode 100644 index 000000000..3682d2ee7 --- /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, 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/nsIAtom.idl b/xpcom/ds/nsIAtom.idl new file mode 100644 index 000000000..c02540838 --- /dev/null +++ b/xpcom/ds/nsIAtom.idl @@ -0,0 +1,166 @@ +/* -*- 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" + +%{C++ +#include "nsStringGlue.h" +#include "nsCOMPtr.h" +#include "nsStringBuffer.h" +%} + +native MallocSizeOf(mozilla::MallocSizeOf); + +/* + * Should this really be scriptable? Using atoms from script or proxies + * could be dangerous since double-wrapping could lead to loss of + * pointer identity. + */ + +[scriptable, builtinclass, uuid(8b8c11d4-3ed5-4079-8974-73c7576cdb34)] +interface nsIAtom : nsISupports +{ + /** + * Get the Unicode or UTF8 value for the string + */ + [binaryname(ScriptableToString)] AString toString(); + [noscript] AUTF8String toUTF8String(); + + /** + * Compare the atom to a specific string value + * Note that this will NEVER return/throw an error condition. + */ + [binaryname(ScriptableEquals)] boolean equals(in AString aString); + + [noscript, notxpcom] + size_t SizeOfIncludingThis(in MallocSizeOf aMallocSizeOf); + +%{C++ + // note this is NOT virtual so this won't muck with the vtable! + inline bool Equals(const nsAString& aString) const { + return aString.Equals(nsDependentString(mString, mLength)); + } + + inline bool IsStaticAtom() const { + return mIsStatic; + } + + inline char16ptr_t GetUTF16String() const { + return mString; + } + + inline uint32_t GetLength() const { + return mLength; + } + + inline void ToString(nsAString& aBuf) { + // See the comment on |mString|'s declaration. + nsStringBuffer::FromData(mString)->ToString(mLength, aBuf); + } + + inline nsStringBuffer* GetStringBuffer() const { + // See the comment on |mString|'s declaration. + return nsStringBuffer::FromData(mString); + } + + /** + * A hashcode that is better distributed than the actual atom + * pointer, for use in situations that need a well-distributed + * hashcode. + */ + inline uint32_t hash() const { + return mHash; + } + +protected: + uint32_t mLength:31; + uint32_t mIsStatic:1; + uint32_t mHash; + /** + * WARNING! There is an invisible constraint on |mString|: the chars it + * points to must belong to an nsStringBuffer. This is so that the + * nsStringBuffer::FromData() calls above are valid. + */ + char16_t* mString; +%} +}; + + +%{C++ +/* + * The four forms of NS_Atomize (for use with |nsCOMPtr|) 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. + */ +extern already_AddRefed NS_Atomize(const char* aUTF8String); + +/** + * Find an atom that matches the given UTF-8 string. Never returns null. + */ +extern 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. + */ +extern already_AddRefed NS_Atomize(const char16_t* aUTF16String); + +/** + * Find an atom that matches the given UTF-16 string. Never returns null. + */ +extern already_AddRefed NS_Atomize(const nsAString& aUTF16String); + +/** + * Return a count of the total number of atoms currently + * alive in the system. + */ +extern nsrefcnt NS_GetNumberOfAtoms(void); + +/** + * Return a pointer for a static atom for the string or null if there's + * no static atom for this string. + */ +extern nsIAtom* NS_GetStaticAtom(const nsAString& aUTF16String); + +/** + * Seal the static atom table + */ +extern void NS_SealStaticAtomTable(); + +class nsAtomString : public nsString +{ +public: + explicit nsAtomString(nsIAtom* aAtom) + { + aAtom->ToString(*this); + } +}; + +class nsAtomCString : public nsCString +{ +public: + explicit nsAtomCString(nsIAtom* aAtom) + { + aAtom->ToUTF8String(*this); + } +}; + +class nsDependentAtomString : public nsDependentString +{ +public: + explicit nsDependentAtomString(nsIAtom* aAtom) + : nsDependentString(aAtom->GetUTF16String(), aAtom->GetLength()) + { + } +}; + +%} diff --git a/xpcom/ds/nsIAtomService.idl b/xpcom/ds/nsIAtomService.idl new file mode 100644 index 000000000..a7885a413 --- /dev/null +++ b/xpcom/ds/nsIAtomService.idl @@ -0,0 +1,35 @@ +/* -*- 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 nsIAtom; + +%{C++ +#define NS_ATOMSERVICE_CID \ +{ /* ed3db3fc-0168-4cab-8818-98f5475a490c */ \ + 0xed3db3fc, \ + 0x0168, \ + 0x4cab, \ + {0x88, 0x18, 0x98, 0xf5, 0x47, 0x5a, 0x49, 0x0c} } + +#define NS_ATOMSERVICE_CONTRACTID "@mozilla.org/atom-service;1" +%} + +/* + * Should this really be scriptable? Using atoms from script or proxies + * could be dangerous since double-wrapping could lead to loss of + * pointer identity. + */ + +[scriptable, uuid(9c1f50b9-f9eb-42d4-a8cb-2c7600aeb241)] +interface nsIAtomService : nsISupports { + + /** + * Version of NS_Atomize that doesn't require linking against the + * XPCOM library. See nsIAtom.idl. + */ + nsIAtom getAtom(in AString value); +}; diff --git a/xpcom/ds/nsICollection.idl b/xpcom/ds/nsICollection.idl new file mode 100644 index 000000000..3cd851419 --- /dev/null +++ b/xpcom/ds/nsICollection.idl @@ -0,0 +1,67 @@ +/* -*- 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 "nsISerializable.idl" + +interface nsIEnumerator; + +[deprecated, scriptable, uuid(83b6019c-cbc4-11d2-8cca-0060b0fc14a3)] +interface nsICollection : nsISerializable +{ + + uint32_t Count(); + nsISupports GetElementAt(in uint32_t index); + void QueryElementAt(in uint32_t index, in nsIIDRef uuid, + [iid_is(uuid),retval] out nsQIResult result); + void SetElementAt(in uint32_t index, in nsISupports item); + void AppendElement(in nsISupports item); + void RemoveElement(in nsISupports item); + + /** + * This clashes with |nsISimpleEnumerator nsIArray.enumerate()| (only on the + * binary side), so it is renamed with a 'Deprecated' prefix in favor of the + * non-deprecated |nsIArray.enumerate|. + */ + [binaryname(DeprecatedEnumerate)] + nsIEnumerator Enumerate(); + + void Clear(); + +}; + +%{C++ + +#ifndef nsCOMPtr_h__ +#include "nsCOMPtr.h" +#endif + +class MOZ_STACK_CLASS nsQueryElementAt : public nsCOMPtr_helper + { + public: + nsQueryElementAt( nsICollection* aCollection, uint32_t aIndex, nsresult* aErrorPtr ) + : mCollection(aCollection), + mIndex(aIndex), + mErrorPtr(aErrorPtr) + { + // nothing else to do here + } + + virtual nsresult NS_FASTCALL operator()( const nsIID& aIID, void** ) + const override; + + private: + nsICollection* MOZ_NON_OWNING_REF mCollection; + uint32_t mIndex; + nsresult* mErrorPtr; + }; + +inline +const nsQueryElementAt +do_QueryElementAt( nsICollection* aCollection, uint32_t aIndex, nsresult* aErrorPtr = 0 ) + { + return nsQueryElementAt(aCollection, aIndex, aErrorPtr); + } + +%} diff --git a/xpcom/ds/nsIEnumerator.idl b/xpcom/ds/nsIEnumerator.idl new file mode 100644 index 000000000..9482ef436 --- /dev/null +++ b/xpcom/ds/nsIEnumerator.idl @@ -0,0 +1,50 @@ +/* -*- 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" + +%{C++ +#define NS_ENUMERATOR_FALSE 1 +%} +/* + * DO NOT USE THIS INTERFACE. IT IS HORRIBLY BROKEN, USES NS_COMFALSE + * AND IS BASICALLY IMPOSSIBLE TO USE CORRECTLY THROUGH PROXIES OR + * XPCONNECT. IF YOU SEE NEW USES OF THIS INTERFACE IN CODE YOU ARE + * REVIEWING, YOU SHOULD INSIST ON nsISimpleEnumerator. + * + * DON'T MAKE ME COME OVER THERE. + */ +[deprecated, scriptable, uuid(ad385286-cbc4-11d2-8cca-0060b0fc14a3)] +interface nsIEnumerator : nsISupports { + /** First will reset the list. will return NS_FAILED if no items + */ + void first(); + + /** Next will advance the list. will return failed if already at end + */ + void next(); + + /** CurrentItem will return the CurrentItem item it will fail if the + * list is empty + */ + nsISupports currentItem(); + + /** return if the collection is at the end. that is the beginning following + * a call to Prev and it is the end of the list following a call to next + */ + void isDone(); +}; + +[deprecated, uuid(75f158a0-cadd-11d2-8cca-0060b0fc14a3)] +interface nsIBidirectionalEnumerator : nsIEnumerator { + + /** Last will reset the list to the end. will return NS_FAILED if no items + */ + void last(); + + /** Prev will decrement the list. will return failed if already at beginning + */ + void prev(); +}; diff --git a/xpcom/ds/nsIHashable.idl b/xpcom/ds/nsIHashable.idl new file mode 100644 index 000000000..aa223787c --- /dev/null +++ b/xpcom/ds/nsIHashable.idl @@ -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/. */ + +#include "nsISupports.idl" + +/** + * Represents an object that can be stored in a hashtable. + */ +[scriptable, uuid(17e595fa-b57a-4933-bd0f-b1812e8ab188)] +interface nsIHashable : nsISupports +{ + /** + * Is this object the equivalent of the other object? + */ + boolean equals(in nsIHashable aOther); + + /** + * A generated hashcode for this object. Objects that are equivalent + * must have the same hash code. Getting this property should never + * throw an exception! + */ + readonly attribute unsigned long hashCode; +}; diff --git a/xpcom/ds/nsIINIParser.idl b/xpcom/ds/nsIINIParser.idl new file mode 100644 index 000000000..166828e5d --- /dev/null +++ b/xpcom/ds/nsIINIParser.idl @@ -0,0 +1,58 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. 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 +{ + /** + * 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 +{ + /** + * Windows and the NSIS installer code sometimes expect INI files to be in + * UTF-16 encoding. On Windows only, this flag to writeFile can be used to + * change the encoding from its default UTF-8. + */ + const unsigned long WRITE_UTF16 = 0x1; + + /** + * 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([optional] in nsIFile aINIFile, + [optional] in unsigned long aFlags); +}; + +[scriptable, uuid(ccae7ea5-1218-4b51-aecb-c2d8ecd46af9)] +interface nsIINIParserFactory : nsISupports +{ + /** + * Create an iniparser instance from a local file. + */ + nsIINIParser createINIParser(in nsIFile aINIFile); +}; diff --git a/xpcom/ds/nsIMutableArray.idl b/xpcom/ds/nsIMutableArray.idl new file mode 100644 index 000000000..67e527a81 --- /dev/null +++ b/xpcom/ds/nsIMutableArray.idl @@ -0,0 +1,110 @@ +/* -*- 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, 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. + * @param weak Whether or not to store the element using a weak + * reference. + * @throws NS_ERROR_FAILURE when a weak reference is requested, + * but the element does not support + * nsIWeakReference. + */ + void appendElement(in nsISupports element, in boolean weak); + + /** + * 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. + * + * @throws NS_ERROR_FAILURE when a weak reference is requested, + * but the element does not support + * nsIWeakReference. + */ + void insertElementAt(in nsISupports element, in unsigned long index, + in boolean weak); + + /** + * 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. + * + * @param weak Whether or not to store the new element using a weak + * reference. + * + * @throws NS_ERROR_FAILURE when a weak reference is requested, + * but the element does not support + * nsIWeakReference. + */ + void replaceElementAt(in nsISupports element, in unsigned long index, + in boolean weak); + + + /** + * 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 000000000..24def4e58 --- /dev/null +++ b/xpcom/ds/nsINIParserImpl.cpp @@ -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/. */ + +#include "nsINIParserImpl.h" + +#include "nsINIParser.h" +#include "nsStringEnumerator.h" +#include "nsTArray.h" +#include "mozilla/Attributes.h" + +class nsINIParserImpl final + : public nsIINIParser +{ + ~nsINIParserImpl() {} + +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIINIPARSER + + nsresult Init(nsIFile* aINIFile) { return mParser.Init(aINIFile); } + +private: + nsINIParser mParser; +}; + +NS_IMPL_ISUPPORTS(nsINIParserFactory, + nsIINIParserFactory, + nsIFactory) + +NS_IMETHODIMP +nsINIParserFactory::CreateINIParser(nsIFile* aINIFile, + nsIINIParser** aResult) +{ + *aResult = nullptr; + + RefPtr p(new nsINIParserImpl()); + if (!p) { + return NS_ERROR_OUT_OF_MEMORY; + } + + nsresult rv = p->Init(aINIFile); + + if (NS_SUCCEEDED(rv)) { + NS_ADDREF(*aResult = p); + } + + return rv; +} + +NS_IMETHODIMP +nsINIParserFactory::CreateInstance(nsISupports* aOuter, + REFNSIID aIID, + void** aResult) +{ + if (NS_WARN_IF(aOuter)) { + return NS_ERROR_NO_AGGREGATION; + } + + // We are our own singleton. + return QueryInterface(aIID, aResult); +} + +NS_IMETHODIMP +nsINIParserFactory::LockFactory(bool aLock) +{ + return NS_OK; +} + +NS_IMPL_ISUPPORTS(nsINIParserImpl, + nsIINIParser) + +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; + if (!strings) { + return NS_ERROR_OUT_OF_MEMORY; + } + + 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) +{ + nsTArray* strings = new nsTArray; + if (!strings) { + return NS_ERROR_OUT_OF_MEMORY; + } + + 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) +{ + return mParser.GetString(PromiseFlatCString(aSection).get(), + PromiseFlatCString(aKey).get(), + aResult); +} diff --git a/xpcom/ds/nsINIParserImpl.h b/xpcom/ds/nsINIParserImpl.h new file mode 100644 index 000000000..8e63f36d7 --- /dev/null +++ b/xpcom/ds/nsINIParserImpl.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 nsINIParserImpl_h__ +#define nsINIParserImpl_h__ + +#include "nsIINIParser.h" +#include "nsIFactory.h" +#include "mozilla/Attributes.h" + +#define NS_INIPARSERFACTORY_CID \ +{ 0xdfac10a9, 0xdd24, 0x43cf, \ + { 0xa0, 0x95, 0x6f, 0xfa, 0x2e, 0x4b, 0x6a, 0x6c } } + +#define NS_INIPARSERFACTORY_CONTRACTID \ + "@mozilla.org/xpcom/ini-parser-factory;1" + +class nsINIParserFactory final + : public nsIINIParserFactory + , public nsIFactory +{ + ~nsINIParserFactory() {} + +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIINIPARSERFACTORY + NS_DECL_NSIFACTORY +}; + +#endif // nsINIParserImpl_h__ diff --git a/xpcom/ds/nsINIProcessor.js b/xpcom/ds/nsINIProcessor.js new file mode 100644 index 000000000..832f35a5f --- /dev/null +++ b/xpcom/ds/nsINIProcessor.js @@ -0,0 +1,192 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 Cc = Components.classes; +const Ci = Components.interfaces; +const Cr = Components.results; +const Cu = Components.utils; + +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); + +function INIProcessorFactory() { +} + +INIProcessorFactory.prototype = { + classID: Components.ID("{6ec5f479-8e13-4403-b6ca-fe4c2dca14fd}"), + QueryInterface : XPCOMUtils.generateQI([Ci.nsIINIParserFactory]), + + createINIParser : function (aINIFile) { + return new INIProcessor(aINIFile); + } + +}; // end of INIProcessorFactory implementation + +const MODE_WRONLY = 0x02; +const MODE_CREATE = 0x08; +const MODE_TRUNCATE = 0x20; + +// nsIINIParser implementation +function INIProcessor(aFile) { + this._iniFile = aFile; + this._iniData = {}; + this._readFile(); +} + +INIProcessor.prototype = { + QueryInterface : XPCOMUtils.generateQI([Ci.nsIINIParser, Ci.nsIINIParserWriter]), + + __utf8Converter : null, // UCS2 <--> UTF8 string conversion + get _utf8Converter() { + if (!this.__utf8Converter) { + this.__utf8Converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]. + createInstance(Ci.nsIScriptableUnicodeConverter); + this.__utf8Converter.charset = "UTF-8"; + } + return this.__utf8Converter; + }, + + __utf16leConverter : null, // UCS2 <--> UTF16LE string conversion + get _utf16leConverter() { + if (!this.__utf16leConverter) { + this.__utf16leConverter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]. + createInstance(Ci.nsIScriptableUnicodeConverter); + this.__utf16leConverter.charset = "UTF-16LE"; + } + return this.__utf16leConverter; + }, + + _utfConverterReset : function() { + this.__utf8Converter = null; + this.__utf16leConverter = null; + }, + + _iniFile : null, + _iniData : null, + + /* + * Reads the INI file and stores the data internally. + */ + _readFile : function() { + // If file doesn't exist, there's nothing to do. + if (!this._iniFile.exists() || 0 == this._iniFile.fileSize) + return; + + let iniParser = Cc["@mozilla.org/xpcom/ini-parser-factory;1"] + .getService(Ci.nsIINIParserFactory).createINIParser(this._iniFile); + for (let section of XPCOMUtils.IterStringEnumerator(iniParser.getSections())) { + this._iniData[section] = {}; + for (let key of XPCOMUtils.IterStringEnumerator(iniParser.getKeys(section))) { + this._iniData[section][key] = iniParser.getString(section, key); + } + } + }, + + // nsIINIParser + + getSections : function() { + let sections = []; + for (let section in this._iniData) + sections.push(section); + return new stringEnumerator(sections); + }, + + getKeys : function(aSection) { + let keys = []; + if (aSection in this._iniData) + for (let key in this._iniData[aSection]) + keys.push(key); + return new stringEnumerator(keys); + }, + + getString : function(aSection, aKey) { + if (!(aSection in this._iniData)) + throw Cr.NS_ERROR_FAILURE; + if (!(aKey in this._iniData[aSection])) + throw Cr.NS_ERROR_FAILURE; + return this._iniData[aSection][aKey]; + }, + + + // nsIINIParserWriter + + setString : function(aSection, aKey, aValue) { + const isSectionIllegal = /[\0\r\n\[\]]/; + const isKeyValIllegal = /[\0\r\n=]/; + + if (isSectionIllegal.test(aSection)) + throw Components.Exception("bad character in section name", + Cr.ERROR_ILLEGAL_VALUE); + if (isKeyValIllegal.test(aKey) || isKeyValIllegal.test(aValue)) + throw Components.Exception("bad character in key/value", + Cr.ERROR_ILLEGAL_VALUE); + + if (!(aSection in this._iniData)) + this._iniData[aSection] = {}; + + this._iniData[aSection][aKey] = aValue; + }, + + writeFile : function(aFile, aFlags) { + + let converter; + function writeLine(data) { + data += "\n"; + data = converter.ConvertFromUnicode(data); + data += converter.Finish(); + outputStream.write(data, data.length); + } + + if (!aFile) + aFile = this._iniFile; + + let safeStream = Cc["@mozilla.org/network/safe-file-output-stream;1"]. + createInstance(Ci.nsIFileOutputStream); + safeStream.init(aFile, MODE_WRONLY | MODE_CREATE | MODE_TRUNCATE, + 0o600, null); + + var outputStream = Cc["@mozilla.org/network/buffered-output-stream;1"]. + createInstance(Ci.nsIBufferedOutputStream); + outputStream.init(safeStream, 8192); + outputStream.QueryInterface(Ci.nsISafeOutputStream); // for .finish() + + if (Ci.nsIINIParserWriter.WRITE_UTF16 == aFlags + && 'nsIWindowsRegKey' in Ci) { + outputStream.write("\xFF\xFE", 2); + converter = this._utf16leConverter; + } else { + converter = this._utf8Converter; + } + + for (let section in this._iniData) { + writeLine("[" + section + "]"); + for (let key in this._iniData[section]) { + writeLine(key + "=" + this._iniData[section][key]); + } + } + + outputStream.finish(); + } +}; + +function stringEnumerator(stringArray) { + this._strings = stringArray; +} +stringEnumerator.prototype = { + QueryInterface : XPCOMUtils.generateQI([Ci.nsIUTF8StringEnumerator]), + + _strings : null, + _enumIndex: 0, + + hasMore : function() { + return (this._enumIndex < this._strings.length); + }, + + getNext : function() { + return this._strings[this._enumIndex++]; + } +}; + +var component = [INIProcessorFactory]; +this.NSGetFactory = XPCOMUtils.generateNSGetFactory(component); diff --git a/xpcom/ds/nsINIProcessor.manifest b/xpcom/ds/nsINIProcessor.manifest new file mode 100644 index 000000000..da19d2aed --- /dev/null +++ b/xpcom/ds/nsINIProcessor.manifest @@ -0,0 +1,2 @@ +component {6ec5f479-8e13-4403-b6ca-fe4c2dca14fd} nsINIProcessor.js +contract @mozilla.org/xpcom/ini-processor-factory;1 {6ec5f479-8e13-4403-b6ca-fe4c2dca14fd} diff --git a/xpcom/ds/nsIObserver.idl b/xpcom/ds/nsIObserver.idl new file mode 100644 index 000000000..cfb4f912b --- /dev/null +++ b/xpcom/ds/nsIObserver.idl @@ -0,0 +1,38 @@ +/* -*- 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 000000000..d5b40e227 --- /dev/null +++ b/xpcom/ds/nsIObserverService.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" + +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, 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, 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. + * + * @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, + in wstring someData ); + + /** + * 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 000000000..e886076e9 --- /dev/null +++ b/xpcom/ds/nsIPersistentProperties.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 __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 000000000..3f24bb773 --- /dev/null +++ b/xpcom/ds/nsIPersistentProperties2.idl @@ -0,0 +1,63 @@ +/* -*- 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; + +[scriptable, uuid(283EE646-1AEF-11D4-98B3-00C04fA0CE9A)] +interface nsIPropertyElement : nsISupports { + attribute AUTF8String key; + attribute AString value; +}; + +[scriptable, 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); +}; + + +%{C++ + +#define NS_IPERSISTENTPROPERTIES_CID \ +{ 0x2245e573, 0x9464, 0x11d2, \ + { 0x9b, 0x8b, 0x0, 0x80, 0x5f, 0x8a, 0x16, 0xd9 } } + +#define NS_PERSISTENTPROPERTIES_CONTRACTID "@mozilla.org/persistent-properties;1" + +%} + diff --git a/xpcom/ds/nsIProperties.idl b/xpcom/ds/nsIProperties.idl new file mode 100644 index 000000000..a87e14443 --- /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. + */ + void getKeys(out uint32_t count, [array, size_is(count), retval] out string keys); +}; diff --git a/xpcom/ds/nsIProperty.idl b/xpcom/ds/nsIProperty.idl new file mode 100644 index 000000000..9e6e1e487 --- /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 000000000..1d1dfced7 --- /dev/null +++ b/xpcom/ds/nsIPropertyBag.idl @@ -0,0 +1,30 @@ +/* -*- 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 000000000..7b4be7f4a --- /dev/null +++ b/xpcom/ds/nsIPropertyBag2.idl @@ -0,0 +1,42 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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. + */ + 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); +}; diff --git a/xpcom/ds/nsISerializable.idl b/xpcom/ds/nsISerializable.idl new file mode 100644 index 000000000..f525f3a8f --- /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. + */ + 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 000000000..cbb0cd200 --- /dev/null +++ b/xpcom/ds/nsISimpleEnumerator.idl @@ -0,0 +1,46 @@ +/* -*- 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 + */ + +[scriptable, uuid(D1899240-F9D2-11D2-BDD6-000064657374)] +interface nsISimpleEnumerator : nsISupports { + /** + * 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 000000000..d9c4130ee --- /dev/null +++ b/xpcom/ds/nsIStringEnumerator.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/. */ + +#include "nsISupports.idl" + +/** + * Used to enumerate over an ordered list of strings. + */ + +[scriptable, uuid(50d3ef6c-9380-4f06-9fb2-95488f7d141c)] +interface nsIStringEnumerator : nsISupports +{ + boolean hasMore(); + AString getNext(); +}; + +[scriptable, uuid(9bdf1010-3695-4907-95ed-83d0410ec307)] +interface nsIUTF8StringEnumerator : nsISupports +{ + boolean hasMore(); + AUTF8String getNext(); +}; + diff --git a/xpcom/ds/nsISupportsArray.idl b/xpcom/ds/nsISupportsArray.idl new file mode 100644 index 000000000..30e2a5035 --- /dev/null +++ b/xpcom/ds/nsISupportsArray.idl @@ -0,0 +1,60 @@ +/* -*- 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 "nsICollection.idl" + +/* + * This entire interface is deprecated and should not be used. + * See nsIArray and nsIMutableArray for the new implementations. + * + * http://groups.google.com/groups?q=nsisupportsarray+group:netscape.public.mozilla.xpcom&hl=en&lr=&ie=UTF-8&oe=UTF-8&selm=3D779491.3050506%40netscape.com&rnum=2 + * http://groups.google.com/groups?q=nsisupportsarray+group:netscape.public.mozilla.xpcom&hl=en&lr=&ie=UTF-8&oe=UTF-8&selm=al8412%245ab2%40ripley.netscape.com&rnum=8 + */ + +%{C++ + +class nsIBidirectionalEnumerator; +class nsISupportsArray; + +#define NS_SUPPORTSARRAY_CID \ +{ /* bda17d50-0d6b-11d3-9331-00104ba0fd40 */ \ + 0xbda17d50, \ + 0x0d6b, \ + 0x11d3, \ + {0x93, 0x31, 0x00, 0x10, 0x4b, 0xa0, 0xfd, 0x40} \ +} +#define NS_SUPPORTSARRAY_CONTRACTID "@mozilla.org/supports-array;1" + +%} + +[deprecated, scriptable, uuid(241addc8-3608-4e73-8083-2fd6fa09eba2)] +interface nsISupportsArray : nsICollection { + + [notxpcom] long IndexOf([const] in nsISupports aPossibleElement); + + // xpcom-compatible versions + long GetIndexOf(in nsISupports aPossibleElement); + + [notxpcom] boolean InsertElementAt(in nsISupports aElement, + in unsigned long aIndex); + [notxpcom] boolean ReplaceElementAt(in nsISupports aElement, + in unsigned long aIndex); + + [notxpcom] boolean RemoveElementAt(in unsigned long aIndex); + + // xpcom-compatible versions + void DeleteElementAt(in unsigned long aIndex); + + nsISupportsArray clone(); +}; + +%{C++ + +// Construct and return a default implementation of nsISupportsArray: +extern MOZ_MUST_USE nsresult +NS_NewISupportsArray(nsISupportsArray** aInstancePtrResult); + +%} diff --git a/xpcom/ds/nsISupportsIterators.idl b/xpcom/ds/nsISupportsIterators.idl new file mode 100644 index 000000000..8d47375d5 --- /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 000000000..71940739c --- /dev/null +++ b/xpcom/ds/nsISupportsPrimitives.idl @@ -0,0 +1,235 @@ +/* -*- 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, 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; + const unsigned short TYPE_VOID = 16; + const unsigned short TYPE_INTERFACE_POINTER = 17; + + readonly attribute unsigned short type; +}; + +/** + * Scriptable storage for nsID structures + */ + +[scriptable, uuid(d18290a0-4a1c-11d3-9890-006008962422)] +interface nsISupportsID : nsISupportsPrimitive +{ + attribute nsIDPtr data; + string toString(); +}; + +/** + * Scriptable storage for ASCII strings + */ + +[scriptable, uuid(d65ff270-4a1c-11d3-9890-006008962422)] +interface nsISupportsCString : nsISupportsPrimitive +{ + attribute ACString data; + string toString(); +}; + +/** + * Scriptable storage for Unicode strings + */ + +[scriptable, 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, uuid(ddc3b490-4a1c-11d3-9890-006008962422)] +interface nsISupportsPRBool : nsISupportsPrimitive +{ + attribute boolean data; + string toString(); +}; + +/** + * Scriptable storage for 8-bit integers + */ + +[scriptable, uuid(dec2e4e0-4a1c-11d3-9890-006008962422)] +interface nsISupportsPRUint8 : nsISupportsPrimitive +{ + attribute uint8_t data; + string toString(); +}; + +/** + * Scriptable storage for unsigned 16-bit integers + */ + +[scriptable, uuid(dfacb090-4a1c-11d3-9890-006008962422)] +interface nsISupportsPRUint16 : nsISupportsPrimitive +{ + attribute uint16_t data; + string toString(); +}; + +/** + * Scriptable storage for unsigned 32-bit integers + */ + +[scriptable, uuid(e01dc470-4a1c-11d3-9890-006008962422)] +interface nsISupportsPRUint32 : nsISupportsPrimitive +{ + attribute uint32_t data; + string toString(); +}; + +/** + * Scriptable storage for 64-bit integers + */ + +[scriptable, uuid(e13567c0-4a1c-11d3-9890-006008962422)] +interface nsISupportsPRUint64 : nsISupportsPrimitive +{ + attribute uint64_t data; + string toString(); +}; + +/** + * Scriptable storage for NSPR date/time values + */ + +[scriptable, 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, uuid(e2b05e40-4a1c-11d3-9890-006008962422)] +interface nsISupportsChar : nsISupportsPrimitive +{ + attribute char data; + string toString(); +}; + +/** + * Scriptable storage for 16-bit integers + */ + +[scriptable, uuid(e30d94b0-4a1c-11d3-9890-006008962422)] +interface nsISupportsPRInt16 : nsISupportsPrimitive +{ + attribute int16_t data; + string toString(); +}; + +/** + * Scriptable storage for 32-bit integers + */ + +[scriptable, uuid(e36c5250-4a1c-11d3-9890-006008962422)] +interface nsISupportsPRInt32 : nsISupportsPrimitive +{ + attribute int32_t data; + string toString(); +}; + +/** + * Scriptable storage for 64-bit integers + */ + +[scriptable, uuid(e3cb0ff0-4a1c-11d3-9890-006008962422)] +interface nsISupportsPRInt64 : nsISupportsPrimitive +{ + attribute int64_t data; + string toString(); +}; + +/** + * Scriptable storage for floating point numbers + */ + +[scriptable, uuid(abeaa390-4ac0-11d3-baea-00805f8a5dd7)] +interface nsISupportsFloat : nsISupportsPrimitive +{ + attribute float data; + string toString(); +}; + +/** + * Scriptable storage for doubles + */ + +[scriptable, uuid(b32523a0-4ac0-11d3-baea-00805f8a5dd7)] +interface nsISupportsDouble : nsISupportsPrimitive +{ + attribute double data; + string toString(); +}; + +/** + * Scriptable storage for generic pointers + */ + +[scriptable, uuid(464484f0-568d-11d3-baf8-00805f8a5dd7)] +interface nsISupportsVoid : nsISupportsPrimitive +{ + [noscript] attribute voidPtr data; + string toString(); +}; + +/** + * Scriptable storage for other XPCOM objects + */ + +[scriptable, 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 000000000..ef5528463 --- /dev/null +++ b/xpcom/ds/nsIVariant.idl @@ -0,0 +1,155 @@ +/* -*- 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" + +[scriptable,uuid(4d12e540-83d7-11d5-90ed-0010a4e73d9a)] +interface nsIDataType : nsISupports +{ + // These MUST match the declarations in xpt_struct.h. + // Otherwise the world is likely to explode. + // From xpt_struct.h ... + const uint16_t VTYPE_INT8 = 0; // TD_INT8 = 0, + const uint16_t VTYPE_INT16 = 1; // TD_INT16 = 1, + const uint16_t VTYPE_INT32 = 2; // TD_INT32 = 2, + const uint16_t VTYPE_INT64 = 3; // TD_INT64 = 3, + const uint16_t VTYPE_UINT8 = 4; // TD_UINT8 = 4, + const uint16_t VTYPE_UINT16 = 5; // TD_UINT16 = 5, + const uint16_t VTYPE_UINT32 = 6; // TD_UINT32 = 6, + const uint16_t VTYPE_UINT64 = 7; // TD_UINT64 = 7, + const uint16_t VTYPE_FLOAT = 8; // TD_FLOAT = 8, + const uint16_t VTYPE_DOUBLE = 9; // TD_DOUBLE = 9, + const uint16_t VTYPE_BOOL = 10; // TD_BOOL = 10, + const uint16_t VTYPE_CHAR = 11; // TD_CHAR = 11, + const uint16_t VTYPE_WCHAR = 12; // TD_WCHAR = 12, + const uint16_t VTYPE_VOID = 13; // TD_VOID = 13, + const uint16_t VTYPE_ID = 14; // TD_PNSIID = 14, + const uint16_t VTYPE_DOMSTRING = 15; // TD_DOMSTRING = 15, + const uint16_t VTYPE_CHAR_STR = 16; // TD_PSTRING = 16, + const uint16_t VTYPE_WCHAR_STR = 17; // TD_PWSTRING = 17, + const uint16_t VTYPE_INTERFACE = 18; // TD_INTERFACE_TYPE = 18, + const uint16_t VTYPE_INTERFACE_IS = 19; // TD_INTERFACE_IS_TYPE = 19, + const uint16_t VTYPE_ARRAY = 20; // TD_ARRAY = 20, + const uint16_t VTYPE_STRING_SIZE_IS = 21; // TD_PSTRING_SIZE_IS = 21, + const uint16_t VTYPE_WSTRING_SIZE_IS = 22; // TD_PWSTRING_SIZE_IS = 22, + const uint16_t VTYPE_UTF8STRING = 23; // TD_UTF8STRING = 23, + const uint16_t VTYPE_CSTRING = 24; // TD_CSTRING = 24, + const uint16_t VTYPE_ASTRING = 25; // TD_ASTRING = 25, + const uint16_t VTYPE_EMPTY_ARRAY = 254; + const uint16_t 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, uuid(81e4c2de-acac-4ad6-901a-b5fb1b851a0d)] +interface nsIVariant : nsISupports +{ + [noscript] 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] DOMString getAsDOMString(); + [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, 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 setAsDOMString(in DOMString 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 000000000..25be0995b --- /dev/null +++ b/xpcom/ds/nsIWindowsRegKey.idl @@ -0,0 +1,332 @@ +/* -*- 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" + +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 000000000..e916b7ccd --- /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 000000000..50f093905 --- /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/nsMathUtils.h b/xpcom/ds/nsMathUtils.h new file mode 100644 index 000000000..b10b8144e --- /dev/null +++ b/xpcom/ds/nsMathUtils.h @@ -0,0 +1,127 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 + +#ifdef 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_WIN32) && 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_WIN32 && _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); +#elif defined(XP_DARWIN) + // Darwin has deprecated |finite| and recommends |isfinite|. The former is + // not present in the iOS SDK. + return std::isfinite(aNum); +#else + return finite(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 000000000..fef8831b8 --- /dev/null +++ b/xpcom/ds/nsObserverList.cpp @@ -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/. */ + +#include "nsObserverList.h" + +#include "nsAutoPtr.h" +#include "nsCOMArray.h" +#include "nsISimpleEnumerator.h" +#include "xpcpublic.h" + +nsresult +nsObserverList::AddObserver(nsIObserver* anObserver, bool ownsWeak) +{ + NS_ASSERTION(anObserver, "Null input"); + + if (!ownsWeak) { + ObserverRef* o = mObservers.AppendElement(anObserver); + if (!o) { + return NS_ERROR_OUT_OF_MEMORY; + } + + return NS_OK; + } + + nsCOMPtr weak = do_GetWeakReference(anObserver); + if (!weak) { + return NS_NOINTERFACE; + } + + ObserverRef* o = mObservers.AppendElement(weak); + if (!o) { + return NS_ERROR_OUT_OF_MEMORY; + } + + return NS_OK; +} + +nsresult +nsObserverList::RemoveObserver(nsIObserver* anObserver) +{ + NS_ASSERTION(anObserver, "Null input"); + + if (mObservers.RemoveElement(static_cast(anObserver))) { + return NS_OK; + } + + nsCOMPtr observerRef = do_GetWeakReference(anObserver); + if (!observerRef) { + return NS_ERROR_FAILURE; + } + + if (!mObservers.RemoveElement(observerRef)) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +void +nsObserverList::GetObserverList(nsISimpleEnumerator** anEnumerator) +{ + RefPtr e(new nsObserverEnumerator(this)); + e.forget(anEnumerator); +} + +void +nsObserverList::FillObserverArray(nsCOMArray& aArray) +{ + aArray.SetCapacity(mObservers.Length()); + + nsTArray observers(mObservers); + + for (int32_t i = observers.Length() - 1; i >= 0; --i) { + if (observers[i].isWeakRef) { + nsCOMPtr o(do_QueryReferent(observers[i].asWeak())); + if (o) { + aArray.AppendObject(o); + } else { + // the object has gone away, remove the weakref + mObservers.RemoveElement(observers[i].asWeak()); + } + } else { + aArray.AppendObject(observers[i].asObserver()); + } + } +} + +void +nsObserverList::AppendStrongObservers(nsCOMArray& aArray) +{ + aArray.SetCapacity(aArray.Length() + mObservers.Length()); + + for (int32_t i = mObservers.Length() - 1; i >= 0; --i) { + if (!mObservers[i].isWeakRef) { + aArray.AppendObject(mObservers[i].asObserver()); + } + } +} + +void +nsObserverList::NotifyObservers(nsISupports* aSubject, + const char* aTopic, + const char16_t* someData) +{ + nsCOMArray observers; + FillObserverArray(observers); + + for (int32_t i = 0; i < observers.Count(); ++i) { + observers[i]->Observe(aSubject, aTopic, someData); + } +} + +NS_IMPL_ISUPPORTS(nsObserverEnumerator, nsISimpleEnumerator) + +nsObserverEnumerator::nsObserverEnumerator(nsObserverList* aObserverList) + : mIndex(0) +{ + aObserverList->FillObserverArray(mObservers); +} + +NS_IMETHODIMP +nsObserverEnumerator::HasMoreElements(bool* aResult) +{ + *aResult = (mIndex < mObservers.Count()); + return NS_OK; +} + +NS_IMETHODIMP +nsObserverEnumerator::GetNext(nsISupports** aResult) +{ + if (mIndex == mObservers.Count()) { + NS_ERROR("Enumerating after HasMoreElements returned false."); + return NS_ERROR_UNEXPECTED; + } + + 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 000000000..90a685ea6 --- /dev/null +++ b/xpcom/ds/nsObserverList.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 nsObserverList_h___ +#define nsObserverList_h___ + +#include "nsISupports.h" +#include "nsTArray.h" +#include "nsCOMPtr.h" +#include "nsCOMArray.h" +#include "nsIObserver.h" +#include "nsIWeakReference.h" +#include "nsHashKeys.h" +#include "nsISimpleEnumerator.h" +#include "mozilla/Attributes.h" + +struct ObserverRef +{ + ObserverRef(const ObserverRef& aO) : isWeakRef(aO.isWeakRef), ref(aO.ref) {} + explicit ObserverRef(nsIObserver* aObserver) : isWeakRef(false), ref(aObserver) {} + explicit ObserverRef(nsIWeakReference* aWeak) : isWeakRef(true), ref(aWeak) {} + + bool isWeakRef; + nsCOMPtr ref; + + nsIObserver* asObserver() + { + NS_ASSERTION(!isWeakRef, "Isn't a strong ref."); + return static_cast((nsISupports*)ref); + } + + nsIWeakReference* asWeak() + { + NS_ASSERTION(isWeakRef, "Isn't a weak ref."); + return static_cast((nsISupports*)ref); + } + + bool operator==(nsISupports* aRhs) const { return ref == aRhs; } +}; + +class nsObserverList : public nsCharPtrHashKey +{ + friend class nsObserverService; + +public: + explicit nsObserverList(const char* aKey) : nsCharPtrHashKey(aKey) + { + MOZ_COUNT_CTOR(nsObserverList); + } + + ~nsObserverList() + { + MOZ_COUNT_DTOR(nsObserverList); + } + + MOZ_MUST_USE nsresult AddObserver(nsIObserver* aObserver, bool aOwnsWeak); + MOZ_MUST_USE nsresult RemoveObserver(nsIObserver* aObserver); + + void NotifyObservers(nsISupports* aSubject, + const char* aTopic, + const char16_t* aSomeData); + void GetObserverList(nsISimpleEnumerator** aEnumerator); + + // Fill an array with the observers of this category. + // The array is filled in last-added-first order. + void FillObserverArray(nsCOMArray& aArray); + + // Like FillObserverArray(), but only for strongly held observers. + void AppendStrongObservers(nsCOMArray& aArray); + +private: + nsTArray mObservers; +}; + +class nsObserverEnumerator final : public nsISimpleEnumerator +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSISIMPLEENUMERATOR + + explicit nsObserverEnumerator(nsObserverList* aObserverList); + +private: + ~nsObserverEnumerator() {} + + 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 000000000..23cc54fa7 --- /dev/null +++ b/xpcom/ds/nsObserverService.cpp @@ -0,0 +1,312 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. 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 "nsAutoPtr.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/net/NeckoCommon.h" +#include "mozilla/Services.h" + +#define NOTIFY_GLOBAL_OBSERVERS + +// 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; + + nsTArray& observers = observerList->mObservers; + for (uint32_t i = 0; i < observers.Length(); i++) { + if (observers[i].isWeakRef) { + nsCOMPtr observerRef( + do_QueryReferent(observers[i].asWeak())); + if (observerRef) { + 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 */ EmptyCString(), + suspectPath, KIND_OTHER, UNITS_COUNT, suspect.mReferentCount, + NS_LITERAL_CSTRING("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() +{ + UnregisterWeakMemoryReporter(this); + + mShuttingDown = true; + + mObserverTopicTable.Clear(); +} + +nsresult +nsObserverService::Create(nsISupports* aOuter, const nsIID& aIID, + void** aInstancePtr) +{ + LOG(("nsObserverService::Create()")); + + RefPtr os = new nsObserverService(); + + if (!os) { + return NS_ERROR_OUT_OF_MEMORY; + } + + // 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(os, &nsObserverService::RegisterReporter)); + + return os->QueryInterface(aIID, aInstancePtr); +} + +#define NS_ENSURE_VALIDCALL \ + 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; \ + } + +NS_IMETHODIMP +nsObserverService::AddObserver(nsIObserver* aObserver, const char* aTopic, + bool aOwnsWeak) +{ + LOG(("nsObserverService::AddObserver(%p: %s)", + (void*)aObserver, aTopic)); + + NS_ENSURE_VALIDCALL + if (NS_WARN_IF(!aObserver) || NS_WARN_IF(!aTopic)) { + return NS_ERROR_INVALID_ARG; + } + + // Specifically allow http-on-opening-request in the child process; + // see bug 1269765. + if (mozilla::net::IsNeckoChild() && !strncmp(aTopic, "http-on-", 8) && + strcmp(aTopic, "http-on-opening-request")) { + nsCOMPtr console(do_GetService(NS_CONSOLESERVICE_CONTRACTID)); + nsCOMPtr error(do_CreateInstance(NS_SCRIPTERROR_CONTRACTID)); + error->Init(NS_LITERAL_STRING("http-on-* observers only work in the parent process"), + EmptyString(), EmptyString(), 0, 0, + nsIScriptError::warningFlag, "chrome javascript"); + console->LogMessage(error); + + return NS_ERROR_NOT_IMPLEMENTED; + } + + 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)); + NS_ENSURE_VALIDCALL + 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; + } + + /* This death grip is to protect against stupid consumers who call + RemoveObserver from their Destructor, see bug 485834/bug 325392. */ + nsCOMPtr kungFuDeathGrip(aObserver); + return observerList->RemoveObserver(aObserver); +} + +NS_IMETHODIMP +nsObserverService::EnumerateObservers(const char* aTopic, + nsISimpleEnumerator** anEnumerator) +{ + NS_ENSURE_VALIDCALL + 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)); + + NS_ENSURE_VALIDCALL + if (NS_WARN_IF(!aTopic)) { + return NS_ERROR_INVALID_ARG; + } + + nsObserverList* observerList = mObserverTopicTable.GetEntry(aTopic); + if (observerList) { + observerList->NotifyObservers(aSubject, aTopic, aSomeData); + } + +#ifdef NOTIFY_GLOBAL_OBSERVERS + observerList = mObserverTopicTable.GetEntry("*"); + if (observerList) { + observerList->NotifyObservers(aSubject, aTopic, aSomeData); + } +#endif + + return NS_OK; +} + +NS_IMETHODIMP +nsObserverService::UnmarkGrayStrongObservers() +{ + NS_ENSURE_VALIDCALL + + 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; +} diff --git a/xpcom/ds/nsObserverService.h b/xpcom/ds/nsObserverService.h new file mode 100644 index 000000000..fa0d9b9d8 --- /dev/null +++ b/xpcom/ds/nsObserverService.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 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 nsIMemoryReporter; + +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(); + + static MOZ_MUST_USE nsresult Create(nsISupports* aOuter, 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(); + + 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 000000000..ea925874c --- /dev/null +++ b/xpcom/ds/nsPersistentProperties.cpp @@ -0,0 +1,666 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. 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 "nsAutoPtr.h" + +#define PL_ARENA_CONST_ALIGN_MASK 3 +#include "nsPersistentProperties.h" +#include "nsIProperties.h" + +struct PropertyTableEntry : public PLDHashEntryHdr +{ + // both of these are arena-allocated + const char* mKey; + const char16_t* mValue; +}; + +static char16_t* +ArenaStrdup(const nsAFlatString& aString, PLArenaPool* aArena) +{ + void* mem; + // add one to include the null terminator + int32_t len = (aString.Length() + 1) * sizeof(char16_t); + PL_ARENA_ALLOCATE(mem, aArena, len); + NS_ASSERTION(mem, "Couldn't allocate space!\n"); + if (mem) { + memcpy(mem, aString.get(), len); + } + return static_cast(mem); +} + +static char* +ArenaStrdup(const nsAFlatCString& aString, PLArenaPool* aArena) +{ + void* mem; + // add one to include the null terminator + int32_t len = (aString.Length() + 1) * sizeof(char); + PL_ARENA_ALLOCATE(mem, aArena, len); + NS_ASSERTION(mem, "Couldn't allocate space!\n"); + if (mem) { + memcpy(mem, aString.get(), len); + } + return static_cast(mem); +} + +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) + : mHaveMultiLine(false) + , mState(eParserState_AwaitingKey) + , 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; + } + MOZ_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) +{ + PL_INIT_ARENA_POOL(&mArena, "PersistentPropertyArena", 2048); +} + +nsPersistentProperties::~nsPersistentProperties() +{ + PL_FinishArenaPool(&mArena); +} + +nsresult +nsPersistentProperties::Create(nsISupports* aOuter, REFNSIID aIID, + void** aResult) +{ + if (aOuter) { + return NS_ERROR_NO_AGGREGATION; + } + RefPtr props = new nsPersistentProperties(); + return props->QueryInterface(aIID, aResult); +} + +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 nsAFlatCString& 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(PromiseFlatString(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 nsAFlatCString& 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); +} + +//////////////////////////////////////////////////////////////////////////////// +// 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(uint32_t* aCount, char*** aKeys) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +//////////////////////////////////////////////////////////////////////////////// +// PropertyElement +//////////////////////////////////////////////////////////////////////////////// + +nsresult +nsPropertyElement::Create(nsISupports* aOuter, REFNSIID aIID, void** aResult) +{ + if (aOuter) { + return NS_ERROR_NO_AGGREGATION; + } + 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 000000000..6c9e9cb51 --- /dev/null +++ b/xpcom/ds/nsPersistentProperties.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 nsPersistentProperties_h___ +#define nsPersistentProperties_h___ + +#include "nsIPersistentProperties2.h" +#include "PLDHashTable.h" +#include "plarena.h" +#include "nsString.h" +#include "nsCOMPtr.h" +#include "mozilla/Attributes.h" + +class nsIUnicharInputStream; + +class nsPersistentProperties final : public nsIPersistentProperties +{ +public: + nsPersistentProperties(); + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIPROPERTIES + NS_DECL_NSIPERSISTENTPROPERTIES + + static MOZ_MUST_USE nsresult + Create(nsISupports* aOuter, REFNSIID aIID, void** aResult); + +private: + ~nsPersistentProperties(); + +protected: + nsCOMPtr mIn; + + PLDHashTable mTable; + PLArenaPool mArena; +}; + +class nsPropertyElement final : public nsIPropertyElement +{ +public: + nsPropertyElement() + { + } + + nsPropertyElement(const nsACString& aKey, const nsAString& aValue) + : mKey(aKey) + , mValue(aValue) + { + } + + NS_DECL_ISUPPORTS + NS_DECL_NSIPROPERTYELEMENT + + static nsresult Create(nsISupports* aOuter, REFNSIID aIID, void** aResult); + +private: + ~nsPropertyElement() {} + +protected: + nsCString mKey; + nsString mValue; +}; + +#endif /* nsPersistentProperties_h___ */ diff --git a/xpcom/ds/nsProperties.cpp b/xpcom/ds/nsProperties.cpp new file mode 100644 index 000000000..3a73ac7da --- /dev/null +++ b/xpcom/ds/nsProperties.cpp @@ -0,0 +1,99 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. 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_AGGREGATED(nsProperties) +NS_INTERFACE_MAP_BEGIN_AGGREGATED(nsProperties) + NS_INTERFACE_MAP_ENTRY(nsIProperties) +NS_INTERFACE_MAP_END + +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; + } + Put(prop, value); + return NS_OK; +} + +NS_IMETHODIMP +nsProperties::Undefine(const char* prop) +{ + if (NS_WARN_IF(!prop)) { + return NS_ERROR_INVALID_ARG; + } + + nsCOMPtr value; + if (!nsProperties_HashBase::Get(prop, getter_AddRefs(value))) { + return NS_ERROR_FAILURE; + } + + Remove(prop); + return NS_OK; +} + +NS_IMETHODIMP +nsProperties::Has(const char* prop, bool* result) +{ + if (NS_WARN_IF(!prop)) { + return NS_ERROR_INVALID_ARG; + } + + nsCOMPtr value; + *result = nsProperties_HashBase::Get(prop, getter_AddRefs(value)); + return NS_OK; +} + +NS_IMETHODIMP +nsProperties::GetKeys(uint32_t* aCount, char*** aKeys) +{ + if (NS_WARN_IF(!aCount) || NS_WARN_IF(!aKeys)) { + return NS_ERROR_INVALID_ARG; + } + + uint32_t count = Count(); + char** keys = (char**)moz_xmalloc(count * sizeof(char*)); + uint32_t j = 0; + + for (auto iter = this->Iter(); !iter.Done(); iter.Next()) { + const char* key = iter.Key(); + keys[j] = strdup(key); + + if (!keys[j]) { + // Free 'em all + for (uint32_t i = 0; i < j; i++) { + free(keys[i]); + } + free(keys); + return NS_ERROR_OUT_OF_MEMORY; + } + j++; + } + + *aCount = count; + *aKeys = keys; + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/xpcom/ds/nsProperties.h b/xpcom/ds/nsProperties.h new file mode 100644 index 000000000..6949017dd --- /dev/null +++ b/xpcom/ds/nsProperties.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 nsProperties_h___ +#define nsProperties_h___ + +#include "nsIProperties.h" +#include "nsInterfaceHashtable.h" +#include "nsHashKeys.h" +#include "nsAgg.h" +#include "mozilla/Attributes.h" + +#define NS_PROPERTIES_CID \ +{ /* 4de2bc90-b1bf-11d3-93b6-00104ba0fd40 */ \ + 0x4de2bc90, \ + 0xb1bf, \ + 0x11d3, \ + {0x93, 0xb6, 0x00, 0x10, 0x4b, 0xa0, 0xfd, 0x40} \ +} + +typedef nsInterfaceHashtable nsProperties_HashBase; + +class nsProperties final + : public nsIProperties + , public nsProperties_HashBase +{ +public: + NS_DECL_AGGREGATED + NS_DECL_NSIPROPERTIES + + explicit nsProperties(nsISupports *aOuter) { NS_INIT_AGGREGATED(aOuter); } + ~nsProperties() {} +}; + +#endif /* nsProperties_h___ */ diff --git a/xpcom/ds/nsStaticAtom.h b/xpcom/ds/nsStaticAtom.h new file mode 100644 index 000000000..7e31623db --- /dev/null +++ b/xpcom/ds/nsStaticAtom.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 nsStaticAtom_h__ +#define nsStaticAtom_h__ + +#include "nsIAtom.h" +#include "nsStringBuffer.h" + +#define NS_STATIC_ATOM(buffer_name, atom_ptr) \ + { (nsStringBuffer*) &buffer_name, atom_ptr } + +#define NS_STATIC_ATOM_BUFFER(buffer_name, str_data) \ + static nsFakeStringBuffer buffer_name = \ + { 1, sizeof(str_data) * sizeof(char16_t), (u"" str_data) }; + +/** + * Holds data used to initialize large number of atoms during startup. Use + * the above macros to initialize these structs. They should never be accessed + * directly other than from AtomTable.cpp + */ +struct nsStaticAtom +{ + // mStringBuffer points to the string buffer for a permanent atom, and is + // therefore safe as a non-owning reference. + nsStringBuffer* MOZ_NON_OWNING_REF mStringBuffer; + nsIAtom** mAtom; +}; + +/** + * This is a struct with the same binary layout as a nsStringBuffer. + */ +template +struct nsFakeStringBuffer +{ + int32_t mRefCnt; + uint32_t mSize; + char16_t mStringData[size]; +}; + +// Register an array of static atoms with the atom table +template +void +NS_RegisterStaticAtoms(const nsStaticAtom (&aAtoms)[N]) +{ + extern void RegisterStaticAtoms(const nsStaticAtom*, uint32_t aAtomCount); + RegisterStaticAtoms(aAtoms, N); +} + +#endif diff --git a/xpcom/ds/nsStaticNameTable.cpp b/xpcom/ds/nsStaticNameTable.cpp new file mode 100644 index 000000000..26bd172a7 --- /dev/null +++ b/xpcom/ds/nsStaticNameTable.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/. */ + +/* Class to manage lookup of static names in a table. */ + +#include "nsCRT.h" + +#include "nscore.h" +#include "mozilla/HashFunctions.h" +#include "nsISupportsImpl.h" + +#define PL_ARENA_CONST_ALIGN_MASK 3 +#include "nsStaticNameTable.h" + +using namespace mozilla; + +struct NameTableKey +{ + NameTableKey(const nsDependentCString aNameArray[], + const nsAFlatCString* aKeyStr) + : mNameArray(aNameArray) + , mIsUnichar(false) + { + mKeyStr.m1b = aKeyStr; + } + + NameTableKey(const nsDependentCString aNameArray[], + const nsAFlatString* aKeyStr) + : mNameArray(aNameArray) + , mIsUnichar(true) + { + mKeyStr.m2b = aKeyStr; + } + + const nsDependentCString* mNameArray; + union + { + const nsAFlatCString* m1b; + const nsAFlatString* 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(nsCRT::IsAscii(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; + } +#ifdef DEBUG + mNameTable.MarkImmutable(); +#endif +} + +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) +{ + NS_ASSERTION(mNameArray, "not inited"); + + const nsAFlatCString& 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) +{ + NS_ASSERTION(mNameArray, "not inited"); + + const nsAFlatString& str = PromiseFlatString(aName); + + NameTableKey key(mNameArray, &str); + auto entry = static_cast(mNameTable.Search(&key)); + + return entry ? entry->mIndex : nsStaticCaseInsensitiveNameTable::NOT_FOUND; +} + +const nsAFlatCString& +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 000000000..0b9df3039 --- /dev/null +++ b/xpcom/ds/nsStaticNameTable.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/. */ + +/* 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); + int32_t Lookup(const nsAString& aName); + const nsAFlatCString& 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 000000000..bdcd53e1a --- /dev/null +++ b/xpcom/ds/nsStringEnumerator.cpp @@ -0,0 +1,262 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. 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 "nsISimpleEnumerator.h" +#include "nsSupportsPrimitives.h" +#include "mozilla/Attributes.h" +#include "nsTArray.h" + +// +// nsStringEnumerator +// + +class nsStringEnumerator final + : public nsIStringEnumerator + , public nsIUTF8StringEnumerator + , public nsISimpleEnumerator +{ +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 + NS_DECL_NSIUTF8STRINGENUMERATOR + + // have to declare nsIStringEnumerator manually, because of + // overlapping method names + NS_IMETHOD GetNext(nsAString& aResult) override; + NS_DECL_NSISIMPLEENUMERATOR + +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(nsStringEnumerator, + nsIStringEnumerator, + nsIUTF8StringEnumerator, + nsISimpleEnumerator) + +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 (mIsUnicode) { + nsSupportsString* stringImpl = new nsSupportsString(); + if (!stringImpl) { + return NS_ERROR_OUT_OF_MEMORY; + } + + stringImpl->SetData(mArray->ElementAt(mIndex++)); + *aResult = stringImpl; + } else { + nsSupportsCString* cstringImpl = new nsSupportsCString(); + if (!cstringImpl) { + return NS_ERROR_OUT_OF_MEMORY; + } + + 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; +} + +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 000000000..226afd7f4 --- /dev/null +++ b/xpcom/ds/nsStringEnumerator.h @@ -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 "nsIStringEnumerator.h" +#include "nsStringFwd.h" +#include "nsTArrayForwardDeclare.h" + +// 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); +// +MOZ_MUST_USE nsresult +NS_NewStringEnumerator(nsIStringEnumerator** aResult, + const nsTArray* aArray, + nsISupports* aOwner); +MOZ_MUST_USE nsresult +NS_NewUTF8StringEnumerator(nsIUTF8StringEnumerator** aResult, + const nsTArray* aArray); + +MOZ_MUST_USE 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); +MOZ_MUST_USE nsresult +NS_NewAdoptingStringEnumerator(nsIStringEnumerator** aResult, + nsTArray* aArray); + +MOZ_MUST_USE 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); +// } +// +MOZ_MUST_USE nsresult +NS_NewUTF8StringEnumerator(nsIUTF8StringEnumerator** aResult, + const nsTArray* aArray, + nsISupports* aOwner); diff --git a/xpcom/ds/nsSupportsArray.cpp b/xpcom/ds/nsSupportsArray.cpp new file mode 100644 index 000000000..67de8ae16 --- /dev/null +++ b/xpcom/ds/nsSupportsArray.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 +#include + +#include "nsArrayEnumerator.h" +#include "nsIObjectInputStream.h" +#include "nsIObjectOutputStream.h" +#include "nsSupportsArray.h" +#include "nsSupportsArrayEnumerator.h" + +// Disable deprecation warnings generated by nsISupportsArray and associated +// classes. +#if defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#elif defined(_MSC_VER) +#pragma warning (push) +#pragma warning (disable : 4996) +#endif + +nsresult +nsQueryElementAt::operator()(const nsIID& aIID, void** aResult) const +{ + nsresult status = + mCollection ? mCollection->QueryElementAt(mIndex, aIID, aResult) : + NS_ERROR_NULL_POINTER; + + if (mErrorPtr) { + *mErrorPtr = status; + } + + return status; +} + +nsSupportsArray::nsSupportsArray() +{ +} + +nsSupportsArray::~nsSupportsArray() +{ + Clear(); +} + +nsresult +nsSupportsArray::Create(nsISupports* aOuter, REFNSIID aIID, void** aResult) +{ + if (aOuter) { + return NS_ERROR_NO_AGGREGATION; + } + + nsCOMPtr it = new nsSupportsArray(); + + return it->QueryInterface(aIID, aResult); +} + +NS_IMPL_ISUPPORTS(nsSupportsArray, nsIArray, nsISupportsArray, nsICollection, + nsISerializable) + +NS_IMETHODIMP +nsSupportsArray::Read(nsIObjectInputStream* aStream) +{ + nsresult rv; + + uint32_t newArraySize; + rv = aStream->Read32(&newArraySize); + if (NS_FAILED(rv)) { + return rv; + } + + uint32_t count; + rv = aStream->Read32(&count); + if (NS_FAILED(rv)) { + return rv; + } + + NS_ASSERTION(count <= newArraySize, "overlarge mCount!"); + if (count > newArraySize) { + count = newArraySize; + } + + // Don't clear out our array until we know we have enough space for the new + // one and have successfully copied everything out of the stream. + nsCOMArray tmp; + tmp.SetCapacity(newArraySize); + tmp.SetCount(count); + + auto elems = tmp.Elements(); + for (uint32_t i = 0; i < count; i++) { + rv = aStream->ReadObject(true, &elems[i]); + if (NS_FAILED(rv)) { + return rv; + } + } + + // Now clear out existing refs and replace with the new array. + mArray.Clear(); + mArray.SwapElements(tmp); + + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsArray::Write(nsIObjectOutputStream* aStream) +{ + nsresult rv; + + rv = aStream->Write32(mArray.Capacity()); + if (NS_FAILED(rv)) { + return rv; + } + + rv = aStream->Write32(mArray.Length()); + if (NS_FAILED(rv)) { + return rv; + } + + for (size_t i = 0; i < mArray.Length(); i++) { + rv = aStream->WriteObject(mArray[i], true); + if (NS_FAILED(rv)) { + return rv; + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsArray::GetElementAt(uint32_t aIndex, nsISupports** aOutPtr) +{ + nsCOMPtr elm = mArray.SafeElementAt(aIndex); + elm.forget(aOutPtr); + return NS_OK; +} + +NS_IMETHODIMP_(int32_t) +nsSupportsArray::IndexOf(const nsISupports* aPossibleElement) +{ + // nsCOMArray takes a non-const param, but it just passes through to + // nsTArray which takes a const param. + return mArray.IndexOf(const_cast(aPossibleElement)); +} + +NS_IMETHODIMP_(bool) +nsSupportsArray::InsertElementAt(nsISupports* aElement, uint32_t aIndex) +{ + return mArray.InsertObjectAt(aElement, aIndex); +} + +NS_IMETHODIMP_(bool) +nsSupportsArray::ReplaceElementAt(nsISupports* aElement, uint32_t aIndex) +{ + // nsCOMArray::ReplaceObjectAt will grow the array if necessary. Instead + // we do the bounds check and only replace if it's in range. + if (aIndex < mArray.Length()) { + mArray.ReplaceElementAt(aIndex, aElement); + return true; + } + return false; +} + +NS_IMETHODIMP_(bool) +nsSupportsArray::RemoveElementAt(uint32_t aIndex) +{ + return mArray.RemoveObjectAt(aIndex); +} + +NS_IMETHODIMP +nsSupportsArray::RemoveElement(nsISupports* aElement) +{ + return mArray.RemoveObject(aElement) ? NS_OK : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsSupportsArray::Clear(void) +{ + mArray.Clear(); + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsArray::DeprecatedEnumerate(nsIEnumerator** aResult) +{ + RefPtr e = new nsSupportsArrayEnumerator(this); + e.forget(aResult); + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsArray::Clone(nsISupportsArray** aResult) +{ + nsCOMPtr newArray; + nsresult rv = NS_NewISupportsArray(getter_AddRefs(newArray)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + for (size_t i = 0; i < mArray.Length(); i++) { + // AppendElement does an odd cast of bool to nsresult, we just cast back + // here. + if (!(bool)newArray->AppendElement(mArray[i])) { + return NS_ERROR_OUT_OF_MEMORY; + } + } + + newArray.forget(aResult); + return NS_OK; +} + +nsresult +NS_NewISupportsArray(nsISupportsArray** aInstancePtrResult) +{ + nsresult rv; + rv = nsSupportsArray::Create(nullptr, NS_GET_IID(nsISupportsArray), + (void**)aInstancePtrResult); + return rv; +} + +/** + * nsIArray adapters. + */ +NS_IMETHODIMP +nsSupportsArray::GetLength(uint32_t* aLength) { + return Count(aLength); +} + +NS_IMETHODIMP +nsSupportsArray::QueryElementAt(uint32_t aIndex, const nsIID& aIID, void** aResult) +{ + nsISupports* element = mArray.SafeElementAt(aIndex); + if (element) { + return element->QueryInterface(aIID, aResult); + } + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsSupportsArray::IndexOf(uint32_t aStartIndex, nsISupports* aElement, uint32_t* aResult) +{ + int32_t idx = mArray.IndexOf(aElement, aStartIndex); + if (idx < 0) { + return NS_ERROR_FAILURE; + } + + *aResult = static_cast(idx); + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsArray::Enumerate(nsISimpleEnumerator** aResult) +{ + return NS_NewArrayEnumerator(aResult, this); +} + +#if defined(__GNUC__) +#pragma GCC diagnostic pop +#elif defined(_MSC_VER) +#pragma warning (pop) +#endif + diff --git a/xpcom/ds/nsSupportsArray.h b/xpcom/ds/nsSupportsArray.h new file mode 100644 index 000000000..eed611104 --- /dev/null +++ b/xpcom/ds/nsSupportsArray.h @@ -0,0 +1,107 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 nsSupportsArray_h__ +#define nsSupportsArray_h__ + +#include "nsIArray.h" +#include "nsCOMArray.h" +#include "mozilla/Attributes.h" + + +// Disable deprecation warnings generated by nsISupportsArray and associated +// classes. +#if defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#elif defined(_MSC_VER) +#pragma warning (push) +#pragma warning (disable : 4996) +#endif + +#include "nsISupportsArray.h" + +class nsSupportsArray final : public nsISupportsArray, + public nsIArray +{ + ~nsSupportsArray(void); // nonvirtual since we're not subclassed + +public: + nsSupportsArray(void); + + static MOZ_MUST_USE nsresult + Create(nsISupports* aOuter, REFNSIID aIID, void** aResult); + + NS_DECL_THREADSAFE_ISUPPORTS + + NS_DECL_NSISERIALIZABLE + + // nsICollection methods: + NS_IMETHOD Count(uint32_t* aResult) override + { + *aResult = mArray.Length(); + return NS_OK; + } + NS_IMETHOD GetElementAt(uint32_t aIndex, nsISupports** aResult) override; + MOZ_MUST_USE NS_IMETHOD + SetElementAt(uint32_t aIndex, nsISupports* aValue) override + { + return ReplaceElementAt(aValue, aIndex) ? NS_OK : NS_ERROR_FAILURE; + } + MOZ_MUST_USE NS_IMETHOD AppendElement(nsISupports* aElement) override + { + // XXX Invalid cast of bool to nsresult (bug 778110) + return (nsresult)InsertElementAt(aElement, mArray.Length())/* ? NS_OK : NS_ERROR_FAILURE*/; + } + // XXX this is badly named - should be RemoveFirstElement + MOZ_MUST_USE NS_IMETHOD RemoveElement(nsISupports* aElement) override; + NS_IMETHOD DeprecatedEnumerate(nsIEnumerator** aResult) override; + NS_IMETHOD Clear(void) override; + + // nsISupportsArray methods: + NS_IMETHOD_(int32_t) IndexOf(const nsISupports* aPossibleElement) override; + + NS_IMETHOD GetIndexOf(nsISupports* aPossibleElement, int32_t* aResult) override + { + *aResult = IndexOf(aPossibleElement); + return NS_OK; + } + + MOZ_MUST_USE NS_IMETHOD_(bool) + InsertElementAt(nsISupports* aElement, uint32_t aIndex) override; + + MOZ_MUST_USE NS_IMETHOD_(bool) + ReplaceElementAt(nsISupports* aElement, uint32_t aIndex) override; + + MOZ_MUST_USE NS_IMETHOD_(bool) + RemoveElementAt(uint32_t aIndex) override; + + MOZ_MUST_USE NS_IMETHOD DeleteElementAt(uint32_t aIndex) override + { + return (RemoveElementAt(aIndex) ? NS_OK : NS_ERROR_FAILURE); + } + + MOZ_MUST_USE NS_IMETHOD Clone(nsISupportsArray** aResult) override; + + /** + * nsIArray adapters. + */ + NS_DECL_NSIARRAY + +private: + // Copy constructors are not allowed + explicit nsSupportsArray(const nsISupportsArray& aOther); + + nsCOMArray mArray; +}; + +#if defined(__GNUC__) +#pragma GCC diagnostic pop +#elif defined(_MSC_VER) +#pragma warning (pop) +#endif + +#endif // nsSupportsArray_h__ diff --git a/xpcom/ds/nsSupportsArrayEnumerator.cpp b/xpcom/ds/nsSupportsArrayEnumerator.cpp new file mode 100644 index 000000000..3eb6a7a58 --- /dev/null +++ b/xpcom/ds/nsSupportsArrayEnumerator.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 "nsSupportsArrayEnumerator.h" + +// Disable deprecation warnings generated by nsISupportsArray and associated +// classes. +#if defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#elif defined(_MSC_VER) +#pragma warning (push) +#pragma warning (disable : 4996) +#endif + +#include "nsISupportsArray.h" + +nsSupportsArrayEnumerator::nsSupportsArrayEnumerator(nsISupportsArray* array) + : mArray(array) + , mCursor(0) +{ + NS_ASSERTION(array, "null array"); +} + +nsSupportsArrayEnumerator::~nsSupportsArrayEnumerator() +{ +} + +NS_IMPL_ISUPPORTS(nsSupportsArrayEnumerator, nsIBidirectionalEnumerator, + nsIEnumerator) + +NS_IMETHODIMP +nsSupportsArrayEnumerator::First() +{ + mCursor = 0; + uint32_t cnt; + nsresult rv = mArray->Count(&cnt); + if (NS_FAILED(rv)) { + return rv; + } + int32_t end = (int32_t)cnt; + if (mCursor < end) { + return NS_OK; + } else { + return NS_ERROR_FAILURE; + } +} + +NS_IMETHODIMP +nsSupportsArrayEnumerator::Next() +{ + uint32_t cnt; + nsresult rv = mArray->Count(&cnt); + if (NS_FAILED(rv)) { + return rv; + } + int32_t end = (int32_t)cnt; + if (mCursor < end) { // don't count upward forever + mCursor++; + } + if (mCursor < end) { + return NS_OK; + } else { + return NS_ERROR_FAILURE; + } +} + +NS_IMETHODIMP +nsSupportsArrayEnumerator::CurrentItem(nsISupports** aItem) +{ + NS_ASSERTION(aItem, "null out parameter"); + uint32_t cnt; + nsresult rv = mArray->Count(&cnt); + if (NS_FAILED(rv)) { + return rv; + } + if (mCursor >= 0 && mCursor < (int32_t)cnt) { + return mArray->GetElementAt(mCursor, aItem); + } + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsSupportsArrayEnumerator::IsDone() +{ + uint32_t cnt; + nsresult rv = mArray->Count(&cnt); + if (NS_FAILED(rv)) { + return rv; + } + // XXX This is completely incompatible with the meaning of nsresult. + // NS_ENUMERATOR_FALSE is defined to be 1. (bug 778111) + return (mCursor >= 0 && mCursor < (int32_t)cnt) + ? (nsresult)NS_ENUMERATOR_FALSE : NS_OK; +} + +//////////////////////////////////////////////////////////////////////////////// + +NS_IMETHODIMP +nsSupportsArrayEnumerator::Last() +{ + uint32_t cnt; + nsresult rv = mArray->Count(&cnt); + if (NS_FAILED(rv)) { + return rv; + } + mCursor = cnt - 1; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsArrayEnumerator::Prev() +{ + if (mCursor >= 0) { + --mCursor; + } + if (mCursor >= 0) { + return NS_OK; + } else { + return NS_ERROR_FAILURE; + } +} + +#if defined(__GNUC__) +#pragma GCC diagnostic pop +#elif defined(_MSC_VER) +#pragma warning (pop) +#endif diff --git a/xpcom/ds/nsSupportsArrayEnumerator.h b/xpcom/ds/nsSupportsArrayEnumerator.h new file mode 100644 index 000000000..bd316d6b9 --- /dev/null +++ b/xpcom/ds/nsSupportsArrayEnumerator.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 nsSupportsArrayEnumerator_h___ +#define nsSupportsArrayEnumerator_h___ + +#include "nsCOMPtr.h" +#include "mozilla/Attributes.h" + +// Disable deprecation warnings generated by nsISupportsArray and associated +// classes. +#if defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#elif defined(_MSC_VER) +#pragma warning (push) +#pragma warning (disable : 4996) +#endif + +#include "nsIEnumerator.h" + +class nsISupportsArray; + +class nsSupportsArrayEnumerator final : public nsIBidirectionalEnumerator +{ +public: + NS_DECL_ISUPPORTS + + explicit nsSupportsArrayEnumerator(nsISupportsArray* aArray); + + // nsIEnumerator methods: + NS_DECL_NSIENUMERATOR + + // nsIBidirectionalEnumerator methods: + NS_DECL_NSIBIDIRECTIONALENUMERATOR + +private: + ~nsSupportsArrayEnumerator(); + +protected: + nsCOMPtr mArray; + int32_t mCursor; + +}; + +#if defined(__GNUC__) +#pragma GCC diagnostic pop +#elif defined(_MSC_VER) +#pragma warning (pop) +#endif + +#endif // __nsSupportsArrayEnumerator_h + diff --git a/xpcom/ds/nsSupportsPrimitives.cpp b/xpcom/ds/nsSupportsPrimitives.cpp new file mode 100644 index 000000000..aa929b9de --- /dev/null +++ b/xpcom/ds/nsSupportsPrimitives.cpp @@ -0,0 +1,849 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. 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 "nsMemory.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]; + + int len = SprintfLiteral(buf, aFormat, aData); + MOZ_ASSERT(len >= 0); + + return static_cast(nsMemory::Clone(buf, std::min(len + 1, size) * + sizeof(char))); +} + +/***************************************************************************/ + +NS_IMPL_ISUPPORTS(nsSupportsID, nsISupportsID, nsISupportsPrimitive) + +nsSupportsID::nsSupportsID() + : mData(nullptr) +{ +} + +NS_IMETHODIMP +nsSupportsID::GetType(uint16_t* aType) +{ + NS_ASSERTION(aType, "Bad pointer"); + *aType = TYPE_ID; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsID::GetData(nsID** aData) +{ + NS_ASSERTION(aData, "Bad pointer"); + + if (mData) { + *aData = static_cast(nsMemory::Clone(mData, sizeof(nsID))); + } else { + *aData = nullptr; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsID::SetData(const nsID* aData) +{ + if (mData) { + free(mData); + } + + if (aData) { + mData = static_cast(nsMemory::Clone(aData, sizeof(nsID))); + } else { + mData = nullptr; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsID::ToString(char** aResult) +{ + NS_ASSERTION(aResult, "Bad pointer"); + + if (mData) { + *aResult = mData->ToString(); + } else { + static const char nullStr[] = "null"; + *aResult = static_cast(nsMemory::Clone(nullStr, sizeof(nullStr))); + } + + return NS_OK; +} + +/***************************************************************************** + * 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); + 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); + 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"); + const char* str = mData ? "true" : "false"; + *aResult = static_cast(nsMemory::Clone(str, (strlen(str) + 1) * + sizeof(char))); + 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(nsSupportsVoid, nsISupportsVoid, + nsISupportsPrimitive) + +nsSupportsVoid::nsSupportsVoid() + : mData(nullptr) +{ +} + +NS_IMETHODIMP +nsSupportsVoid::GetType(uint16_t* aType) +{ + NS_ASSERTION(aType, "Bad pointer"); + *aType = TYPE_VOID; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsVoid::GetData(void** aData) +{ + NS_ASSERTION(aData, "Bad pointer"); + *aData = mData; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsVoid::SetData(void* aData) +{ + mData = aData; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsVoid::ToString(char** aResult) +{ + NS_ASSERTION(aResult, "Bad pointer"); + static const char str[] = "[raw data]"; + *aResult = static_cast(nsMemory::Clone(str, sizeof(str))); + 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"); + + if (mIID) { + *aIID = static_cast(nsMemory::Clone(mIID, sizeof(nsID))); + } else { + *aIID = nullptr; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsInterfacePointer::SetDataIID(const nsID* aIID) +{ + if (mIID) { + free(mIID); + } + + if (aIID) { + mIID = static_cast(nsMemory::Clone(aIID, sizeof(nsID))); + } else { + mIID = nullptr; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsInterfacePointer::ToString(char** aResult) +{ + NS_ASSERTION(aResult, "Bad pointer"); + + static const char str[] = "[interface pointer]"; + // jband sez: think about asking nsIInterfaceInfoManager whether + // the interface has a known human-readable name + *aResult = static_cast(nsMemory::Clone(str, sizeof(str))); + 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); + 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 000000000..17ed0f47f --- /dev/null +++ b/xpcom/ds/nsSupportsPrimitives.h @@ -0,0 +1,327 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 nsSupportsID final : public nsISupportsID +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSISUPPORTSPRIMITIVE + NS_DECL_NSISUPPORTSID + + nsSupportsID(); + +private: + ~nsSupportsID() {} + + nsID* mData; +}; + +/***************************************************************************/ + +class nsSupportsCString final : public nsISupportsCString +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSISUPPORTSPRIMITIVE + NS_DECL_NSISUPPORTSCSTRING + + nsSupportsCString() {} + +private: + ~nsSupportsCString() {} + + nsCString mData; +}; + +/***************************************************************************/ + +class nsSupportsString final : public nsISupportsString +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSISUPPORTSPRIMITIVE + NS_DECL_NSISUPPORTSSTRING + + nsSupportsString() {} + +private: + ~nsSupportsString() {} + + nsString mData; +}; + +/***************************************************************************/ + +class nsSupportsPRBool final : public nsISupportsPRBool +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSISUPPORTSPRIMITIVE + NS_DECL_NSISUPPORTSPRBOOL + + nsSupportsPRBool(); + +private: + ~nsSupportsPRBool() {} + + bool mData; +}; + +/***************************************************************************/ + +class nsSupportsPRUint8 final : public nsISupportsPRUint8 +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSISUPPORTSPRIMITIVE + NS_DECL_NSISUPPORTSPRUINT8 + + nsSupportsPRUint8(); + +private: + ~nsSupportsPRUint8() {} + + uint8_t mData; +}; + +/***************************************************************************/ + +class nsSupportsPRUint16 final : public nsISupportsPRUint16 +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSISUPPORTSPRIMITIVE + NS_DECL_NSISUPPORTSPRUINT16 + + nsSupportsPRUint16(); + +private: + ~nsSupportsPRUint16() {} + + uint16_t mData; +}; + +/***************************************************************************/ + +class nsSupportsPRUint32 final : public nsISupportsPRUint32 +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSISUPPORTSPRIMITIVE + NS_DECL_NSISUPPORTSPRUINT32 + + nsSupportsPRUint32(); + +private: + ~nsSupportsPRUint32() {} + + uint32_t mData; +}; + +/***************************************************************************/ + +class nsSupportsPRUint64 final : public nsISupportsPRUint64 +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSISUPPORTSPRIMITIVE + NS_DECL_NSISUPPORTSPRUINT64 + + nsSupportsPRUint64(); + +private: + ~nsSupportsPRUint64() {} + + uint64_t mData; +}; + +/***************************************************************************/ + +class nsSupportsPRTime final : public nsISupportsPRTime +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSISUPPORTSPRIMITIVE + NS_DECL_NSISUPPORTSPRTIME + + nsSupportsPRTime(); + +private: + ~nsSupportsPRTime() {} + + PRTime mData; +}; + +/***************************************************************************/ + +class nsSupportsChar final : public nsISupportsChar +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSISUPPORTSPRIMITIVE + NS_DECL_NSISUPPORTSCHAR + + nsSupportsChar(); + +private: + ~nsSupportsChar() {} + + char mData; +}; + +/***************************************************************************/ + +class nsSupportsPRInt16 final : public nsISupportsPRInt16 +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSISUPPORTSPRIMITIVE + NS_DECL_NSISUPPORTSPRINT16 + + nsSupportsPRInt16(); + +private: + ~nsSupportsPRInt16() {} + + int16_t mData; +}; + +/***************************************************************************/ + +class nsSupportsPRInt32 final : public nsISupportsPRInt32 +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSISUPPORTSPRIMITIVE + NS_DECL_NSISUPPORTSPRINT32 + + nsSupportsPRInt32(); + +private: + ~nsSupportsPRInt32() {} + + int32_t mData; +}; + +/***************************************************************************/ + +class nsSupportsPRInt64 final : public nsISupportsPRInt64 +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSISUPPORTSPRIMITIVE + NS_DECL_NSISUPPORTSPRINT64 + + nsSupportsPRInt64(); + +private: + ~nsSupportsPRInt64() {} + + int64_t mData; +}; + +/***************************************************************************/ + +class nsSupportsFloat final : public nsISupportsFloat +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSISUPPORTSPRIMITIVE + NS_DECL_NSISUPPORTSFLOAT + + nsSupportsFloat(); + +private: + ~nsSupportsFloat() {} + + float mData; +}; + +/***************************************************************************/ + +class nsSupportsDouble final : public nsISupportsDouble +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSISUPPORTSPRIMITIVE + NS_DECL_NSISUPPORTSDOUBLE + + nsSupportsDouble(); + +private: + ~nsSupportsDouble() {} + + double mData; +}; + +/***************************************************************************/ + +class nsSupportsVoid final : public nsISupportsVoid +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSISUPPORTSPRIMITIVE + NS_DECL_NSISUPPORTSVOID + + nsSupportsVoid(); + +private: + ~nsSupportsVoid() {} + + void* 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() {} + + nsDependentCString mData; +}; + +#endif /* nsSupportsPrimitives_h__ */ diff --git a/xpcom/ds/nsVariant.cpp b/xpcom/ds/nsVariant.cpp new file mode 100644 index 000000000..edb020139 --- /dev/null +++ b/xpcom/ds/nsVariant.cpp @@ -0,0 +1,2220 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. 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 "xpt_struct.h" +#include "nsReadableUtils.h" +#include "nsMemory.h" +#include "nsString.h" +#include "nsCRTGlue.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); + 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.mInt32Value = u.mUint32Value; + aOutData->mType = nsIDataType::VTYPE_INT32; + 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_DOMSTRING: + 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_DOMSTRING: + 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 allocatedValueCount = 0; + nsresult rv = NS_OK; + 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_DOMSTRING: + 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); + if (!*aOutValue) { + return NS_ERROR_OUT_OF_MEMORY; + } + + // 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; + } + MOZ_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) { + if (!(*(outp++) = (nsID*)nsMemory::Clone((char*)idp, sizeof(nsID)))) { + goto bad; + } + } else { + *(outp++) = nullptr; + } + allocatedValueCount++; + } + 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) { + if (!(*(outp++) = (char*)nsMemory::Clone( + str, (strlen(str) + 1) * sizeof(char)))) { + goto bad; + } + } else { + *(outp++) = nullptr; + } + allocatedValueCount++; + } + 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) { + if (!(*(outp++) = (char16_t*)nsMemory::Clone( + str, (NS_strlen(str) + 1) * sizeof(char16_t)))) { + goto bad; + } + } else { + *(outp++) = nullptr; + } + allocatedValueCount++; + } + 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_DOMSTRING: + 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; + +bad: + if (*aOutValue) { + char** p = (char**)*aOutValue; + for (i = allocatedValueCount; i > 0; ++p, --i) + if (*p) { + free(*p); + } + free((char*)*aOutValue); + *aOutValue = nullptr; + } + return rv; +} + +/***************************************************************************/ + +#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(Ctype_, min_, max_) \ + case nsIDataType::VTYPE_DOUBLE: \ + { \ + double value = tempData.u.mDoubleValue; \ + if (value < min_ || value > max_) \ + return NS_ERROR_LOSS_OF_SIGNIFICANT_DATA; \ + *aResult = ( Ctype_ ) value; \ + return rv; \ + } + +#define CASE__NUMERIC_CONVERSION_DOUBLE_MIN_MAX_INT(Ctype_, min_, max_) \ + case nsIDataType::VTYPE_DOUBLE: \ + { \ + double value = tempData.u.mDoubleValue; \ + if (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 convertions 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 convertions need to be fixed! +NUMERIC_CONVERSION_METHOD_BEGIN(VTYPE_CHAR, char, Char) + CASE__NUMERIC_CONVERSION_INT32_JUST_CAST(char) + CASE__NUMERIC_CONVERSION_UINT32_JUST_CAST(char) + CASE__NUMERIC_CONVERSION_DOUBLE_JUST_CAST(char) +NUMERIC_CONVERSION_METHOD_END + +// XXX toWChar convertions need to be fixed! +NUMERIC_CONVERSION_METHOD_BEGIN(VTYPE_WCHAR, char16_t, WChar) + CASE__NUMERIC_CONVERSION_INT32_JUST_CAST(char16_t) + CASE__NUMERIC_CONVERSION_UINT32_JUST_CAST(char16_t) + CASE__NUMERIC_CONVERSION_DOUBLE_JUST_CAST(char16_t) +NUMERIC_CONVERSION_METHOD_END + +#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; + } + *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: + // XXX should check for data loss here! + *aResult = tempData.u.mDoubleValue; + 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: + case nsIDataType::VTYPE_DOMSTRING: + 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); + 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_DOMSTRING: + 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 +{ + char* ptr; + + switch (mType) { + // all the stuff we don't handle... + case nsIDataType::VTYPE_ASTRING: + case nsIDataType::VTYPE_DOMSTRING: + 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!"); + MOZ_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: + ptr = u.mIDValue.ToString(); + if (!ptr) { + return NS_ERROR_OUT_OF_MEMORY; + } + aOutString.Assign(ptr); + free(ptr); + return NS_OK; + + // Can't use PR_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 PR_smprintf'd and use common code. + +#define CASE__SMPRINTF_NUMBER(type_, format_, cast_, member_) \ + case nsIDataType::type_: \ + ptr = PR_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, "%lld", 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, "%llu", 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 (!ptr) { + return NS_ERROR_OUT_OF_MEMORY; + } + aOutString.Assign(ptr); + PR_smprintf_free(ptr); + return NS_OK; +} + +nsresult +nsDiscriminatedUnion::ConvertToAString(nsAString& aResult) const +{ + switch (mType) { + case nsIDataType::VTYPE_ASTRING: + case nsIDataType::VTYPE_DOMSTRING: + 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(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: + case nsIDataType::VTYPE_DOMSTRING: + 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: + case nsIDataType::VTYPE_DOMSTRING: + 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(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: + case nsIDataType::VTYPE_DOMSTRING: + *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: + case nsIDataType::VTYPE_DOMSTRING: + *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 = (nsIID*)nsMemory::Clone(piid, sizeof(nsIID)); + if (!*aIID) { + return NS_ERROR_OUT_OF_MEMORY; + } + + 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) +{ + uint16_t type; + nsresult rv; + + Cleanup(); + + rv = aValue->GetDataType(&type); + if (NS_FAILED(rv)) { + return rv; + } + + 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_DOMSTRING: + case nsIDataType::VTYPE_WCHAR_STR: + case nsIDataType::VTYPE_WSTRING_SIZE_IS: + CASE__SET_FROM_VARIANT_VTYPE_PROLOGUE(VTYPE_ASTRING); + u.mAStringValue = new nsString(); + if (!u.mAStringValue) { + return NS_ERROR_OUT_OF_MEMORY; + } + 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(); + if (!u.mCStringValue) { + return NS_ERROR_OUT_OF_MEMORY; + } + 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(); + if (!u.mUTF8StringValue) { + return NS_ERROR_OUT_OF_MEMORY; + } + 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::SetFromDOMString(const nsAString& aValue) +{ + DATA_SETTER_PROLOGUE; + u.mAStringValue = new nsString(aValue); + DATA_SETTER_EPILOGUE(VTYPE_DOMSTRING); +} + +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; + } + if (!(u.str.mStringValue = + (char*)nsMemory::Clone(aValue, (aSize + 1) * sizeof(char)))) { + return NS_ERROR_OUT_OF_MEMORY; + } + 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; + } + if (!(u.wstr.mWStringValue = + (char16_t*)nsMemory::Clone(aValue, (aSize + 1) * sizeof(char16_t)))) { + return NS_ERROR_OUT_OF_MEMORY; + } + 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: + case nsIDataType::VTYPE_DOMSTRING: + 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) +{ +#ifdef DEBUG + { + // Assert that the nsIDataType consts match the values #defined in + // xpt_struct.h. Bad things happen somewhere if they don't. + struct THE_TYPES + { + uint16_t a; + uint16_t b; + }; + static const THE_TYPES array[] = { + {nsIDataType::VTYPE_INT8 , TD_INT8 }, + {nsIDataType::VTYPE_INT16 , TD_INT16 }, + {nsIDataType::VTYPE_INT32 , TD_INT32 }, + {nsIDataType::VTYPE_INT64 , TD_INT64 }, + {nsIDataType::VTYPE_UINT8 , TD_UINT8 }, + {nsIDataType::VTYPE_UINT16 , TD_UINT16 }, + {nsIDataType::VTYPE_UINT32 , TD_UINT32 }, + {nsIDataType::VTYPE_UINT64 , TD_UINT64 }, + {nsIDataType::VTYPE_FLOAT , TD_FLOAT }, + {nsIDataType::VTYPE_DOUBLE , TD_DOUBLE }, + {nsIDataType::VTYPE_BOOL , TD_BOOL }, + {nsIDataType::VTYPE_CHAR , TD_CHAR }, + {nsIDataType::VTYPE_WCHAR , TD_WCHAR }, + {nsIDataType::VTYPE_VOID , TD_VOID }, + {nsIDataType::VTYPE_ID , TD_PNSIID }, + {nsIDataType::VTYPE_DOMSTRING , TD_DOMSTRING }, + {nsIDataType::VTYPE_CHAR_STR , TD_PSTRING }, + {nsIDataType::VTYPE_WCHAR_STR , TD_PWSTRING }, + {nsIDataType::VTYPE_INTERFACE , TD_INTERFACE_TYPE }, + {nsIDataType::VTYPE_INTERFACE_IS , TD_INTERFACE_IS_TYPE}, + {nsIDataType::VTYPE_ARRAY , TD_ARRAY }, + {nsIDataType::VTYPE_STRING_SIZE_IS , TD_PSTRING_SIZE_IS }, + {nsIDataType::VTYPE_WSTRING_SIZE_IS , TD_PWSTRING_SIZE_IS }, + {nsIDataType::VTYPE_UTF8STRING , TD_UTF8STRING }, + {nsIDataType::VTYPE_CSTRING , TD_CSTRING }, + {nsIDataType::VTYPE_ASTRING , TD_ASTRING } + }; + static const int length = sizeof(array) / sizeof(array[0]); + static bool inited = false; + if (!inited) { + for (int i = 0; i < length; ++i) { + NS_ASSERTION(array[i].a == array[i].b, "bad const declaration"); + } + inited = true; + } + } +#endif +} + +// For all the data getters we just forward to the static (and sharable) +// 'ConvertTo' functions. + +NS_IMETHODIMP +nsVariantBase::GetDataType(uint16_t* aDataType) +{ + *aDataType = mData.GetType(); + return NS_OK; +} + +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::GetAsDOMString(nsAString& aResult) +{ + // A DOMString maps to an AString internally, so we can re-use + // ConvertToAString here. + 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::SetAsDOMString(const nsAString& aValue) +{ + if (!mWritable) { + return NS_ERROR_OBJECT_IS_IMMUTABLE; + } + + mData.SetFromDOMString(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 000000000..5be2d18ee --- /dev/null +++ b/xpcom/ds/nsVariant.h @@ -0,0 +1,229 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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) {} + 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; } + + MOZ_MUST_USE nsresult ConvertToInt8(uint8_t* aResult) const; + MOZ_MUST_USE nsresult ConvertToInt16(int16_t* aResult) const; + MOZ_MUST_USE nsresult ConvertToInt32(int32_t* aResult) const; + MOZ_MUST_USE nsresult ConvertToInt64(int64_t* aResult) const; + MOZ_MUST_USE nsresult ConvertToUint8(uint8_t* aResult) const; + MOZ_MUST_USE nsresult ConvertToUint16(uint16_t* aResult) const; + MOZ_MUST_USE nsresult ConvertToUint32(uint32_t* aResult) const; + MOZ_MUST_USE nsresult ConvertToUint64(uint64_t* aResult) const; + MOZ_MUST_USE nsresult ConvertToFloat(float* aResult) const; + MOZ_MUST_USE nsresult ConvertToDouble(double* aResult) const; + MOZ_MUST_USE nsresult ConvertToBool(bool* aResult) const; + MOZ_MUST_USE nsresult ConvertToChar(char* aResult) const; + MOZ_MUST_USE nsresult ConvertToWChar(char16_t* aResult) const; + + MOZ_MUST_USE nsresult ConvertToID(nsID* aResult) const; + + MOZ_MUST_USE nsresult ConvertToAString(nsAString& aResult) const; + MOZ_MUST_USE nsresult ConvertToAUTF8String(nsAUTF8String& aResult) const; + MOZ_MUST_USE nsresult ConvertToACString(nsACString& aResult) const; + MOZ_MUST_USE nsresult ConvertToString(char** aResult) const; + MOZ_MUST_USE nsresult ConvertToWString(char16_t** aResult) const; + MOZ_MUST_USE nsresult ConvertToStringWithSize(uint32_t* aSize, char** aStr) const; + MOZ_MUST_USE nsresult ConvertToWStringWithSize(uint32_t* aSize, char16_t** aStr) const; + + MOZ_MUST_USE nsresult ConvertToISupports(nsISupports** aResult) const; + MOZ_MUST_USE nsresult ConvertToInterface(nsIID** aIID, void** aInterface) const; + MOZ_MUST_USE nsresult ConvertToArray(uint16_t* aType, nsIID* aIID, + uint32_t* aCount, void** aPtr) const; + + MOZ_MUST_USE 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 SetFromDOMString(const nsAString& aValue); + void SetFromAUTF8String(const nsAUTF8String& aValue); + void SetFromACString(const nsACString& aValue); + MOZ_MUST_USE nsresult SetFromString(const char* aValue); + MOZ_MUST_USE nsresult SetFromWString(const char16_t* aValue); + void SetFromISupports(nsISupports* aValue); + void SetFromInterface(const nsIID& aIID, nsISupports* aValue); + MOZ_MUST_USE nsresult SetFromArray(uint16_t aType, const nsIID* aIID, + uint32_t aCount, void* aValue); + MOZ_MUST_USE nsresult SetFromStringWithSize(uint32_t aSize, + const char* aValue); + MOZ_MUST_USE 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: + MOZ_MUST_USE nsresult + ToManageableNumber(nsDiscriminatedUnion* aOutData) const; + void FreeArray(); + MOZ_MUST_USE bool String2ID(nsID* aPid) const; + MOZ_MUST_USE 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() {}; + + nsDiscriminatedUnion mData; + bool mWritable; +}; + +class nsVariant final : public nsVariantBase +{ +public: + NS_DECL_ISUPPORTS + + nsVariant() {}; + +private: + ~nsVariant() {}; +}; + +class nsVariantCC final : public nsVariantBase +{ +public: + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_CLASS(nsVariantCC) + + nsVariantCC() {}; + +private: + ~nsVariantCC() {}; +}; + +/** + * 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 000000000..bfe32f8dc --- /dev/null +++ b/xpcom/ds/nsWhitespaceTokenizer.h @@ -0,0 +1,110 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 "nsCRT.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 nsSubstring& aSource) + : nsTWhitespaceTokenizer(aSource) + { + } +}; + +typedef nsWhitespaceTokenizerTemplate<> nsWhitespaceTokenizer; + +template +class nsCWhitespaceTokenizerTemplate + : public nsTWhitespaceTokenizer +{ +public: + explicit nsCWhitespaceTokenizerTemplate(const nsCSubstring& 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 000000000..55571d8f9 --- /dev/null +++ b/xpcom/ds/nsWindowsRegKey.cpp @@ -0,0 +1,579 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. 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" +#include "nsAutoPtr.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; + } + + nsAString::iterator begin; + aResult.BeginWriting(begin); + + rv = RegQueryValueExW(mKey, flatName.get(), 0, &type, (LPBYTE)begin.get(), + &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; + } + + nsAString::iterator begin; + expandedResult.BeginWriting(begin); + + resultLen = ExpandEnvironmentStringsW(flatSource.get(), + wwc(begin.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; + } + + nsACString::iterator begin; + aResult.BeginWriting(begin); + + rv = RegQueryValueExW(mKey, PromiseFlatString(aName).get(), 0, nullptr, + (LPBYTE)begin.get(), &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 = CreateEvent(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(nsISupports* aDelegate, const nsIID& aIID, + void** aResult) +{ + if (aDelegate) { + return NS_ERROR_NO_AGGREGATION; + } + + 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 000000000..d7930579a --- /dev/null +++ b/xpcom/ds/nsWindowsRegKey.h @@ -0,0 +1,43 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 } } + +extern MOZ_MUST_USE nsresult nsWindowsRegKeyConstructor(nsISupports* aOuter, + const nsIID& aIID, + void** aResult); + +#endif // IMPL_LIBXUL + +//----------------------------------------------------------------------------- + +#endif // nsWindowsRegKey_h__ diff --git a/xpcom/glue/AppData.cpp b/xpcom/glue/AppData.cpp new file mode 100644 index 000000000..845267e60 --- /dev/null +++ b/xpcom/glue/AppData.cpp @@ -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/. */ + +#include "mozilla/AppData.h" +#include "nsXULAppAPI.h" +#include "nsINIParser.h" +#include "nsIFile.h" +#include "nsCRTGlue.h" +#include "nsAutoPtr.h" + +namespace mozilla { + +void +SetAllocatedString(const char*& aStr, const char* aNewValue) +{ + NS_Free(const_cast(aStr)); + if (aNewValue) { + aStr = NS_strdup(aNewValue); + } else { + aStr = nullptr; + } +} + +void +SetAllocatedString(const char*& aStr, const nsACString& aNewValue) +{ + NS_Free(const_cast(aStr)); + if (aNewValue.IsEmpty()) { + aStr = nullptr; + } else { + aStr = ToNewCString(aNewValue); + } +} + +ScopedAppData::ScopedAppData(const nsXREAppData* aAppData) +{ + Zero(); + + this->size = aAppData->size; + + SetAllocatedString(this->vendor, aAppData->vendor); + SetAllocatedString(this->name, aAppData->name); + SetAllocatedString(this->remotingName, aAppData->remotingName); + SetAllocatedString(this->version, aAppData->version); + SetAllocatedString(this->buildID, aAppData->buildID); + SetAllocatedString(this->ID, aAppData->ID); + SetAllocatedString(this->copyright, aAppData->copyright); + SetAllocatedString(this->profile, aAppData->profile); + SetStrongPtr(this->directory, aAppData->directory); + this->flags = aAppData->flags; + + if (aAppData->size > offsetof(nsXREAppData, xreDirectory)) { + SetStrongPtr(this->xreDirectory, aAppData->xreDirectory); + SetAllocatedString(this->minVersion, aAppData->minVersion); + SetAllocatedString(this->maxVersion, aAppData->maxVersion); + } + + if (aAppData->size > offsetof(nsXREAppData, crashReporterURL)) { + SetAllocatedString(this->crashReporterURL, aAppData->crashReporterURL); + } + + if (aAppData->size > offsetof(nsXREAppData, UAName)) { + SetAllocatedString(this->UAName, aAppData->UAName); + } + +#if defined(XP_WIN) && defined(MOZ_SANDBOX) + sandboxBrokerServices = aAppData->sandboxBrokerServices; +#endif +} + +ScopedAppData::~ScopedAppData() +{ + SetAllocatedString(this->vendor, nullptr); + SetAllocatedString(this->name, nullptr); + SetAllocatedString(this->remotingName, nullptr); + SetAllocatedString(this->version, nullptr); + SetAllocatedString(this->buildID, nullptr); + SetAllocatedString(this->ID, nullptr); + SetAllocatedString(this->copyright, nullptr); + SetAllocatedString(this->profile, nullptr); + + NS_IF_RELEASE(this->directory); + + SetStrongPtr(this->xreDirectory, (nsIFile*)nullptr); + SetAllocatedString(this->minVersion, nullptr); + SetAllocatedString(this->maxVersion, nullptr); + + SetAllocatedString(this->crashReporterURL, nullptr); + SetAllocatedString(this->UAName, nullptr); +} + +} // namespace mozilla diff --git a/xpcom/glue/AppData.h b/xpcom/glue/AppData.h new file mode 100644 index 000000000..0134df32c --- /dev/null +++ b/xpcom/glue/AppData.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_AppData_h +#define mozilla_AppData_h + +#include "nsXREAppData.h" +#include "nscore.h" +#include "nsStringGlue.h" +#include "nsISupportsUtils.h" + +namespace mozilla { + +// Like nsXREAppData, but releases all strong refs/allocated memory +// in the destructor. +class ScopedAppData : public nsXREAppData +{ +public: + ScopedAppData() + { + Zero(); + this->size = sizeof(*this); + } + + explicit ScopedAppData(const nsXREAppData* aAppData); + + void Zero() { memset(this, 0, sizeof(*this)); } + + ~ScopedAppData(); +}; + +/** + * Given |aStr| is holding a string allocated with NS_Alloc, or null: + * replace the value in |aStr| with a new value. + * + * @param aNewValue Null is permitted. The string is cloned with NS_strdup. + */ +void SetAllocatedString(const char*& aStr, const char* aNewValue); + +/** + * Given "str" is holding a string allocated with NS_Alloc, or null: + * replace the value in "str" with a new value. + * + * @param aNewValue If |aNewValue| is the empty string, |aStr| will be set + * to null. + */ +void SetAllocatedString(const char*& aStr, const nsACString& aNewValue); + +template +void +SetStrongPtr(T*& aPtr, T* aNewValue) +{ + NS_IF_RELEASE(aPtr); + aPtr = aNewValue; + NS_IF_ADDREF(aPtr); +} + +} // namespace mozilla + +#endif diff --git a/xpcom/glue/AutoRestore.h b/xpcom/glue/AutoRestore.h new file mode 100644 index 000000000..20909c92c --- /dev/null +++ b/xpcom/glue/AutoRestore.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/. */ + +/* 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 +#include "mozilla/GuardObjects.h" + +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; + MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER +public: + explicit AutoRestore(T& aValue MOZ_GUARD_OBJECT_NOTIFIER_PARAM) + : mLocation(aValue) + , mValue(aValue) + { + MOZ_GUARD_OBJECT_NOTIFIER_INIT; + } + ~AutoRestore() + { + mLocation = mValue; + } + T SavedValue() const + { + return mValue; + } +}; + +} // namespace mozilla + +#endif /* !defined(mozilla_AutoRestore_h_) */ diff --git a/xpcom/glue/BlockingResourceBase.cpp b/xpcom/glue/BlockingResourceBase.cpp new file mode 100644 index 000000000..ada02c72c --- /dev/null +++ b/xpcom/glue/BlockingResourceBase.cpp @@ -0,0 +1,511 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. 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" + +#include "nsAutoPtr.h" + +#ifndef MOZ_CALLSTACK_DISABLED +#include "CodeAddressService.h" +#include "nsHashKeys.h" +#include "mozilla/StackWalk.h" +#include "nsTHashtable.h" +#endif + +#include "mozilla/CondVar.h" +#include "mozilla/DeadlockDetector.h" +#include "mozilla/ReentrantMonitor.h" +#include "mozilla/Mutex.h" + +#if defined(MOZILLA_INTERNAL_API) +#include "GeckoProfiler.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" +}; + +#ifdef DEBUG + +PRCallOnceType BlockingResourceBase::sCallOnce; +unsigned BlockingResourceBase::sResourceAcqnChainFrontTPI = (unsigned)-1; +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->AppendElement(aPc); +#endif +} + +void +BlockingResourceBase::GetStackTrace(AcquisitionState& aState) +{ +#ifndef MOZ_CALLSTACK_DISABLED + // Skip this function and the calling function. + const uint32_t kSkipFrames = 2; + + aState.Clear(); + + // NB: Ignore the return value, there's nothing useful we can do if this + // this fails. + MozStackWalk(StackWalkCallback, kSkipFrames, 24, &aState, 0, nullptr); +#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| + */ +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::elem_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::elem_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; +} + +#ifndef MOZ_CALLSTACK_DISABLED +struct CodeAddressServiceLock final +{ + static void Unlock() { } + static void Lock() { } + static bool IsLocked() { return true; } +}; + +struct CodeAddressServiceStringAlloc final +{ + static char* copy(const char* aString) { return ::strdup(aString); } + static void free(char* aString) { ::free(aString); } +}; + +class CodeAddressServiceStringTable final +{ +public: + CodeAddressServiceStringTable() : mSet(32) {} + + const char* Intern(const char* aString) + { + nsCharPtrHashKey* e = mSet.PutEntry(aString); + return e->GetKey(); + } + + size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const + { + return mSet.SizeOfExcludingThis(aMallocSizeOf); + } + +private: + typedef nsTHashtable StringSet; + StringSet mSet; +}; + +typedef CodeAddressService WalkTheStackCodeAddressService; +#endif + +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; + + WalkTheStackCodeAddressService addressService; + + for (uint32_t i = 0; i < state.Length(); i++) { + const size_t kMaxLength = 1024; + char buffer[kMaxLength]; + addressService.GetLocation(i + 1, state[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)) { + NS_RUNTIMEABORT("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() +{ + PR_NewThreadPrivateIndex(&sResourceAcqnChainFrontTPI, 0); + sDeadlockDetector = new DDT(); + if (!sDeadlockDetector) { + NS_RUNTIMEABORT("can't allocate deadlock detector"); + } + return PR_SUCCESS; +} + + +void +BlockingResourceBase::Shutdown() +{ + delete sDeadlockDetector; + sDeadlockDetector = 0; +} + + +void +BlockingResourceBase::CheckAcquire() +{ + if (mType == eCondVar) { + NS_NOTYETIMPLEMENTED( + "FIXME bug 456272: annots. to allow CheckAcquire()ing condvars"); + return; + } + + BlockingResourceBase* chainFront = ResourceChainFront(); + nsAutoPtr cycle( + sDeadlockDetector->CheckAcquisition( + chainFront ? chainFront : 0, this)); + if (!cycle) { + return; + } + +#ifndef MOZ_CALLSTACK_DISABLED + // Update the current stack before printing. + GetStackTrace(mAcquired); +#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"); + } + + // XXX can customize behavior on whether we /think/ deadlock is + // XXX about to happen. for example: + // XXX if (maybeImminent) + // NS_RUNTIMEABORT(out.get()); + NS_ERROR(out.get()); +} + + +void +BlockingResourceBase::Acquire() +{ + if (mType == eCondVar) { + NS_NOTYETIMPLEMENTED( + "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); + if (mFirstSeen.IsEmpty()) { + mFirstSeen = mAcquired; + } +#endif +} + + +void +BlockingResourceBase::Release() +{ + if (mType == eCondVar) { + NS_NOTYETIMPLEMENTED( + "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 { + // not an error, but makes code hard to reason about. + NS_WARNING("Resource acquired is being released in non-LIFO order; why?\n"); + nsCString tmp; + Print(tmp); + + // 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(); + PR_Lock(mLock); + Acquire(); // protected by mLock +} + +void +OffTheBooksMutex::Unlock() +{ + Release(); // protected by mLock + PRStatus status = PR_Unlock(mLock); + NS_ASSERTION(PR_SUCCESS == status, "bad Mutex::Unlock()"); +} + + +// +// 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 = GetAcquisitionState(); + BlockingResourceBase* savedChainPrev = mChainPrev; + mEntryCount = 0; + ClearAcquisitionState(); + mChainPrev = 0; + + nsresult rv; +#if defined(MOZILLA_INTERNAL_API) + { + GeckoProfilerSleepRAII profiler_sleep; +#endif //MOZILLA_INTERNAL_API + + // give up the monitor until we're back from Wait() + rv = PR_Wait(mReentrantMonitor, aInterval) == PR_SUCCESS ? NS_OK : + NS_ERROR_FAILURE; + +#if defined(MOZILLA_INTERNAL_API) + } +#endif //MOZILLA_INTERNAL_API + + // restore saved state + mEntryCount = savedEntryCount; + SetAcquisitionState(savedAcquisitionState); + mChainPrev = savedChainPrev; + + return rv; +} + + +// +// Debug implementation of CondVar +nsresult +CondVar::Wait(PRIntervalTime aInterval) +{ + AssertCurrentThreadOwnsMutex(); + + // save mutex state and reset to empty + AcquisitionState savedAcquisitionState = mLock->GetAcquisitionState(); + BlockingResourceBase* savedChainPrev = mLock->mChainPrev; + mLock->ClearAcquisitionState(); + mLock->mChainPrev = 0; + + // give up mutex until we're back from Wait() + nsresult rv = + PR_WaitCondVar(mCvar, aInterval) == PR_SUCCESS ? NS_OK : NS_ERROR_FAILURE; + + // restore saved state + mLock->SetAcquisitionState(savedAcquisitionState); + mLock->mChainPrev = savedChainPrev; + + return rv; +} + +#endif // ifdef DEBUG + + +} // namespace mozilla diff --git a/xpcom/glue/BlockingResourceBase.h b/xpcom/glue/BlockingResourceBase.h new file mode 100644 index 000000000..d70fbcbbf --- /dev/null +++ b/xpcom/glue/BlockingResourceBase.h @@ -0,0 +1,344 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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/Logging.h" + +#include "nscore.h" +#include "nsDebug.h" +#include "nsError.h" +#include "nsISupportsImpl.h" + +#ifdef DEBUG + +// NB: Comment this out to enable callstack tracking. +#define MOZ_CALLSTACK_DISABLED + +#include "prinit.h" + +#include "nsStringGlue.h" + +#ifndef MOZ_CALLSTACK_DISABLED +#include "nsTArray.h" +#endif + +#include "nsXPCOM.h" +#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 }; + + /** + * 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 + typedef AutoTArray 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 + (BlockingResourceBase*)PR_GetThreadPrivate(sResourceAcqnChainFrontTPI); + } + + /** + * 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; + PR_SetThreadPrivate(sResourceAcqnChainFrontTPI, 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"); + PR_SetThreadPrivate(sResourceAcqnChainFrontTPI, mChainPrev); + } //NS_NEEDS_RESOURCE(this) + + /** + * GetAcquisitionState + * Return whether or not this resource was acquired. + * + * *NOT* thread safe. Requires ownership of underlying resource. + */ + AcquisitionState GetAcquisitionState() + { + return mAcquired; + } + + /** + * SetAcquisitionState + * Set whether or not this resource was acquired. + * + * *NOT* thread safe. Requires ownership of underlying resource. + */ + void SetAcquisitionState(const AcquisitionState& aAcquisitionState) + { + mAcquired = 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.Clear(); +#endif + } + + /** + * IsAcquired + * Indicates if this resource is acquired. + * + * *NOT* thread safe. Requires ownership of underlying resource. + */ + bool IsAcquired() const + { +#ifdef MOZ_CALLSTACK_DISABLED + return mAcquired; +#else + return !mAcquired.IsEmpty(); +#endif + } + + /** + * 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; + + /** + * sResourceAcqnChainFrontTPI + * Thread-private index to the front of each thread's resource + * acquisition chain. + */ + static unsigned sResourceAcqnChainFrontTPI; + + /** + * 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); + +# 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/glue/CondVar.h b/xpcom/glue/CondVar.h new file mode 100644 index 000000000..5d05464ec --- /dev/null +++ b/xpcom/glue/CondVar.h @@ -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/. */ + +#ifndef mozilla_CondVar_h +#define mozilla_CondVar_h + +#include "prcvar.h" + +#include "mozilla/BlockingResourceBase.h" +#include "mozilla/Mutex.h" + +#ifdef MOZILLA_INTERNAL_API +#include "GeckoProfiler.h" +#endif //MOZILLA_INTERNAL_API + +namespace mozilla { + + +/** + * CondVar + * Vanilla condition variable. Please don't use this unless you have a + * compelling reason --- Monitor provides a simpler API. + */ +class CondVar : BlockingResourceBase +{ +public: + /** + * CondVar + * + * 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() + **/ + CondVar(Mutex& aLock, const char* aName) + : BlockingResourceBase(aName, eCondVar) + , mLock(&aLock) + { + MOZ_COUNT_CTOR(CondVar); + // |aLock| must necessarily already be known to the deadlock detector + mCvar = PR_NewCondVar(mLock->mLock); + if (!mCvar) { + NS_RUNTIMEABORT("Can't allocate mozilla::CondVar"); + } + } + + /** + * ~CondVar + * Clean up after this CondVar, but NOT its associated Mutex. + **/ + ~CondVar() + { + NS_ASSERTION(mCvar && mLock, + "improperly constructed CondVar or double free"); + PR_DestroyCondVar(mCvar); + mCvar = 0; + mLock = 0; + MOZ_COUNT_DTOR(CondVar); + } + +#ifndef DEBUG + /** + * Wait + * @see prcvar.h + **/ + nsresult Wait(PRIntervalTime aInterval = PR_INTERVAL_NO_TIMEOUT) + { + +#ifdef MOZILLA_INTERNAL_API + GeckoProfilerSleepRAII profiler_sleep; +#endif //MOZILLA_INTERNAL_API + // NSPR checks for lock ownership + return PR_WaitCondVar(mCvar, aInterval) == PR_SUCCESS ? NS_OK : + NS_ERROR_FAILURE; + } +#else + nsresult Wait(PRIntervalTime aInterval = PR_INTERVAL_NO_TIMEOUT); +#endif // ifndef DEBUG + + /** + * Notify + * @see prcvar.h + **/ + nsresult Notify() + { + // NSPR checks for lock ownership + return PR_NotifyCondVar(mCvar) == PR_SUCCESS ? NS_OK : NS_ERROR_FAILURE; + } + + /** + * NotifyAll + * @see prcvar.h + **/ + nsresult NotifyAll() + { + // NSPR checks for lock ownership + return PR_NotifyAllCondVar(mCvar) == PR_SUCCESS ? NS_OK : NS_ERROR_FAILURE; + } + +#ifdef DEBUG + /** + * AssertCurrentThreadOwnsMutex + * @see Mutex::AssertCurrentThreadOwns + **/ + void AssertCurrentThreadOwnsMutex() + { + mLock->AssertCurrentThreadOwns(); + } + + /** + * AssertNotCurrentThreadOwnsMutex + * @see Mutex::AssertNotCurrentThreadOwns + **/ + void AssertNotCurrentThreadOwnsMutex() + { + mLock->AssertNotCurrentThreadOwns(); + } + +#else + void AssertCurrentThreadOwnsMutex() {} + void AssertNotCurrentThreadOwnsMutex() {} + +#endif // ifdef DEBUG + +private: + CondVar(); + CondVar(CondVar&); + CondVar& operator=(CondVar&); + + Mutex* mLock; + PRCondVar* mCvar; +}; + + +} // namespace mozilla + + +#endif // ifndef mozilla_CondVar_h diff --git a/xpcom/glue/DeadlockDetector.h b/xpcom/glue/DeadlockDetector.h new file mode 100644 index 000000000..382e4ef4a --- /dev/null +++ b/xpcom/glue/DeadlockDetector.h @@ -0,0 +1,382 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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) { + NS_RUNTIMEABORT("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 (auto iter = mOrdering.ConstIter(); !iter.Done(); iter.Next()) { + // NB: Key is accounted for in the entry. + n += iter.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.Put(aResource, new OrderingEntry(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) { + NS_RUNTIMEABORT("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) { + NS_RUNTIMEABORT("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->RemoveElementAt(aChain->Length() - 1); + } + 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/glue/EnumeratedArrayCycleCollection.h b/xpcom/glue/EnumeratedArrayCycleCollection.h new file mode 100644 index 000000000..faabb5a52 --- /dev/null +++ b/xpcom/glue/EnumeratedArrayCycleCollection.h @@ -0,0 +1,43 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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/glue/FileUtils.cpp b/xpcom/glue/FileUtils.cpp new file mode 100644 index 000000000..699812461 --- /dev/null +++ b/xpcom/glue/FileUtils.cpp @@ -0,0 +1,568 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. 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 "nscore.h" +#include "nsStringGlue.h" +#include "private/pprio.h" +#include "mozilla/Assertions.h" +#include "mozilla/FileUtils.h" + +#if defined(XP_MACOSX) +#include +#include +#include +#include +#include +#include +#include +#include +#elif defined(XP_UNIX) +#include +#include +#if defined(LINUX) +#include +#endif +#include +#include +#elif defined(XP_WIN) +#include +#endif + +// Functions that are not to be used in standalone glue must be implemented +// within this #if block +#if !defined(XPCOM_GLUE) + +bool +mozilla::fallocate(PRFileDesc* aFD, int64_t aLength) +{ +#if defined(HAVE_POSIX_FALLOCATE) + return posix_fallocate(PR_FileDesc2NativeHandle(aFD), 0, aLength) == 0; +#elif defined(XP_WIN) + int64_t oldpos = PR_Seek64(aFD, 0, PR_SEEK_CUR); + if (oldpos == -1) { + return false; + } + + if (PR_Seek64(aFD, aLength, PR_SEEK_SET) != aLength) { + return false; + } + + bool retval = (0 != SetEndOfFile((HANDLE)PR_FileDesc2NativeHandle(aFD))); + + PR_Seek64(aFD, oldpos, PR_SEEK_SET); + return retval; +#elif defined(XP_MACOSX) + int fd = PR_FileDesc2NativeHandle(aFD); + fstore_t store = {F_ALLOCATECONTIG, F_PEOFPOSMODE, 0, aLength}; + // Try to get a continous chunk of disk space + int ret = fcntl(fd, F_PREALLOCATE, &store); + if (ret == -1) { + // OK, perhaps we are too fragmented, allocate non-continuous + store.fst_flags = F_ALLOCATEALL; + ret = fcntl(fd, F_PREALLOCATE, &store); + if (ret == -1) { + return false; + } + } + return ftruncate(fd, aLength) == 0; +#elif defined(XP_UNIX) + // The following is copied from fcntlSizeHint in sqlite + /* If the OS does not have posix_fallocate(), fake it. First use + ** ftruncate() to set the file size, then write a single byte to + ** the last byte in each block within the extended region. This + ** is the same technique used by glibc to implement posix_fallocate() + ** on systems that do not have a real fallocate() system call. + */ + int64_t oldpos = PR_Seek64(aFD, 0, PR_SEEK_CUR); + if (oldpos == -1) { + return false; + } + + struct stat buf; + int fd = PR_FileDesc2NativeHandle(aFD); + if (fstat(fd, &buf)) { + return false; + } + + if (buf.st_size >= aLength) { + return false; + } + + const int nBlk = buf.st_blksize; + + if (!nBlk) { + return false; + } + + if (ftruncate(fd, aLength)) { + return false; + } + + int nWrite; // Return value from write() + int64_t iWrite = ((buf.st_size + 2 * nBlk - 1) / nBlk) * nBlk - 1; // Next offset to write to + while (iWrite < aLength) { + nWrite = 0; + if (PR_Seek64(aFD, iWrite, PR_SEEK_SET) == iWrite) { + nWrite = PR_Write(aFD, "", 1); + } + if (nWrite != 1) { + break; + } + iWrite += nBlk; + } + + PR_Seek64(aFD, oldpos, PR_SEEK_SET); + return nWrite == 1; +#endif + return false; +} + +#ifdef ReadSysFile_PRESENT + +bool +mozilla::ReadSysFile( + const char* aFilename, + char* aBuf, + size_t aBufSize) +{ + int fd = MOZ_TEMP_FAILURE_RETRY(open(aFilename, O_RDONLY)); + if (fd < 0) { + return false; + } + ScopedClose autoClose(fd); + if (aBufSize == 0) { + return true; + } + ssize_t bytesRead; + size_t offset = 0; + do { + bytesRead = MOZ_TEMP_FAILURE_RETRY(read(fd, aBuf + offset, + aBufSize - offset)); + if (bytesRead == -1) { + return false; + } + offset += bytesRead; + } while (bytesRead > 0 && offset < aBufSize); + MOZ_ASSERT(offset <= aBufSize); + if (offset > 0 && aBuf[offset - 1] == '\n') { + offset--; + } + if (offset == aBufSize) { + MOZ_ASSERT(offset > 0); + offset--; + } + aBuf[offset] = '\0'; + return true; +} + +bool +mozilla::ReadSysFile( + const char* aFilename, + int* aVal) +{ + char valBuf[32]; + if (!ReadSysFile(aFilename, valBuf, sizeof(valBuf))) { + return false; + } + return sscanf(valBuf, "%d", aVal) == 1; +} + +bool +mozilla::ReadSysFile( + const char* aFilename, + bool* aVal) +{ + int v; + if (!ReadSysFile(aFilename, &v)) { + return false; + } + *aVal = (v != 0); + return true; +} + +#endif /* ReadSysFile_PRESENT */ + +#ifdef WriteSysFile_PRESENT + +bool +mozilla::WriteSysFile( + const char* aFilename, + const char* aBuf) +{ + size_t aBufSize = strlen(aBuf); + int fd = MOZ_TEMP_FAILURE_RETRY(open(aFilename, O_WRONLY)); + if (fd < 0) { + return false; + } + ScopedClose autoClose(fd); + ssize_t bytesWritten; + size_t offset = 0; + do { + bytesWritten = MOZ_TEMP_FAILURE_RETRY(write(fd, aBuf + offset, + aBufSize - offset)); + if (bytesWritten == -1) { + return false; + } + offset += bytesWritten; + } while (bytesWritten > 0 && offset < aBufSize); + MOZ_ASSERT(offset == aBufSize); + return true; +} + +#endif /* WriteSysFile_PRESENT */ + +void +mozilla::ReadAheadLib(nsIFile* aFile) +{ +#if defined(XP_WIN) + nsAutoString path; + if (!aFile || NS_FAILED(aFile->GetPath(path))) { + return; + } + ReadAheadLib(path.get()); +#elif defined(LINUX) && !defined(ANDROID) || defined(XP_MACOSX) + nsAutoCString nativePath; + if (!aFile || NS_FAILED(aFile->GetNativePath(nativePath))) { + return; + } + ReadAheadLib(nativePath.get()); +#endif +} + +void +mozilla::ReadAheadFile(nsIFile* aFile, const size_t aOffset, + const size_t aCount, mozilla::filedesc_t* aOutFd) +{ +#if defined(XP_WIN) + nsAutoString path; + if (!aFile || NS_FAILED(aFile->GetPath(path))) { + return; + } + ReadAheadFile(path.get(), aOffset, aCount, aOutFd); +#elif defined(LINUX) && !defined(ANDROID) || defined(XP_MACOSX) + nsAutoCString nativePath; + if (!aFile || NS_FAILED(aFile->GetNativePath(nativePath))) { + return; + } + ReadAheadFile(nativePath.get(), aOffset, aCount, aOutFd); +#endif +} + +#endif // !defined(XPCOM_GLUE) + +#if defined(LINUX) && !defined(ANDROID) + +static const unsigned int bufsize = 4096; + +#ifdef __LP64__ +typedef Elf64_Ehdr Elf_Ehdr; +typedef Elf64_Phdr Elf_Phdr; +static const unsigned char ELFCLASS = ELFCLASS64; +typedef Elf64_Off Elf_Off; +#else +typedef Elf32_Ehdr Elf_Ehdr; +typedef Elf32_Phdr Elf_Phdr; +static const unsigned char ELFCLASS = ELFCLASS32; +typedef Elf32_Off Elf_Off; +#endif + +#elif defined(XP_MACOSX) + +#if defined(__i386__) +static const uint32_t CPU_TYPE = CPU_TYPE_X86; +#elif defined(__x86_64__) +static const uint32_t CPU_TYPE = CPU_TYPE_X86_64; +#elif defined(__ppc__) +static const uint32_t CPU_TYPE = CPU_TYPE_POWERPC; +#elif defined(__ppc64__) +static const uint32_t CPU_TYPE = CPU_TYPE_POWERPC64; +#else +#error Unsupported CPU type +#endif + +#ifdef __LP64__ +#undef LC_SEGMENT +#define LC_SEGMENT LC_SEGMENT_64 +#undef MH_MAGIC +#define MH_MAGIC MH_MAGIC_64 +#define cpu_mach_header mach_header_64 +#define segment_command segment_command_64 +#else +#define cpu_mach_header mach_header +#endif + +class ScopedMMap +{ +public: + explicit ScopedMMap(const char* aFilePath) + : buf(nullptr) + { + fd = open(aFilePath, O_RDONLY); + if (fd < 0) { + return; + } + struct stat st; + if (fstat(fd, &st) < 0) { + return; + } + size = st.st_size; + buf = (char*)mmap(nullptr, size, PROT_READ, MAP_PRIVATE, fd, 0); + } + ~ScopedMMap() + { + if (buf) { + munmap(buf, size); + } + if (fd >= 0) { + close(fd); + } + } + operator char*() { return buf; } + int getFd() { return fd; } +private: + int fd; + char* buf; + size_t size; +}; +#endif + +void +mozilla::ReadAhead(mozilla::filedesc_t aFd, const size_t aOffset, + const size_t aCount) +{ +#if defined(XP_WIN) + + LARGE_INTEGER fpOriginal; + LARGE_INTEGER fpOffset; +#if defined(HAVE_LONG_LONG) + fpOffset.QuadPart = 0; +#else + fpOffset.u.LowPart = 0; + fpOffset.u.HighPart = 0; +#endif + + // Get the current file pointer so that we can restore it. This isn't + // really necessary other than to provide the same semantics regarding the + // file pointer that other platforms do + if (!SetFilePointerEx(aFd, fpOffset, &fpOriginal, FILE_CURRENT)) { + return; + } + + if (aOffset) { +#if defined(HAVE_LONG_LONG) + fpOffset.QuadPart = static_cast(aOffset); +#else + fpOffset.u.LowPart = aOffset; + fpOffset.u.HighPart = 0; +#endif + + if (!SetFilePointerEx(aFd, fpOffset, nullptr, FILE_BEGIN)) { + return; + } + } + + char buf[64 * 1024]; + size_t totalBytesRead = 0; + DWORD dwBytesRead; + // Do dummy reads to trigger kernel-side readhead via FILE_FLAG_SEQUENTIAL_SCAN. + // Abort when underfilling because during testing the buffers are read fully + // A buffer that's not keeping up would imply that readahead isn't working right + while (totalBytesRead < aCount && + ReadFile(aFd, buf, sizeof(buf), &dwBytesRead, nullptr) && + dwBytesRead == sizeof(buf)) { + totalBytesRead += dwBytesRead; + } + + // Restore the file pointer + SetFilePointerEx(aFd, fpOriginal, nullptr, FILE_BEGIN); + +#elif defined(LINUX) && !defined(ANDROID) + + readahead(aFd, aOffset, aCount); + +#elif defined(XP_MACOSX) + + struct radvisory ra; + ra.ra_offset = aOffset; + ra.ra_count = aCount; + // The F_RDADVISE fcntl is equivalent to Linux' readahead() system call. + fcntl(aFd, F_RDADVISE, &ra); + +#endif +} + +void +mozilla::ReadAheadLib(mozilla::pathstr_t aFilePath) +{ + if (!aFilePath) { + return; + } +#if defined(XP_WIN) + ReadAheadFile(aFilePath); +#elif defined(LINUX) && !defined(ANDROID) + int fd = open(aFilePath, O_RDONLY); + if (fd < 0) { + return; + } + + union + { + char buf[bufsize]; + Elf_Ehdr ehdr; + } elf; + // Read ELF header (ehdr) and program header table (phdr). + // We check that the ELF magic is found, that the ELF class matches + // our own, and that the program header table as defined in the ELF + // headers fits in the buffer we read. + if ((read(fd, elf.buf, bufsize) <= 0) || + (memcmp(elf.buf, ELFMAG, 4)) || + (elf.ehdr.e_ident[EI_CLASS] != ELFCLASS) || + // Upcast e_phentsize so the multiplication is done in the same precision + // as the subsequent addition, to satisfy static analyzers and avoid + // issues with abnormally large program header tables. + (elf.ehdr.e_phoff + (static_cast(elf.ehdr.e_phentsize) * + elf.ehdr.e_phnum) >= bufsize)) { + close(fd); + return; + } + // The program header table contains segment definitions. One such + // segment type is PT_LOAD, which describes how the dynamic loader + // is going to map the file in memory. We use that information to + // find the biggest offset from the library that will be mapped in + // memory. + Elf_Phdr* phdr = (Elf_Phdr*)&elf.buf[elf.ehdr.e_phoff]; + Elf_Off end = 0; + for (int phnum = elf.ehdr.e_phnum; phnum; phdr++, phnum--) { + if ((phdr->p_type == PT_LOAD) && + (end < phdr->p_offset + phdr->p_filesz)) { + end = phdr->p_offset + phdr->p_filesz; + } + } + // Let the kernel read ahead what the dynamic loader is going to + // map in memory soon after. + if (end > 0) { + ReadAhead(fd, 0, end); + } + close(fd); +#elif defined(XP_MACOSX) + ScopedMMap buf(aFilePath); + char* base = buf; + if (!base) { + return; + } + + // An OSX binary might either be a fat (universal) binary or a + // Mach-O binary. A fat binary actually embeds several Mach-O + // binaries. If we have a fat binary, find the offset where the + // Mach-O binary for our CPU type can be found. + struct fat_header* fh = (struct fat_header*)base; + + if (OSSwapBigToHostInt32(fh->magic) == FAT_MAGIC) { + uint32_t nfat_arch = OSSwapBigToHostInt32(fh->nfat_arch); + struct fat_arch* arch = (struct fat_arch*)&buf[sizeof(struct fat_header)]; + for (; nfat_arch; arch++, nfat_arch--) { + if (OSSwapBigToHostInt32(arch->cputype) == CPU_TYPE) { + base += OSSwapBigToHostInt32(arch->offset); + break; + } + } + if (base == buf) { + return; + } + } + + // Check Mach-O magic in the Mach header + struct cpu_mach_header* mh = (struct cpu_mach_header*)base; + if (mh->magic != MH_MAGIC) { + return; + } + + // The Mach header is followed by a sequence of load commands. + // Each command has a header containing the command type and the + // command size. LD_SEGMENT commands describes how the dynamic + // loader is going to map the file in memory. We use that + // information to find the biggest offset from the library that + // will be mapped in memory. + char* cmd = &base[sizeof(struct cpu_mach_header)]; + uint32_t end = 0; + for (uint32_t ncmds = mh->ncmds; ncmds; ncmds--) { + struct segment_command* sh = (struct segment_command*)cmd; + if (sh->cmd != LC_SEGMENT) { + continue; + } + if (end < sh->fileoff + sh->filesize) { + end = sh->fileoff + sh->filesize; + } + cmd += sh->cmdsize; + } + // Let the kernel read ahead what the dynamic loader is going to + // map in memory soon after. + if (end > 0) { + ReadAhead(buf.getFd(), base - buf, end); + } +#endif +} + +void +mozilla::ReadAheadFile(mozilla::pathstr_t aFilePath, const size_t aOffset, + const size_t aCount, mozilla::filedesc_t* aOutFd) +{ +#if defined(XP_WIN) + if (!aFilePath) { + if (aOutFd) { + *aOutFd = INVALID_HANDLE_VALUE; + } + return; + } + HANDLE fd = CreateFileW(aFilePath, GENERIC_READ, FILE_SHARE_READ, nullptr, + OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, nullptr); + if (aOutFd) { + *aOutFd = fd; + } + if (fd == INVALID_HANDLE_VALUE) { + return; + } + ReadAhead(fd, aOffset, aCount); + if (!aOutFd) { + CloseHandle(fd); + } +#elif defined(LINUX) && !defined(ANDROID) || defined(XP_MACOSX) + if (!aFilePath) { + if (aOutFd) { + *aOutFd = -1; + } + return; + } + int fd = open(aFilePath, O_RDONLY); + if (aOutFd) { + *aOutFd = fd; + } + if (fd < 0) { + return; + } + size_t count; + if (aCount == SIZE_MAX) { + struct stat st; + if (fstat(fd, &st) < 0) { + if (!aOutFd) { + close(fd); + } + return; + } + count = st.st_size; + } else { + count = aCount; + } + ReadAhead(fd, aOffset, count); + if (!aOutFd) { + close(fd); + } +#endif +} + diff --git a/xpcom/glue/FileUtils.h b/xpcom/glue/FileUtils.h new file mode 100644 index 000000000..aaf912b21 --- /dev/null +++ b/xpcom/glue/FileUtils.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 mozilla_FileUtils_h +#define mozilla_FileUtils_h + +#include "nscore.h" // nullptr + +#if defined(XP_UNIX) +# include +#elif defined(XP_WIN) +# include +#endif +#include "prio.h" + +#include "mozilla/Scoped.h" +#include "nsIFile.h" +#include +#include + +namespace mozilla { + +#if defined(XP_WIN) +typedef void* filedesc_t; +typedef const wchar_t* pathstr_t; +#else +typedef int filedesc_t; +typedef const char* pathstr_t; +#endif + +/** + * ScopedCloseFD is a RAII wrapper for POSIX file descriptors + * + * Instances |close()| their fds when they go out of scope. + */ +struct ScopedCloseFDTraits +{ + typedef int type; + static type empty() { return -1; } + static void release(type aFd) + { + if (aFd != -1) { + while (close(aFd) == -1 && errno == EINTR) { + } + } + } +}; +typedef Scoped ScopedClose; + +#if !defined(XPCOM_GLUE) + +/** + * AutoFDClose is a RAII wrapper for PRFileDesc. + * + * Instances |PR_Close| their fds when they go out of scope. + **/ +struct ScopedClosePRFDTraits +{ + typedef PRFileDesc* type; + static type empty() { return nullptr; } + static void release(type aFd) + { + if (aFd) { + PR_Close(aFd); + } + } +}; +typedef Scoped AutoFDClose; + +/* RAII wrapper for FILE descriptors */ +struct ScopedCloseFileTraits +{ + typedef FILE* type; + static type empty() { return nullptr; } + static void release(type aFile) + { + if (aFile) { + fclose(aFile); + } + } +}; +typedef Scoped ScopedCloseFile; + +/** + * Fallocate efficiently and continuously allocates files via fallocate-type APIs. + * This is useful for avoiding fragmentation. + * On sucess the file be padded with zeros to grow to aLength. + * + * @param aFD file descriptor. + * @param aLength length of file to grow to. + * @return true on success. + */ +bool fallocate(PRFileDesc* aFD, int64_t aLength); + +/** + * Use readahead to preload shared libraries into the file cache before loading. + * WARNING: This function should not be used without a telemetry field trial + * demonstrating a clear performance improvement! + * + * @param aFile nsIFile representing path to shared library + */ +void ReadAheadLib(nsIFile* aFile); + +/** + * Use readahead to preload a file into the file cache before reading. + * WARNING: This function should not be used without a telemetry field trial + * demonstrating a clear performance improvement! + * + * @param aFile nsIFile representing path to shared library + * @param aOffset Offset into the file to begin preloading + * @param aCount Number of bytes to preload (SIZE_MAX implies file size) + * @param aOutFd Pointer to file descriptor. If specified, ReadAheadFile will + * return its internal, opened file descriptor instead of closing it. + */ +void ReadAheadFile(nsIFile* aFile, const size_t aOffset = 0, + const size_t aCount = SIZE_MAX, + filedesc_t* aOutFd = nullptr); + +#endif // !defined(XPCOM_GLUE) + +/** + * Use readahead to preload shared libraries into the file cache before loading. + * WARNING: This function should not be used without a telemetry field trial + * demonstrating a clear performance improvement! + * + * @param aFilePath path to shared library + */ +void ReadAheadLib(pathstr_t aFilePath); + +/** + * Use readahead to preload a file into the file cache before loading. + * WARNING: This function should not be used without a telemetry field trial + * demonstrating a clear performance improvement! + * + * @param aFilePath path to shared library + * @param aOffset Offset into the file to begin preloading + * @param aCount Number of bytes to preload (SIZE_MAX implies file size) + * @param aOutFd Pointer to file descriptor. If specified, ReadAheadFile will + * return its internal, opened file descriptor instead of closing it. + */ +void ReadAheadFile(pathstr_t aFilePath, const size_t aOffset = 0, + const size_t aCount = SIZE_MAX, + filedesc_t* aOutFd = nullptr); + +/** + * Use readahead to preload a file into the file cache before reading. + * When this function exits, the file pointer is guaranteed to be in the same + * position it was in before this function was called. + * WARNING: This function should not be used without a telemetry field trial + * demonstrating a clear performance improvement! + * + * @param aFd file descriptor opened for read access + * (on Windows, file must be opened with FILE_FLAG_SEQUENTIAL_SCAN) + * @param aOffset Offset into the file to begin preloading + * @param aCount Number of bytes to preload (SIZE_MAX implies file size) + */ +void ReadAhead(filedesc_t aFd, const size_t aOffset = 0, + const size_t aCount = SIZE_MAX); + + +#if defined(MOZ_WIDGET_GONK) || defined(XP_UNIX) +#define MOZ_TEMP_FAILURE_RETRY(exp) (__extension__({ \ + typeof (exp) _rc; \ + do { \ + _rc = (exp); \ + } while (_rc == -1 && errno == EINTR); \ + _rc; \ +})) +#endif + +/* Define ReadSysFile() and WriteSysFile() only on GONK to avoid unnecessary + * libxul bloat. Also define it in debug builds, so that unit tests for it can + * be written and run in non-GONK builds. */ +#if (defined(MOZ_WIDGET_GONK) || defined(DEBUG)) && defined(XP_UNIX) + +#ifndef ReadSysFile_PRESENT +#define ReadSysFile_PRESENT +#endif /* ReadSysFile_PRESENT */ + +#ifndef WriteSysFile_PRESENT +#define WriteSysFile_PRESENT +#endif /* WriteSysFile_PRESENT */ + +/** + * Read the contents of a file. + * This function is intended for reading a single-lined text files from + * /sys/. If the file ends with a newline ('\n') then it will be discarded. + * The output buffer will always be '\0'-terminated on successful completion. + * If aBufSize == 0, then this function will return true if the file exists + * and is readable (it will not attempt to read anything from it). + * On failure the contents of aBuf after this call will be undefined and the + * value of the global variable errno will be set accordingly. + * @return true on success, notice that less than requested bytes could have + * been read if the file was smaller + */ +bool ReadSysFile(const char* aFilename, char* aBuf, size_t aBufSize); + +/** + * Parse the contents of a file, assuming it contains a decimal integer. + * @return true on success + */ +bool ReadSysFile(const char* aFilename, int* aVal); + +/** + * Parse the contents of a file, assuming it contains a boolean value + * (either 0 or 1). + * @return true on success + */ +bool ReadSysFile(const char* aFilename, bool* aVal); + +bool WriteSysFile(const char* aFilename, const char* aBuf); + +#endif /* (MOZ_WIDGET_GONK || DEBUG) && XP_UNIX */ + +} // namespace mozilla + +#endif diff --git a/xpcom/glue/GenericFactory.cpp b/xpcom/glue/GenericFactory.cpp new file mode 100644 index 000000000..5bf0b97a4 --- /dev/null +++ b/xpcom/glue/GenericFactory.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 "mozilla/GenericFactory.h" + +namespace mozilla { + +NS_IMPL_ISUPPORTS(GenericFactory, nsIFactory) + +NS_IMETHODIMP +GenericFactory::CreateInstance(nsISupports* aOuter, REFNSIID aIID, + void** aResult) +{ + return mCtor(aOuter, aIID, aResult); +} + +NS_IMETHODIMP +GenericFactory::LockFactory(bool aLock) +{ + NS_ERROR("Vestigial method, never called!"); + return NS_ERROR_FAILURE; +} + +} // namespace mozilla diff --git a/xpcom/glue/GenericFactory.h b/xpcom/glue/GenericFactory.h new file mode 100644 index 000000000..4561f2a2d --- /dev/null +++ b/xpcom/glue/GenericFactory.h @@ -0,0 +1,43 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 "mozilla/Attributes.h" + +#include "mozilla/Module.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() {} + +public: + typedef Module::ConstructorProcPtr ConstructorProcPtr; + + 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/glue/GenericModule.cpp b/xpcom/glue/GenericModule.cpp new file mode 100644 index 000000000..0b5cd3a7c --- /dev/null +++ b/xpcom/glue/GenericModule.cpp @@ -0,0 +1,98 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. 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/ModuleUtils.h" +#include "mozilla/GenericFactory.h" + +#include "nsICategoryManager.h" +#include "nsIComponentManager.h" +#include "nsIComponentRegistrar.h" +#include "nsServiceManagerUtils.h" +#include "nsXPCOMCID.h" +#include "nsStringAPI.h" + +namespace mozilla { + +NS_IMPL_ISUPPORTS(GenericModule, nsIModule) + +NS_IMETHODIMP +GenericModule::GetClassObject(nsIComponentManager* aCompMgr, + const nsCID& aCID, + const nsIID& aIID, + void** aResult) +{ + for (const Module::CIDEntry* e = mData->mCIDs; e->cid; ++e) { + if (e->cid->Equals(aCID)) { + nsCOMPtr f; + if (e->getFactoryProc) { + f = e->getFactoryProc(*mData, *e); + } else { + NS_ASSERTION(e->constructorProc, "No constructor proc?"); + f = new GenericFactory(e->constructorProc); + } + if (!f) { + return NS_ERROR_FAILURE; + } + + return f->QueryInterface(aIID, aResult); + } + } + NS_ERROR("Asking a module for a CID it doesn't implement."); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +GenericModule::RegisterSelf(nsIComponentManager* aCompMgr, + nsIFile* aLocation, + const char* aLoaderStr, + const char* aType) +{ + nsCOMPtr registrar = do_QueryInterface(aCompMgr); + for (const Module::CIDEntry* e = mData->mCIDs; e->cid; ++e) { + registrar->RegisterFactoryLocation(*e->cid, "", nullptr, aLocation, + aLoaderStr, aType); + } + + for (const Module::ContractIDEntry* e = mData->mContractIDs; + e && e->contractid; + ++e) { + registrar->RegisterFactoryLocation(*e->cid, "", e->contractid, aLocation, + aLoaderStr, aType); + } + + nsCOMPtr catman; + for (const Module::CategoryEntry* e = mData->mCategoryEntries; + e && e->category; + ++e) { + if (!catman) { + catman = do_GetService(NS_CATEGORYMANAGER_CONTRACTID); + } + + nsAutoCString prevValue; + catman->AddCategoryEntry(e->category, e->entry, e->value, true, true, + getter_Copies(prevValue)); + } + return NS_OK; +} + +NS_IMETHODIMP +GenericModule::UnregisterSelf(nsIComponentManager* aCompMgr, + nsIFile* aFile, + const char* aLoaderStr) +{ + NS_ERROR("Nobody should ever call UnregisterSelf!"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +GenericModule::CanUnload(nsIComponentManager* aCompMgr, bool* aResult) +{ + NS_ERROR("Nobody should ever call CanUnload!"); + *aResult = false; + return NS_OK; +} + +} // namespace mozilla diff --git a/xpcom/glue/IntentionalCrash.h b/xpcom/glue/IntentionalCrash.h new file mode 100644 index 000000000..d01006dff --- /dev/null +++ b/xpcom/glue/IntentionalCrash.h @@ -0,0 +1,58 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. 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 + +#ifdef XP_WIN +#include +#define getpid _getpid +#else +#include +#endif + +#ifndef mozilla_IntentionalCrash_h +#define mozilla_IntentionalCrash_h + +namespace mozilla { + +inline void +NoteIntentionalCrash(const char* aProcessType) +{ + char* f = getenv("XPCOM_MEM_BLOAT_LOG"); + if (!f) { + return; + } + + fprintf(stderr, "XPCOM_MEM_BLOAT_LOG: %s\n", f); + + 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); + } + + std::ostringstream bloatName; + bloatName << bloatLog << "_" << aProcessType << "_pid" << getpid(); + if (hasExt) { + bloatName << ".log"; + } + + fprintf(stderr, "Writing to log: %s\n", bloatName.str().c_str()); + + FILE* processfd = fopen(bloatName.str().c_str(), "a"); + fprintf(processfd, "==> process %d will purposefully crash\n", getpid()); + fclose(processfd); +} + +} // namespace mozilla + +#endif // mozilla_IntentionalCrash_h diff --git a/xpcom/glue/MainThreadUtils.h b/xpcom/glue/MainThreadUtils.h new file mode 100644 index 000000000..8d76f84ec --- /dev/null +++ b/xpcom/glue/MainThreadUtils.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 MainThreadUtils_h_ +#define MainThreadUtils_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 +// Fast access to the current thread. 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(); +#endif + +#ifdef MOZILLA_INTERNAL_API +bool NS_IsMainThread(); +#else +/** + * Test to see if the current thread is the main thread. + * + * @returns true if the current thread is the main thread, and false + * otherwise. + */ +extern bool NS_IsMainThread(); +#endif + +#endif // MainThreadUtils_h_ diff --git a/xpcom/glue/Monitor.h b/xpcom/glue/Monitor.h new file mode 100644 index 000000000..60750acb3 --- /dev/null +++ b/xpcom/glue/Monitor.h @@ -0,0 +1,135 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 Monitor +{ +public: + explicit Monitor(const char* aName) + : mMutex(aName) + , mCondVar(mMutex, "[Monitor.mCondVar]") + { + } + + ~Monitor() {} + + void Lock() { mMutex.Lock(); } + void Unlock() { mMutex.Unlock(); } + + nsresult Wait(PRIntervalTime aInterval = PR_INTERVAL_NO_TIMEOUT) + { + return mCondVar.Wait(aInterval); + } + + nsresult Notify() { return mCondVar.Notify(); } + nsresult NotifyAll() { return mCondVar.NotifyAll(); } + + void AssertCurrentThreadOwns() const + { + mMutex.AssertCurrentThreadOwns(); + } + + void AssertNotCurrentThreadOwns() const + { + mMutex.AssertNotCurrentThreadOwns(); + } + +private: + Monitor(); + Monitor(const Monitor&); + Monitor& operator=(const Monitor&); + + Mutex mMutex; + CondVar mCondVar; +}; + +/** + * 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_STACK_CLASS MonitorAutoLock +{ +public: + explicit MonitorAutoLock(Monitor& aMonitor) + : mMonitor(&aMonitor) + { + mMonitor->Lock(); + } + + ~MonitorAutoLock() + { + mMonitor->Unlock(); + } + + nsresult Wait(PRIntervalTime aInterval = PR_INTERVAL_NO_TIMEOUT) + { + return mMonitor->Wait(aInterval); + } + + nsresult Notify() { return mMonitor->Notify(); } + nsresult NotifyAll() { return mMonitor->NotifyAll(); } + +private: + MonitorAutoLock(); + MonitorAutoLock(const MonitorAutoLock&); + MonitorAutoLock& operator=(const MonitorAutoLock&); + static void* operator new(size_t) CPP_THROW_NEW; + + Monitor* mMonitor; +}; + +/** + * 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. + */ +class MOZ_STACK_CLASS MonitorAutoUnlock +{ +public: + explicit MonitorAutoUnlock(Monitor& aMonitor) + : mMonitor(&aMonitor) + { + mMonitor->Unlock(); + } + + ~MonitorAutoUnlock() + { + mMonitor->Lock(); + } + +private: + MonitorAutoUnlock(); + MonitorAutoUnlock(const MonitorAutoUnlock&); + MonitorAutoUnlock& operator=(const MonitorAutoUnlock&); + static void* operator new(size_t) CPP_THROW_NEW; + + Monitor* mMonitor; +}; + +} // namespace mozilla + +#endif // mozilla_Monitor_h diff --git a/xpcom/glue/Mutex.h b/xpcom/glue/Mutex.h new file mode 100644 index 000000000..16ad44f4c --- /dev/null +++ b/xpcom/glue/Mutex.h @@ -0,0 +1,229 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 "prlock.h" + +#include "mozilla/BlockingResourceBase.h" +#include "mozilla/GuardObjects.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 OffTheBooksMutex : 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) + { + mLock = PR_NewLock(); + if (!mLock) { + NS_RUNTIMEABORT("Can't allocate mozilla::Mutex"); + } + } + + ~OffTheBooksMutex() + { + NS_ASSERTION(mLock, + "improperly constructed Lock or double free"); + // NSPR does consistency checks for us + PR_DestroyLock(mLock); + mLock = 0; + } + +#ifndef DEBUG + /** + * Lock + * @see prlock.h + **/ + void Lock() { PR_Lock(mLock); } + + /** + * Unlock + * @see prlock.h + **/ + void Unlock() { PR_Unlock(mLock); } + + /** + * AssertCurrentThreadOwns + * @see prlock.h + **/ + void AssertCurrentThreadOwns() const {} + + /** + * AssertNotCurrentThreadOwns + * @see prlock.h + **/ + void AssertNotCurrentThreadOwns() const {} + +#else + void Lock(); + void Unlock(); + + void AssertCurrentThreadOwns() const + { + PR_ASSERT_CURRENT_THREAD_OWNS_LOCK(mLock); + } + + void AssertNotCurrentThreadOwns() const + { + // FIXME bug 476536 + } + +#endif // ifndef DEBUG + +private: + OffTheBooksMutex(); + OffTheBooksMutex(const OffTheBooksMutex&); + OffTheBooksMutex& operator=(const OffTheBooksMutex&); + + PRLock* mLock; + + friend class CondVar; + + // MozPromise needs to access mLock for debugging purpose. + template + friend class MozPromise; +}; + +/** + * 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); + } + + ~Mutex() + { + MOZ_COUNT_DTOR(Mutex); + } + +private: + Mutex(); + Mutex(const Mutex&); + Mutex& operator=(const Mutex&); +}; + +/** + * 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 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_GUARD_OBJECT_NOTIFIER_PARAM) + : mLock(&aLock) + { + MOZ_GUARD_OBJECT_NOTIFIER_INIT; + NS_ASSERTION(mLock, "null mutex"); + mLock->Lock(); + } + + ~BaseAutoLock(void) + { + mLock->Unlock(); + } + +private: + BaseAutoLock(); + BaseAutoLock(BaseAutoLock&); + BaseAutoLock& operator=(BaseAutoLock&); + static void* operator new(size_t) CPP_THROW_NEW; + + T* mLock; + MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER +}; + +typedef BaseAutoLock MutexAutoLock; +typedef BaseAutoLock OffTheBooksMutexAutoLock; + +/** + * MutexAutoUnlock + * 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 BaseAutoUnlock +{ +public: + explicit BaseAutoUnlock(T& aLock MOZ_GUARD_OBJECT_NOTIFIER_PARAM) + : mLock(&aLock) + { + MOZ_GUARD_OBJECT_NOTIFIER_INIT; + NS_ASSERTION(mLock, "null lock"); + mLock->Unlock(); + } + + ~BaseAutoUnlock() + { + mLock->Lock(); + } + +private: + BaseAutoUnlock(); + BaseAutoUnlock(BaseAutoUnlock&); + BaseAutoUnlock& operator=(BaseAutoUnlock&); + static void* operator new(size_t) CPP_THROW_NEW; + + T* mLock; + MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER +}; + +typedef BaseAutoUnlock MutexAutoUnlock; +typedef BaseAutoUnlock OffTheBooksMutexAutoUnlock; + +} // namespace mozilla + + +#endif // ifndef mozilla_Mutex_h diff --git a/xpcom/glue/Observer.h b/xpcom/glue/Observer.h new file mode 100644 index 000000000..958e5e4a9 --- /dev/null +++ b/xpcom/glue/Observer.h @@ -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/. */ + +#ifndef mozilla_Observer_h +#define mozilla_Observer_h + +#include "nsTArray.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() {} + 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.AppendElement(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(); + } + + void Broadcast(const T& aParam) + { + nsTArray*> observersCopy(mObservers); + uint32_t size = observersCopy.Length(); + for (uint32_t i = 0; i < size; ++i) { + observersCopy[i]->Notify(aParam); + } + } + +protected: + nsTArray*> mObservers; +}; + +} // namespace mozilla + +#endif // mozilla_Observer_h diff --git a/xpcom/glue/PLDHashTable.cpp b/xpcom/glue/PLDHashTable.cpp new file mode 100644 index 000000000..6152e9000 --- /dev/null +++ b/xpcom/glue/PLDHashTable.cpp @@ -0,0 +1,801 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. 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 "mozilla/HashFunctions.h" +#include "mozilla/MathAlgorithms.h" +#include "mozilla/OperatorNewExtensions.h" +#include "nsAlgorithm.h" +#include "mozilla/Likely.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/ChaosMode.h" + +using namespace mozilla; + +#ifdef DEBUG + +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 (PLDHashNumber)(ptrdiff_t)aKey >> 2; +} + +/* 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) +{ + uint64_t nbytes64 = uint64_t(aCapacity) * uint64_t(aEntrySize); + *aNbytes = aCapacity * aEntrySize; + 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) +{ + // 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 kHashBits - log2; +} + +PLDHashTable::PLDHashTable(const PLDHashTableOps* aOps, uint32_t aEntrySize, + uint32_t aLength) + : mOps(aOps) + , mHashShift(HashShift(aEntrySize, aLength)) + , mEntrySize(aEntrySize) + , mEntryCount(0) + , mRemovedCount(0) + , mEntryStore() +#ifdef DEBUG + , mChecker() +#endif +{ +} + +PLDHashTable& +PLDHashTable::operator=(PLDHashTable&& aOther) +{ + if (this == &aOther) { + return *this; + } + + // Destruct |this|. + this->~PLDHashTable(); + + // |mOps| and |mEntrySize| are const so we can't assign them. Instead, we + // require that they are equal. The justification for this is that 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); + MOZ_RELEASE_ASSERT(mEntrySize == aOther.mEntrySize); + + // Move non-const pieces over. + mHashShift = Move(aOther.mHashShift); + mEntryCount = Move(aOther.mEntryCount); + mRemovedCount = Move(aOther.mRemovedCount); + mEntryStore = Move(aOther.mEntryStore); +#ifdef DEBUG + mChecker = Move(aOther.mChecker); +#endif + + // Clear up |aOther| so its destruction will be a no-op. + { +#ifdef DEBUG + AutoDestructorOp op(mChecker); +#endif + aOther.mEntryStore.Set(nullptr); + } + + return *this; +} + +PLDHashNumber +PLDHashTable::Hash1(PLDHashNumber aHash0) +{ + return aHash0 >> mHashShift; +} + +// Double hashing needs the second hash code to be relatively prime to table +// size, so we simply make hash2 odd. +void +PLDHashTable::Hash2(PLDHashNumber aHash, + uint32_t& aHash2Out, uint32_t& aSizeMaskOut) +{ + uint32_t sizeLog2 = kHashBits - mHashShift; + aHash2Out = ((aHash << sizeLog2) >> mHashShift) | 1; + aSizeMaskOut = (PLDHashNumber(1) << sizeLog2) - 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::MatchEntryKeyhash(PLDHashEntryHdr* aEntry, PLDHashNumber aKeyHash) +{ + return (aEntry->mKeyHash & ~kCollisionFlag) == aKeyHash; +} + +// Compute the address of the indexed entry in table. +PLDHashEntryHdr* +PLDHashTable::AddressEntry(uint32_t aIndex) +{ + return reinterpret_cast( + mEntryStore.Get() + aIndex * mEntrySize); +} + +PLDHashTable::~PLDHashTable() +{ +#ifdef DEBUG + AutoDestructorOp op(mChecker); +#endif + + if (!mEntryStore.Get()) { + return; + } + + // Clear any remaining live entries. + char* entryAddr = mEntryStore.Get(); + char* entryLimit = entryAddr + Capacity() * mEntrySize; + while (entryAddr < entryLimit) { + PLDHashEntryHdr* entry = (PLDHashEntryHdr*)entryAddr; + if (EntryIsLive(entry)) { + mOps->clearEntry(this, entry); + } + entryAddr += mEntrySize; + } + + // 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. +template +PLDHashEntryHdr* NS_FASTCALL +PLDHashTable::SearchTable(const void* aKey, PLDHashNumber aKeyHash) +{ + MOZ_ASSERT(mEntryStore.Get()); + NS_ASSERTION(!(aKeyHash & kCollisionFlag), + "!(aKeyHash & kCollisionFlag)"); + + // Compute the primary hash address. + PLDHashNumber hash1 = Hash1(aKeyHash); + PLDHashEntryHdr* entry = AddressEntry(hash1); + + // Miss: return space for a new entry. + if (EntryIsFree(entry)) { + return (Reason == ForAdd) ? entry : nullptr; + } + + // Hit: return entry. + PLDHashMatchEntry matchEntry = mOps->matchEntry; + if (MatchEntryKeyhash(entry, aKeyHash) && + matchEntry(entry, aKey)) { + return entry; + } + + // Collision: double hash. + PLDHashNumber hash2; + uint32_t sizeMask; + Hash2(aKeyHash, hash2, sizeMask); + + // Save the first removed entry pointer so Add() can recycle it. (Only used + // if Reason==ForAdd.) + PLDHashEntryHdr* firstRemoved = nullptr; + + for (;;) { + if (Reason == ForAdd) { + if (MOZ_UNLIKELY(EntryIsRemoved(entry))) { + if (!firstRemoved) { + firstRemoved = entry; + } + } else { + entry->mKeyHash |= kCollisionFlag; + } + } + + hash1 -= hash2; + hash1 &= sizeMask; + + entry = AddressEntry(hash1); + if (EntryIsFree(entry)) { + return (Reason == ForAdd) ? (firstRemoved ? firstRemoved : entry) + : nullptr; + } + + if (MatchEntryKeyhash(entry, aKeyHash) && + matchEntry(entry, aKey)) { + return entry; + } + } + + // NOTREACHED + return nullptr; +} + +// 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 PLDHashEntryHdr* +PLDHashTable::FindFreeEntry(PLDHashNumber aKeyHash) +{ + MOZ_ASSERT(mEntryStore.Get()); + NS_ASSERTION(!(aKeyHash & kCollisionFlag), + "!(aKeyHash & kCollisionFlag)"); + + // Compute the primary hash address. + PLDHashNumber hash1 = Hash1(aKeyHash); + PLDHashEntryHdr* entry = AddressEntry(hash1); + + // Miss: return space for a new entry. + if (EntryIsFree(entry)) { + return entry; + } + + // Collision: double hash. + PLDHashNumber hash2; + uint32_t sizeMask; + Hash2(aKeyHash, hash2, sizeMask); + + for (;;) { + NS_ASSERTION(!EntryIsRemoved(entry), + "!EntryIsRemoved(entry)"); + entry->mKeyHash |= kCollisionFlag; + + hash1 -= hash2; + hash1 &= sizeMask; + + entry = AddressEntry(hash1); + if (EntryIsFree(entry)) { + return entry; + } + } + + // NOTREACHED +} + +bool +PLDHashTable::ChangeTable(int32_t aDeltaLog2) +{ + MOZ_ASSERT(mEntryStore.Get()); + + // Look, but don't touch, until we succeed in getting new entry store. + int32_t oldLog2 = kHashBits - 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*)malloc(nbytes); + if (!newEntryStore) { + return false; + } + + // We can't fail from here on, so update table parameters. + mHashShift = kHashBits - newLog2; + mRemovedCount = 0; + + // Assign the new entry store to table. + memset(newEntryStore, 0, nbytes); + char* oldEntryStore; + char* oldEntryAddr; + oldEntryAddr = oldEntryStore = mEntryStore.Get(); + mEntryStore.Set(newEntryStore); + PLDHashMoveEntry moveEntry = mOps->moveEntry; + + // Copy only live entries, leaving removed ones behind. + uint32_t oldCapacity = 1u << oldLog2; + for (uint32_t i = 0; i < oldCapacity; ++i) { + PLDHashEntryHdr* oldEntry = (PLDHashEntryHdr*)oldEntryAddr; + if (EntryIsLive(oldEntry)) { + oldEntry->mKeyHash &= ~kCollisionFlag; + PLDHashEntryHdr* newEntry = FindFreeEntry(oldEntry->mKeyHash); + NS_ASSERTION(EntryIsFree(newEntry), "EntryIsFree(newEntry)"); + moveEntry(this, oldEntry, newEntry); + newEntry->mKeyHash = oldEntry->mKeyHash; + } + oldEntryAddr += mEntrySize; + } + + free(oldEntryStore); + return true; +} + +MOZ_ALWAYS_INLINE PLDHashNumber +PLDHashTable::ComputeKeyHash(const void* aKey) +{ + MOZ_ASSERT(mEntryStore.Get()); + + PLDHashNumber keyHash = mOps->hashKey(aKey); + keyHash *= kGoldenRatio; + + // 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) +{ +#ifdef DEBUG + AutoReadOp op(mChecker); +#endif + + PLDHashEntryHdr* entry = mEntryStore.Get() + ? SearchTable(aKey, + ComputeKeyHash(aKey)) + : nullptr; + return entry; +} + +PLDHashEntryHdr* +PLDHashTable::Add(const void* aKey, const mozilla::fallible_t&) +{ +#ifdef DEBUG + AutoWriteOp op(mChecker); +#endif + + // Allocate the entry storage if it hasn't already been allocated. + if (!mEntryStore.Get()) { + 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*)malloc(nbytes)); + if (!mEntryStore.Get()) { + return nullptr; + } + memset(mEntryStore.Get(), 0, nbytes); + } + + // 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; + if (mRemovedCount >= capacity >> 2) { + deltaLog2 = 0; + } else { + deltaLog2 = 1; + } + + // 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 nullptr; + } + } + + // 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); + PLDHashEntryHdr* entry = SearchTable(aKey, keyHash); + if (!EntryIsLive(entry)) { + // Initialize the entry, indicating that it's no longer free. + if (EntryIsRemoved(entry)) { + mRemovedCount--; + keyHash |= kCollisionFlag; + } + if (mOps->initEntry) { + mOps->initEntry(entry, aKey); + } + entry->mKeyHash = keyHash; + mEntryCount++; + } + + return entry; +} + +PLDHashEntryHdr* +PLDHashTable::Add(const void* aKey) +{ + PLDHashEntryHdr* entry = Add(aKey, fallible); + if (!entry) { + if (!mEntryStore.Get()) { + // 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 entry; +} + +void +PLDHashTable::Remove(const void* aKey) +{ +#ifdef DEBUG + AutoWriteOp op(mChecker); +#endif + + PLDHashEntryHdr* entry = mEntryStore.Get() + ? SearchTable(aKey, + ComputeKeyHash(aKey)) + : nullptr; + if (entry) { + RawRemove(entry); + ShrinkIfAppropriate(); + } +} + +void +PLDHashTable::RemoveEntry(PLDHashEntryHdr* aEntry) +{ +#ifdef DEBUG + AutoWriteOp op(mChecker); +#endif + + RawRemove(aEntry); + ShrinkIfAppropriate(); +} + +void +PLDHashTable::RawRemove(PLDHashEntryHdr* aEntry) +{ + // 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.Get()); + + MOZ_ASSERT(EntryIsLive(aEntry), "EntryIsLive(aEntry)"); + + // Load keyHash first in case clearEntry() goofs it. + PLDHashNumber keyHash = aEntry->mKeyHash; + mOps->clearEntry(this, aEntry); + if (keyHash & kCollisionFlag) { + MarkEntryRemoved(aEntry); + mRemovedCount++; + } else { + MarkEntryFree(aEntry); + } + 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 - (kHashBits - mHashShift); + MOZ_ASSERT(deltaLog2 <= 0); + + (void) ChangeTable(deltaLog2); + } +} + +size_t +PLDHashTable::ShallowSizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const +{ +#ifdef DEBUG + AutoReadOp op(mChecker); +#endif + + return aMallocSizeOf(mEntryStore.Get()); +} + +size_t +PLDHashTable::ShallowSizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const +{ + return aMallocSizeOf(this) + ShallowSizeOfExcludingThis(aMallocSizeOf); +} + +PLDHashTable::Iterator::Iterator(Iterator&& aOther) + : mTable(aOther.mTable) + , mStart(aOther.mStart) + , mLimit(aOther.mLimit) + , mCurrent(aOther.mCurrent) + , mNexts(aOther.mNexts) + , mNextsLimit(aOther.mNextsLimit) + , mHaveRemoved(aOther.mHaveRemoved) +{ + // No need to change |mChecker| here. + aOther.mTable = nullptr; + aOther.mStart = nullptr; + aOther.mLimit = nullptr; + aOther.mCurrent = nullptr; + aOther.mNexts = 0; + aOther.mNextsLimit = 0; + aOther.mHaveRemoved = false; +} + +PLDHashTable::Iterator::Iterator(PLDHashTable* aTable) + : mTable(aTable) + , mStart(mTable->mEntryStore.Get()) + , mLimit(mTable->mEntryStore.Get() + mTable->Capacity() * mTable->mEntrySize) + , mCurrent(mTable->mEntryStore.Get()) + , mNexts(0) + , mNextsLimit(mTable->EntryCount()) + , mHaveRemoved(false) +{ +#ifdef DEBUG + 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. + mCurrent += ChaosMode::randomUint32LessThan(mTable->Capacity()) * + mTable->mEntrySize; + } + + // Advance to the first live entry, if there is one. + if (!Done()) { + while (IsOnNonLiveEntry()) { + MoveToNextEntry(); + } + } +} + +PLDHashTable::Iterator::~Iterator() +{ + if (mTable) { + if (mHaveRemoved) { + mTable->ShrinkIfAppropriate(); + } +#ifdef DEBUG + mTable->mChecker.EndReadOp(); +#endif + } +} + +MOZ_ALWAYS_INLINE bool +PLDHashTable::Iterator::IsOnNonLiveEntry() const +{ + MOZ_ASSERT(!Done()); + return !EntryIsLive(reinterpret_cast(mCurrent)); +} + +MOZ_ALWAYS_INLINE void +PLDHashTable::Iterator::MoveToNextEntry() +{ + mCurrent += mTable->mEntrySize; + if (mCurrent == mLimit) { + mCurrent = mStart; // Wrap-around. Possible due to Chaos Mode. + } +} + +void +PLDHashTable::Iterator::Next() +{ + MOZ_ASSERT(!Done()); + + mNexts++; + + // Advance to the next live entry, if there is one. + if (!Done()) { + do { + MoveToNextEntry(); + } while (IsOnNonLiveEntry()); + } +} + +void +PLDHashTable::Iterator::Remove() +{ + // This cast is needed for the same reason as the one in the destructor. + mTable->RawRemove(Get()); + mHaveRemoved = true; +} + +#ifdef DEBUG +void +PLDHashTable::MarkImmutable() +{ + mChecker.SetNonWritable(); +} +#endif diff --git a/xpcom/glue/PLDHashTable.h b/xpcom/glue/PLDHashTable.h new file mode 100644 index 000000000..cd1323dbe --- /dev/null +++ b/xpcom/glue/PLDHashTable.h @@ -0,0 +1,621 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 PLDHashTable_h +#define PLDHashTable_h + +#include "mozilla/Atomics.h" +#include "mozilla/Attributes.h" // for MOZ_ALWAYS_INLINE +#include "mozilla/fallible.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/Move.h" +#include "mozilla/Types.h" +#include "nscore.h" + +typedef uint32_t PLDHashNumber; + +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. The mKeyHash member contains the result of multiplying the +// hash code returned from the hashKey callback (see below) by kGoldenRatio, +// then constraining the result to avoid the magic 0 and 1 values. The stored +// mKeyHash value is table size invariant, and it is maintained automatically +// -- users need never access it. +struct PLDHashEntryHdr +{ +private: + friend class PLDHashTable; + + PLDHashNumber mKeyHash; +}; + +#ifdef DEBUG + +// 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(1) {} + + Checker& operator=(Checker&& aOther) { + // Atomic<> doesn't have an |operator=(Atomic<>&&)|. + mState = uint32_t(aOther.mState); + mIsWritable = uint32_t(aOther.mIsWritable); + + aOther.mState = kIdle; + + 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 = 0; } + + // 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_ASSERT(IsIdle(oldState) || IsRead(oldState)); + MOZ_ASSERT(oldState < kReadMax); // check for overflow + } + + void EndReadOp() + { + uint32_t oldState = mState--; // this is an atomic decrement + MOZ_ASSERT(IsRead(oldState)); + } + + void StartWriteOp() + { + MOZ_ASSERT(IsWritable()); + uint32_t oldState = mState.exchange(kWrite); + MOZ_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_ASSERT(IsWritable()); + uint32_t oldState = mState.exchange(kIdle); + MOZ_ASSERT(IsWrite(oldState)); + } + + void StartIteratorRemovalOp() + { + // When doing removals at the end of iteration, we go from Read1 state to + // Write and then back. + MOZ_ASSERT(IsWritable()); + uint32_t oldState = mState.exchange(kWrite); + MOZ_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_ASSERT(IsWritable()); + uint32_t oldState = mState.exchange(kRead1); + MOZ_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_ASSERT(IsIdle(oldState)); + } + + void EndDestructorOp() + { + uint32_t oldState = mState.exchange(kIdle); + MOZ_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; + + mutable mozilla::Atomic mState; + mutable 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: + // This class maintains the invariant that every time the entry store is + // changed, the generation is updated. + class EntryStore + { + private: + char* mEntryStore; + uint32_t mGeneration; + + public: + EntryStore() : mEntryStore(nullptr), mGeneration(0) {} + + ~EntryStore() + { + free(mEntryStore); + mEntryStore = nullptr; + mGeneration++; // a little paranoid, but why not be extra safe? + } + + char* Get() { return mEntryStore; } + const char* Get() const { return mEntryStore; } + + void Set(char* aEntryStore) + { + mEntryStore = aEntryStore; + mGeneration++; + } + + uint32_t Generation() const { return mGeneration; } + }; + + const PLDHashTableOps* const mOps; // Virtual operations; see below. + int16_t mHashShift; // Multiplicative hash shift. + const uint32_t mEntrySize; // Number of bytes in an entry. + uint32_t mEntryCount; // Number of entries in table. + uint32_t mRemovedCount; // Removed entry sentinels in table. + EntryStore mEntryStore; // (Lazy) entry storage and generation. + +#ifdef DEBUG + 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) + // These two fields are |const|. Initialize them here because the + // move assignment operator cannot modify them. + : mOps(aOther.mOps) + , mEntrySize(aOther.mEntrySize) + // Initialize this field because it is required for a safe call to the + // destructor, which the move assignment operator does. + , mEntryStore() +#ifdef DEBUG + , mChecker() +#endif + { + *this = mozilla::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.Get() ? CapacityFromHashShift() : 0; + } + + uint32_t EntrySize() const { return mEntrySize; } + uint32_t EntryCount() const { return mEntryCount; } + uint32_t Generation() const { return mEntryStore.Generation(); } + + // 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); + + // 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, |aEntry->mKeyHash| has been set so that + // PLDHashTable::EntryIsFree(entry) is false, and it is up to the caller to + // initialize the key and value parts of the entry sub-type, if they have not + // been set already (i.e. if entry was not already in the table, and if the + // optional initEntry hook was not used). + 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 ShallowSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const; + + // Like ShallowSizeOfExcludingThis(), but includes sizeof(*this). + size_t ShallowSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const; + +#ifdef DEBUG + // 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(); +#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); + + // 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); + Iterator(Iterator&& aOther); + ~Iterator(); + + // Have we finished? + bool Done() const { return mNexts == mNextsLimit; } + + // Get the current entry. + PLDHashEntryHdr* Get() const + { + MOZ_ASSERT(!Done()); + + PLDHashEntryHdr* entry = reinterpret_cast(mCurrent); + MOZ_ASSERT(EntryIsLive(entry)); + return entry; + } + + // 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(); + + protected: + PLDHashTable* mTable; // Main table pointer. + + private: + char* mStart; // The first entry. + char* mLimit; // One past the last entry. + char* 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? + + bool IsOnNonLiveEntry() const; + void MoveToNextEntry(); + + Iterator() = delete; + Iterator(const Iterator&) = delete; + 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: + // Multiplicative hash uses an unsigned 32 bit integer and the golden ratio, + // expressed as a fixed-point 32-bit fraction. + static const uint32_t kHashBits = 32; + static const uint32_t kGoldenRatio = 0x9E3779B9U; + + static uint32_t HashShift(uint32_t aEntrySize, uint32_t aLength); + + static const PLDHashNumber kCollisionFlag = 1; + + static bool EntryIsFree(PLDHashEntryHdr* aEntry) + { + return aEntry->mKeyHash == 0; + } + static bool EntryIsRemoved(PLDHashEntryHdr* aEntry) + { + return aEntry->mKeyHash == 1; + } + static bool EntryIsLive(PLDHashEntryHdr* aEntry) + { + return aEntry->mKeyHash >= 2; + } + + static void MarkEntryFree(PLDHashEntryHdr* aEntry) + { + aEntry->mKeyHash = 0; + } + static void MarkEntryRemoved(PLDHashEntryHdr* aEntry) + { + aEntry->mKeyHash = 1; + } + + PLDHashNumber Hash1(PLDHashNumber aHash0); + void Hash2(PLDHashNumber aHash, uint32_t& aHash2Out, uint32_t& aSizeMaskOut); + + static bool MatchEntryKeyhash(PLDHashEntryHdr* aEntry, PLDHashNumber aHash); + PLDHashEntryHdr* AddressEntry(uint32_t aIndex); + + // We store mHashShift rather than sizeLog2 to optimize the collision-free + // case in SearchTable. + uint32_t CapacityFromHashShift() const + { + return ((uint32_t)1 << (kHashBits - mHashShift)); + } + + PLDHashNumber ComputeKeyHash(const void* aKey); + + enum SearchReason { ForSearchOrRemove, ForAdd }; + + template + PLDHashEntryHdr* NS_FASTCALL + SearchTable(const void* aKey, PLDHashNumber aKeyHash); + + PLDHashEntryHdr* FindFreeEntry(PLDHashNumber aKeyHash); + + bool ChangeTable(int aDeltaLog2); + + void ShrinkIfAppropriate(); + + 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, apart from mKeyHash. This function is called when +// Add() finds no existing entry for the given key, and must add a new one. At +// that point, |aEntry->mKeyHash| is not set yet, to avoid claiming the last +// free entry in a severely overloaded table. +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 successful Add(tbl, key), the returned entry pointer +// addresses an entry struct whose mKeyHash member has been set non-zero, but +// all other 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; + PLDHashClearEntry clearEntry; + + // Optional hooks start here. If null, these are not called. + 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/glue/ReentrantMonitor.h b/xpcom/glue/ReentrantMonitor.h new file mode 100644 index 000000000..0798fe2af --- /dev/null +++ b/xpcom/glue/ReentrantMonitor.h @@ -0,0 +1,249 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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" + +#ifdef MOZILLA_INTERNAL_API +#include "GeckoProfiler.h" +#endif //MOZILLA_INTERNAL_API + +#include "mozilla/BlockingResourceBase.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 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) { + NS_RUNTIMEABORT("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() { PR_EnterMonitor(mReentrantMonitor); } + + /** + * Exit + * @see prmon.h + **/ + void Exit() { PR_ExitMonitor(mReentrantMonitor); } + + /** + * Wait + * @see prmon.h + **/ + nsresult Wait(PRIntervalTime aInterval = PR_INTERVAL_NO_TIMEOUT) + { +#ifdef MOZILLA_INTERNAL_API + GeckoProfilerSleepRAII profiler_sleep; +#endif //MOZILLA_INTERNAL_API + return PR_Wait(mReentrantMonitor, aInterval) == PR_SUCCESS ? + NS_OK : NS_ERROR_FAILURE; + } + +#else // ifndef DEBUG + void Enter(); + void Exit(); + 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() + { + PR_ASSERT_CURRENT_THREAD_IN_MONITOR(mReentrantMonitor); + } + + /** + * AssertNotCurrentThreadIn + * @see prmon.h + **/ + void AssertNotCurrentThreadIn() + { + // FIXME bug 476536 + } + +#else + void AssertCurrentThreadIn() {} + void AssertNotCurrentThreadIn() {} + +#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_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) + : mReentrantMonitor(&aReentrantMonitor) + { + NS_ASSERTION(mReentrantMonitor, "null monitor"); + mReentrantMonitor->Enter(); + } + + ~ReentrantMonitorAutoEnter(void) + { + 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) CPP_THROW_NEW; + + 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_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) + : mReentrantMonitor(&aReentrantMonitor) + { + NS_ASSERTION(mReentrantMonitor, "null monitor"); + mReentrantMonitor->AssertCurrentThreadIn(); + mReentrantMonitor->Exit(); + } + + ~ReentrantMonitorAutoExit(void) + { + mReentrantMonitor->Enter(); + } + +private: + ReentrantMonitorAutoExit(); + ReentrantMonitorAutoExit(const ReentrantMonitorAutoExit&); + ReentrantMonitorAutoExit& operator=(const ReentrantMonitorAutoExit&); + static void* operator new(size_t) CPP_THROW_NEW; + + ReentrantMonitor* mReentrantMonitor; +}; + +} // namespace mozilla + + +#endif // ifndef mozilla_ReentrantMonitor_h diff --git a/xpcom/glue/moz.build b/xpcom/glue/moz.build new file mode 100644 index 000000000..95c18b273 --- /dev/null +++ b/xpcom/glue/moz.build @@ -0,0 +1,123 @@ +# -*- 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('nsString*'): + BUG_COMPONENT = ('Core', 'String') + +DIRS += ['standalone'] + +# On win we build two glue libs - glue linked to crt dlls here and in staticruntime we build +# a statically linked glue lib. +if CONFIG['OS_ARCH'] == 'WINNT': + DIRS += ['staticruntime'] + +EXPORTS += [ + 'MainThreadUtils.h', + 'nsArrayEnumerator.h', + 'nsArrayUtils.h', + 'nsBaseHashtable.h', + 'nsCategoryCache.h', + 'nsClassHashtable.h', + 'nsCOMArray.h', + 'nsComponentManagerUtils.h', + 'nsCOMPtr.h', + 'nsCRTGlue.h', + 'nsCycleCollectionNoteChild.h', + 'nsCycleCollectionNoteRootCallback.h', + 'nsCycleCollectionParticipant.h', + 'nsCycleCollectionTraversalCallback.h', + 'nsDataHashtable.h', + 'nsDebug.h', + 'nsDeque.h', + 'nsEnumeratorUtils.h', + 'nsHashKeys.h', + 'nsIClassInfoImpl.h', + 'nsID.h', + 'nsIInterfaceRequestorUtils.h', + 'nsINIParser.h', + 'nsInterfaceHashtable.h', + 'nsISupportsImpl.h', + 'nsISupportsUtils.h', + 'nsIWeakReferenceUtils.h', + 'nsJSThingHashtable.h', + 'nsMemory.h', + 'nsPointerHashKeys.h', + 'nsProxyRelease.h', + 'nsQuickSort.h', + 'nsRefPtrHashtable.h', + 'nsServiceManagerUtils.h', + 'nsStringAPI.h', + 'nsStringGlue.h', + 'nsTArray-inl.h', + 'nsTArray.h', + 'nsTArrayForwardDeclare.h', + 'nsTextFormatter.h', + 'nsTHashtable.h', + 'nsThreadUtils.h', + 'nsTObserverArray.h', + 'nsTPriorityQueue.h', + 'nsTWeakRef.h', + 'nsVersionComparator.h', + 'nsWeakReference.h', + 'nsXPTCUtils.h', + 'PLDHashTable.h', +] + +EXPORTS.mozilla += [ + 'AppData.h', + 'AutoRestore.h', + 'BlockingResourceBase.h', + 'CondVar.h', + 'DeadlockDetector.h', + 'EnumeratedArrayCycleCollection.h', + 'FileUtils.h', + 'GenericFactory.h', + 'IntentionalCrash.h', + 'Monitor.h', + 'Mutex.h', + 'Observer.h', + 'ReentrantMonitor.h', +] + +include('objs.mozbuild') + +UNIFIED_SOURCES += xpcom_gluens_src_cppsrcs +UNIFIED_SOURCES += xpcom_glue_src_cppsrcs + +UNIFIED_SOURCES += [ + 'GenericModule.cpp', + 'nsStringAPI.cpp', +] + +Library('xpcomglue_s') + +SDK_LIBRARY = True + +FORCE_STATIC_LIB = True + +if CONFIG['_MSC_VER']: + DEFINES['_USE_ANSI_CPP'] = True + # Don't include directives about which CRT to use + CFLAGS += ['-Zl'] + CXXFLAGS += ['-Zl'] + +LOCAL_INCLUDES += [ + '../build', + '../threads', +] + +if CONFIG['ENABLE_TESTS']: + DIRS += ['tests/gtest'] + +# Include fallible for third party code using the xpcom glue +USE_LIBS += [ + 'fallible', +] + +# Force to build a static library only +NO_EXPAND_LIBS = True + +DIST_INSTALL = True diff --git a/xpcom/glue/nsArrayEnumerator.cpp b/xpcom/glue/nsArrayEnumerator.cpp new file mode 100644 index 000000000..2d2ef6da7 --- /dev/null +++ b/xpcom/glue/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 "nsISimpleEnumerator.h" + +#include "nsCOMArray.h" +#include "nsCOMPtr.h" +#include "mozilla/RefPtr.h" + +class nsSimpleArrayEnumerator final : public nsISimpleEnumerator +{ +public: + // nsISupports interface + NS_DECL_ISUPPORTS + + // nsISimpleEnumerator interface + NS_DECL_NSISIMPLEENUMERATOR + + // nsSimpleArrayEnumerator methods + explicit nsSimpleArrayEnumerator(nsIArray* aValueArray) + : mValueArray(aValueArray) + , mIndex(0) + { + } + +private: + ~nsSimpleArrayEnumerator() {} + +protected: + nsCOMPtr mValueArray; + uint32_t mIndex; +}; + +NS_IMPL_ISUPPORTS(nsSimpleArrayEnumerator, nsISimpleEnumerator) + +NS_IMETHODIMP +nsSimpleArrayEnumerator::HasMoreElements(bool* aResult) +{ + NS_PRECONDITION(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) +{ + NS_PRECONDITION(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) +{ + RefPtr enumer = new nsSimpleArrayEnumerator(aArray); + 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 nsISimpleEnumerator +{ +public: + // nsISupports interface + NS_DECL_ISUPPORTS + + // nsISimpleEnumerator interface + NS_DECL_NSISIMPLEENUMERATOR + + // nsSimpleArrayEnumerator methods + nsCOMArrayEnumerator() : mIndex(0) {} + + // specialized operator to make sure we make room for mValues + void* operator new(size_t aSize, const nsCOMArray_base& aArray) CPP_THROW_NEW; + void operator delete(void* aPtr) { ::operator delete(aPtr); } + +private: + ~nsCOMArrayEnumerator(void); + +protected: + uint32_t mIndex; // current position + uint32_t mArraySize; // size of the array + + // this is actually bigger + nsISupports* mValueArray[1]; +}; + +NS_IMPL_ISUPPORTS(nsCOMArrayEnumerator, nsISimpleEnumerator) + +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) +{ + NS_PRECONDITION(aResult != 0, "null ptr"); + if (!aResult) { + return NS_ERROR_NULL_POINTER; + } + + *aResult = (mIndex < mArraySize); + return NS_OK; +} + +NS_IMETHODIMP +nsCOMArrayEnumerator::GetNext(nsISupports** aResult) +{ + NS_PRECONDITION(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; +} + +void* +nsCOMArrayEnumerator::operator new(size_t aSize, + const nsCOMArray_base& aArray) CPP_THROW_NEW +{ + // 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 + aSize += (aArray.Count() - 1) * sizeof(aArray[0]); + + // do the actual allocation + nsCOMArrayEnumerator* result = + static_cast(::operator new(aSize)); + + // 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 + uint32_t i; + uint32_t max = result->mArraySize = aArray.Count(); + for (i = 0; i < max; ++i) { + result->mValueArray[i] = aArray[i]; + NS_IF_ADDREF(result->mValueArray[i]); + } + + return result; +} + +nsresult +NS_NewArrayEnumerator(nsISimpleEnumerator** aResult, + const nsCOMArray_base& aArray) +{ + RefPtr enumerator = new (aArray) nsCOMArrayEnumerator(); + enumerator.forget(aResult); + return NS_OK; +} diff --git a/xpcom/glue/nsArrayEnumerator.h b/xpcom/glue/nsArrayEnumerator.h new file mode 100644 index 000000000..341ae86ed --- /dev/null +++ b/xpcom/glue/nsArrayEnumerator.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 nsArrayEnumerator_h__ +#define nsArrayEnumerator_h__ + +// enumerator implementation for nsIArray + +#include "nscore.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); + +// 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); + +#endif diff --git a/xpcom/glue/nsArrayUtils.cpp b/xpcom/glue/nsArrayUtils.cpp new file mode 100644 index 000000000..480863737 --- /dev/null +++ b/xpcom/glue/nsArrayUtils.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 "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/glue/nsArrayUtils.h b/xpcom/glue/nsArrayUtils.h new file mode 100644 index 000000000..68032e8c0 --- /dev/null +++ b/xpcom/glue/nsArrayUtils.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 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/glue/nsBaseHashtable.h b/xpcom/glue/nsBaseHashtable.h new file mode 100644 index 000000000..f52df3dd1 --- /dev/null +++ b/xpcom/glue/nsBaseHashtable.h @@ -0,0 +1,270 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 "mozilla/MemoryReporting.h" +#include "mozilla/Move.h" +#include "nsTHashtable.h" +#include "nsDebug.h" + +template +class nsBaseHashtable; // forward declaration + +/** + * 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: + DataType mData; + friend class nsTHashtable>; + +private: + typedef typename KeyClass::KeyType KeyType; + typedef typename KeyClass::KeyTypePointer KeyTypePointer; + + explicit nsBaseHashtableET(KeyTypePointer aKey); + nsBaseHashtableET(nsBaseHashtableET&& aToMove); + ~nsBaseHashtableET(); +}; + +/** + * templated hashtable for simple data types + * This class manages simple data types that do not need construction or + * destruction. + * + * @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. If UserDataType is not the same, + * DataType must implicitly cast to UserDataType + * @param UserDataType the user sees, for example uint32_t or nsISupports* + */ +template +class nsBaseHashtable + : protected nsTHashtable> +{ + typedef mozilla::fallible_t fallible_t; + +public: + typedef typename KeyClass::KeyType KeyType; + typedef nsBaseHashtableET EntryType; + + using nsTHashtable::Contains; + + nsBaseHashtable() {} + explicit nsBaseHashtable(uint32_t aInitLength) + : nsTHashtable(aInitLength) + { + } + + /** + * Return the number of entries in the table. + * @return number of entries + */ + uint32_t Count() const { return nsTHashtable::Count(); } + + /** + * retrieve the value for a key. + * @param aKey the key to retreive + * @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. + */ + bool Get(KeyType aKey, UserDataType* aData) const + { + EntryType* ent = this->GetEntry(aKey); + if (!ent) { + return false; + } + + if (aData) { + *aData = 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. + * + * @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. + */ + UserDataType Get(KeyType aKey) const + { + EntryType* ent = this->GetEntry(aKey); + if (!ent) { + return UserDataType{}; + } + + return ent->mData; + } + + /** + * Add key to the table if not already present, and return a reference to its + * value. If key is not already in the table then the value is default + * constructed. + */ + DataType& GetOrInsert(const KeyType& aKey) + { + EntryType* ent = this->GetEntry(aKey); + if (ent) { + return ent->mData; + } + + ent = this->PutEntry(aKey); + return ent->mData; + } + + /** + * put a new value for the associated key + * @param aKey the key to put + * @param aData the new data + * @return always true, unless memory allocation failed + */ + void Put(KeyType aKey, const UserDataType& aData) + { + if (!Put(aKey, aData, mozilla::fallible)) { + NS_ABORT_OOM(this->mTable.EntrySize() * this->mTable.EntryCount()); + } + } + + MOZ_MUST_USE bool Put(KeyType aKey, const UserDataType& aData, + const fallible_t&) + { + EntryType* ent = this->PutEntry(aKey, mozilla::fallible); + if (!ent) { + return false; + } + + ent->mData = aData; + + return true; + } + + /** + * remove the data for the associated key + * @param aKey the key to remove from the hashtable + */ + void Remove(KeyType aKey) { this->RemoveEntry(aKey); } + + // 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 : public PLDHashTable::Iterator + { + public: + typedef PLDHashTable::Iterator Base; + + explicit Iterator(nsBaseHashtable* aTable) : Base(&aTable->mTable) {} + Iterator(Iterator&& aOther) : Base(aOther.mTable) {} + ~Iterator() {} + + KeyType Key() const { return static_cast(Get())->GetKey(); } + UserDataType UserData() const + { + return static_cast(Get())->mData; + } + DataType& Data() const { return static_cast(Get())->mData; } + + private: + Iterator() = delete; + Iterator(const Iterator&) = delete; + Iterator& operator=(const Iterator&) = delete; + Iterator& operator=(const Iterator&&) = delete; + }; + + Iterator Iter() { return Iterator(this); } + + Iterator ConstIter() const + { + return Iterator(const_cast(this)); + } + + /** + * 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); + } + + +#ifdef DEBUG + using nsTHashtable::MarkImmutable; +#endif +}; + +// +// nsBaseHashtableET definitions +// + +template +nsBaseHashtableET::nsBaseHashtableET(KeyTypePointer aKey) + : KeyClass(aKey) + , mData() +{ +} + +template +nsBaseHashtableET::nsBaseHashtableET( + nsBaseHashtableET&& aToMove) + : KeyClass(mozilla::Move(aToMove)) + , mData(mozilla::Move(aToMove.mData)) +{ +} + +template +nsBaseHashtableET::~nsBaseHashtableET() +{ +} + +#endif // nsBaseHashtable_h__ diff --git a/xpcom/glue/nsCOMArray.cpp b/xpcom/glue/nsCOMArray.cpp new file mode 100644 index 000000000..3522f7b0d --- /dev/null +++ b/xpcom/glue/nsCOMArray.cpp @@ -0,0 +1,323 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. 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 "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); + } + // 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::nsCOMArrayComparator(const void* aElement1, + const void* aElement2, + void* aData) +{ + nsCOMArrayComparatorContext* ctx = + static_cast(aData); + return (*ctx->mComparatorFunc)(*static_cast(aElement1), + *static_cast(aElement2), + ctx->mData); +} + +void +nsCOMArray_base::Sort(nsBaseArrayComparatorFunc aFunc, void* aData) +{ + if (mArray.Length() > 1) { + nsCOMArrayComparatorContext ctx = {aFunc, aData}; + NS_QuickSort(mArray.Elements(), mArray.Length(), sizeof(nsISupports*), + nsCOMArrayComparator, &ctx); + } +} + +bool +nsCOMArray_base::InsertObjectAt(nsISupports* aObject, int32_t aIndex) +{ + if ((uint32_t)aIndex > mArray.Length()) { + return false; + } + + if (!mArray.InsertElementAt(aIndex, aObject)) { + return false; + } + + 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; + } + + if (!mArray.InsertElementsAt(aIndex, aObjects.mArray)) { + return false; + } + + // 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; + objects.SwapElements(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; +} + +void +nsCOMArray_base::Adopt(nsISupports** aElements, uint32_t aSize) +{ + Clear(); + mArray.AppendElements(aElements, aSize); + + // Free the allocated array as well. + NS_Free(aElements); +} + +uint32_t +nsCOMArray_base::Forget(nsISupports*** aElements) +{ + uint32_t length = Length(); + size_t array_size = sizeof(nsISupports*) * length; + nsISupports** array = static_cast(NS_Alloc(array_size)); + memmove(array, Elements(), array_size); + *aElements = array; + // Don't Release the contained pointers; the caller of the method will + // do this eventually. + mArray.Clear(); + + return length; +} diff --git a/xpcom/glue/nsCOMArray.h b/xpcom/glue/nsCOMArray.h new file mode 100644 index 000000000..56d077a2f --- /dev/null +++ b/xpcom/glue/nsCOMArray.h @@ -0,0 +1,473 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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/MemoryReporting.h" + +#include "nsCycleCollectionNoteChild.h" +#include "nsTArray.h" +#include "nsISupports.h" + +// 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() {} + explicit nsCOMArray_base(int32_t aCount) : mArray(aCount) {} + nsCOMArray_base(const nsCOMArray_base& aOther); + ~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 (*nsBaseArrayComparatorFunc)(nsISupports* aElement1, + nsISupports* aElement2, + void* aData); + + struct nsCOMArrayComparatorContext + { + nsBaseArrayComparatorFunc mComparatorFunc; + void* mData; + }; + + static int nsCOMArrayComparator(const void* aElement1, const void* aElement2, + void* aData); + void Sort(nsBaseArrayComparatorFunc 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(), mozilla::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); + } + + void Adopt(nsISupports** aElements, uint32_t aCount); + uint32_t Forget(nsISupports*** aElements); +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: + nsCOMArray() {} + explicit nsCOMArray(int32_t aCount) : nsCOMArray_base(aCount) {} + explicit nsCOMArray(const nsCOMArray& aOther) : nsCOMArray_base(aOther) {} + nsCOMArray(nsCOMArray&& aOther) { SwapElements(aOther); } + ~nsCOMArray() {} + + // We have a move assignment operator, but no copy assignment operator. + nsCOMArray& operator=(nsCOMArray && aOther) + { + SwapElements(aOther); + return *this; + } + + // 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); + } + + // Enumerator callback function. Return false to stop + // Here's a more readable form: + // bool enumerate(T* aElement, void* aData) + typedef bool (*nsCOMArrayEnumFunc)(T* aElement, void* aData); + + // enumerate through the array with a callback. + bool EnumerateForwards(nsCOMArrayEnumFunc aFunc, void* aData) + { + return nsCOMArray_base::EnumerateForwards(nsBaseArrayEnumFunc(aFunc), + aData); + } + + bool EnumerateBackwards(nsCOMArrayEnumFunc aFunc, void* aData) + { + return nsCOMArray_base::EnumerateBackwards(nsBaseArrayEnumFunc(aFunc), + aData); + } + + typedef int (*nsCOMArrayComparatorFunc)(T* aElement1, T* aElement2, + void* aData); + + void Sort(nsCOMArrayComparatorFunc aFunc, void* aData) + { + nsCOMArray_base::Sort(nsBaseArrayComparatorFunc(aFunc), aData); + } + + // 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(mozilla::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); + } + + /** + * Adopt parameters that resulted from an XPIDL outparam. The aElements + * parameter will be freed as a result of the call. + * + * Example usage: + * nsCOMArray array; + * nsISomeInterface** elements; + * uint32_t length; + * ptr->GetSomeArray(&elements, &length); + * array.Adopt(elements, length); + */ + void Adopt(T** aElements, uint32_t aSize) + { + nsCOMArray_base::Adopt(reinterpret_cast(aElements), aSize); + } + + /** + * Export the contents of this array to an XPIDL outparam. The array will be + * Clear()'d after this operation. + * + * Example usage: + * nsCOMArray array; + * *length = array.Forget(retval); + */ + uint32_t Forget(T*** aElements) + { + return nsCOMArray_base::Forget(reinterpret_cast(aElements)); + } + +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/glue/nsCOMPtr.cpp b/xpcom/glue/nsCOMPtr.cpp new file mode 100644 index 000000000..263fa62c2 --- /dev/null +++ b/xpcom/glue/nsCOMPtr.cpp @@ -0,0 +1,128 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. 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 +nsQueryInterface::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 +nsQueryInterfaceWithError::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; +} + +void +nsCOMPtr_base::assign_with_AddRef(nsISupports* aRawPtr) +{ + if (aRawPtr) { + NSCAP_ADDREF(this, aRawPtr); + } + assign_assuming_AddRef(aRawPtr); +} + +void +nsCOMPtr_base::assign_from_qi(const nsQueryInterface aQI, const nsIID& aIID) +{ + void* newRawPtr; + if (NS_FAILED(aQI(aIID, &newRawPtr))) { + newRawPtr = nullptr; + } + assign_assuming_AddRef(static_cast(newRawPtr)); +} + +void +nsCOMPtr_base::assign_from_qi_with_error(const nsQueryInterfaceWithError& aQI, + const nsIID& aIID) +{ + void* newRawPtr; + if (NS_FAILED(aQI(aIID, &newRawPtr))) { + newRawPtr = nullptr; + } + assign_assuming_AddRef(static_cast(newRawPtr)); +} + +void +nsCOMPtr_base::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)); +} + +void +nsCOMPtr_base::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)); +} + +void +nsCOMPtr_base::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)); +} + +void +nsCOMPtr_base::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)); +} + +void +nsCOMPtr_base::assign_from_helper(const nsCOMPtr_helper& aHelper, + const nsIID& aIID) +{ + void* newRawPtr; + if (NS_FAILED(aHelper(aIID, &newRawPtr))) { + newRawPtr = nullptr; + } + assign_assuming_AddRef(static_cast(newRawPtr)); +} + +void** +nsCOMPtr_base::begin_assignment() +{ + assign_assuming_AddRef(nullptr); + return reinterpret_cast(&mRawPtr); +} diff --git a/xpcom/glue/nsCOMPtr.h b/xpcom/glue/nsCOMPtr.h new file mode 100644 index 000000000..373f55cbb --- /dev/null +++ b/xpcom/glue/nsCOMPtr.h @@ -0,0 +1,1472 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 User Manual at: + * http://www.mozilla.org/projects/xpcom/nsCOMPtr.html + * + * + * nsCOMPtr + * better than a raw pointer + * for owning objects + * -- scc + */ + +#include "mozilla/AlreadyAddRefed.h" +#include "mozilla/Assertions.h" +#include "mozilla/Attributes.h" +#include "mozilla/Move.h" +#include "mozilla/TypeTraits.h" + +#include "nsDebug.h" // for |NS_ASSERTION| +#include "nsISupportsUtils.h" // for |nsresult|, |NS_ADDREF|, |NS_GET_TEMPLATE_IID| et al +#include "mozilla/RefPtr.h" + +#include "nsCycleCollectionNoteChild.h" + + +/* + * 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 + +#define NSCAP_FEATURE_USE_BASE + +#ifdef DEBUG + #define NSCAP_FEATURE_TEST_DONTQUERY_CASES + #undef NSCAP_FEATURE_USE_BASE +#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 + +#if defined(NSCAP_DISABLE_DEBUG_PTR_TYPES) + #define NSCAP_FEATURE_USE_BASE +#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 mozilla::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 nsQueryInterface final +{ +public: + explicit + nsQueryInterface(nsISupports* aRawPtr) : mRawPtr(aRawPtr) {} + + nsresult NS_FASTCALL operator()(const nsIID& aIID, void**) const; + +private: + nsISupports* MOZ_OWNING_REF mRawPtr; +}; + +class nsQueryInterfaceWithError final +{ +public: + nsQueryInterfaceWithError(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; +}; + +inline nsQueryInterface +do_QueryInterface(nsISupports* aRawPtr) +{ + return nsQueryInterface(aRawPtr); +} + +inline nsQueryInterfaceWithError +do_QueryInterface(nsISupports* 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; +}; + +/** + * Factors implementation for all template versions of nsCOMPtr. + * + * Here's the way people normally do things like this: + * + * template class Foo { ... }; + * template<> class Foo { ... }; + * template class Foo : private Foo { ... }; + */ +class nsCOMPtr_base +{ +public: + explicit nsCOMPtr_base(nsISupports* aRawPtr = nullptr) : mRawPtr(aRawPtr) {} + + NS_CONSTRUCTOR_FASTCALL ~nsCOMPtr_base() + { + NSCAP_LOG_RELEASE(this, mRawPtr); + if (mRawPtr) { + NSCAP_RELEASE(this, mRawPtr); + } + } + + void NS_FASTCALL + assign_with_AddRef(nsISupports*); + void NS_FASTCALL + assign_from_qi(const nsQueryInterface, const nsIID&); + void NS_FASTCALL + assign_from_qi_with_error(const nsQueryInterfaceWithError&, const nsIID&); + void NS_FASTCALL + assign_from_gs_cid(const nsGetServiceByCID, const nsIID&); + void NS_FASTCALL + assign_from_gs_cid_with_error(const nsGetServiceByCIDWithError&, const nsIID&); + void NS_FASTCALL + assign_from_gs_contractid(const nsGetServiceByContractID, const nsIID&); + void NS_FASTCALL + assign_from_gs_contractid_with_error(const nsGetServiceByContractIDWithError&, + const nsIID&); + void NS_FASTCALL + assign_from_helper(const nsCOMPtr_helper&, const nsIID&); + void** NS_FASTCALL + begin_assignment(); + +protected: + NS_MAY_ALIAS_PTR(nsISupports) MOZ_OWNING_REF mRawPtr; + + void assign_assuming_AddRef(nsISupports* aNewPtr) + { + // |AddRef()|ing the new value (before entering this function) before + // |Release()|ing the old lets us safely ignore the self-assignment case. + // We must, however, be careful only to |Release()| _after_ doing the + // assignment, in case the |Release()| leads to our _own_ destruction, + // which would, in turn, cause an incorrect second |Release()| of our old + // pointer. Thank for discovering this. + nsISupports* oldPtr = mRawPtr; + mRawPtr = aNewPtr; + NSCAP_LOG_ASSIGNMENT(this, aNewPtr); + NSCAP_LOG_RELEASE(this, oldPtr); + if (oldPtr) { + NSCAP_RELEASE(this, oldPtr); + } + } +}; + +// template class nsGetterAddRefs; + +// Helper for assert_validity method +template +char (&TestForIID(decltype(&NS_GET_TEMPLATE_IID(T))))[2]; +template +char TestForIID(...); + +template +class nsCOMPtr final +#ifdef NSCAP_FEATURE_USE_BASE + : private nsCOMPtr_base +#endif +{ + +#ifdef NSCAP_FEATURE_USE_BASE + #define NSCAP_CTOR_BASE(x) nsCOMPtr_base(x) +#else + #define NSCAP_CTOR_BASE(x) mRawPtr(x) + +private: + void assign_with_AddRef(nsISupports*); + void assign_from_qi(const nsQueryInterface, const nsIID&); + 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_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; +#endif + + 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; + +#ifndef NSCAP_FEATURE_USE_BASE + ~nsCOMPtr() + { + NSCAP_LOG_RELEASE(this, mRawPtr); + if (mRawPtr) { + NSCAP_RELEASE(this, mRawPtr); + } + } +#endif + +#ifdef NSCAP_FEATURE_TEST_DONTQUERY_CASES + void Assert_NoQueryNeeded() + { + if (mRawPtr) { + nsCOMPtr query_result(do_QueryInterface(mRawPtr)); + NS_ASSERTION(query_result.get() == mRawPtr, "QueryInterface needed"); + } + } + + #define NSCAP_ASSERT_NO_QUERY_NEEDED() Assert_NoQueryNeeded(); +#else + #define NSCAP_ASSERT_NO_QUERY_NEEDED() +#endif + + + // Constructors + + nsCOMPtr() + : NSCAP_CTOR_BASE(nullptr) + { + assert_validity(); + NSCAP_LOG_ASSIGNMENT(this, nullptr); + } + + MOZ_IMPLICIT nsCOMPtr(decltype(nullptr)) + : NSCAP_CTOR_BASE(nullptr) + { + assert_validity(); + NSCAP_LOG_ASSIGNMENT(this, nullptr); + } + + nsCOMPtr(const nsCOMPtr& aSmartPtr) + : NSCAP_CTOR_BASE(aSmartPtr.mRawPtr) + { + assert_validity(); + if (mRawPtr) { + NSCAP_ADDREF(this, mRawPtr); + } + NSCAP_LOG_ASSIGNMENT(this, aSmartPtr.mRawPtr); + } + + nsCOMPtr(nsCOMPtr&& aSmartPtr) + : NSCAP_CTOR_BASE(aSmartPtr.mRawPtr) + { + assert_validity(); + aSmartPtr.mRawPtr = nullptr; + NSCAP_LOG_ASSIGNMENT(this, mRawPtr); + NSCAP_ASSERT_NO_QUERY_NEEDED(); + } + + MOZ_IMPLICIT nsCOMPtr(T* aRawPtr) + : NSCAP_CTOR_BASE(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) + : NSCAP_CTOR_BASE(aSmartPtr.take()) + { + assert_validity(); + NSCAP_LOG_ASSIGNMENT(this, mRawPtr); + NSCAP_ASSERT_NO_QUERY_NEEDED(); + } + + // Construct from |otherComPtr.forget()|. + MOZ_IMPLICIT nsCOMPtr(already_AddRefed&& aSmartPtr) + : NSCAP_CTOR_BASE(aSmartPtr.take()) + { + assert_validity(); + NSCAP_LOG_ASSIGNMENT(this, mRawPtr); + NSCAP_ASSERT_NO_QUERY_NEEDED(); + } + + // Construct from |already_AddRefed|. + template + MOZ_IMPLICIT nsCOMPtr(already_AddRefed& aSmartPtr) + : NSCAP_CTOR_BASE(static_cast(aSmartPtr.take())) + { + assert_validity(); + // But make sure that U actually inherits from T. + static_assert(mozilla::IsBaseOf::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) + : NSCAP_CTOR_BASE(static_cast(aSmartPtr.take())) + { + assert_validity(); + // But make sure that U actually inherits from T. + static_assert(mozilla::IsBaseOf::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)|. + MOZ_IMPLICIT nsCOMPtr(const nsQueryInterface aQI) + : NSCAP_CTOR_BASE(nullptr) + { + assert_validity(); + NSCAP_LOG_ASSIGNMENT(this, nullptr); + assign_from_qi(aQI, NS_GET_TEMPLATE_IID(T)); + } + + // Construct from |do_QueryInterface(expr, &rv)|. + MOZ_IMPLICIT nsCOMPtr(const nsQueryInterfaceWithError& aQI) + : NSCAP_CTOR_BASE(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) + : NSCAP_CTOR_BASE(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) + : NSCAP_CTOR_BASE(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) + : NSCAP_CTOR_BASE(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) + : NSCAP_CTOR_BASE(nullptr) + { + assert_validity(); + NSCAP_LOG_ASSIGNMENT(this, nullptr); + assign_from_gs_contractid_with_error(aGS, 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) + : NSCAP_CTOR_BASE(nullptr) + { + assert_validity(); + NSCAP_LOG_ASSIGNMENT(this, nullptr); + assign_from_helper(aHelper, NS_GET_TEMPLATE_IID(T)); + NSCAP_ASSERT_NO_QUERY_NEEDED(); + } + + // Defined in OwningNonNull.h + template + MOZ_IMPLICIT nsCOMPtr(const mozilla::OwningNonNull& aOther); + + + // Assignment operators + + nsCOMPtr& operator=(const nsCOMPtr& aRhs) + { + assign_with_AddRef(aRhs.mRawPtr); + return *this; + } + + nsCOMPtr& operator=(T* aRhs) + { + assign_with_AddRef(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(mozilla::IsBaseOf::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(mozilla::IsBaseOf::value, + "U is not a subclass of T"); + assign_assuming_AddRef(static_cast(aRhs.take())); + NSCAP_ASSERT_NO_QUERY_NEEDED(); + return *this; + } + + // Assign from |do_QueryInterface(expr)|. + nsCOMPtr& operator=(const nsQueryInterface aRhs) + { + assign_from_qi(aRhs, NS_GET_TEMPLATE_IID(T)); + return *this; + } + + // Assign from |do_QueryInterface(expr, &rv)|. + 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; + } + + // 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; + } + + // 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) + { +#ifdef NSCAP_FEATURE_USE_BASE + nsISupports* temp = aRhs.mRawPtr; +#else + T* temp = aRhs.mRawPtr; +#endif + 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) + { +#ifdef NSCAP_FEATURE_USE_BASE + nsISupports* temp = aRhs; +#else + T* temp = aRhs; +#endif + 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 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 +#ifdef MOZ_HAVE_REF_QUALIFIERS + & +#endif + { return get(); } + +#ifdef MOZ_HAVE_REF_QUALIFIERS + // 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; } +#endif + + 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 + } +}; + + +/* + * Specializing nsCOMPtr for nsISupports allows us to use nsCOMPtr + * the same way people use nsISupports* and void*, i.e., as a `catch-all' + * pointing to any valid [XP]COM interface. Otherwise, an nsCOMPtr + * would only be able to point to the single [XP]COM-correct nsISupports + * instance within an object; extra querying ensues. Clients need to be able to + * pass around arbitrary interface pointers, without hassles, through + * intermediary code that doesn't know the exact type. + */ +template<> +class nsCOMPtr + : private nsCOMPtr_base +{ +public: + typedef nsISupports element_type; + + // Constructors + + nsCOMPtr() + : nsCOMPtr_base(nullptr) + { + NSCAP_LOG_ASSIGNMENT(this, nullptr); + } + + MOZ_IMPLICIT nsCOMPtr(decltype(nullptr)) + : nsCOMPtr_base(nullptr) + { + NSCAP_LOG_ASSIGNMENT(this, nullptr); + } + + nsCOMPtr(const nsCOMPtr& aSmartPtr) + : nsCOMPtr_base(aSmartPtr.mRawPtr) + { + if (mRawPtr) { + NSCAP_ADDREF(this, mRawPtr); + } + NSCAP_LOG_ASSIGNMENT(this, aSmartPtr.mRawPtr); + } + + MOZ_IMPLICIT nsCOMPtr(nsISupports* aRawPtr) + : nsCOMPtr_base(aRawPtr) + { + if (mRawPtr) { + NSCAP_ADDREF(this, mRawPtr); + } + NSCAP_LOG_ASSIGNMENT(this, aRawPtr); + } + + // Construct from |already_AddRefed|. + MOZ_IMPLICIT nsCOMPtr(already_AddRefed& aSmartPtr) + : nsCOMPtr_base(aSmartPtr.take()) + { + NSCAP_LOG_ASSIGNMENT(this, mRawPtr); + } + + // Construct from |otherComPtr.forget()|. + MOZ_IMPLICIT nsCOMPtr(already_AddRefed&& aSmartPtr) + : nsCOMPtr_base(aSmartPtr.take()) + { + NSCAP_LOG_ASSIGNMENT(this, mRawPtr); + } + + // Construct from |do_QueryInterface(expr)|. + MOZ_IMPLICIT nsCOMPtr(const nsQueryInterface aQI) + : nsCOMPtr_base(nullptr) + { + NSCAP_LOG_ASSIGNMENT(this, nullptr); + assign_from_qi(aQI, NS_GET_IID(nsISupports)); + } + + // Construct from |do_QueryInterface(expr, &rv)|. + MOZ_IMPLICIT nsCOMPtr(const nsQueryInterfaceWithError& aQI) + : nsCOMPtr_base(nullptr) + { + NSCAP_LOG_ASSIGNMENT(this, nullptr); + assign_from_qi_with_error(aQI, NS_GET_IID(nsISupports)); + } + + // Construct from |do_GetService(cid_expr)|. + MOZ_IMPLICIT nsCOMPtr(const nsGetServiceByCID aGS) + : nsCOMPtr_base(nullptr) + { + NSCAP_LOG_ASSIGNMENT(this, nullptr); + assign_from_gs_cid(aGS, NS_GET_IID(nsISupports)); + } + + // Construct from |do_GetService(cid_expr, &rv)|. + MOZ_IMPLICIT nsCOMPtr(const nsGetServiceByCIDWithError& aGS) + : nsCOMPtr_base(nullptr) + { + NSCAP_LOG_ASSIGNMENT(this, nullptr); + assign_from_gs_cid_with_error(aGS, NS_GET_IID(nsISupports)); + } + + // Construct from |do_GetService(contractid_expr)|. + MOZ_IMPLICIT nsCOMPtr(const nsGetServiceByContractID aGS) + : nsCOMPtr_base(nullptr) + { + NSCAP_LOG_ASSIGNMENT(this, nullptr); + assign_from_gs_contractid(aGS, NS_GET_IID(nsISupports)); + } + + // Construct from |do_GetService(contractid_expr, &rv)|. + MOZ_IMPLICIT nsCOMPtr(const nsGetServiceByContractIDWithError& aGS) + : nsCOMPtr_base(nullptr) + { + NSCAP_LOG_ASSIGNMENT(this, nullptr); + assign_from_gs_contractid_with_error(aGS, NS_GET_IID(nsISupports)); + } + + // And finally, anything else we might need to construct from can exploit + // the |nsCOMPtr_helper| facility + MOZ_IMPLICIT nsCOMPtr(const nsCOMPtr_helper& aHelper) + : nsCOMPtr_base(nullptr) + { + NSCAP_LOG_ASSIGNMENT(this, nullptr); + assign_from_helper(aHelper, NS_GET_IID(nsISupports)); + } + + + // Assignment operators + + nsCOMPtr& operator=(const nsCOMPtr& aRhs) + { + assign_with_AddRef(aRhs.mRawPtr); + return *this; + } + + nsCOMPtr& operator=(nsISupports* aRhs) + { + assign_with_AddRef(aRhs); + return *this; + } + + nsCOMPtr& operator=(decltype(nullptr)) + { + assign_assuming_AddRef(nullptr); + return *this; + } + + // Assign from |already_AddRefed|. + nsCOMPtr& operator=(already_AddRefed& aRhs) + { + assign_assuming_AddRef(aRhs.take()); + return *this; + } + + // Assign from |otherComPtr.forget()|. + nsCOMPtr& operator=(already_AddRefed&& aRhs) + { + assign_assuming_AddRef(aRhs.take()); + return *this; + } + + // Assign from |do_QueryInterface(expr)|. + nsCOMPtr& operator=(const nsQueryInterface aRhs) + { + assign_from_qi(aRhs, NS_GET_IID(nsISupports)); + return *this; + } + + // Assign from |do_QueryInterface(expr, &rv)|. + nsCOMPtr& operator=(const nsQueryInterfaceWithError& aRhs) + { + assign_from_qi_with_error(aRhs, NS_GET_IID(nsISupports)); + return *this; + } + + // Assign from |do_GetService(cid_expr)|. + nsCOMPtr& operator=(const nsGetServiceByCID aRhs) + { + assign_from_gs_cid(aRhs, NS_GET_IID(nsISupports)); + return *this; + } + + // Assign from |do_GetService(cid_expr, &rv)|. + nsCOMPtr& operator=(const nsGetServiceByCIDWithError& aRhs) + { + assign_from_gs_cid_with_error(aRhs, NS_GET_IID(nsISupports)); + return *this; + } + + // Assign from |do_GetService(contractid_expr)|. + nsCOMPtr& operator=(const nsGetServiceByContractID aRhs) + { + assign_from_gs_contractid(aRhs, NS_GET_IID(nsISupports)); + return *this; + } + + // Assign from |do_GetService(contractid_expr, &rv)|. + nsCOMPtr& operator=(const nsGetServiceByContractIDWithError& aRhs) + { + assign_from_gs_contractid_with_error(aRhs, NS_GET_IID(nsISupports)); + 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_IID(nsISupports)); + return *this; + } + + // Exchange ownership with |aRhs|; can save a pair of refcount operations. + void swap(nsCOMPtr& aRhs) + { + nsISupports* 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; + } + + // Exchange ownership with |aRhs|; can save a pair of refcount operations. + void swap(nsISupports*& aRhs) + { + nsISupports* temp = aRhs; + NSCAP_LOG_ASSIGNMENT(this, temp); + NSCAP_LOG_RELEASE(this, mRawPtr); + aRhs = mRawPtr; + mRawPtr = temp; + } + + // Return the value of mRawPtr and null out mRawPtr. Useful for + // already_AddRefed return values. + already_AddRefed forget() + { + nsISupports* 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. + void forget(nsISupports** aRhs) + { + NS_ASSERTION(aRhs, "Null pointer passed to forget!"); + *aRhs = nullptr; + swap(*aRhs); + } + + // Other pointer operators + + // Prefer the implicit conversion provided automatically by + // |operator nsISupports*() const|. Use |get()| to resolve ambiguity or to + // get a castable pointer. + nsISupports* 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 nsISupports* () const { return get(); } + + nsISupports* 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: + + nsISupports& operator*() const + { + MOZ_ASSERT(mRawPtr != nullptr, + "You can't dereference a NULL nsCOMPtr with operator*()."); + return *get(); + } + + nsISupports** 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); +} + +#ifndef NSCAP_FEATURE_USE_BASE +template +void +nsCOMPtr::assign_with_AddRef(nsISupports* aRawPtr) +{ + if (aRawPtr) { + NSCAP_ADDREF(this, aRawPtr); + } + assign_assuming_AddRef(reinterpret_cast(aRawPtr)); +} + +template +void +nsCOMPtr::assign_from_qi(const nsQueryInterface aQI, const nsIID& aIID) +{ + void* newRawPtr; + if (NS_FAILED(aQI(aIID, &newRawPtr))) { + newRawPtr = nullptr; + } + assign_assuming_AddRef(static_cast(newRawPtr)); +} + +template +void +nsCOMPtr::assign_from_qi_with_error(const nsQueryInterfaceWithError& aQI, + const nsIID& aIID) +{ + 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_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; +} +#endif + +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 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 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; +} + + +#endif // !defined(nsCOMPtr_h___) diff --git a/xpcom/glue/nsCRTGlue.cpp b/xpcom/glue/nsCRTGlue.cpp new file mode 100644 index 000000000..7a9f6db03 --- /dev/null +++ b/xpcom/glue/nsCRTGlue.cpp @@ -0,0 +1,441 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. 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/UniquePtr.h" +#endif + +#ifdef ANDROID +#include +#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' : *aStrA - *aStrB; +} + +char16_t* +NS_strdup(const char16_t* aString) +{ + uint32_t len = NS_strlen(aString); + return NS_strndup(aString, len); +} + +template +CharT* +NS_strndup(const CharT* aString, uint32_t aLen) +{ + auto newBuf = (CharT*)NS_Alloc((aLen + 1) * sizeof(CharT)); + if (newBuf) { + memcpy(newBuf, aString, aLen * sizeof(CharT)); + newBuf[aLen] = '\0'; + } + return newBuf; +} + +template char16_t* NS_strndup(const char16_t* aString, uint32_t aLen); +template char* NS_strndup(const char* aString, uint32_t aLen); + +char* +NS_strdup(const char* aString) +{ + uint32_t len = strlen(aString); + char* str = (char*)NS_Alloc(len + 1); + if (str) { + memcpy(str, aString, len); + str[len] = '\0'; + } + return str; +} + +// 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 +}; + +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]; +} + +bool +NS_IsAscii(char16_t aChar) +{ + return (0x0080 > aChar); +} + +bool +NS_IsAscii(const char16_t* aString) +{ + while (*aString) { + if (0x0080 <= *aString) { + return false; + } + aString++; + } + return true; +} + +bool +NS_IsAscii(const char* aString) +{ + while (*aString) { + if (0x80 & *aString) { + return false; + } + aString++; + } + return true; +} + +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; +} + +bool +NS_IsAsciiAlpha(char16_t aChar) +{ + return (aChar >= 'A' && aChar <= 'Z') || + (aChar >= 'a' && aChar <= 'z'); +} + +bool +NS_IsAsciiWhitespace(char16_t aChar) +{ + return aChar == ' ' || + aChar == '\r' || + aChar == '\n' || + aChar == '\t'; +} + +bool +NS_IsAsciiDigit(char16_t aChar) +{ + return aChar >= '0' && aChar <= '9'; +} + +#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 + +static StderrCallback sStderrCallback = nullptr; + +void +set_stderr_callback(StderrCallback aCallback) +{ + sStderrCallback = aCallback; +} + +#if defined(ANDROID) && !defined(RELEASE_OR_BETA) +static FILE* sStderrCopy = nullptr; + +void +stderr_to_file(const char* aFmt, va_list aArgs) +{ + vfprintf(sStderrCopy, aFmt, aArgs); +} + +void +copy_stderr_to_file(const char* aFile) +{ + if (sStderrCopy) { + return; + } + size_t buflen = strlen(aFile) + 16; + char* buf = (char*)malloc(buflen); + snprintf(buf, buflen, "%s.%u", aFile, (uint32_t)getpid()); + sStderrCopy = fopen(buf, "w"); + free(buf); + set_stderr_callback(stderr_to_file); +} +#endif + +#ifdef HAVE_VA_COPY +#define VARARGS_ASSIGN(foo, bar) VA_COPY(foo,bar) +#elif defined(HAVE_VA_LIST_AS_ARRAY) +#define VARARGS_ASSIGN(foo, bar) foo[0] = bar[0] +#else +#define VARARGS_ASSIGN(foo, bar) (foo) = (bar) +#endif + +#if defined(XP_WIN) +void +vprintf_stderr(const char* aFmt, va_list aArgs) +{ + if (sStderrCallback) { + va_list argsCpy; + VARARGS_ASSIGN(argsCpy, aArgs); + sStderrCallback(aFmt, aArgs); + va_end(argsCpy); + } + + if (IsDebuggerPresent()) { + int lengthNeeded = _vscprintf(aFmt, aArgs); + if (lengthNeeded) { + lengthNeeded++; + auto buf = MakeUnique(lengthNeeded); + if (buf) { + va_list argsCpy; + VARARGS_ASSIGN(argsCpy, aArgs); + vsnprintf(buf.get(), lengthNeeded, aFmt, argsCpy); + buf[lengthNeeded - 1] = '\0'; + va_end(argsCpy); + OutputDebugStringA(buf.get()); + } + } + } + + FILE* fp = _fdopen(_dup(2), "a"); + if (!fp) { + return; + } + + vfprintf(fp, aFmt, aArgs); + + fclose(fp); +} + +#elif defined(ANDROID) +void +vprintf_stderr(const char* aFmt, va_list aArgs) +{ + if (sStderrCallback) { + va_list argsCpy; + VARARGS_ASSIGN(argsCpy, aArgs); + sStderrCallback(aFmt, aArgs); + va_end(argsCpy); + } + + __android_log_vprint(ANDROID_LOG_INFO, "Gecko", aFmt, aArgs); +} +#else +void +vprintf_stderr(const char* aFmt, va_list aArgs) +{ + if (sStderrCallback) { + va_list argsCpy; + VARARGS_ASSIGN(argsCpy, aArgs); + sStderrCallback(aFmt, aArgs); + va_end(argsCpy); + } + + 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); +} diff --git a/xpcom/glue/nsCRTGlue.h b/xpcom/glue/nsCRTGlue.h new file mode 100644 index 000000000..8caa1ae27 --- /dev/null +++ b/xpcom/glue/nsCRTGlue.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 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 NS_Alloc allocator. + */ +char16_t* NS_strdup(const char16_t* aString); + +/** + * "strdup", but using the NS_Alloc allocator. + */ +char* NS_strdup(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 NS_Alloc allocator. + * + * CharT may be either char16_t or char. + */ +template +CharT* NS_strndup(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); + +bool NS_IsAscii(char16_t aChar); +bool NS_IsAscii(const char16_t* aString); +bool NS_IsAsciiAlpha(char16_t aChar); +bool NS_IsAsciiDigit(char16_t aChar); +bool NS_IsAsciiWhitespace(char16_t aChar); +bool NS_IsAscii(const char* aString); +bool NS_IsAscii(const char* aString, uint32_t aLength); + +#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 */ + +// We use the most restrictive filesystem as our default set of illegal filename +// characters. This is currently Windows. +#define OS_FILE_ILLEGAL_CHARACTERS "/:*?\"<>|" +// 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" + +#define FILE_ILLEGAL_CHARACTERS CONTROL_CHARACTERS OS_FILE_ILLEGAL_CHARACTERS + +#endif // nsCRTGlue_h__ diff --git a/xpcom/glue/nsCategoryCache.cpp b/xpcom/glue/nsCategoryCache.cpp new file mode 100644 index 000000000..30501b8e3 --- /dev/null +++ b/xpcom/glue/nsCategoryCache.cpp @@ -0,0 +1,149 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. 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 "nsISupportsPrimitives.h" +#include "nsIStringEnumerator.h" + +#include "nsXPCOMCID.h" + +#include "nsCategoryCache.h" + +nsCategoryObserver::nsCategoryObserver(const char* aCategory) + : mCategory(aCategory) + , mObserversRemoved(false) +{ + // 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; + } + + nsCOMPtr strings = do_QueryInterface(enumerator); + MOZ_ASSERT(strings); + + bool more; + while (NS_SUCCEEDED(strings->HasMore(&more)) && more) { + nsAutoCString entryName; + strings->GetNext(entryName); + + nsCString entryValue; + rv = catMan->GetCategoryEntry(aCategory, + entryName.get(), + getter_Copies(entryValue)); + if (NS_SUCCEEDED(rv)) { + nsCOMPtr service = do_GetService(entryValue.get()); + if (service) { + mHash.Put(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() +{ +} + +NS_IMPL_ISUPPORTS(nsCategoryObserver, nsIObserver) + +void +nsCategoryObserver::ListenerDied() +{ + RemoveObservers(); +} + +void +nsCategoryObserver::RemoveObservers() +{ + if (mObserversRemoved) { + return; + } + + 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) +{ + 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.get(), + str.get(), + getter_Copies(entryValue)); + + nsCOMPtr service = do_GetService(entryValue.get()); + + if (service) { + mHash.Put(str, service); + } + } else if (strcmp(aTopic, NS_XPCOM_CATEGORY_ENTRY_REMOVED_OBSERVER_ID) == 0) { + mHash.Remove(str); + } else if (strcmp(aTopic, NS_XPCOM_CATEGORY_CLEARED_OBSERVER_ID) == 0) { + mHash.Clear(); + } + return NS_OK; +} diff --git a/xpcom/glue/nsCategoryCache.h b/xpcom/glue/nsCategoryCache.h new file mode 100644 index 000000000..023aa7a75 --- /dev/null +++ b/xpcom/glue/nsCategoryCache.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 nsCategoryCache_h_ +#define nsCategoryCache_h_ + +#include "mozilla/Attributes.h" + +#include "nsICategoryManager.h" +#include "nsIObserver.h" +#include "nsISimpleEnumerator.h" +#include "nsISupportsPrimitives.h" + +#include "nsServiceManagerUtils.h" + +#include "nsAutoPtr.h" +#include "nsCOMArray.h" +#include "nsInterfaceHashtable.h" + +#include "nsXPCOM.h" + +class nsCategoryObserver final : public nsIObserver +{ + ~nsCategoryObserver(); + +public: + explicit nsCategoryObserver(const char* aCategory); + + void ListenerDied(); + nsInterfaceHashtable& GetHash() + { + return mHash; + } + + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER +private: + void RemoveObservers(); + + nsInterfaceHashtable mHash; + nsCString mCategory; + 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) + { + } + ~nsCategoryCache() + { + if (mObserver) { + mObserver->ListenerDied(); + } + } + + void GetEntries(nsCOMArray& aResult) + { + // Lazy initialization, so that services in this category can't + // cause reentrant getService (bug 386376) + if (!mObserver) { + mObserver = new nsCategoryObserver(mCategoryName.get()); + } + + for (auto iter = mObserver->GetHash().Iter(); !iter.Done(); iter.Next()) { + nsISupports* entry = iter.UserData(); + nsCOMPtr service = do_QueryInterface(entry); + if (service) { + aResult.AppendElement(service.forget()); + } + } + } + +private: + // Not to be implemented + nsCategoryCache(const nsCategoryCache&); + + nsCString mCategoryName; + RefPtr mObserver; +}; + +#endif diff --git a/xpcom/glue/nsClassHashtable.h b/xpcom/glue/nsClassHashtable.h new file mode 100644 index 000000000..53ca5676b --- /dev/null +++ b/xpcom/glue/nsClassHashtable.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 nsClassHashtable_h__ +#define nsClassHashtable_h__ + +#include "mozilla/Move.h" +#include "nsBaseHashtable.h" +#include "nsHashKeys.h" +#include "nsAutoPtr.h" + +/** + * 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*> +{ +public: + typedef typename KeyClass::KeyType KeyType; + typedef T* UserDataType; + typedef nsBaseHashtable, T*> base_type; + + using base_type::IsEmpty; + + nsClassHashtable() {} + explicit nsClassHashtable(uint32_t aInitLength) + : nsBaseHashtable, T*>(aInitLength) + { + } + + /** + * Looks up aKey in the hash table. If it doesn't exist a new object of + * KeyClass will be created (using the arguments provided) and then returned. + */ + template + UserDataType LookupOrAdd(KeyType aKey, Args&&... aConstructionArgs); + + /** + * @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. + */ + UserDataType Get(KeyType aKey) const; + + /** + * Remove the entry for the given key from the hashtable and return it in + * aOut. If the key is not in the hashtable, aOut's pointer is set to + * nullptr. + * + * Normally, an entry is deleted when it's removed from an nsClassHashtable, + * but this function transfers ownership of the entry back to the caller + * through aOut -- the entry will be deleted when aOut goes out of scope. + * + * @param aKey the key to get and remove from the hashtable + */ + void RemoveAndForget(KeyType aKey, nsAutoPtr& aOut); +}; + +// +// nsClassHashtable definitions +// + +template +template +T* +nsClassHashtable::LookupOrAdd(KeyType aKey, + Args&&... aConstructionArgs) +{ + typename base_type::EntryType* ent = this->PutEntry(aKey); + if (!ent->mData) { + ent->mData = new T(mozilla::Forward(aConstructionArgs)...); + } + return ent->mData; +} + +template +bool +nsClassHashtable::Get(KeyType aKey, T** aRetVal) const +{ + typename base_type::EntryType* ent = this->GetEntry(aKey); + + if (ent) { + if (aRetVal) { + *aRetVal = ent->mData; + } + + 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->mData; +} + +template +void +nsClassHashtable::RemoveAndForget(KeyType aKey, nsAutoPtr& aOut) +{ + aOut = nullptr; + + typename base_type::EntryType* ent = this->GetEntry(aKey); + if (!ent) { + return; + } + + // Transfer ownership from ent->mData into aOut. + aOut = mozilla::Move(ent->mData); + + this->Remove(aKey); +} + +#endif // nsClassHashtable_h__ diff --git a/xpcom/glue/nsClassInfoImpl.cpp b/xpcom/glue/nsClassInfoImpl.cpp new file mode 100644 index 000000000..6eb34f9f7 --- /dev/null +++ b/xpcom/glue/nsClassInfoImpl.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 "nsIClassInfoImpl.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(uint32_t* aCount, nsIID*** aArray) +{ + return mData->getinterfaces(aCount, aArray); +} + +NS_IMETHODIMP +GenericClassInfo::GetScriptableHelper(nsIXPCScriptable** aHelper) +{ + if (mData->getscriptablehelper) { + return mData->getscriptablehelper(aHelper); + } + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +GenericClassInfo::GetContractID(char** aContractID) +{ + NS_ERROR("GetContractID not implemented"); + *aContractID = nullptr; + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +GenericClassInfo::GetClassDescription(char** aDescription) +{ + *aDescription = nullptr; + 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/glue/nsComponentManagerUtils.cpp b/xpcom/glue/nsComponentManagerUtils.cpp new file mode 100644 index 000000000..d8a590fa7 --- /dev/null +++ b/xpcom/glue/nsComponentManagerUtils.cpp @@ -0,0 +1,301 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 (NS_WARN_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 (NS_WARN_IF(!compMgr)) { + return NS_ERROR_NOT_INITIALIZED; + } + + return compMgr->nsComponentManagerImpl::GetServiceByContractID(aContractID, + aIID, + aResult); +} + +#endif + +#ifndef MOZILLA_INTERNAL_API + +nsresult +CallCreateInstance(const nsCID& aCID, nsISupports* aDelegate, + const nsIID& aIID, void** aResult) +{ + nsCOMPtr compMgr; + nsresult status = NS_GetComponentManager(getter_AddRefs(compMgr)); + if (compMgr) { + status = compMgr->CreateInstance(aCID, aDelegate, aIID, aResult); + } + return status; +} + +nsresult +CallCreateInstance(const char* aContractID, nsISupports* aDelegate, + const nsIID& aIID, void** aResult) +{ + nsCOMPtr compMgr; + nsresult status = NS_GetComponentManager(getter_AddRefs(compMgr)); + if (compMgr) + status = compMgr->CreateInstanceByContractID(aContractID, aDelegate, + 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, nsISupports* aDelegate, + const nsIID& aIID, void** aResult) +{ + nsComponentManagerImpl* compMgr = nsComponentManagerImpl::gComponentManager; + if (NS_WARN_IF(!compMgr)) { + return NS_ERROR_NOT_INITIALIZED; + } + + return compMgr->nsComponentManagerImpl::CreateInstance(aCID, aDelegate, aIID, + aResult); +} + +nsresult +CallCreateInstance(const char* aContractID, nsISupports* aDelegate, + const nsIID& aIID, void** aResult) +{ + nsComponentManagerImpl* compMgr = nsComponentManagerImpl::gComponentManager; + if (NS_WARN_IF(!compMgr)) { + return NS_ERROR_NOT_INITIALIZED; + } + + return + compMgr->nsComponentManagerImpl::CreateInstanceByContractID(aContractID, + aDelegate, 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, nullptr, 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, nullptr, 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(nullptr, 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/glue/nsComponentManagerUtils.h b/xpcom/glue/nsComponentManagerUtils.h new file mode 100644 index 000000000..2a7a4fbd7 --- /dev/null +++ b/xpcom/glue/nsComponentManagerUtils.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 nsComponentManagerUtils_h__ +#define nsComponentManagerUtils_h__ + +#include "nscore.h" +#include "nsCOMPtr.h" + +#include "nsIFactory.h" + + +nsresult CallCreateInstance(const nsCID& aClass, nsISupports* aDelegate, + const nsIID& aIID, void** aResult); + +nsresult CallCreateInstance(const char* aContractID, nsISupports* aDelegate, + const nsIID& aIID, void** aResult); + +nsresult CallGetClassObject(const nsCID& aClass, 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, + nsISupports* aDelegate, + DestinationType** aDestination) +{ + NS_PRECONDITION(aDestination, "null parameter"); + + return CallCreateInstance(aClass, aDelegate, + NS_GET_TEMPLATE_IID(DestinationType), + reinterpret_cast(aDestination)); +} + +template +inline nsresult +CallCreateInstance(const nsCID& aClass, DestinationType** aDestination) +{ + NS_PRECONDITION(aDestination, "null parameter"); + + return CallCreateInstance(aClass, nullptr, + NS_GET_TEMPLATE_IID(DestinationType), + reinterpret_cast(aDestination)); +} + +template +inline nsresult +CallCreateInstance(const char* aContractID, + nsISupports* aDelegate, + DestinationType** aDestination) +{ + NS_PRECONDITION(aContractID, "null parameter"); + NS_PRECONDITION(aDestination, "null parameter"); + + return CallCreateInstance(aContractID, + aDelegate, + NS_GET_TEMPLATE_IID(DestinationType), + reinterpret_cast(aDestination)); +} + +template +inline nsresult +CallCreateInstance(const char* aContractID, DestinationType** aDestination) +{ + NS_PRECONDITION(aContractID, "null parameter"); + NS_PRECONDITION(aDestination, "null parameter"); + + return CallCreateInstance(aContractID, nullptr, + NS_GET_TEMPLATE_IID(DestinationType), + reinterpret_cast(aDestination)); +} + +template +inline nsresult +CallCreateInstance(nsIFactory* aFactory, + nsISupports* aDelegate, + DestinationType** aDestination) +{ + NS_PRECONDITION(aFactory, "null parameter"); + NS_PRECONDITION(aDestination, "null parameter"); + + return aFactory->CreateInstance(aDelegate, + NS_GET_TEMPLATE_IID(DestinationType), + reinterpret_cast(aDestination)); +} + +template +inline nsresult +CallCreateInstance(nsIFactory* aFactory, DestinationType** aDestination) +{ + NS_PRECONDITION(aFactory, "null parameter"); + NS_PRECONDITION(aDestination, "null parameter"); + + return aFactory->CreateInstance(nullptr, + NS_GET_TEMPLATE_IID(DestinationType), + reinterpret_cast(aDestination)); +} + +template +inline nsresult +CallGetClassObject(const nsCID& aClass, DestinationType** aDestination) +{ + NS_PRECONDITION(aDestination, "null parameter"); + + return CallGetClassObject(aClass, NS_GET_TEMPLATE_IID(DestinationType), + reinterpret_cast(aDestination)); +} + +template +inline nsresult +CallGetClassObject(const char* aContractID, DestinationType** aDestination) +{ + NS_PRECONDITION(aDestination, "null parameter"); + + return CallGetClassObject(aContractID, NS_GET_TEMPLATE_IID(DestinationType), + reinterpret_cast(aDestination)); +} + +#endif /* nsComponentManagerUtils_h__ */ diff --git a/xpcom/glue/nsCycleCollectionNoteChild.h b/xpcom/glue/nsCycleCollectionNoteChild.h new file mode 100644 index 000000000..5d47caefd --- /dev/null +++ b/xpcom/glue/nsCycleCollectionNoteChild.h @@ -0,0 +1,101 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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" +#include "mozilla/TypeTraits.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/glue/nsCycleCollectionNoteRootCallback.h b/xpcom/glue/nsCycleCollectionNoteRootCallback.h new file mode 100644 index 000000000..42c43f301 --- /dev/null +++ b/xpcom/glue/nsCycleCollectionNoteRootCallback.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 nsCycleCollectionNoteRootCallback_h__ +#define nsCycleCollectionNoteRootCallback_h__ + +class nsCycleCollectionParticipant; +class nsISupports; + +class nsCycleCollectionNoteRootCallback +{ +public: + NS_IMETHOD_(void) NoteXPCOMRoot(nsISupports* aRoot) = 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/glue/nsCycleCollectionParticipant.cpp b/xpcom/glue/nsCycleCollectionParticipant.cpp new file mode 100644 index 000000000..973ef2ff5 --- /dev/null +++ b/xpcom/glue/nsCycleCollectionParticipant.cpp @@ -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/. */ + +#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/glue/nsCycleCollectionParticipant.h b/xpcom/glue/nsCycleCollectionParticipant.h new file mode 100644 index 000000000..2dfbb6750 --- /dev/null +++ b/xpcom/glue/nsCycleCollectionParticipant.h @@ -0,0 +1,852 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 "mozilla/MacroArgs.h" +#include "mozilla/MacroForEach.h" +#include "nsCycleCollectionNoteChild.h" +#include "js/RootingAPI.h" + +#define NS_XPCOMCYCLECOLLECTIONPARTICIPANT_IID \ +{ \ + 0x9674489b, \ + 0x1f6f, \ + 0x4550, \ + { 0xa7, 0x30, 0xcc, 0xae, 0xdd, 0x10, 0x4c, 0xf9 } \ +} + +/** + * 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 } \ +} + +/** + * 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) + +namespace JS { +template class Heap; +} /* 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(JSObject** 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(JSObject** 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: + constexpr nsCycleCollectionParticipant() : mMightSkip(false) {} + constexpr explicit nsCycleCollectionParticipant(bool aSkip) : mMightSkip(aSkip) {} + + NS_IMETHOD Traverse(void* aPtr, nsCycleCollectionTraversalCallback& aCb) = 0; + + 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) {} + + // If CanSkip returns true, p is removed from the purple buffer during + // a call to nsCycleCollector_forgetSkippable(). + // Note, calling CanSkip may remove objects from the purple buffer! + // If aRemovingAllowed is true, p can be removed from the purple buffer. + bool CanSkip(void* aPtr, bool aRemovingAllowed) + { + return mMightSkip ? CanSkipReal(aPtr, aRemovingAllowed) : false; + } + + // If CanSkipInCC returns true, p is skipped when selecting roots for the + // cycle collector graph. + // Note, calling CanSkipInCC may remove other objects from the purple buffer! + bool CanSkipInCC(void* aPtr) + { + return mMightSkip ? CanSkipInCCReal(aPtr) : false; + } + + // If CanSkipThis returns true, p is not added to the graph. + // This method is called during cycle collection, so don't + // change the state of any objects! + bool CanSkipThis(void* aPtr) + { + return mMightSkip ? CanSkipThisReal(aPtr) : false; + } + + NS_IMETHOD_(void) DeleteCycleCollectable(void* aPtr) = 0; + +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: + const bool mMightSkip; +}; + +class NS_NO_VTABLE nsScriptObjectTracer : public nsCycleCollectionParticipant +{ +public: + constexpr nsScriptObjectTracer() + : nsCycleCollectionParticipant(false) + { + } + constexpr explicit nsScriptObjectTracer(bool aSkip) + : nsCycleCollectionParticipant(aSkip) + { + } + + NS_IMETHOD_(void) Trace(void* aPtr, const TraceCallbacks& aCb, + void* aClosure) override = 0; + + // Implemented in nsCycleCollectorTraceJSHelpers.cpp. + static void NoteJSChild(JS::GCCellPtr aGCThing, const char* aName, + void* aClosure); +}; + +class NS_NO_VTABLE nsXPCOMCycleCollectionParticipant : public nsScriptObjectTracer +{ +public: + constexpr nsXPCOMCycleCollectionParticipant() + : nsScriptObjectTracer(false) + { + } + constexpr explicit nsXPCOMCycleCollectionParticipant(bool aSkip) + : nsScriptObjectTracer(aSkip) + { + } + + 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 + +#define NS_IMPL_QUERY_CYCLE_COLLECTION(_class) \ + if ( aIID.Equals(NS_GET_IID(nsXPCOMCycleCollectionParticipant)) ) { \ + *aInstancePtr = NS_CYCLE_COLLECTION_PARTICIPANT(_class); \ + return NS_OK; \ + } else + +#define NS_IMPL_QUERY_CYCLE_COLLECTION_ISUPPORTS(_class) \ + if ( aIID.Equals(NS_GET_IID(nsCycleCollectionISupports)) ) { \ + *aInstancePtr = NS_CYCLE_COLLECTION_CLASSNAME(_class)::Upcast(this); \ + return NS_OK; \ + } else + +#define NS_INTERFACE_MAP_ENTRY_CYCLE_COLLECTION(_class) \ + NS_IMPL_QUERY_CYCLE_COLLECTION(_class) + +#define NS_INTERFACE_MAP_ENTRY_CYCLE_COLLECTION_ISUPPORTS(_class) \ + NS_IMPL_QUERY_CYCLE_COLLECTION_ISUPPORTS(_class) + +#define NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(_class) \ + NS_INTERFACE_MAP_ENTRY_CYCLE_COLLECTION(_class) \ + NS_INTERFACE_MAP_ENTRY_CYCLE_COLLECTION_ISUPPORTS(_class) + +#define NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(_class) \ + NS_INTERFACE_MAP_BEGIN(_class) \ + NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(_class) + +#define NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(_class) \ + NS_INTERFACE_MAP_BEGIN(_class) \ + NS_INTERFACE_MAP_ENTRY_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) + +#define NS_INTERFACE_TABLE_HEAD_CYCLE_COLLECTION_INHERITED(_class) \ + NS_IMETHODIMP _class::QueryInterface(REFNSIID aIID, void** aInstancePtr) \ + { \ + NS_PRECONDITION(aInstancePtr, "null out param"); \ + \ + if ( aIID.Equals(NS_GET_IID(nsXPCOMCycleCollectionParticipant)) ) { \ + *aInstancePtr = NS_CYCLE_COLLECTION_PARTICIPANT(_class); \ + return NS_OK; \ + } \ + nsresult rv; + +#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 +/////////////////////////////////////////////////////////////////////////////// + +#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; \ + } + +#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; \ + } + +#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_STATIC_ASSERT_VALID_ARG_COUNT(__VA_ARGS__); \ + 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)::Traverse \ + (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)::Traverse(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_STATIC_ASSERT_VALID_ARG_COUNT(__VA_ARGS__); \ + 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_SCRIPT_OBJECTS \ + { \ + TraceCallbackFunc noteJsChild(&nsScriptObjectTracer::NoteJSChild); \ + Trace(p, noteJsChild, &cb); \ + } + +#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_NO_UNLINK(_class, _base) \ +public: \ + NS_IMETHOD Traverse(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); + +#define NS_DECL_CYCLE_COLLECTION_CLASS_BODY(_class, _base) \ + NS_DECL_CYCLE_COLLECTION_CLASS_BODY_NO_UNLINK(_class, _base) \ + 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 \ +{ \ + 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 NS_CYCLE_COLLECTION_INNERCLASS () \ + : nsXPCOMCycleCollectionParticipant(true) {} \ +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 \ +{ \ + 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 NS_CYCLE_COLLECTION_INNERCLASS () \ + : nsXPCOMCycleCollectionParticipant(true) {} \ +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) \ +{ \ + 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_NO_UNLINK(_class, \ + _base_class) \ +public: \ + NS_IMETHOD Traverse(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))); \ + } + +#define NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED_BODY(_class, _base_class) \ + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED_BODY_NO_UNLINK(_class, _base_class) \ + 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: \ + 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_CLASS_INHERITED_NO_UNLINK(_class, \ + _base_class) \ +class NS_CYCLE_COLLECTION_INNERCLASS \ + : public NS_CYCLE_COLLECTION_CLASSNAME(_base_class) \ +{ \ +public: \ + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED_BODY_NO_UNLINK(_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) \ +{ \ + 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 *n) override; \ + NS_IMETHOD_(void) Unlink(void *n) override; \ + NS_IMETHOD_(void) Unroot(void *n) override; \ + NS_IMETHOD Traverse(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 \ + { \ + 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 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_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 \ + { \ + 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_ROOT_NATIVE(_class, _root_function) \ + NS_IMETHODIMP_(void) \ + NS_CYCLE_COLLECTION_CLASSNAME(_class)::Root(void *p) \ + { \ + _class *tmp = static_cast<_class*>(p); \ + tmp->_root_function(); \ + } + +#define NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(_class, _unroot_function) \ + NS_IMETHODIMP_(void) \ + NS_CYCLE_COLLECTION_CLASSNAME(_class)::Unroot(void *p) \ + { \ + _class *tmp = static_cast<_class*>(p); \ + tmp->_unroot_function(); \ + } + +#define NS_IMPL_CYCLE_COLLECTION_CLASS(_class) \ + _class::NS_CYCLE_COLLECTION_INNERCLASS _class::NS_CYCLE_COLLECTION_INNERNAME; + +// 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 + +#endif // nsCycleCollectionParticipant_h__ diff --git a/xpcom/glue/nsCycleCollectionTraversalCallback.h b/xpcom/glue/nsCycleCollectionTraversalCallback.h new file mode 100644 index 000000000..9e314af9b --- /dev/null +++ b/xpcom/glue/nsCycleCollectionTraversalCallback.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 nsCycleCollectionTraversalCallback_h__ +#define nsCycleCollectionTraversalCallback_h__ + +#include "jspubtd.h" +#include "js/HeapAPI.h" +#include "nsISupports.h" + +class nsCycleCollectionParticipant; + +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(const JS::GCCellPtr& aThing) = 0; + NS_IMETHOD_(void) NoteNativeChild(void* aChild, + nsCycleCollectionParticipant* aHelper) = 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/glue/nsDataHashtable.h b/xpcom/glue/nsDataHashtable.h new file mode 100644 index 000000000..19c0728b4 --- /dev/null +++ b/xpcom/glue/nsDataHashtable.h @@ -0,0 +1,58 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 nsDataHashtable_h__ +#define nsDataHashtable_h__ + +#include "nsHashKeys.h" +#include "nsBaseHashtable.h" +#include "mozilla/Maybe.h" + +/** + * templated hashtable class maps keys to simple datatypes. + * See nsBaseHashtable for complete declaration + * @param KeyClass a wrapper-class for the hashtable key, see nsHashKeys.h + * for a complete specification. + * @param DataType the simple datatype being wrapped + * @see nsInterfaceHashtable, nsClassHashtable + */ +template +class nsDataHashtable + : public nsBaseHashtable +{ +private: + typedef nsBaseHashtable BaseClass; + +public: + using typename BaseClass::KeyType; + using typename BaseClass::EntryType; + + nsDataHashtable() {} + explicit nsDataHashtable(uint32_t aInitLength) + : BaseClass(aInitLength) + { + } + + /** + * 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. + */ + mozilla::Maybe GetAndRemove(KeyType aKey) + { + mozilla::Maybe value; + if (EntryType* ent = this->GetEntry(aKey)) { + value.emplace(mozilla::Move(ent->mData)); + this->RemoveEntry(ent); + } + return value; + } +}; + +#endif // nsDataHashtable_h__ diff --git a/xpcom/glue/nsDebug.h b/xpcom/glue/nsDebug.h new file mode 100644 index 000000000..7365f9ce3 --- /dev/null +++ b/xpcom/glue/nsDebug.h @@ -0,0 +1,460 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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/Likely.h" +#include + +#ifdef DEBUG +#include "prprf.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 MOZ_MUST_USE 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 +inline MOZ_MUST_USE 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(0) +#else +#define NS_WARNING_ASSERTION(_expr, _msg) do { /* nothing */ } while(0) +#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 + +/** + * NS_PRECONDITION/POSTCONDITION are synonyms for NS_ASSERTION. + */ +#define NS_PRECONDITION(expr, str) NS_ASSERTION(expr, str) +#define NS_POSTCONDITION(expr, str) NS_ASSERTION(expr, str) + +/** + * This macros triggers a program failure if executed. It indicates that + * an attempt was made to execute some unimplemented functionality. + */ +#ifdef DEBUG +#define NS_NOTYETIMPLEMENTED(str) \ + do { \ + NS_DebugBreak(NS_DEBUG_ASSERTION, str, "NotYetImplemented", __FILE__, __LINE__); \ + MOZ_PretendNoReturn(); \ + } while(0) +#else +#define NS_NOTYETIMPLEMENTED(str) do { /* nothing */ } while(0) +#endif + +/** + * This macros triggers a program failure if executed. It indicates that + * an attempt was made to execute a codepath which should not be reachable. + */ +#ifdef DEBUG +#define NS_NOTREACHED(str) \ + do { \ + NS_DebugBreak(NS_DEBUG_ASSERTION, str, "Not Reached", __FILE__, __LINE__); \ + MOZ_PretendNoReturn(); \ + } while(0) +#else +#define NS_NOTREACHED(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 an debug-only abort. + * + * @see NS_RUNTIMEABORT for release-mode asserts. + */ +#ifdef DEBUG +#define NS_ABORT() \ + do { \ + NS_DebugBreak(NS_DEBUG_ABORT, nullptr, nullptr, __FILE__, __LINE__); \ + MOZ_PretendNoReturn(); \ + } while(0) +#else +#define NS_ABORT() 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(0) + +#define STATIC_ASSERT_RUNTIME(COND) \ + do { \ + __attribute__((assert_static_runtime(#COND), unused)) \ + int STATIC_PASTE1(assert_static_runtime_, __COUNTER__); \ + } while(0) + +#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(0) +#define STATIC_ASSERT_RUNTIME(COND) do { /* nothing */ } while(0) + +#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. +******************************************************************************/ + +/** + * Terminate execution immediately, and if possible on the current + * platform, in such a way that execution can't be continued by other + * code (e.g., by intercepting a signal). + */ +#define NS_RUNTIMEABORT(msg) \ + NS_DebugBreak(NS_DEBUG_ABORT, msg, nullptr, __FILE__, __LINE__) + + +/* 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(0) + +#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(0) + +#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) \ + char *msg = PR_smprintf("NS_ENSURE_SUCCESS(%s, %s) failed with " \ + "result 0x%X", #res, #ret, __rv); \ + NS_WARNING(msg); \ + PR_smprintf_free(msg); + +#define NS_ENSURE_SUCCESS_BODY_VOID(res) \ + char *msg = PR_smprintf("NS_ENSURE_SUCCESS_VOID(%s) failed with " \ + "result 0x%X", #res, __rv); \ + NS_WARNING(msg); \ + PR_smprintf_free(msg); + +#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(0) + +#define NS_ENSURE_SUCCESS_VOID(res) \ + do { \ + nsresult __rv = res; \ + if (NS_FAILED(__rv)) { \ + NS_ENSURE_SUCCESS_BODY_VOID(res) \ + return; \ + } \ + } while(0) + +/****************************************************************************** +** 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) + +#define NS_ENSURE_NO_AGGREGATION(outer) \ + NS_ENSURE_FALSE(outer, NS_ERROR_NO_AGGREGATION) + +/*****************************************************************************/ + +#if (defined(DEBUG) || (defined(NIGHTLY_BUILD) && !defined(MOZ_PROFILING))) && !defined(XPCOM_GLUE_AVOID_NSPR) + #define MOZ_THREAD_SAFETY_OWNERSHIP_CHECKS_SUPPORTED 1 +#endif + +#ifdef XPCOM_GLUE + #define NS_CheckThreadSafe(owningThread, msg) +#else + #define NS_CheckThreadSafe(owningThread, msg) \ + if (MOZ_UNLIKELY(owningThread != PR_GetCurrentThread())) { \ + MOZ_CRASH(msg); \ + } +#endif + +#ifdef MOZILLA_INTERNAL_API +void NS_ABORT_OOM(size_t aSize); +#else +inline void NS_ABORT_OOM(size_t) +{ + MOZ_CRASH(); +} +#endif + +typedef void (*StderrCallback)(const char* aFmt, va_list aArgs); +/* 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: + * - it calls the callback set through set_stderr_callback + * - 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); + +/** + * 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); + +// used by the profiler to log stderr in the profiler for more +// advanced performance debugging and display/layers visualization. +void set_stderr_callback(StderrCallback aCallback); + +#if defined(ANDROID) && !defined(RELEASE_OR_BETA) +// Call this if you want a copy of stderr logging sent to a file. This is +// useful to get around logcat overflow problems on android devices, which use +// a circular logcat buffer and can intermittently drop messages if there's too +// much spew. +// +// This is intended for local debugging only, DO NOT USE IN PRODUCTION CODE. +// (This is ifndef RELEASE_OR_BETA to catch uses of it that accidentally get +// checked in). Using this will also prevent the profiler from getting a copy of +// the stderr messages which it uses for various visualization features. +// +// This function can be called from any thread, but if it is called multiple +// times all invocations must be on the same thread. Invocations after the +// first one are ignored, so you can safely put it inside a loop, for example. +// Once this is called there is no way to turn it off; all stderr output from +// that point forward will go to the file. Note that the output is subject to +// buffering so make sure you have enough output to flush the messages you care +// about before you terminate the process. +// +// The file passed in should be writable, so on Android devices a path like +// "/data/local/tmp/blah" is a good one to use as it is world-writable and will +// work even in B2G child processes which have reduced privileges. Note that the +// actual file created will have the PID appended to the path you pass in, so +// that on B2G the output from each process goes to a separate file. +void copy_stderr_to_file(const char* aFile); +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* nsDebug_h___ */ diff --git a/xpcom/glue/nsDeque.cpp b/xpcom/glue/nsDeque.cpp new file mode 100644 index 000000000..f9eb18b40 --- /dev/null +++ b/xpcom/glue/nsDeque.cpp @@ -0,0 +1,361 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. 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 +#ifdef DEBUG_rickg +#include +#endif + +#include "mozilla/CheckedInt.h" + +#define modulus(x,y) ((x)%(y)) + +/** + * Standard constructor + * @param deallocator, called by Erase and ~nsDeque + */ +nsDeque::nsDeque(nsDequeFunctor* aDeallocator) +{ + MOZ_COUNT_CTOR(nsDeque); + mDeallocator = aDeallocator; + 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 + */ +nsDeque::~nsDeque() +{ + MOZ_COUNT_DTOR(nsDeque); + +#ifdef DEBUG_rickg + char buffer[30]; + printf("Capacity: %i\n", mCapacity); + + static int mCaps[15] = {0}; + switch (mCapacity) { + case 4: mCaps[0]++; break; + case 8: mCaps[1]++; break; + case 16: mCaps[2]++; break; + case 32: mCaps[3]++; break; + case 64: mCaps[4]++; break; + case 128: mCaps[5]++; break; + case 256: mCaps[6]++; break; + case 512: mCaps[7]++; break; + case 1024: mCaps[8]++; break; + case 2048: mCaps[9]++; break; + case 4096: mCaps[10]++; break; + default: + break; + } +#endif + + Erase(); + if (mData && mData != mBuffer) { + free(mData); + } + mData = 0; + SetDeallocator(0); +} + +size_t +nsDeque::SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const +{ + size_t size = 0; + if (mData != mBuffer) { + size += aMallocSizeOf(mData); + } + + if (mDeallocator) { + size += aMallocSizeOf(mDeallocator); + } + + return size; +} + +size_t +nsDeque::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const +{ + return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); +} + +/** + * Set the functor to be called by Erase() + * The deque owns the functor. + * + * @param aDeallocator functor object for use by Erase() + */ +void +nsDeque::SetDeallocator(nsDequeFunctor* aDeallocator) +{ + delete mDeallocator; + mDeallocator = aDeallocator; +} + +/** + * Remove all items from container without destroying them. + */ +void +nsDeque::Empty() +{ + if (mSize && mData) { + memset(mData, 0, mCapacity * sizeof(*mData)); + } + mSize = 0; + mOrigin = 0; +} + +/** + * Remove and delete all items from container + */ +void +nsDeque::Erase() +{ + if (mDeallocator && mSize) { + ForEach(*mDeallocator); + } + Empty(); +} + +/** + * 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 +nsDeque::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 +nsDeque::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 +nsDeque::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* +nsDeque::Pop() +{ + void* result = 0; + if (mSize > 0) { + --mSize; + size_t offset = modulus(mSize + mOrigin, mCapacity); + result = mData[offset]; + mData[offset] = 0; + 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* +nsDeque::PopFront() +{ + void* result = 0; + if (mSize > 0) { + NS_ASSERTION(mOrigin < mCapacity, "Error: Bad origin"); + result = mData[mOrigin]; + mData[mOrigin++] = 0; //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* +nsDeque::Peek() const +{ + void* result = 0; + 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* +nsDeque::PeekFront() const +{ + void* result = 0; + 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* +nsDeque::ObjectAt(size_t aIndex) const +{ + void* result = 0; + if (aIndex < mSize) { + result = mData[modulus(mOrigin + aIndex, mCapacity)]; + } + return result; +} + +/** + * Call this method when you want to iterate all the + * members of the container, passing a functor along + * to call your code. + * + * @param aFunctor object to call for each member + * @return *this + */ +void +nsDeque::ForEach(nsDequeFunctor& aFunctor) const +{ + for (size_t i = 0; i < mSize; ++i) { + aFunctor(ObjectAt(i)); + } +} diff --git a/xpcom/glue/nsDeque.h b/xpcom/glue/nsDeque.h new file mode 100644 index 000000000..ace7607d3 --- /dev/null +++ b/xpcom/glue/nsDeque.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/. */ + +/** + * MODULE NOTES: + * + * The Deque is a very small, very efficient container object + * than can hold elements of type void*, offering the following features: + * Its interface supports pushing and popping of elements. + * It can iterate (via an interator class) its elements. + * 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 + * element 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 "nscore.h" +#include "nsDebug.h" +#include "mozilla/Attributes.h" +#include "mozilla/fallible.h" +#include "mozilla/MemoryReporting.h" + +/** + * 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(); + * + */ + +class nsDequeFunctor +{ +public: + virtual void* operator()(void* aObject) = 0; + virtual ~nsDequeFunctor() {} +}; + +/****************************************************** + * 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. + */ + +class nsDequeIterator; + +class nsDeque +{ + typedef mozilla::fallible_t fallible_t; +public: + explicit nsDeque(nsDequeFunctor* aDeallocator = nullptr); + ~nsDeque(); + + /** + * Returns the number of elements currently stored in + * this deque. + * + * @return number of elements currently in the deque + */ + inline size_t GetSize() const { return mSize; } + + /** + * Appends new member at the end of the deque. + * + * @param item to store in deque + */ + void Push(void* aItem) + { + if (!Push(aItem, mozilla::fallible)) { + NS_ABORT_OOM(mSize * sizeof(void*)); + } + } + + MOZ_MUST_USE bool Push(void* aItem, const fallible_t&); + + /** + * Inserts new member at the front of the deque. + * + * @param item to store in deque + */ + void PushFront(void* aItem) + { + if (!PushFront(aItem, mozilla::fallible)) { + NS_ABORT_OOM(mSize * sizeof(void*)); + } + } + + MOZ_MUST_USE 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 bottom item without removing it. + * + * @return the first item in container + */ + + void* Peek() const; + /** + * Return topmost 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 element in list + */ + void* ObjectAt(size_t aIndex) const; + + /** + * Remove and delete all items from container. + * Deletes are handled by the deallocator nsDequeFunctor + * which is specified at deque construction. + */ + void Erase(); + + /** + * Call this method when you want to iterate all the + * members of the container, passing a functor along + * to call your code. + * + * @param aFunctor object to call for each member + */ + void ForEach(nsDequeFunctor& aFunctor) const; + + size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const; + size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const; + +protected: + size_t mSize; + size_t mCapacity; + size_t mOrigin; + nsDequeFunctor* mDeallocator; + void* mBuffer[8]; + void** mData; + +private: + + /** + * Copy constructor (PRIVATE) + * + * @param aOther another deque + */ + nsDeque(const nsDeque& aOther); + + /** + * Deque assignment operator (PRIVATE) + * + * @param aOther another deque + * @return *this + */ + nsDeque& operator=(const nsDeque& aOther); + + bool GrowCapacity(); + void SetDeallocator(nsDequeFunctor* aDeallocator); + + /** + * Remove all items from container without destroying them. + */ + void Empty(); +}; +#endif diff --git a/xpcom/glue/nsEnumeratorUtils.cpp b/xpcom/glue/nsEnumeratorUtils.cpp new file mode 100644 index 000000000..d1843a78a --- /dev/null +++ b/xpcom/glue/nsEnumeratorUtils.cpp @@ -0,0 +1,291 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. 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 "nsISimpleEnumerator.h" +#include "nsIStringEnumerator.h" + +#include "nsCOMPtr.h" +#include "mozilla/RefPtr.h" + +class EmptyEnumeratorImpl + : public nsISimpleEnumerator + , public nsIUTF8StringEnumerator + , public nsIStringEnumerator +{ +public: + EmptyEnumeratorImpl() {} + + // nsISupports interface + NS_DECL_ISUPPORTS_INHERITED // not really inherited, but no mRefCnt + + // nsISimpleEnumerator + NS_DECL_NSISIMPLEENUMERATOR + NS_DECL_NSIUTF8STRINGENUMERATOR + // 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(EmptyEnumeratorImpl, nsISimpleEnumerator, + 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_UNEXPECTED; +} + +NS_IMETHODIMP +EmptyEnumeratorImpl::GetNext(nsACString& aResult) +{ + return NS_ERROR_UNEXPECTED; +} + +NS_IMETHODIMP +EmptyEnumeratorImpl::GetNext(nsAString& aResult) +{ + return NS_ERROR_UNEXPECTED; +} + +nsresult +NS_NewEmptyEnumerator(nsISimpleEnumerator** aResult) +{ + *aResult = EmptyEnumeratorImpl::GetInstance(); + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////////////// + +class nsSingletonEnumerator final : public nsISimpleEnumerator +{ +public: + NS_DECL_ISUPPORTS + + // nsISimpleEnumerator methods + NS_IMETHOD HasMoreElements(bool* aResult) override; + NS_IMETHOD GetNext(nsISupports** aResult) override; + + explicit nsSingletonEnumerator(nsISupports* aValue); + +private: + ~nsSingletonEnumerator(); + +protected: + nsCOMPtr mValue; + bool mConsumed; +}; + +nsSingletonEnumerator::nsSingletonEnumerator(nsISupports* aValue) + : mValue(aValue) +{ + mConsumed = (mValue ? false : true); +} + +nsSingletonEnumerator::~nsSingletonEnumerator() +{ +} + +NS_IMPL_ISUPPORTS(nsSingletonEnumerator, nsISimpleEnumerator) + +NS_IMETHODIMP +nsSingletonEnumerator::HasMoreElements(bool* aResult) +{ + NS_PRECONDITION(aResult != 0, "null ptr"); + if (!aResult) { + return NS_ERROR_NULL_POINTER; + } + + *aResult = !mConsumed; + return NS_OK; +} + + +NS_IMETHODIMP +nsSingletonEnumerator::GetNext(nsISupports** aResult) +{ + NS_PRECONDITION(aResult != 0, "null ptr"); + if (!aResult) { + return NS_ERROR_NULL_POINTER; + } + + if (mConsumed) { + return NS_ERROR_UNEXPECTED; + } + + 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 nsISimpleEnumerator +{ +public: + NS_DECL_ISUPPORTS + + // nsISimpleEnumerator methods + NS_IMETHOD HasMoreElements(bool* aResult) override; + NS_IMETHOD GetNext(nsISupports** aResult) override; + + nsUnionEnumerator(nsISimpleEnumerator* aFirstEnumerator, + nsISimpleEnumerator* aSecondEnumerator); + +private: + ~nsUnionEnumerator(); + +protected: + nsCOMPtr mFirstEnumerator, mSecondEnumerator; + bool mConsumed; + bool mAtSecond; +}; + +nsUnionEnumerator::nsUnionEnumerator(nsISimpleEnumerator* aFirstEnumerator, + nsISimpleEnumerator* aSecondEnumerator) + : mFirstEnumerator(aFirstEnumerator) + , mSecondEnumerator(aSecondEnumerator) + , mConsumed(false) + , mAtSecond(false) +{ +} + +nsUnionEnumerator::~nsUnionEnumerator() +{ +} + +NS_IMPL_ISUPPORTS(nsUnionEnumerator, nsISimpleEnumerator) + +NS_IMETHODIMP +nsUnionEnumerator::HasMoreElements(bool* aResult) +{ + NS_PRECONDITION(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) +{ + NS_PRECONDITION(aResult != 0, "null ptr"); + if (!aResult) { + return NS_ERROR_NULL_POINTER; + } + + if (mConsumed) { + return NS_ERROR_UNEXPECTED; + } + + 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 { + nsUnionEnumerator* enumer = new nsUnionEnumerator(aFirstEnumerator, + aSecondEnumerator); + if (!enumer) { + return NS_ERROR_OUT_OF_MEMORY; + } + *aResult = enumer; + } + NS_ADDREF(*aResult); + return NS_OK; +} diff --git a/xpcom/glue/nsEnumeratorUtils.h b/xpcom/glue/nsEnumeratorUtils.h new file mode 100644 index 000000000..f7a0db099 --- /dev/null +++ b/xpcom/glue/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/glue/nsHashKeys.h b/xpcom/glue/nsHashKeys.h new file mode 100644 index 000000000..9c688691f --- /dev/null +++ b/xpcom/glue/nsHashKeys.h @@ -0,0 +1,660 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 "nsIHashable.h" +#include "nsAutoPtr.h" +#include "nsCOMPtr.h" +#include "PLDHashTable.h" +#include + +#include "nsStringGlue.h" +#include "nsCRTGlue.h" +#include "nsUnicharUtils.h" +#include "nsPointerHashKeys.h" + +#include +#include + +#include "mozilla/HashFunctions.h" +#include "mozilla/Move.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 + * nsPtrHashKey + * nsClearingPtrHashKey + * nsVoidPtrHashKey + * nsClearingVoidPtrHashKey + * nsISupportsHashKey + * nsIDHashKey + * nsDepCharHashKey + * nsCharPtrHashKey + * nsUnicharPtrHashKey + * nsHashableHashKey + * 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& aToCopy) : mStr(aToCopy.mStr) {} + ~nsStringHashKey() {} + + 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: + const nsString mStr; +}; + +#ifdef MOZILLA_INTERNAL_API + +/** + * hashkey wrapper using nsAString KeyType + * + * This is internal-API only because nsCaseInsensitiveStringComparator is + * internal-only. + * + * @see nsTHashtable::EntryType for specification + */ +class nsStringCaseInsensitiveHashKey : public PLDHashEntryHdr +{ +public: + typedef const nsAString& KeyType; + typedef const nsAString* KeyTypePointer; + + explicit nsStringCaseInsensitiveHashKey(KeyTypePointer aStr) + : mStr(*aStr) + { + // take it easy just deal HashKey + } + nsStringCaseInsensitiveHashKey(const nsStringCaseInsensitiveHashKey& aToCopy) + : mStr(aToCopy.mStr) + { + } + ~nsStringCaseInsensitiveHashKey() {} + + KeyType GetKey() const { return mStr; } + bool KeyEquals(const KeyTypePointer aKey) const + { + return mStr.Equals(*aKey, nsCaseInsensitiveStringComparator()); + } + + static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; } + static PLDHashNumber HashKey(const KeyTypePointer aKey) + { + nsAutoString 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 nsString mStr; +}; + +#endif + +/** + * 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(const nsCStringHashKey& aToCopy) : mStr(aToCopy.mStr) {} + ~nsCStringHashKey() {} + + 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 uint32_t KeyType + * + * @see nsTHashtable::EntryType for specification + */ +class nsUint32HashKey : public PLDHashEntryHdr +{ +public: + typedef const uint32_t& KeyType; + typedef const uint32_t* KeyTypePointer; + + explicit nsUint32HashKey(KeyTypePointer aKey) : mValue(*aKey) {} + nsUint32HashKey(const nsUint32HashKey& aToCopy) : mValue(aToCopy.mValue) {} + ~nsUint32HashKey() {} + + 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 *aKey; } + enum { ALLOW_MEMMOVE = true }; + +private: + const uint32_t mValue; +}; + +/** + * hashkey wrapper using uint64_t KeyType + * + * @see nsTHashtable::EntryType for specification + */ +class nsUint64HashKey : public PLDHashEntryHdr +{ +public: + typedef const uint64_t& KeyType; + typedef const uint64_t* KeyTypePointer; + + explicit nsUint64HashKey(KeyTypePointer aKey) : mValue(*aKey) {} + nsUint64HashKey(const nsUint64HashKey& aToCopy) : mValue(aToCopy.mValue) {} + ~nsUint64HashKey() {} + + 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 PLDHashNumber(*aKey); + } + enum { ALLOW_MEMMOVE = true }; + +private: + const uint64_t mValue; +}; + +/** + * 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(const nsFloatHashKey& aToCopy) : mValue(aToCopy.mValue) {} + ~nsFloatHashKey() {} + + 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 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(const nsISupportsHashKey& aToCopy) + : mSupports(aToCopy.mSupports) + { + } + ~nsISupportsHashKey() {} + + 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(const nsRefPtrHashKey& aToCopy) : mKey(aToCopy.mKey) {} + ~nsRefPtrHashKey() {} + + 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(const nsClearingPtrHashKey& aToCopy) + : nsPtrHashKey(aToCopy) + { + } + ~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() {} + + 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(const nsIDHashKey& aToCopy) : mID(aToCopy.mID) {} + ~nsIDHashKey() {} + + 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: + 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(const nsDepCharHashKey& aToCopy) : mKey(aToCopy.mKey) {} + ~nsDepCharHashKey() {} + + 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& aToCopy) + : mKey(strdup(aToCopy.mKey)) + { + } + + nsCharPtrHashKey(nsCharPtrHashKey&& 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_strdup(aKey)) {} + nsUnicharPtrHashKey(const nsUnicharPtrHashKey& aToCopy) + : mKey(NS_strdup(aToCopy.mKey)) + { + } + + nsUnicharPtrHashKey(nsUnicharPtrHashKey&& aOther) + : mKey(aOther.mKey) + { + aOther.mKey = nullptr; + } + + ~nsUnicharPtrHashKey() + { + if (mKey) { + NS_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; +}; + +/** + * Hashtable key class to use with objects that support nsIHashable + */ +class nsHashableHashKey : public PLDHashEntryHdr +{ +public: + typedef nsIHashable* KeyType; + typedef const nsIHashable* KeyTypePointer; + + explicit nsHashableHashKey(const nsIHashable* aKey) + : mKey(const_cast(aKey)) + { + } + nsHashableHashKey(const nsHashableHashKey& aToCopy) : mKey(aToCopy.mKey) {} + ~nsHashableHashKey() {} + + nsIHashable* GetKey() const { return mKey; } + + bool KeyEquals(const nsIHashable* aKey) const + { + bool eq; + if (NS_SUCCEEDED(mKey->Equals(const_cast(aKey), &eq))) { + return eq; + } + return false; + } + + static const nsIHashable* KeyToPointer(nsIHashable* aKey) { return aKey; } + static PLDHashNumber HashKey(const nsIHashable* aKey) + { + uint32_t code = 8888; // magic number if GetHashCode fails :-( +#ifdef DEBUG + nsresult rv = +#endif + const_cast(aKey)->GetHashCode(&code); + NS_ASSERTION(NS_SUCCEEDED(rv), "GetHashCode should not throw!"); + return code; + } + + enum { ALLOW_MEMMOVE = true }; + +private: + nsCOMPtr 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& aOther) : mKey(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/glue/nsIClassInfoImpl.h b/xpcom/glue/nsIClassInfoImpl.h new file mode 100644 index 000000000..44303f2be --- /dev/null +++ b/xpcom/glue/nsIClassInfoImpl.h @@ -0,0 +1,179 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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(GetInterfacesProc)(uint32_t* aCountP, nsIID*** 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) \ + (uint32_t *, nsIID ***); + +#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)(uint32_t *count, nsIID ***array) \ +{ \ + *count = _c; \ + *array = (nsIID **)moz_xmalloc(sizeof (nsIID *) * _c); \ + uint32_t i = 0; + +#define NS_CLASSINFO_HELPER_ENTRY(_interface) \ + (*array)[i++] = (nsIID*)nsMemory::Clone(&NS_GET_IID(_interface), \ + sizeof(nsIID)); + +#define NS_CLASSINFO_HELPER_END \ + MOZ_ASSERT(i == *count, "Incorrent number of entries"); \ + return NS_OK; \ +} + +#define NS_IMPL_CI_INTERFACE_GETTER(aClass, ...) \ + MOZ_STATIC_ASSERT_VALID_ARG_COUNT(__VA_ARGS__); \ + NS_CLASSINFO_HELPER_BEGIN(aClass, \ + MOZ_PASTE_PREFIX_AND_ARG_COUNT(/* No prefix */, \ + __VA_ARGS__)) \ + MOZ_FOR_EACH(NS_CLASSINFO_HELPER_ENTRY, (), (__VA_ARGS__)) \ + NS_CLASSINFO_HELPER_END + +#define NS_IMPL_QUERY_INTERFACE_CI(aClass, ...) \ + MOZ_STATIC_ASSERT_VALID_ARG_COUNT(__VA_ARGS__); \ + 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) \ + NS_INTERFACE_MAP_END + +#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/glue/nsID.cpp b/xpcom/glue/nsID.cpp new file mode 100644 index 000000000..0a73bf6c3 --- /dev/null +++ b/xpcom/glue/nsID.cpp @@ -0,0 +1,133 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. 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 "nsMemory.h" +#include "mozilla/Sprintf.h" + +void nsID::Clear() +{ + m0 = 0; + m1 = 0; + m2 = 0; + for (int i = 0; i < 8; ++i) { + m3[i] = 0; + } +} + +/** + * 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 + +static const char gIDFormat[] = + "{%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x}"; + +/* + * Returns an allocated string in {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx} + * format. The string is allocated with NS_Alloc and should be freed by + * the caller. + */ + +char* +nsID::ToString() const +{ + char* res = (char*)NS_Alloc(NSID_LENGTH); + + if (res) { + snprintf(res, NSID_LENGTH, gIDFormat, + m0, (uint32_t)m1, (uint32_t)m2, + (uint32_t)m3[0], (uint32_t)m3[1], (uint32_t)m3[2], + (uint32_t)m3[3], (uint32_t)m3[4], (uint32_t)m3[5], + (uint32_t)m3[6], (uint32_t)m3[7]); + } + return res; +} + +void +nsID::ToProvidedString(char (&aDest)[NSID_LENGTH]) const +{ + SprintfLiteral(aDest, gIDFormat, + m0, (uint32_t)m1, (uint32_t)m2, + (uint32_t)m3[0], (uint32_t)m3[1], (uint32_t)m3[2], + (uint32_t)m3[3], (uint32_t)m3[4], (uint32_t)m3[5], + (uint32_t)m3[6], (uint32_t)m3[7]); +} + +#endif // XPCOM_GLUE_AVOID_NSPR diff --git a/xpcom/glue/nsID.h b/xpcom/glue/nsID.h new file mode 100644 index 000000000..c04007d4e --- /dev/null +++ b/xpcom/glue/nsID.h @@ -0,0 +1,179 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 + +/** + * A "unique identifier". This is modeled after OSF DCE UUIDs. + */ + +struct nsID +{ + /** + * @name Identifier values + */ + + //@{ + uint32_t m0; + uint16_t m1; + uint16_t m2; + uint8_t m3[8]; + //@} + + /** + * @name Methods + */ + + //@{ + /** + * 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 + { + // Unfortunately memcmp isn't faster than this. + 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]); + } + + 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 an allocated string in + * {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx} format. Caller should free string. + * YOU SHOULD ONLY USE THIS IF YOU CANNOT USE ToProvidedString() BELOW. + */ + char* 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 + + //@} +}; + +#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& + +/** + * Define an IID + * obsolete - do not use this macro + */ + +#define NS_DEFINE_IID(_name, _iidspec) \ + const nsIID _name = _iidspec + +/** + * 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/glue/nsIInterfaceRequestorUtils.cpp b/xpcom/glue/nsIInterfaceRequestorUtils.cpp new file mode 100644 index 000000000..8a2dfe3f8 --- /dev/null +++ b/xpcom/glue/nsIInterfaceRequestorUtils.cpp @@ -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/. */ + +#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/glue/nsIInterfaceRequestorUtils.h b/xpcom/glue/nsIInterfaceRequestorUtils.h new file mode 100644 index 000000000..718cf387b --- /dev/null +++ b/xpcom/glue/nsIInterfaceRequestorUtils.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 __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) +{ + NS_PRECONDITION(aSource, "null parameter"); + NS_PRECONDITION(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/glue/nsINIParser.cpp b/xpcom/glue/nsINIParser.cpp new file mode 100644 index 000000000..53eae7292 --- /dev/null +++ b/xpcom/glue/nsINIParser.cpp @@ -0,0 +1,331 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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/FileUtils.h" // AutoFILE + +// System headers (alphabetical) +#include +#include +#ifdef XP_WIN +#include +#endif + +#if defined(XP_WIN) +#define READ_BINARYMODE L"rb" +#else +#define READ_BINARYMODE "r" +#endif + +using namespace mozilla; + +#ifdef XP_WIN +inline FILE* +TS_tfopen(const char* aPath, const wchar_t* aMode) +{ + wchar_t wPath[MAX_PATH]; + MultiByteToWideChar(CP_UTF8, 0, aPath, -1, wPath, MAX_PATH); + return _wfopen(wPath, aMode); +} +#else +inline FILE* +TS_tfopen(const char* aPath, const char* aMode) +{ + return fopen(aPath, aMode); +} +#endif + +// Stack based FILE wrapper to ensure that fclose is called, copied from +// toolkit/mozapps/update/updater/readstrings.cpp + +class AutoFILE +{ +public: + explicit AutoFILE(FILE* aFp = nullptr) : fp_(aFp) {} + ~AutoFILE() + { + if (fp_) { + fclose(fp_); + } + } + operator FILE*() { return fp_; } + FILE** operator&() { return &fp_; } + void operator=(FILE* aFp) { fp_ = aFp; } +private: + FILE* fp_; +}; + +nsresult +nsINIParser::Init(nsIFile* aFile) +{ + /* open the file. Don't use OpenANSIFileDesc, because you mustn't + pass FILE* across shared library boundaries, which may be using + different CRTs */ + + AutoFILE fd; + +#ifdef XP_WIN + nsAutoString path; + nsresult rv = aFile->GetPath(path); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + fd = _wfopen(path.get(), READ_BINARYMODE); +#else + nsAutoCString path; + aFile->GetNativePath(path); + + fd = fopen(path.get(), READ_BINARYMODE); +#endif + + if (!fd) { + return NS_ERROR_FAILURE; + } + + return InitFromFILE(fd); +} + +nsresult +nsINIParser::Init(const char* aPath) +{ + /* open the file */ + AutoFILE fd(TS_tfopen(aPath, READ_BINARYMODE)); + if (!fd) { + return NS_ERROR_FAILURE; + } + + return InitFromFILE(fd); +} + +static const char kNL[] = "\r\n"; +static const char kEquals[] = "="; +static const char kWhitespace[] = " \t"; +static const char kRBracket[] = "]"; + +nsresult +nsINIParser::InitFromFILE(FILE* aFd) +{ + /* get file size */ + if (fseek(aFd, 0, SEEK_END) != 0) { + return NS_ERROR_FAILURE; + } + + long flen = ftell(aFd); + /* zero-sized file, or an error */ + if (flen <= 0) { + return NS_ERROR_FAILURE; + } + + /* malloc an internal buf the size of the file */ + mFileContents = MakeUnique(flen + 2); + if (!mFileContents) { + return NS_ERROR_OUT_OF_MEMORY; + } + + /* read the file in one swoop */ + if (fseek(aFd, 0, SEEK_SET) != 0) { + return NS_BASE_STREAM_OSERROR; + } + + int rd = fread(mFileContents.get(), sizeof(char), flen, aFd); + if (rd != flen) { + return NS_BASE_STREAM_OSERROR; + } + + // We write a UTF16 null so that the file is easier to convert to UTF8 + mFileContents[flen] = mFileContents[flen + 1] = '\0'; + + char* buffer = &mFileContents[0]; + + if (flen >= 3 && + mFileContents[0] == '\xEF' && + mFileContents[1] == '\xBB' && + mFileContents[2] == '\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. + buffer = &mFileContents[3]; + } + +#ifdef XP_WIN + if (flen >= 2 && + mFileContents[0] == '\xFF' && + mFileContents[1] == '\xFE') { + // Someone set us up the Utf-16LE BOM + buffer = &mFileContents[2]; + // Get the size required for our Utf8 buffer + flen = WideCharToMultiByte(CP_UTF8, + 0, + reinterpret_cast(buffer), + -1, + nullptr, + 0, + nullptr, + nullptr); + if (flen == 0) { + return NS_ERROR_FAILURE; + } + + UniquePtr utf8Buffer(new char[flen]); + if (WideCharToMultiByte(CP_UTF8, 0, reinterpret_cast(buffer), -1, + utf8Buffer.get(), flen, nullptr, nullptr) == 0) { + return NS_ERROR_FAILURE; + } + mFileContents = Move(utf8Buffer); + buffer = mFileContents.get(); + } +#endif + + 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; + } + + INIValue* v; + if (!mSections.Get(currSection, &v)) { + v = new INIValue(key, token); + if (!v) { + return NS_ERROR_OUT_OF_MEMORY; + } + + mSections.Put(currSection, v); + continue; + } + + // Check whether this key has already been specified; overwrite + // if so, or append if not. + while (v) { + if (!strcmp(key, v->key)) { + v->value = token; + break; + } + if (!v->next) { + v->next = MakeUnique(key, token); + if (!v->next) { + return NS_ERROR_OUT_OF_MEMORY; + } + break; + } + v = v->next.get(); + } + NS_ASSERTION(v, "v should never be null coming out of this loop"); + } + + return NS_OK; +} + +nsresult +nsINIParser::GetString(const char* aSection, const char* aKey, + nsACString& aResult) +{ + 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) +{ + 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 (auto iter = mSections.Iter(); !iter.Done(); iter.Next()) { + if (!aCB(iter.Key(), aClosure)) { + break; + } + } + return NS_OK; +} + +nsresult +nsINIParser::GetStrings(const char* aSection, + INIStringCallback aCB, void* aClosure) +{ + 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; +} diff --git a/xpcom/glue/nsINIParser.h b/xpcom/glue/nsINIParser.h new file mode 100644 index 000000000..d0f553d49 --- /dev/null +++ b/xpcom/glue/nsINIParser.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/. */ + +// 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() {} + + /** + * 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); + + /** + * Initialize the INIParser with a file path. 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(const char* aPath); + + /** + * 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); + +private: + struct INIValue + { + INIValue(const char* aKey, const char* aValue) + : key(aKey) + , value(aValue) + { + } + + const char* key; + const char* value; + mozilla::UniquePtr next; + }; + + nsClassHashtable mSections; + mozilla::UniquePtr mFileContents; + + nsresult InitFromFILE(FILE* aFd); +}; + +#endif /* nsINIParser_h__ */ diff --git a/xpcom/glue/nsISupportsImpl.cpp b/xpcom/glue/nsISupportsImpl.cpp new file mode 100644 index 000000000..c60c0bfa7 --- /dev/null +++ b/xpcom/glue/nsISupportsImpl.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 "nsISupportsImpl.h" + +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; +} diff --git a/xpcom/glue/nsISupportsImpl.h b/xpcom/glue/nsISupportsImpl.h new file mode 100644 index 000000000..26db3c525 --- /dev/null +++ b/xpcom/glue/nsISupportsImpl.h @@ -0,0 +1,1090 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 "nsISupportsBase.h" +#include "nsISupportsUtils.h" + + +#if !defined(XPCOM_GLUE_AVOID_NSPR) +#include "prthread.h" /* needed for thread-safety checks */ +#endif // !XPCOM_GLUE_AVOID_NSPR + +#include "nsDebug.h" +#include "nsXPCOM.h" +#include "mozilla/Atomics.h" +#include "mozilla/Attributes.h" +#include "mozilla/Assertions.h" +#include "mozilla/Compiler.h" +#include "mozilla/Likely.h" +#include "mozilla/MacroArgs.h" +#include "mozilla/MacroForEach.h" +#include "mozilla/TypeTraits.h" + +#define MOZ_ASSERT_TYPE_OK_FOR_REFCOUNTING(X) \ + static_assert(!mozilla::IsDestructible::value, \ + "Reference-counted class " #X " should not have a public destructor. " \ + "Make this class's destructor non-public"); + +inline nsISupports* +ToSupports(nsISupports* aSupports) +{ + return aSupports; +} + +inline nsISupports* +ToCanonicalSupports(nsISupports* aSupports) +{ + return nullptr; +} + +//////////////////////////////////////////////////////////////////////////////// +// Macros to help detect thread-safety: + +#ifdef MOZ_THREAD_SAFETY_OWNERSHIP_CHECKS_SUPPORTED + +class nsAutoOwningThread +{ +public: + nsAutoOwningThread() { mThread = PR_GetCurrentThread(); } + void* GetThread() const { return mThread; } + +private: + void* mThread; +}; + +#define NS_DECL_OWNINGTHREAD nsAutoOwningThread _mOwningThread; +#define NS_ASSERT_OWNINGTHREAD_AGGREGATE(agg, _class) \ + NS_CheckThreadSafe(agg->_mOwningThread.GetThread(), #_class " not thread-safe") +#define NS_ASSERT_OWNINGTHREAD(_class) NS_ASSERT_OWNINGTHREAD_AGGREGATE(this, _class) +#else // !DEBUG && !(NIGHTLY_BUILD && !MOZ_PROFILING) + +#define NS_DECL_OWNINGTHREAD /* nothing */ +#define NS_ASSERT_OWNINGTHREAD_AGGREGATE(agg, _class) ((void)0) +#define NS_ASSERT_OWNINGTHREAD(_class) ((void)0) + +#endif // DEBUG || (NIGHTLY_BUILD && !MOZ_PROFILING) + + +// 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)) + +#include "mozilla/TypeTraits.h" +#define MOZ_ASSERT_CLASSNAME(_type) \ + static_assert(mozilla::IsClass<_type>::value, \ + "Token '" #_type "' is not a class type.") + +// 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); \ + 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); \ + 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); \ + 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); \ + 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) + +/* 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),static_cast(_p)) + +#define NSCAP_LOG_RELEASE(_c, _p) \ + if (_p) \ + NS_LogCOMPtrRelease((_c), static_cast(_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) + +#endif /* NS_BUILD_REFCNT_LOGGING */ + + +// 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: + 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; + + MOZ_ALWAYS_INLINE uintptr_t incr(nsISupports* aOwner) + { + return incr(aOwner, nullptr); + } + + 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); + NS_CycleCollectorSuspect3(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; + } + + MOZ_ALWAYS_INLINE uintptr_t decr(nsISupports* aOwner, + bool* aShouldDelete = nullptr) + { + return decr(aOwner, nullptr, aShouldDelete); + } + + 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'! + NS_CycleCollectorSuspect3(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++() { return ++mValue; } + MOZ_ALWAYS_INLINE nsrefcnt operator--() { return --mValue; } + + MOZ_ALWAYS_INLINE nsrefcnt operator=(nsrefcnt aValue) + { + return (mValue = aValue); + } + MOZ_ALWAYS_INLINE operator nsrefcnt() const { return mValue; } + MOZ_ALWAYS_INLINE nsrefcnt get() const { return mValue; } + + static const bool isThreadSafe = true; +private: + nsrefcnt operator++(int) = delete; + nsrefcnt operator--(int) = delete; + // In theory, RelaseAcquire consistency (but no weaker) is sufficient for + // the counter. Making it weaker could speed up builds on ARM (but not x86), + // but could break pre-existing code that assumes sequential consistency. + 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; \ + typedef mozilla::FalseType HasThreadSafeRefCnt; \ +protected: \ + nsAutoRefCnt mRefCnt; \ + NS_DECL_OWNINGTHREAD \ +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; \ + typedef mozilla::TrueType HasThreadSafeRefCnt; \ +protected: \ + ::mozilla::ThreadSafeAutoRefCnt mRefCnt; \ + NS_DECL_OWNINGTHREAD \ +public: + +#define NS_DECL_CYCLE_COLLECTING_ISUPPORTS \ +public: \ + NS_IMETHOD QueryInterface(REFNSIID aIID, \ + void** aInstancePtr) override; \ + NS_IMETHOD_(MozExternalRefCountType) AddRef(void) override; \ + NS_IMETHOD_(MozExternalRefCountType) Release(void) override; \ + NS_IMETHOD_(void) DeleteCycleCollectable(void); \ + typedef mozilla::FalseType HasThreadSafeRefCnt; \ +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_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) \ +public: \ + NS_METHOD_(MozExternalRefCountType) AddRef(void) { \ + NS_IMPL_CC_NATIVE_ADDREF_BODY(_class) \ + } \ + NS_METHOD_(MozExternalRefCountType) Release(void) { \ + NS_IMPL_CC_NATIVE_RELEASE_BODY(_class) \ + } \ + typedef mozilla::FalseType HasThreadSafeRefCnt; \ +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 optional override Mark the AddRef & Release methods as overrides. + */ +#define NS_INLINE_DECL_REFCOUNTING_WITH_DESTROY(_class, _destroy, ...) \ +public: \ + NS_METHOD_(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; \ + } \ + NS_METHOD_(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; \ + } \ + typedef mozilla::FalseType HasThreadSafeRefCnt; \ +protected: \ + nsAutoRefCnt 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 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__) + +#define NS_INLINE_DECL_THREADSAFE_REFCOUNTING_META(_class, _decl, ...) \ +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) { \ + delete (this); \ + return 0; \ + } \ + return count; \ + } \ + typedef mozilla::TrueType HasThreadSafeRefCnt; \ +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 + */ +#define NS_INLINE_DECL_THREADSAFE_REFCOUNTING(_class, ...) \ +NS_INLINE_DECL_THREADSAFE_REFCOUNTING_META(_class, NS_METHOD_, __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_REFCOUNTING_META(_class, NS_IMETHOD_, __VA_ARGS__) + +/** + * 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_IMETHODIMP_(MozExternalRefCountType) _class::AddRef(void) \ +{ \ + MOZ_ASSERT_TYPE_OK_FOR_REFCOUNTING(_class) \ + MOZ_ASSERT(int32_t(mRefCnt) >= 0, "illegal refcnt"); \ + if (!mRefCnt.isThreadSafe) \ + NS_ASSERT_OWNINGTHREAD(_class); \ + nsrefcnt count = ++mRefCnt; \ + NS_LOG_ADDREF(this, count, #_class, sizeof(*this)); \ + return count; \ +} + +/** + * 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) \ + NS_PRECONDITION(_aggregator, "null aggregator"); \ + return (_aggregator)->AddRef(); \ +} + +/** + * Use this macro to implement the Release method for a given + * _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. + * + * For example, + * + * NS_IMPL_RELEASE_WITH_DESTROY(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_RELEASE_WITH_DESTROY(_class, _destroy) \ +NS_IMETHODIMP_(MozExternalRefCountType) _class::Release(void) \ +{ \ + MOZ_ASSERT(int32_t(mRefCnt) > 0, "dup release"); \ + if (!mRefCnt.isThreadSafe) \ + NS_ASSERT_OWNINGTHREAD(_class); \ + nsrefcnt count = --mRefCnt; \ + NS_LOG_RELEASE(this, count, #_class); \ + if (count == 0) { \ + mRefCnt = 1; /* stabilize */ \ + _destroy; \ + return 0; \ + } \ + return count; \ +} + +/** + * 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)) + +/** + * 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) \ +{ \ + NS_PRECONDITION(_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_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; \ +} + +/////////////////////////////////////////////////////////////////////////////// + +/** + * 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 + +#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_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, ...) \ + MOZ_STATIC_ASSERT_VALID_ARG_COUNT(__VA_ARGS__); \ + 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. + * + * Note that I didn't make these inlined because they're virtual methods. + */ + +#define NS_IMPL_ADDREF_INHERITED(Class, Super) \ +NS_IMETHODIMP_(MozExternalRefCountType) Class::AddRef(void) \ +{ \ + MOZ_ASSERT_TYPE_OK_FOR_REFCOUNTING(Class) \ + nsrefcnt r = Super::AddRef(); \ + NS_LOG_ADDREF(this, r, #Class, sizeof(*this)); \ + return r; \ +} + +#define NS_IMPL_RELEASE_INHERITED(Class, Super) \ +NS_IMETHODIMP_(MozExternalRefCountType) Class::Release(void) \ +{ \ + nsrefcnt r = Super::Release(); \ + NS_LOG_RELEASE(this, r, #Class); \ + return r; \ +} + +/** + * 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, ...) \ + MOZ_STATIC_ASSERT_VALID_ARG_COUNT(__VA_ARGS__); \ + 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__) + +#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) + +/* + * 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; + + +/////////////////////////////////////////////////////////////////////////////// +/** + * + * Threadsafe implementations of the ISupports convenience macros. + * + * @note These are not available when linking against the standalone glue, + * because the implementation requires PR_ symbols. + */ +#define NS_INTERFACE_MAP_END_THREADSAFE NS_IMPL_QUERY_TAIL_GUTS + +/** + * Macro to generate nsIClassInfo methods for classes which do not have + * corresponding nsIFactory implementations. + */ +#define NS_IMPL_THREADSAFE_CI(_class) \ +NS_IMETHODIMP \ +_class::GetInterfaces(uint32_t* _count, nsIID*** _array) \ +{ \ + return NS_CI_INTERFACE_GETTER_NAME(_class)(_count, _array); \ +} \ + \ +NS_IMETHODIMP \ +_class::GetScriptableHelper(nsIXPCScriptable** _retval) \ +{ \ + *_retval = nullptr; \ + return NS_OK; \ +} \ + \ +NS_IMETHODIMP \ +_class::GetContractID(char** _contractID) \ +{ \ + *_contractID = nullptr; \ + return NS_OK; \ +} \ + \ +NS_IMETHODIMP \ +_class::GetClassDescription(char** _classDescription) \ +{ \ + *_classDescription = nullptr; \ + 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/glue/nsISupportsUtils.h b/xpcom/glue/nsISupportsUtils.h new file mode 100644 index 000000000..4d306d3d1 --- /dev/null +++ b/xpcom/glue/nsISupportsUtils.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 nsISupportsUtils_h__ +#define nsISupportsUtils_h__ + +#include "nscore.h" +#include "nsISupportsBase.h" +#include "nsError.h" +#include "nsDebug.h" +#include "nsISupportsImpl.h" +#include "mozilla/RefPtr.h" +#include "mozilla/TypeTraits.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(!mozilla::IsSame::value || + mozilla::IsSame::value, + "don't use CallQueryInterface for compile-time-determinable casts"); + + NS_PRECONDITION(aSource, "null parameter"); + NS_PRECONDITION(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/glue/nsIWeakReferenceUtils.h b/xpcom/glue/nsIWeakReferenceUtils.h new file mode 100644 index 000000000..1c84e00df --- /dev/null +++ b/xpcom/glue/nsIWeakReferenceUtils.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 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) +{ + NS_PRECONDITION(aSource, "null parameter"); + NS_PRECONDITION(aDestination, "null parameter"); + + return aSource->QueryReferent(NS_GET_TEMPLATE_IID(DestinationType), + reinterpret_cast(aDestination)); +} + + +class MOZ_STACK_CLASS nsQueryReferent final : public nsCOMPtr_helper +{ +public: + nsQueryReferent(nsIWeakReference* aWeakPtr, nsresult* aError) + : mWeakPtr(aWeakPtr) + , mErrorPtr(aError) + { + } + + virtual nsresult NS_FASTCALL operator()(const nsIID& aIID, void**) const + override; + +private: + nsIWeakReference* MOZ_NON_OWNING_REF mWeakPtr; + nsresult* mErrorPtr; +}; + +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); + +/** + * |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 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/glue/nsInterfaceHashtable.h b/xpcom/glue/nsInterfaceHashtable.h new file mode 100644 index 000000000..14368af63 --- /dev/null +++ b/xpcom/glue/nsInterfaceHashtable.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 nsInterfaceHashtable_h__ +#define nsInterfaceHashtable_h__ + +#include "nsBaseHashtable.h" +#include "nsHashKeys.h" +#include "nsCOMPtr.h" + +/** + * templated hashtable class maps keys to interface pointers. + * See nsBaseHashtable for complete declaration. + * @param KeyClass a wrapper-class for the hashtable key, see nsHashKeys.h + * for a complete specification. + * @param Interface the interface-type being wrapped + * @see nsDataHashtable, nsClassHashtable + */ +template +class nsInterfaceHashtable + : public nsBaseHashtable, Interface*> +{ +public: + typedef typename KeyClass::KeyType KeyType; + typedef Interface* UserDataType; + typedef nsBaseHashtable, Interface*> base_type; + + nsInterfaceHashtable() {} + explicit nsInterfaceHashtable(uint32_t aInitLength) + : nsBaseHashtable, Interface*>(aInitLength) + { + } + + /** + * @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, UserDataType* aData) const; + + /** + * @copydoc nsBaseHashtable::Get + */ + 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! + */ + Interface* GetWeak(KeyType aKey, bool* aFound = nullptr) const; +}; + +template +inline void +ImplCycleCollectionUnlink(nsInterfaceHashtable& aField) +{ + aField.Clear(); +} + +template +inline void +ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& aCallback, + const nsInterfaceHashtable& aField, + const char* aName, + uint32_t aFlags = 0) +{ + for (auto iter = aField.ConstIter(); !iter.Done(); iter.Next()) { + CycleCollectionNoteChild(aCallback, iter.UserData(), aName, aFlags); + } +} + +// +// nsInterfaceHashtable definitions +// + +template +bool +nsInterfaceHashtable::Get(KeyType aKey, + UserDataType* aInterface) const +{ + typename base_type::EntryType* ent = this->GetEntry(aKey); + + if (ent) { + if (aInterface) { + *aInterface = ent->mData; + + NS_IF_ADDREF(*aInterface); + } + + return true; + } + + // if the key doesn't exist, set *aInterface to null + // so that it is a valid XPCOM getter + if (aInterface) { + *aInterface = nullptr; + } + + return false; +} + +template +already_AddRefed +nsInterfaceHashtable::Get(KeyType aKey) const +{ + typename base_type::EntryType* ent = this->GetEntry(aKey); + if (!ent) { + return nullptr; + } + + nsCOMPtr copy = ent->mData; + return copy.forget(); +} + +template +Interface* +nsInterfaceHashtable::GetWeak(KeyType aKey, + bool* aFound) const +{ + typename base_type::EntryType* ent = this->GetEntry(aKey); + + if (ent) { + if (aFound) { + *aFound = true; + } + + return ent->mData; + } + + // Key does not exist, return nullptr and set aFound to false + if (aFound) { + *aFound = false; + } + return nullptr; +} + +#endif // nsInterfaceHashtable_h__ diff --git a/xpcom/glue/nsJSThingHashtable.h b/xpcom/glue/nsJSThingHashtable.h new file mode 100644 index 000000000..8c1b1447d --- /dev/null +++ b/xpcom/glue/nsJSThingHashtable.h @@ -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/. */ + +#ifndef nsJSThingHashtable_h__ +#define nsJSThingHashtable_h__ + +#include "nsHashKeys.h" +#include "nsBaseHashtable.h" + +namespace JS { +template +class Heap; +} /* namespace JS */ + +/** + * A wrapper for hash keys that sets ALLOW_MEMMOVE to false. + * + * This is used in the implementation of nsJSThingHashtable and is not intended + * to be used directly. + * + * It is necessary for hash tables containing JS::Heap values as these must + * be copied rather than memmoved. + */ +template +class nsHashKeyDisallowMemmove : public T +{ +public: + explicit nsHashKeyDisallowMemmove(const typename T::KeyTypePointer aKey) : T(aKey) {} + enum { ALLOW_MEMMOVE = false }; +}; + + +/** + * Templated hashtable class for use on the heap where the values are JS GC things. + * + * Storing JS GC thing pointers on the heap requires wrapping them in a + * JS::Heap, and this class takes care of that while presenting an interface + * in terms of the wrapped type T. + * + * For example, to store a hashtable mapping strings to JSObject pointers, you + * can declare a data member like this: + * + * nsJSThingHashtable mStringToObjectMap; + * + * See nsBaseHashtable for complete declaration + * @param KeyClass a wrapper-class for the hashtable key, see nsHashKeys.h + * for a complete specification. + * @param DataType the datatype being wrapped, must be a JS GC thing. + * @see nsInterfaceHashtable, nsClassHashtable + */ +template +class nsJSThingHashtable + : public nsBaseHashtable, + JS::Heap, DataType> +{ +}; + +#endif // nsJSThingHashtable_h__ diff --git a/xpcom/glue/nsMemory.cpp b/xpcom/glue/nsMemory.cpp new file mode 100644 index 000000000..3bf7c1f0f --- /dev/null +++ b/xpcom/glue/nsMemory.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 "nsXPCOM.h" +#include "nsMemory.h" +#include "nsIMemory.h" +#include "nsXPCOMPrivate.h" +#include "nsDebug.h" +#include "nsISupportsUtils.h" +#include "nsCOMPtr.h" + +//////////////////////////////////////////////////////////////////////////////// +// nsMemory static helper routines + +nsresult +nsMemory::HeapMinimize(bool aImmediate) +{ + nsCOMPtr mem; + nsresult rv = NS_GetMemoryManager(getter_AddRefs(mem)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return mem->HeapMinimize(aImmediate); +} + +void* +nsMemory::Clone(const void* aPtr, size_t aSize) +{ + void* newPtr = NS_Alloc(aSize); + if (newPtr) { + memcpy(newPtr, aPtr, aSize); + } + return newPtr; +} + +nsIMemory* +nsMemory::GetGlobalMemoryService() +{ + nsIMemory* mem; + nsresult rv = NS_GetMemoryManager(&mem); + if (NS_FAILED(rv)) { + return nullptr; + } + + return mem; +} + +//---------------------------------------------------------------------- + diff --git a/xpcom/glue/nsMemory.h b/xpcom/glue/nsMemory.h new file mode 100644 index 000000000..6f19b8117 --- /dev/null +++ b/xpcom/glue/nsMemory.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 nsMemory_h__ +#define nsMemory_h__ + +#include "nsXPCOM.h" + +class nsIMemory; + +#define NS_MEMORY_CONTRACTID "@mozilla.org/xpcom/memory-service;1" +#define NS_MEMORY_CID \ +{ /* 30a04e40-38e7-11d4-8cf5-0060b0fc14a3 */ \ + 0x30a04e40, \ + 0x38e7, \ + 0x11d4, \ + {0x8c, 0xf5, 0x00, 0x60, 0xb0, 0xfc, 0x14, 0xa3} \ +} + + +/** + * Static helper routines to manage memory. These routines allow easy access + * to xpcom's built-in (global) nsIMemory implementation, without needing + * to go through the service manager to get it. However this requires clients + * to link with the xpcom DLL. + * + * This class is not threadsafe and is intented for use only on the main + * thread. + */ +class nsMemory +{ +public: + static nsresult HeapMinimize(bool aImmediate); + static void* Clone(const void* aPtr, size_t aSize); + static nsIMemory* GetGlobalMemoryService(); // AddRefs +}; + +/** + * Macro to free all elements of an XPCOM array of a given size using + * freeFunc, then frees the array itself using free(). + * + * Note that this macro (and its wrappers) can be used to deallocate a + * partially- or completely-built array while unwinding an error + * condition inside the XPCOM routine that was going to return the + * array. For this to work on a partially-built array, your code + * needs to be building the array from index 0 upwards, and simply + * pass the number of elements that have already been built (and thus + * need to be freed) as |size|. + * + * Thanks to for suggesting this form, which + * allows the macro to be used with NS_RELEASE / NS_RELEASE_IF in + * addition to free. + * + * @param size Number of elements in the array. If not a constant, this + * should be a int32_t. Note that this means this macro + * will not work if size >= 2^31. + * @param array The array to be freed. + * @param freeFunc The function or macro to be used to free it. + * For arrays of nsISupports (or any class derived + * from it), NS_IF_RELEASE (or NS_RELEASE) should be + * passed as freeFunc. For most (all?) other pointer + * types (including XPCOM strings and wstrings), + * free should be used. + */ +#define NS_FREE_XPCOM_POINTER_ARRAY(size, array, freeFunc) \ + PR_BEGIN_MACRO \ + int32_t iter_ = int32_t(size); \ + while (--iter_ >= 0) \ + freeFunc((array)[iter_]); \ + NS_Free((array)); \ + PR_END_MACRO + +// convenience macros for commonly used calls. mmmmm. syntactic sugar. + +/** + * Macro to free arrays of non-refcounted objects allocated by the + * shared allocator (nsMemory) such as strings and wstrings. A + * convenience wrapper around NS_FREE_XPCOM_POINTER_ARRAY. + * + * @param size Number of elements in the array. If not a constant, this + * should be a int32_t. Note that this means this macro + * will not work if size >= 2^31. + * @param array The array to be freed. + */ +#define NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(size, array) \ + NS_FREE_XPCOM_POINTER_ARRAY((size), (array), NS_Free) + +/** + * Macro to free an array of pointers to nsISupports (or classes + * derived from it). A convenience wrapper around + * NS_FREE_XPCOM_POINTER_ARRAY. + * + * Note that if you know that none of your nsISupports pointers are + * going to be 0, you can gain a bit of speed by calling + * NS_FREE_XPCOM_POINTER_ARRAY directly and using NS_RELEASE as your + * free function. + * + * @param size Number of elements in the array. If not a constant, this + * should be a int32_t. Note that this means this macro + * will not work if size >= 2^31. + * @param array The array to be freed. + */ +#define NS_FREE_XPCOM_ISUPPORTS_POINTER_ARRAY(size, array) \ + NS_FREE_XPCOM_POINTER_ARRAY((size), (array), NS_IF_RELEASE) + +/** + * A macro, NS_ALIGNMENT_OF(t_) that determines the alignment + * requirements of a type. + */ +namespace mozilla { +template +struct AlignmentTestStruct +{ + char c; + T t; +}; +} // namespace mozilla + +#define NS_ALIGNMENT_OF(t_) \ + (sizeof(mozilla::AlignmentTestStruct) - sizeof(t_)) + +/** + * An enumeration type used to represent a method of assignment. + */ +enum nsAssignmentType +{ + NS_ASSIGNMENT_COPY, // copy by value + NS_ASSIGNMENT_DEPEND, // copy by reference + NS_ASSIGNMENT_ADOPT // copy by reference (take ownership of resource) +}; + +#endif // nsMemory_h__ + diff --git a/xpcom/glue/nsPointerHashKeys.h b/xpcom/glue/nsPointerHashKeys.h new file mode 100644 index 000000000..a89843101 --- /dev/null +++ b/xpcom/glue/nsPointerHashKeys.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/. */ + +/* Definitions for nsPtrHashKey and nsVoidPtrHashKey. */ + +#ifndef nsPointerHashKeys_h +#define nsPointerHashKeys_h + +#include "nscore.h" + +#include "mozilla/Attributes.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(const nsPtrHashKey& aToCopy) : mKey(aToCopy.mKey) {} + ~nsPtrHashKey() {} + + 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 }; + +protected: + T* MOZ_NON_OWNING_REF mKey; +}; + +typedef nsPtrHashKey nsVoidPtrHashKey; + +#endif // nsPointerHashKeys_h diff --git a/xpcom/glue/nsProxyRelease.cpp b/xpcom/glue/nsProxyRelease.cpp new file mode 100644 index 000000000..1a8150cc6 --- /dev/null +++ b/xpcom/glue/nsProxyRelease.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 "nsProxyRelease.h" +#include "nsThreadUtils.h" + +namespace detail { + +/* static */ void +ProxyReleaseChooser::ProxyReleaseISupports(nsIEventTarget* aTarget, + nsISupports* aDoomed, + bool aAlwaysProxy) +{ + ::detail::ProxyRelease(aTarget, dont_AddRef(aDoomed), + aAlwaysProxy); +} + +} // namespace detail diff --git a/xpcom/glue/nsProxyRelease.h b/xpcom/glue/nsProxyRelease.h new file mode 100644 index 000000000..d99f970b9 --- /dev/null +++ b/xpcom/glue/nsProxyRelease.h @@ -0,0 +1,353 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 "nsIEventTarget.h" +#include "nsIThread.h" +#include "nsCOMPtr.h" +#include "nsAutoPtr.h" +#include "MainThreadUtils.h" +#include "nsThreadUtils.h" +#include "mozilla/Likely.h" +#include "mozilla/Move.h" +#include "mozilla/TypeTraits.h" +#include "mozilla/Unused.h" + +#ifdef XPCOM_GLUE_AVOID_NSPR +#error NS_ProxyRelease implementation depends on NSPR. +#endif + +namespace detail { + +template +class ProxyReleaseEvent : public mozilla::Runnable +{ +public: + explicit ProxyReleaseEvent(already_AddRefed aDoomed) + : mDoomed(aDoomed.take()) {} + + NS_IMETHOD Run() override + { + NS_IF_RELEASE(mDoomed); + return NS_OK; + } + +private: + T* MOZ_OWNING_REF mDoomed; +}; + +template +void +ProxyRelease(nsIEventTarget* aTarget, already_AddRefed aDoomed, bool aAlwaysProxy) +{ + // Auto-managing release of the pointer. + RefPtr doomed = aDoomed; + nsresult rv; + + if (!doomed || !aTarget) { + return; + } + + if (!aAlwaysProxy) { + bool onCurrentThread = false; + rv = aTarget->IsOnCurrentThread(&onCurrentThread); + if (NS_SUCCEEDED(rv) && onCurrentThread) { + return; + } + } + + nsCOMPtr ev = new ProxyReleaseEvent(doomed.forget()); + + rv = aTarget->Dispatch(ev, NS_DISPATCH_NORMAL); + if (NS_FAILED(rv)) { + NS_WARNING("failed to post proxy release event, leaking!"); + // It is better to leak the aDoomed object than risk crashing as + // a result of deleting it on the wrong thread. + } +} + +template +struct ProxyReleaseChooser +{ + template + static void ProxyRelease(nsIEventTarget* aTarget, + already_AddRefed aDoomed, + bool aAlwaysProxy) + { + ::detail::ProxyRelease(aTarget, mozilla::Move(aDoomed), aAlwaysProxy); + } +}; + +template<> +struct ProxyReleaseChooser +{ + // We need an intermediate step for handling classes with ambiguous + // inheritance to nsISupports. + template + static void ProxyRelease(nsIEventTarget* aTarget, + already_AddRefed aDoomed, + bool aAlwaysProxy) + { + ProxyReleaseISupports(aTarget, ToSupports(aDoomed.take()), aAlwaysProxy); + } + + static void ProxyReleaseISupports(nsIEventTarget* aTarget, + nsISupports* aDoomed, + bool aAlwaysProxy); +}; + +} // namespace detail + +/** + * Ensures that the delete of a smart pointer occurs on the target thread. + * + * @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. + */ +template +inline NS_HIDDEN_(void) +NS_ProxyRelease(nsIEventTarget* aTarget, already_AddRefed aDoomed, + bool aAlwaysProxy = false) +{ + ::detail::ProxyReleaseChooser::value> + ::ProxyRelease(aTarget, mozilla::Move(aDoomed), aAlwaysProxy); +} + +/** + * Ensures that the delete of a smart pointer occurs on the main thread. + * + * @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(already_AddRefed aDoomed, + bool aAlwaysProxy = false) +{ + // 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 mainThread; + if (!NS_IsMainThread() || aAlwaysProxy) { + nsresult rv = NS_GetMainThread(getter_AddRefs(mainThread)); + + if (NS_FAILED(rv)) { + MOZ_ASSERT_UNREACHABLE("Could not get main thread; leaking an object!"); + mozilla::Unused << aDoomed.take(); + return; + } + } + + NS_ProxyRelease(mainThread, mozilla::Move(aDoomed), 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 recently began to + * apply 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 (now) 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 nsMainThreadPtrHolder final +{ +public: + // We can only acquire a pointer on the main thread. We 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. + explicit nsMainThreadPtrHolder(T* aPtr, bool aStrict = true) + : mRawPtr(nullptr) + , mStrict(aStrict) + { + // 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 = aPtr); + } + explicit nsMainThreadPtrHolder(already_AddRefed aPtr, bool aString = true) + : mRawPtr(aPtr.take()) + , mStrict(aString) + { + // Since we don't need to AddRef the pointer, this constructor is safe to + // call on any thread. + } + +private: + // We can be released on any thread. + ~nsMainThreadPtrHolder() + { + if (NS_IsMainThread()) { + NS_IF_RELEASE(mRawPtr); + } else if (mRawPtr) { + NS_ReleaseOnMainThread(dont_AddRef(mRawPtr)); + } + } + +public: + T* get() + { + // 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; + + // Whether to strictly enforce thread invariants in this class. + bool mStrict; + + // Copy constructor and operator= not implemented. Once constructed, the + // holder is immutable. + T& operator=(nsMainThreadPtrHolder& aOther); + nsMainThreadPtrHolder(const nsMainThreadPtrHolder& aOther); +}; + +template +class nsMainThreadPtrHandle +{ + RefPtr> mPtr; + +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) + : mPtr(aOther.mPtr) + { + } + nsMainThreadPtrHandle& operator=(const nsMainThreadPtrHandle& aOther) + { + mPtr = aOther.mPtr; + return *this; + } + 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. Off-main-thread consumers must treat + // these handles as opaque. + T* get() + { + if (mPtr) { + return mPtr.get()->get(); + } + return nullptr; + } + const T* get() const + { + if (mPtr) { + return mPtr.get()->get(); + } + return nullptr; + } + + operator T*() { return get(); } + T* operator->() 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; + } +}; + +namespace mozilla { + +template +using PtrHolder = nsMainThreadPtrHolder; + +template +using PtrHandle = nsMainThreadPtrHandle; + +} // namespace mozilla + +#endif diff --git a/xpcom/glue/nsQuickSort.cpp b/xpcom/glue/nsQuickSort.cpp new file mode 100644 index 000000000..f409b875e --- /dev/null +++ b/xpcom/glue/nsQuickSort.cpp @@ -0,0 +1,187 @@ +/* -*- 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/glue/nsQuickSort.h b/xpcom/glue/nsQuickSort.h new file mode 100644 index 000000000..e8d8ed870 --- /dev/null +++ b/xpcom/glue/nsQuickSort.h @@ -0,0 +1,41 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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/glue/nsRefPtrHashtable.h b/xpcom/glue/nsRefPtrHashtable.h new file mode 100644 index 000000000..0ff1e0181 --- /dev/null +++ b/xpcom/glue/nsRefPtrHashtable.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 nsRefPtrHashtable_h__ +#define nsRefPtrHashtable_h__ + +#include "nsBaseHashtable.h" +#include "nsHashKeys.h" +#include "nsAutoPtr.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 nsDataHashtable, nsClassHashtable + */ +template +class nsRefPtrHashtable + : public nsBaseHashtable, PtrType*> +{ +public: + typedef typename KeyClass::KeyType KeyType; + typedef PtrType* UserDataType; + typedef nsBaseHashtable, PtrType*> base_type; + + nsRefPtrHashtable() {} + explicit nsRefPtrHashtable(uint32_t aInitLength) + : nsBaseHashtable, PtrType*>(aInitLength) + { + } + + /** + * @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, UserDataType* aData) 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! + */ + PtrType* GetWeak(KeyType aKey, bool* aFound = nullptr) const; + + // Overload Put, rather than overriding it. + using base_type::Put; + + void Put(KeyType aKey, already_AddRefed aData); + + MOZ_MUST_USE bool Put(KeyType aKey, already_AddRefed aData, + const mozilla::fallible_t&); + + // Overload Remove, rather than overriding it. + using base_type::Remove; + + /** + * Remove the data for the associated key, swapping the current value into + * pData, thereby avoiding calls to AddRef and Release. + * @param aKey the key to remove from the hashtable + * @param aData This is an XPCOM getter, so aData is already_addrefed. + * If the key doesn't exist, aData will be set to nullptr. Must be non-null. + */ + bool Remove(KeyType aKey, UserDataType* aData); +}; + +template +inline void +ImplCycleCollectionUnlink(nsRefPtrHashtable& aField) +{ + aField.Clear(); +} + +template +inline void +ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& aCallback, + nsRefPtrHashtable& aField, + const char* aName, + uint32_t aFlags = 0) +{ + for (auto iter = aField.ConstIter(); !iter.Done(); iter.Next()) { + CycleCollectionNoteChild(aCallback, iter.UserData(), aName, aFlags); + } +} + +// +// nsRefPtrHashtable definitions +// + +template +bool +nsRefPtrHashtable::Get(KeyType aKey, + UserDataType* aRefPtr) const +{ + typename base_type::EntryType* ent = this->GetEntry(aKey); + + if (ent) { + if (aRefPtr) { + *aRefPtr = ent->mData; + + 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 +PtrType* +nsRefPtrHashtable::GetWeak(KeyType aKey, bool* aFound) const +{ + typename base_type::EntryType* ent = this->GetEntry(aKey); + + if (ent) { + if (aFound) { + *aFound = true; + } + + return ent->mData; + } + + // Key does not exist, return nullptr and set aFound to false + if (aFound) { + *aFound = false; + } + + return nullptr; +} + +template +void +nsRefPtrHashtable::Put(KeyType aKey, + already_AddRefed aData) +{ + if (!Put(aKey, mozilla::Move(aData), mozilla::fallible)) { + NS_ABORT_OOM(this->mTable.EntrySize() * this->mTable.EntryCount()); + } +} + +template +bool +nsRefPtrHashtable::Put(KeyType aKey, + already_AddRefed aData, + const mozilla::fallible_t&) +{ + typename base_type::EntryType* ent = this->PutEntry(aKey); + + if (!ent) { + return false; + } + + ent->mData = aData; + + return true; +} + +template +bool +nsRefPtrHashtable::Remove(KeyType aKey, + UserDataType* aRefPtr) +{ + MOZ_ASSERT(aRefPtr); + typename base_type::EntryType* ent = this->GetEntry(aKey); + + if (ent) { + ent->mData.forget(aRefPtr); + this->Remove(aKey); + return true; + } + + // If the key doesn't exist, set *aRefPtr to null + // so that it is a valid XPCOM getter. + *aRefPtr = nullptr; + return false; +} + +#endif // nsRefPtrHashtable_h__ diff --git a/xpcom/glue/nsServiceManagerUtils.h b/xpcom/glue/nsServiceManagerUtils.h new file mode 100644 index 000000000..d1ea408a2 --- /dev/null +++ b/xpcom/glue/nsServiceManagerUtils.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 nsServiceManagerUtils_h__ +#define nsServiceManagerUtils_h__ + +#include "nsIServiceManager.h" +#include "nsCOMPtr.h" + +inline const nsGetServiceByCID +do_GetService(const nsCID& aCID) +{ + return nsGetServiceByCID(aCID); +} + +inline const nsGetServiceByCIDWithError +do_GetService(const nsCID& aCID, nsresult* aError) +{ + return nsGetServiceByCIDWithError(aCID, aError); +} + +inline const nsGetServiceByContractID +do_GetService(const char* aContractID) +{ + return nsGetServiceByContractID(aContractID); +} + +inline const nsGetServiceByContractIDWithError +do_GetService(const char* aContractID, nsresult* aError) +{ + return nsGetServiceByContractIDWithError(aContractID, aError); +} + +class MOZ_STACK_CLASS nsGetServiceFromCategory final : public nsCOMPtr_helper +{ +public: + nsGetServiceFromCategory(const char* aCategory, const char* aEntry, + nsresult* aErrorPtr) + : mCategory(aCategory) + , mEntry(aEntry) + , mErrorPtr(aErrorPtr) + { + } + + virtual nsresult NS_FASTCALL operator()(const nsIID&, void**) const + override; +protected: + const char* mCategory; + const char* mEntry; + nsresult* mErrorPtr; +}; + +inline const nsGetServiceFromCategory +do_GetServiceFromCategory(const char* aCategory, const char* aEntry, + nsresult* aError = 0) +{ + return nsGetServiceFromCategory(aCategory, aEntry, 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) +{ + NS_PRECONDITION(aDestination, "null parameter"); + + return CallGetService(aClass, + NS_GET_TEMPLATE_IID(DestinationType), + reinterpret_cast(aDestination)); +} + +template +inline nsresult +CallGetService(const char* aContractID, + DestinationType** aDestination) +{ + NS_PRECONDITION(aContractID, "null parameter"); + NS_PRECONDITION(aDestination, "null parameter"); + + return CallGetService(aContractID, + NS_GET_TEMPLATE_IID(DestinationType), + reinterpret_cast(aDestination)); +} + +#endif diff --git a/xpcom/glue/nsStringAPI.cpp b/xpcom/glue/nsStringAPI.cpp new file mode 100644 index 000000000..e5114a149 --- /dev/null +++ b/xpcom/glue/nsStringAPI.cpp @@ -0,0 +1,1304 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nscore.h" +#include "nsCRTGlue.h" +#include "prprf.h" +#include "nsStringAPI.h" +#include "nsXPCOMStrings.h" +#include "nsDebug.h" + +#include "mozilla/Sprintf.h" + +#include + +// nsAString + +uint32_t +nsAString::BeginReading(const char_type** aBegin, const char_type** aEnd) const +{ + uint32_t len = NS_StringGetData(*this, aBegin); + if (aEnd) { + *aEnd = *aBegin + len; + } + + return len; +} + +const nsAString::char_type* +nsAString::BeginReading() const +{ + const char_type* data; + NS_StringGetData(*this, &data); + return data; +} + +const nsAString::char_type* +nsAString::EndReading() const +{ + const char_type* data; + uint32_t len = NS_StringGetData(*this, &data); + return data + len; +} + +uint32_t +nsAString::BeginWriting(char_type** aBegin, char_type** aEnd, uint32_t aNewSize) +{ + uint32_t len = NS_StringGetMutableData(*this, aNewSize, aBegin); + if (aEnd) { + *aEnd = *aBegin + len; + } + + return len; +} + +nsAString::char_type* +nsAString::BeginWriting(uint32_t aLen) +{ + char_type* data; + NS_StringGetMutableData(*this, aLen, &data); + return data; +} + +nsAString::char_type* +nsAString::EndWriting() +{ + char_type* data; + uint32_t len = NS_StringGetMutableData(*this, UINT32_MAX, &data); + return data + len; +} + +bool +nsAString::SetLength(uint32_t aLen) +{ + char_type* data; + NS_StringGetMutableData(*this, aLen, &data); + return data != nullptr; +} + +void +nsAString::AssignLiteral(const char* aStr) +{ + uint32_t len = strlen(aStr); + char16_t* buf = BeginWriting(len); + if (!buf) { + return; + } + + for (; *aStr; ++aStr, ++buf) { + *buf = *aStr; + } +} + +void +nsAString::AppendLiteral(const char* aASCIIStr) +{ + uint32_t appendLen = strlen(aASCIIStr); + + uint32_t thisLen = Length(); + char16_t* begin; + char16_t* end; + BeginWriting(&begin, &end, appendLen + thisLen); + if (!begin) { + return; + } + + for (begin += thisLen; begin < end; ++begin, ++aASCIIStr) { + *begin = *aASCIIStr; + } +} + +void +nsAString::StripChars(const char* aSet) +{ + nsString copy(*this); + + const char_type* source; + const char_type* sourceEnd; + copy.BeginReading(&source, &sourceEnd); + + char_type* dest; + BeginWriting(&dest); + if (!dest) { + return; + } + + char_type* curDest = dest; + + for (; source < sourceEnd; ++source) { + const char* test; + for (test = aSet; *test; ++test) { + if (*source == char_type(*test)) { + break; + } + } + + if (!*test) { + // not stripped, copy this char + *curDest = *source; + ++curDest; + } + } + + SetLength(curDest - dest); +} + +void +nsAString::Trim(const char* aSet, bool aLeading, bool aTrailing) +{ + NS_ASSERTION(aLeading || aTrailing, "Ineffective Trim"); + + const char16_t* start; + const char16_t* end; + uint32_t cutLen; + + if (aLeading) { + BeginReading(&start, &end); + for (cutLen = 0; start < end; ++start, ++cutLen) { + const char* test; + for (test = aSet; *test; ++test) { + if (*test == *start) { + break; + } + } + if (!*test) { + break; + } + } + if (cutLen) { + NS_StringCutData(*this, 0, cutLen); + } + } + if (aTrailing) { + uint32_t len = BeginReading(&start, &end); + --end; + for (cutLen = 0; end >= start; --end, ++cutLen) { + const char* test; + for (test = aSet; *test; ++test) { + if (*test == *end) { + break; + } + } + if (!*test) { + break; + } + } + if (cutLen) { + NS_StringCutData(*this, len - cutLen, cutLen); + } + } +} + +int32_t +nsAString::DefaultComparator(const char_type* aStrA, const char_type* aStrB, + uint32_t aLen) +{ + for (const char_type* end = aStrA + aLen; aStrA < end; ++aStrA, ++aStrB) { + if (*aStrA == *aStrB) { + continue; + } + + return *aStrA < *aStrB ? -1 : 1; + } + + return 0; +} + +int32_t +nsAString::Compare(const char_type* aOther, ComparatorFunc aComparator) const +{ + const char_type* cself; + uint32_t selflen = NS_StringGetData(*this, &cself); + uint32_t otherlen = NS_strlen(aOther); + uint32_t comparelen = selflen <= otherlen ? selflen : otherlen; + + int32_t result = aComparator(cself, aOther, comparelen); + if (result == 0) { + if (selflen < otherlen) { + return -1; + } else if (selflen > otherlen) { + return 1; + } + } + return result; +} + +int32_t +nsAString::Compare(const self_type& aOther, ComparatorFunc aComparator) const +{ + const char_type* cself; + const char_type* cother; + uint32_t selflen = NS_StringGetData(*this, &cself); + uint32_t otherlen = NS_StringGetData(aOther, &cother); + uint32_t comparelen = selflen <= otherlen ? selflen : otherlen; + + int32_t result = aComparator(cself, cother, comparelen); + if (result == 0) { + if (selflen < otherlen) { + return -1; + } else if (selflen > otherlen) { + return 1; + } + } + return result; +} + +bool +nsAString::Equals(const char_type* aOther, ComparatorFunc aComparator) const +{ + const char_type* cself; + uint32_t selflen = NS_StringGetData(*this, &cself); + uint32_t otherlen = NS_strlen(aOther); + + if (selflen != otherlen) { + return false; + } + + return aComparator(cself, aOther, selflen) == 0; +} + +bool +nsAString::Equals(const self_type& aOther, ComparatorFunc aComparator) const +{ + const char_type* cself; + const char_type* cother; + uint32_t selflen = NS_StringGetData(*this, &cself); + uint32_t otherlen = NS_StringGetData(aOther, &cother); + + if (selflen != otherlen) { + return false; + } + + return aComparator(cself, cother, selflen) == 0; +} + +bool +nsAString::EqualsLiteral(const char* aASCIIString) const +{ + const char16_t* begin; + const char16_t* end; + BeginReading(&begin, &end); + + for (; begin < end; ++begin, ++aASCIIString) { + if (!*aASCIIString || !NS_IsAscii(*begin) || + (char)*begin != *aASCIIString) { + return false; + } + } + + return *aASCIIString == '\0'; +} + +bool +nsAString::LowerCaseEqualsLiteral(const char* aASCIIString) const +{ + const char16_t* begin; + const char16_t* end; + BeginReading(&begin, &end); + + for (; begin < end; ++begin, ++aASCIIString) { + if (!*aASCIIString || !NS_IsAscii(*begin) || + NS_ToLower((char)*begin) != *aASCIIString) { + return false; + } + } + + return *aASCIIString == '\0'; +} + +int32_t +nsAString::Find(const self_type& aStr, uint32_t aOffset, + ComparatorFunc aComparator) const +{ + const char_type* begin; + const char_type* end; + uint32_t selflen = BeginReading(&begin, &end); + + if (aOffset > selflen) { + return -1; + } + + const char_type* other; + uint32_t otherlen = aStr.BeginReading(&other); + + if (otherlen > selflen - aOffset) { + return -1; + } + + // We want to stop searching otherlen characters before the end of the string + end -= otherlen; + + for (const char_type* cur = begin + aOffset; cur <= end; ++cur) { + if (!aComparator(cur, other, otherlen)) { + return cur - begin; + } + } + return -1; +} + +static bool +ns_strnmatch(const char16_t* aStr, const char* aSubstring, uint32_t aLen) +{ + for (; aLen; ++aStr, ++aSubstring, --aLen) { + if (!NS_IsAscii(*aStr)) { + return false; + } + + if ((char)*aStr != *aSubstring) { + return false; + } + } + + return true; +} + +static bool +ns_strnimatch(const char16_t* aStr, const char* aSubstring, uint32_t aLen) +{ + for (; aLen; ++aStr, ++aSubstring, --aLen) { + if (!NS_IsAscii(*aStr)) { + return false; + } + + if (NS_ToLower((char)*aStr) != NS_ToLower(*aSubstring)) { + return false; + } + } + + return true; +} + +int32_t +nsAString::Find(const char* aStr, uint32_t aOffset, bool aIgnoreCase) const +{ + bool (*match)(const char16_t*, const char*, uint32_t) = + aIgnoreCase ? ns_strnimatch : ns_strnmatch; + + const char_type* begin; + const char_type* end; + uint32_t selflen = BeginReading(&begin, &end); + + if (aOffset > selflen) { + return -1; + } + + uint32_t otherlen = strlen(aStr); + + if (otherlen > selflen - aOffset) { + return -1; + } + + // We want to stop searching otherlen characters before the end of the string + end -= otherlen; + + for (const char_type* cur = begin + aOffset; cur <= end; ++cur) { + if (match(cur, aStr, otherlen)) { + return cur - begin; + } + } + return -1; +} + +int32_t +nsAString::RFind(const self_type& aStr, int32_t aOffset, + ComparatorFunc aComparator) const +{ + const char_type* begin; + const char_type* end; + uint32_t selflen = BeginReading(&begin, &end); + + const char_type* other; + uint32_t otherlen = aStr.BeginReading(&other); + + if (selflen < otherlen) { + return -1; + } + + if (aOffset < 0 || uint32_t(aOffset) > (selflen - otherlen)) { + end -= otherlen; + } else { + end = begin + aOffset; + } + + for (const char_type* cur = end; cur >= begin; --cur) { + if (!aComparator(cur, other, otherlen)) { + return cur - begin; + } + } + return -1; +} + +int32_t +nsAString::RFind(const char* aStr, int32_t aOffset, bool aIgnoreCase) const +{ + bool (*match)(const char16_t*, const char*, uint32_t) = + aIgnoreCase ? ns_strnimatch : ns_strnmatch; + + const char_type* begin; + const char_type* end; + uint32_t selflen = BeginReading(&begin, &end); + uint32_t otherlen = strlen(aStr); + + if (selflen < otherlen) { + return -1; + } + + if (aOffset < 0 || uint32_t(aOffset) > (selflen - otherlen)) { + end -= otherlen; + } else { + end = begin + aOffset; + } + + for (const char_type* cur = end; cur >= begin; --cur) { + if (match(cur, aStr, otherlen)) { + return cur - begin; + } + } + return -1; +} + +int32_t +nsAString::FindChar(char_type aChar, uint32_t aOffset) const +{ + const char_type* start; + const char_type* end; + uint32_t len = BeginReading(&start, &end); + if (aOffset > len) { + return -1; + } + + const char_type* cur; + + for (cur = start + aOffset; cur < end; ++cur) { + if (*cur == aChar) { + return cur - start; + } + } + + return -1; +} + +int32_t +nsAString::RFindChar(char_type aChar) const +{ + const char16_t* start; + const char16_t* end; + BeginReading(&start, &end); + + do { + --end; + + if (*end == aChar) { + return end - start; + } + + } while (end >= start); + + return -1; +} + +void +nsAString::AppendInt(int aInt, int32_t aRadix) +{ + const char* fmt; + switch (aRadix) { + case 8: + fmt = "%o"; + break; + + case 10: + fmt = "%d"; + break; + + case 16: + fmt = "%x"; + break; + + default: + NS_ERROR("Unrecognized radix"); + fmt = ""; + } + + char buf[20]; + int len = SprintfLiteral(buf, fmt, aInt); + Append(NS_ConvertASCIItoUTF16(buf, len)); +} + +// Strings + +#ifndef XPCOM_GLUE_AVOID_NSPR +int32_t +nsAString::ToInteger(nsresult* aErrorCode, uint32_t aRadix) const +{ + NS_ConvertUTF16toUTF8 narrow(*this); + + const char* fmt; + switch (aRadix) { + case 10: + fmt = "%i"; + break; + + case 16: + fmt = "%x"; + break; + + default: + NS_ERROR("Unrecognized radix!"); + *aErrorCode = NS_ERROR_INVALID_ARG; + return 0; + } + + int32_t result = 0; + if (PR_sscanf(narrow.get(), fmt, &result) == 1) { + *aErrorCode = NS_OK; + } else { + *aErrorCode = NS_ERROR_FAILURE; + } + + return result; +} + +int64_t +nsAString::ToInteger64(nsresult* aErrorCode, uint32_t aRadix) const +{ + NS_ConvertUTF16toUTF8 narrow(*this); + + const char* fmt; + switch (aRadix) { + case 10: + fmt = "%lli"; + break; + + case 16: + fmt = "%llx"; + break; + + default: + NS_ERROR("Unrecognized radix!"); + *aErrorCode = NS_ERROR_INVALID_ARG; + return 0; + } + + int64_t result = 0; + if (PR_sscanf(narrow.get(), fmt, &result) == 1) { + *aErrorCode = NS_OK; + } else { + *aErrorCode = NS_ERROR_FAILURE; + } + + return result; +} +#endif // XPCOM_GLUE_AVOID_NSPR + +// nsACString + +uint32_t +nsACString::BeginReading(const char_type** aBegin, const char_type** aEnd) const +{ + uint32_t len = NS_CStringGetData(*this, aBegin); + if (aEnd) { + *aEnd = *aBegin + len; + } + + return len; +} + +const nsACString::char_type* +nsACString::BeginReading() const +{ + const char_type* data; + NS_CStringGetData(*this, &data); + return data; +} + +const nsACString::char_type* +nsACString::EndReading() const +{ + const char_type* data; + uint32_t len = NS_CStringGetData(*this, &data); + return data + len; +} + +uint32_t +nsACString::BeginWriting(char_type** aBegin, char_type** aEnd, + uint32_t aNewSize) +{ + uint32_t len = NS_CStringGetMutableData(*this, aNewSize, aBegin); + if (aEnd) { + *aEnd = *aBegin + len; + } + + return len; +} + +nsACString::char_type* +nsACString::BeginWriting(uint32_t aLen) +{ + char_type* data; + NS_CStringGetMutableData(*this, aLen, &data); + return data; +} + +nsACString::char_type* +nsACString::EndWriting() +{ + char_type* data; + uint32_t len = NS_CStringGetMutableData(*this, UINT32_MAX, &data); + return data + len; +} + +bool +nsACString::SetLength(uint32_t aLen) +{ + char_type* data; + NS_CStringGetMutableData(*this, aLen, &data); + return data != nullptr; +} + +void +nsACString::StripChars(const char* aSet) +{ + nsCString copy(*this); + + const char_type* source; + const char_type* sourceEnd; + copy.BeginReading(&source, &sourceEnd); + + char_type* dest; + BeginWriting(&dest); + if (!dest) { + return; + } + + char_type* curDest = dest; + + for (; source < sourceEnd; ++source) { + const char* test; + for (test = aSet; *test; ++test) { + if (*source == char_type(*test)) { + break; + } + } + + if (!*test) { + // not stripped, copy this char + *curDest = *source; + ++curDest; + } + } + + SetLength(curDest - dest); +} + +void +nsACString::Trim(const char* aSet, bool aLeading, bool aTrailing) +{ + NS_ASSERTION(aLeading || aTrailing, "Ineffective Trim"); + + const char* start; + const char* end; + uint32_t cutLen; + + if (aLeading) { + BeginReading(&start, &end); + for (cutLen = 0; start < end; ++start, ++cutLen) { + const char* test; + for (test = aSet; *test; ++test) { + if (*test == *start) { + break; + } + } + if (!*test) { + break; + } + } + if (cutLen) { + NS_CStringCutData(*this, 0, cutLen); + } + } + if (aTrailing) { + uint32_t len = BeginReading(&start, &end); + --end; + for (cutLen = 0; end >= start; --end, ++cutLen) { + const char* test; + for (test = aSet; *test; ++test) { + if (*test == *end) { + break; + } + } + if (!*test) { + break; + } + } + if (cutLen) { + NS_CStringCutData(*this, len - cutLen, cutLen); + } + } +} + +int32_t +nsACString::DefaultComparator(const char_type* aStrA, const char_type* aStrB, + uint32_t aLen) +{ + return memcmp(aStrA, aStrB, aLen); +} + +int32_t +nsACString::Compare(const char_type* aOther, ComparatorFunc aComparator) const +{ + const char_type* cself; + uint32_t selflen = NS_CStringGetData(*this, &cself); + uint32_t otherlen = strlen(aOther); + uint32_t comparelen = selflen <= otherlen ? selflen : otherlen; + + int32_t result = aComparator(cself, aOther, comparelen); + if (result == 0) { + if (selflen < otherlen) { + return -1; + } else if (selflen > otherlen) { + return 1; + } + } + return result; +} + +int32_t +nsACString::Compare(const self_type& aOther, ComparatorFunc aComparator) const +{ + const char_type* cself; + const char_type* cother; + uint32_t selflen = NS_CStringGetData(*this, &cself); + uint32_t otherlen = NS_CStringGetData(aOther, &cother); + uint32_t comparelen = selflen <= otherlen ? selflen : otherlen; + + int32_t result = aComparator(cself, cother, comparelen); + if (result == 0) { + if (selflen < otherlen) { + return -1; + } else if (selflen > otherlen) { + return 1; + } + } + return result; +} + +bool +nsACString::Equals(const char_type* aOther, ComparatorFunc aComparator) const +{ + const char_type* cself; + uint32_t selflen = NS_CStringGetData(*this, &cself); + uint32_t otherlen = strlen(aOther); + + if (selflen != otherlen) { + return false; + } + + return aComparator(cself, aOther, selflen) == 0; +} + +bool +nsACString::Equals(const self_type& aOther, ComparatorFunc aComparator) const +{ + const char_type* cself; + const char_type* cother; + uint32_t selflen = NS_CStringGetData(*this, &cself); + uint32_t otherlen = NS_CStringGetData(aOther, &cother); + + if (selflen != otherlen) { + return false; + } + + return aComparator(cself, cother, selflen) == 0; +} + +int32_t +nsACString::Find(const self_type& aStr, uint32_t aOffset, + ComparatorFunc aComparator) const +{ + const char_type* begin; + const char_type* end; + uint32_t selflen = BeginReading(&begin, &end); + + if (aOffset > selflen) { + return -1; + } + + const char_type* other; + uint32_t otherlen = aStr.BeginReading(&other); + + if (otherlen > selflen - aOffset) { + return -1; + } + + // We want to stop searching otherlen characters before the end of the string + end -= otherlen; + + for (const char_type* cur = begin + aOffset; cur <= end; ++cur) { + if (!aComparator(cur, other, otherlen)) { + return cur - begin; + } + } + return -1; +} + +int32_t +nsACString::Find(const char_type* aStr, ComparatorFunc aComparator) const +{ + return Find(aStr, strlen(aStr), aComparator); +} + +int32_t +nsACString::Find(const char_type* aStr, uint32_t aLen, + ComparatorFunc aComparator) const +{ + const char_type* begin; + const char_type* end; + uint32_t selflen = BeginReading(&begin, &end); + + if (aLen == 0) { + NS_WARNING("Searching for zero-length string."); + return -1; + } + + if (aLen > selflen) { + return -1; + } + + // We want to stop searching otherlen characters before the end of the string + end -= aLen; + + for (const char_type* cur = begin; cur <= end; ++cur) { + if (!aComparator(cur, aStr, aLen)) { + return cur - begin; + } + } + return -1; +} + +int32_t +nsACString::RFind(const self_type& aStr, int32_t aOffset, + ComparatorFunc aComparator) const +{ + const char_type* begin; + const char_type* end; + uint32_t selflen = BeginReading(&begin, &end); + + const char_type* other; + uint32_t otherlen = aStr.BeginReading(&other); + + if (selflen < otherlen) { + return -1; + } + + if (aOffset < 0 || uint32_t(aOffset) > (selflen - otherlen)) { + end -= otherlen; + } else { + end = begin + aOffset; + } + + for (const char_type* cur = end; cur >= begin; --cur) { + if (!aComparator(cur, other, otherlen)) { + return cur - begin; + } + } + return -1; +} + +int32_t +nsACString::RFind(const char_type* aStr, ComparatorFunc aComparator) const +{ + return RFind(aStr, strlen(aStr), aComparator); +} + +int32_t +nsACString::RFind(const char_type* aStr, int32_t aLen, + ComparatorFunc aComparator) const +{ + const char_type* begin; + const char_type* end; + uint32_t selflen = BeginReading(&begin, &end); + + if (aLen <= 0) { + NS_WARNING("Searching for zero-length string."); + return -1; + } + + if (uint32_t(aLen) > selflen) { + return -1; + } + + // We want to start searching otherlen characters before the end of the string + end -= aLen; + + for (const char_type* cur = end; cur >= begin; --cur) { + if (!aComparator(cur, aStr, aLen)) { + return cur - begin; + } + } + return -1; +} + +int32_t +nsACString::FindChar(char_type aChar, uint32_t aOffset) const +{ + const char_type* start; + const char_type* end; + uint32_t len = BeginReading(&start, &end); + if (aOffset > len) { + return -1; + } + + const char_type* cur; + + for (cur = start + aOffset; cur < end; ++cur) { + if (*cur == aChar) { + return cur - start; + } + } + + return -1; +} + +int32_t +nsACString::RFindChar(char_type aChar) const +{ + const char* start; + const char* end; + BeginReading(&start, &end); + + for (; end >= start; --end) { + if (*end == aChar) { + return end - start; + } + } + + return -1; +} + +void +nsACString::AppendInt(int aInt, int32_t aRadix) +{ + const char* fmt; + switch (aRadix) { + case 8: + fmt = "%o"; + break; + + case 10: + fmt = "%d"; + break; + + case 16: + fmt = "%x"; + break; + + default: + NS_ERROR("Unrecognized radix"); + fmt = ""; + } + + char buf[20]; + int len = SprintfLiteral(buf, fmt, aInt); + Append(buf, len); +} + +#ifndef XPCOM_GLUE_AVOID_NSPR +int32_t +nsACString::ToInteger(nsresult* aErrorCode, uint32_t aRadix) const +{ + const char* fmt; + switch (aRadix) { + case 10: + fmt = "%i"; + break; + + case 16: + fmt = "%x"; + break; + + default: + NS_ERROR("Unrecognized radix!"); + *aErrorCode = NS_ERROR_INVALID_ARG; + return 0; + } + + int32_t result = 0; + if (PR_sscanf(nsCString(*this).get(), fmt, &result) == 1) { + *aErrorCode = NS_OK; + } else { + *aErrorCode = NS_ERROR_FAILURE; + } + + return result; +} + +int64_t +nsACString::ToInteger64(nsresult* aErrorCode, uint32_t aRadix) const +{ + const char* fmt; + switch (aRadix) { + case 10: + fmt = "%lli"; + break; + + case 16: + fmt = "%llx"; + break; + + default: + NS_ERROR("Unrecognized radix!"); + *aErrorCode = NS_ERROR_INVALID_ARG; + return 0; + } + + int64_t result = 0; + if (PR_sscanf(nsCString(*this).get(), fmt, &result) == 1) { + *aErrorCode = NS_OK; + } else { + *aErrorCode = NS_ERROR_FAILURE; + } + + return result; +} +#endif // XPCOM_GLUE_AVOID_NSPR + +// Substrings + +nsDependentSubstring::nsDependentSubstring(const abstract_string_type& aStr, + uint32_t aStartPos) +{ + const char16_t* data; + uint32_t len = NS_StringGetData(aStr, &data); + + if (aStartPos > len) { + aStartPos = len; + } + + NS_StringContainerInit2(*this, data + aStartPos, len - aStartPos, + NS_STRING_CONTAINER_INIT_DEPEND | + NS_STRING_CONTAINER_INIT_SUBSTRING); +} + +nsDependentSubstring::nsDependentSubstring(const abstract_string_type& aStr, + uint32_t aStartPos, + uint32_t aLength) +{ + const char16_t* data; + uint32_t len = NS_StringGetData(aStr, &data); + + if (aStartPos > len) { + aStartPos = len; + } + + if (aStartPos + aLength > len) { + aLength = len - aStartPos; + } + + NS_StringContainerInit2(*this, data + aStartPos, aLength, + NS_STRING_CONTAINER_INIT_DEPEND | + NS_STRING_CONTAINER_INIT_SUBSTRING); +} + +nsDependentCSubstring::nsDependentCSubstring(const abstract_string_type& aStr, + uint32_t aStartPos) +{ + const char* data; + uint32_t len = NS_CStringGetData(aStr, &data); + + if (aStartPos > len) { + aStartPos = len; + } + + NS_CStringContainerInit2(*this, data + aStartPos, len - aStartPos, + NS_CSTRING_CONTAINER_INIT_DEPEND | + NS_CSTRING_CONTAINER_INIT_SUBSTRING); +} + +nsDependentCSubstring::nsDependentCSubstring(const abstract_string_type& aStr, + uint32_t aStartPos, + uint32_t aLength) +{ + const char* data; + uint32_t len = NS_CStringGetData(aStr, &data); + + if (aStartPos > len) { + aStartPos = len; + } + + if (aStartPos + aLength > len) { + aLength = len - aStartPos; + } + + NS_CStringContainerInit2(*this, data + aStartPos, aLength, + NS_CSTRING_CONTAINER_INIT_DEPEND | + NS_CSTRING_CONTAINER_INIT_SUBSTRING); +} + +// Utils + +char* +ToNewUTF8String(const nsAString& aSource) +{ + nsCString temp; + CopyUTF16toUTF8(aSource, temp); + return NS_CStringCloneData(temp); +} + +void +CompressWhitespace(nsAString& aString) +{ + char16_t* start; + uint32_t len = NS_StringGetMutableData(aString, UINT32_MAX, &start); + char16_t* end = start + len; + char16_t* from = start; + char16_t* to = start; + + // Skip any leading whitespace + while (from < end && NS_IsAsciiWhitespace(*from)) { + from++; + } + + while (from < end) { + char16_t theChar = *from++; + + if (NS_IsAsciiWhitespace(theChar)) { + // We found a whitespace char, so skip over any more + while (from < end && NS_IsAsciiWhitespace(*from)) { + from++; + } + + // Turn all whitespace into spaces + theChar = ' '; + } + + *to++ = theChar; + } + + // Drop any trailing space + if (to > start && to[-1] == ' ') { + to--; + } + + // Re-terminate the string + *to = '\0'; + + // Set the new length + aString.SetLength(to - start); +} + +uint32_t +ToLowerCase(nsACString& aStr) +{ + char* begin; + char* end; + uint32_t len = aStr.BeginWriting(&begin, &end); + + for (; begin < end; ++begin) { + *begin = NS_ToLower(*begin); + } + + return len; +} + +uint32_t +ToUpperCase(nsACString& aStr) +{ + char* begin; + char* end; + uint32_t len = aStr.BeginWriting(&begin, &end); + + for (; begin < end; ++begin) { + *begin = NS_ToUpper(*begin); + } + + return len; +} + +uint32_t +ToLowerCase(const nsACString& aSrc, nsACString& aDest) +{ + const char* begin; + const char* end; + uint32_t len = aSrc.BeginReading(&begin, &end); + + char* dest; + NS_CStringGetMutableData(aDest, len, &dest); + + for (; begin < end; ++begin, ++dest) { + *dest = NS_ToLower(*begin); + } + + return len; +} + +uint32_t +ToUpperCase(const nsACString& aSrc, nsACString& aDest) +{ + const char* begin; + const char* end; + uint32_t len = aSrc.BeginReading(&begin, &end); + + char* dest; + NS_CStringGetMutableData(aDest, len, &dest); + + for (; begin < end; ++begin, ++dest) { + *dest = NS_ToUpper(*begin); + } + + return len; +} + +int32_t +CaseInsensitiveCompare(const char* aStrA, const char* aStrB, + uint32_t aLen) +{ + for (const char* aend = aStrA + aLen; aStrA < aend; ++aStrA, ++aStrB) { + char la = NS_ToLower(*aStrA); + char lb = NS_ToLower(*aStrB); + + if (la == lb) { + continue; + } + + return la < lb ? -1 : 1; + } + + return 0; +} + +bool +ParseString(const nsACString& aSource, char aDelimiter, + nsTArray& aArray) +{ + int32_t start = 0; + int32_t end = aSource.Length(); + + uint32_t oldLength = aArray.Length(); + + for (;;) { + int32_t delimiter = aSource.FindChar(aDelimiter, start); + if (delimiter < 0) { + delimiter = end; + } + + if (delimiter != start) { + if (!aArray.AppendElement(Substring(aSource, start, delimiter - start))) { + aArray.RemoveElementsAt(oldLength, aArray.Length() - oldLength); + return false; + } + } + + if (delimiter == end) { + break; + } + start = ++delimiter; + if (start == end) { + break; + } + } + + return true; +} diff --git a/xpcom/glue/nsStringAPI.h b/xpcom/glue/nsStringAPI.h new file mode 100644 index 000000000..d5e368695 --- /dev/null +++ b/xpcom/glue/nsStringAPI.h @@ -0,0 +1,1596 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 provides wrapper classes around the frozen string API + * which are roughly equivalent to the internal string classes. + */ + +#ifdef MOZILLA_INTERNAL_API +#error nsStringAPI.h is only usable from non-MOZILLA_INTERNAL_API code! +#endif + +#ifndef nsStringAPI_h__ +#define nsStringAPI_h__ + +#include "mozilla/Attributes.h" +#include "mozilla/Char16.h" + +#include "nsXPCOMStrings.h" +#include "nsISupportsImpl.h" +#include "mozilla/Logging.h" +#include "nsTArray.h" + +/** + * Comparison function for use with nsACString::Equals + */ +NS_HIDDEN_(int32_t) CaseInsensitiveCompare(const char* aStrA, const char* aStrB, + uint32_t aLength); + +class nsAString +{ +public: + typedef char16_t char_type; + typedef nsAString self_type; + typedef uint32_t size_type; + typedef uint32_t index_type; + + /** + * Returns the length, beginning, and end of a string in one operation. + */ + NS_HIDDEN_(uint32_t) BeginReading(const char_type** aBegin, + const char_type** aEnd = nullptr) const; + + NS_HIDDEN_(const char_type*) BeginReading() const; + NS_HIDDEN_(const char_type*) EndReading() const; + + NS_HIDDEN_(char_type) CharAt(uint32_t aPos) const + { + NS_ASSERTION(aPos < Length(), "Out of bounds"); + return BeginReading()[aPos]; + } + NS_HIDDEN_(char_type) operator [](uint32_t aPos) const + { + return CharAt(aPos); + } + NS_HIDDEN_(char_type) First() const + { + return CharAt(0); + } + NS_HIDDEN_(char_type) Last() const + { + const char_type* data; + uint32_t dataLen = NS_StringGetData(*this, &data); + return data[dataLen - 1]; + } + + /** + * Get the length, begin writing, and optionally set the length of a + * string all in one operation. + * + * @param newSize Size the string to this length. Pass UINT32_MAX + * to leave the length unchanged. + * @return The new length of the string, or 0 if resizing failed. + */ + NS_HIDDEN_(uint32_t) BeginWriting(char_type** aBegin, + char_type** aEnd = nullptr, + uint32_t aNewSize = UINT32_MAX); + + NS_HIDDEN_(char_type*) BeginWriting(uint32_t = UINT32_MAX); + NS_HIDDEN_(char_type*) EndWriting(); + + NS_HIDDEN_(bool) SetLength(uint32_t aLen); + + NS_HIDDEN_(size_type) Length() const + { + const char_type* data; + return NS_StringGetData(*this, &data); + } + + NS_HIDDEN_(bool) IsEmpty() const { return Length() == 0; } + + NS_HIDDEN_(void) SetIsVoid(bool aVal) { NS_StringSetIsVoid(*this, aVal); } + NS_HIDDEN_(bool) IsVoid() const { return NS_StringGetIsVoid(*this); } + + NS_HIDDEN_(void) Assign(const self_type& aString) + { + NS_StringCopy(*this, aString); + } + NS_HIDDEN_(void) Assign(const char_type* aData, size_type aLength = UINT32_MAX) + { + NS_StringSetData(*this, aData, aLength); + } + NS_HIDDEN_(void) Assign(char_type aChar) + { + NS_StringSetData(*this, &aChar, 1); + } +#ifdef MOZ_USE_CHAR16_WRAPPER + NS_HIDDEN_(void) Assign(char16ptr_t aData, size_type aLength = UINT32_MAX) + { + NS_StringSetData(*this, aData, aLength); + } +#endif + + NS_HIDDEN_(void) AssignLiteral(const char* aStr); + NS_HIDDEN_(void) AssignASCII(const char* aStr) + { + AssignLiteral(aStr); + } + + NS_HIDDEN_(self_type&) operator=(const self_type& aString) + { + Assign(aString); + return *this; + } + NS_HIDDEN_(self_type&) operator=(const char_type* aPtr) + { + Assign(aPtr); + return *this; + } + NS_HIDDEN_(self_type&) operator=(char_type aChar) + { + Assign(aChar); + return *this; + } +#ifdef MOZ_USE_CHAR16_WRAPPER + NS_HIDDEN_(self_type&) operator=(char16ptr_t aPtr) + { + Assign(aPtr); + return *this; + } +#endif + + NS_HIDDEN_(void) Replace(index_type aCutStart, size_type aCutLength, + const char_type* aData, + size_type aLength = size_type(-1)) + { + NS_StringSetDataRange(*this, aCutStart, aCutLength, aData, aLength); + } + NS_HIDDEN_(void) Replace(index_type aCutStart, size_type aCutLength, + char_type aChar) + { + Replace(aCutStart, aCutLength, &aChar, 1); + } + NS_HIDDEN_(void) Replace(index_type aCutStart, size_type aCutLength, + const self_type& aReadable) + { + const char_type* data; + uint32_t dataLen = NS_StringGetData(aReadable, &data); + NS_StringSetDataRange(*this, aCutStart, aCutLength, data, dataLen); + } + NS_HIDDEN_(void) SetCharAt(char_type aChar, index_type aPos) + { + Replace(aPos, 1, &aChar, 1); + } + + NS_HIDDEN_(void) Append(char_type aChar) + { + Replace(size_type(-1), 0, aChar); + } + NS_HIDDEN_(void) Append(const char_type* aData, + size_type aLength = size_type(-1)) + { + Replace(size_type(-1), 0, aData, aLength); + } +#ifdef MOZ_USE_CHAR16_WRAPPER + NS_HIDDEN_(void) Append(char16ptr_t aData, size_type aLength = size_type(-1)) + { + Append(static_cast(aData), aLength); + } +#endif + NS_HIDDEN_(void) Append(const self_type& aReadable) + { + Replace(size_type(-1), 0, aReadable); + } + NS_HIDDEN_(void) AppendLiteral(const char* aASCIIStr); + NS_HIDDEN_(void) AppendASCII(const char* aASCIIStr) + { + AppendLiteral(aASCIIStr); + } + + NS_HIDDEN_(self_type&) operator+=(char_type aChar) + { + Append(aChar); + return *this; + } + NS_HIDDEN_(self_type&) operator+=(const char_type* aData) + { + Append(aData); + return *this; + } + NS_HIDDEN_(self_type&) operator+=(const self_type& aReadable) + { + Append(aReadable); + return *this; + } + + NS_HIDDEN_(void) Insert(char_type aChar, index_type aPos) + { + Replace(aPos, 0, aChar); + } + NS_HIDDEN_(void) Insert(const char_type* aData, index_type aPos, + size_type aLength = size_type(-1)) + { + Replace(aPos, 0, aData, aLength); + } + NS_HIDDEN_(void) Insert(const self_type& aReadable, index_type aPos) + { + Replace(aPos, 0, aReadable); + } + + NS_HIDDEN_(void) Cut(index_type aCutStart, size_type aCutLength) + { + Replace(aCutStart, aCutLength, nullptr, 0); + } + + NS_HIDDEN_(void) Truncate(size_type aNewLength = 0) + { + NS_ASSERTION(aNewLength <= Length(), "Truncate cannot make string longer"); + SetLength(aNewLength); + } + + /** + * Remove all occurences of characters in aSet from the string. + */ + NS_HIDDEN_(void) StripChars(const char* aSet); + + /** + * Strip whitespace characters from the string. + */ + NS_HIDDEN_(void) StripWhitespace() { StripChars("\b\t\r\n "); } + + NS_HIDDEN_(void) Trim(const char* aSet, bool aLeading = true, + bool aTrailing = true); + + /** + * Compare strings of characters. Return 0 if the characters are equal, + */ + typedef int32_t (*ComparatorFunc)(const char_type* aStrA, + const char_type* aStrB, + uint32_t aLength); + + static NS_HIDDEN_(int32_t) DefaultComparator(const char_type* aStrA, + const char_type* aStrB, + uint32_t aLength); + + NS_HIDDEN_(int32_t) Compare(const char_type* aOther, + ComparatorFunc aComparator = DefaultComparator) const; + + NS_HIDDEN_(int32_t) Compare(const self_type& aOther, + ComparatorFunc aComparator = DefaultComparator) const; + + NS_HIDDEN_(bool) Equals(const char_type* aOther, + ComparatorFunc aComparator = DefaultComparator) const; + + NS_HIDDEN_(bool) Equals(const self_type& aOther, + ComparatorFunc aComparator = DefaultComparator) const; + + NS_HIDDEN_(bool) operator<(const self_type& aOther) const + { + return Compare(aOther) < 0; + } + NS_HIDDEN_(bool) operator<(const char_type* aOther) const + { + return Compare(aOther) < 0; + } + + NS_HIDDEN_(bool) operator<=(const self_type& aOther) const + { + return Compare(aOther) <= 0; + } + NS_HIDDEN_(bool) operator<=(const char_type* aOther) const + { + return Compare(aOther) <= 0; + } + + NS_HIDDEN_(bool) operator==(const self_type& aOther) const + { + return Equals(aOther); + } + NS_HIDDEN_(bool) operator==(const char_type* aOther) const + { + return Equals(aOther); + } +#ifdef MOZ_USE_CHAR16_WRAPPER + NS_HIDDEN_(bool) operator==(char16ptr_t aOther) const + { + return Equals(aOther); + } +#endif + + NS_HIDDEN_(bool) operator>=(const self_type& aOther) const + { + return Compare(aOther) >= 0; + } + NS_HIDDEN_(bool) operator>=(const char_type* aOther) const + { + return Compare(aOther) >= 0; + } + + NS_HIDDEN_(bool) operator>(const self_type& aOther) const + { + return Compare(aOther) > 0; + } + NS_HIDDEN_(bool) operator>(const char_type* aOther) const + { + return Compare(aOther) > 0; + } + + NS_HIDDEN_(bool) operator!=(const self_type& aOther) const + { + return !Equals(aOther); + } + NS_HIDDEN_(bool) operator!=(const char_type* aOther) const + { + return !Equals(aOther); + } + + NS_HIDDEN_(bool) EqualsLiteral(const char* aASCIIString) const; + NS_HIDDEN_(bool) EqualsASCII(const char* aASCIIString) const + { + return EqualsLiteral(aASCIIString); + } + + /** + * Case-insensitive match this string to a lowercase ASCII string. + */ + NS_HIDDEN_(bool) LowerCaseEqualsLiteral(const char* aASCIIString) const; + + /** + * Find the first occurrence of aStr in this string. + * + * @return the offset of aStr, or -1 if not found + */ + NS_HIDDEN_(int32_t) Find(const self_type& aStr, + ComparatorFunc aComparator = DefaultComparator) const + { + return Find(aStr, 0, aComparator); + } + + /** + * Find the first occurrence of aStr in this string, beginning at aOffset. + * + * @return the offset of aStr, or -1 if not found + */ + NS_HIDDEN_(int32_t) Find(const self_type& aStr, uint32_t aOffset, + ComparatorFunc aComparator = DefaultComparator) const; + + /** + * Find an ASCII string within this string. + * + * @return the offset of aStr, or -1 if not found. + */ + NS_HIDDEN_(int32_t) Find(const char* aStr, bool aIgnoreCase = false) const + { + return Find(aStr, 0, aIgnoreCase); + } + + NS_HIDDEN_(int32_t) Find(const char* aStr, uint32_t aOffset, + bool aIgnoreCase = false) const; + + /** + * Find the last occurrence of aStr in this string. + * + * @return The offset of aStr from the beginning of the string, + * or -1 if not found. + */ + NS_HIDDEN_(int32_t) RFind(const self_type& aStr, + ComparatorFunc aComparator = DefaultComparator) const + { + return RFind(aStr, -1, aComparator); + } + + /** + * Find the last occurrence of aStr in this string, beginning at aOffset. + * + * @param aOffset the offset from the beginning of the string to begin + * searching. If aOffset < 0, search from end of this string. + * @return The offset of aStr from the beginning of the string, + * or -1 if not found. + */ + NS_HIDDEN_(int32_t) RFind(const self_type& aStr, int32_t aOffset, + ComparatorFunc aComparator = DefaultComparator) const; + + /** + * Find the last occurrence of an ASCII string within this string. + * + * @return The offset of aStr from the beginning of the string, + * or -1 if not found. + */ + NS_HIDDEN_(int32_t) RFind(const char* aStr, bool aIgnoreCase = false) const + { + return RFind(aStr, -1, aIgnoreCase); + } + + /** + * Find the last occurrence of an ASCII string beginning at aOffset. + * + * @param aOffset the offset from the beginning of the string to begin + * searching. If aOffset < 0, search from end of this string. + * @return The offset of aStr from the beginning of the string, + * or -1 if not found. + */ + NS_HIDDEN_(int32_t) RFind(const char* aStr, int32_t aOffset, + bool aIgnoreCase) const; + + /** + * Search for the offset of the first occurrence of a character in a + * string. + * + * @param aOffset the offset from the beginning of the string to begin + * searching + * @return The offset of the character from the beginning of the string, + * or -1 if not found. + */ + NS_HIDDEN_(int32_t) FindChar(char_type aChar, uint32_t aOffset = 0) const; + + /** + * Search for the offset of the last occurrence of a character in a + * string. + * + * @return The offset of the character from the beginning of the string, + * or -1 if not found. + */ + NS_HIDDEN_(int32_t) RFindChar(char_type aChar) const; + + /** + * Append a string representation of a number. + */ + NS_HIDDEN_(void) AppendInt(int aInt, int32_t aRadix = 10); + +#ifndef XPCOM_GLUE_AVOID_NSPR + /** + * Convert this string to an integer. + * + * @param aErrorCode pointer to contain result code. + * @param aRadix must be 10 or 16 + */ + NS_HIDDEN_(int32_t) ToInteger(nsresult* aErrorCode, + uint32_t aRadix = 10) const; + /** + * Convert this string to a 64-bit integer. + * + * @param aErrorCode pointer to contain result code. + * @param aRadix must be 10 or 16 + */ + NS_HIDDEN_(int64_t) ToInteger64(nsresult* aErrorCode, + uint32_t aRadix = 10) const; +#endif // XPCOM_GLUE_AVOID_NSPR + +protected: + // Prevent people from allocating a nsAString directly. + ~nsAString() {} +}; + +class nsACString +{ +public: + typedef char char_type; + typedef nsACString self_type; + typedef uint32_t size_type; + typedef uint32_t index_type; + + /** + * Returns the length, beginning, and end of a string in one operation. + */ + NS_HIDDEN_(uint32_t) BeginReading(const char_type** aBegin, + const char_type** aEnd = nullptr) const; + + NS_HIDDEN_(const char_type*) BeginReading() const; + NS_HIDDEN_(const char_type*) EndReading() const; + + NS_HIDDEN_(char_type) CharAt(uint32_t aPos) const + { + NS_ASSERTION(aPos < Length(), "Out of bounds"); + return BeginReading()[aPos]; + } + NS_HIDDEN_(char_type) operator [](uint32_t aPos) const + { + return CharAt(aPos); + } + NS_HIDDEN_(char_type) First() const + { + return CharAt(0); + } + NS_HIDDEN_(char_type) Last() const + { + const char_type* data; + uint32_t dataLen = NS_CStringGetData(*this, &data); + return data[dataLen - 1]; + } + + /** + * Get the length, begin writing, and optionally set the length of a + * string all in one operation. + * + * @param newSize Size the string to this length. Pass UINT32_MAX + * to leave the length unchanged. + * @return The new length of the string, or 0 if resizing failed. + */ + NS_HIDDEN_(uint32_t) BeginWriting(char_type** aBegin, + char_type** aEnd = nullptr, + uint32_t aNewSize = UINT32_MAX); + + NS_HIDDEN_(char_type*) BeginWriting(uint32_t aLen = UINT32_MAX); + NS_HIDDEN_(char_type*) EndWriting(); + + NS_HIDDEN_(bool) SetLength(uint32_t aLen); + + NS_HIDDEN_(size_type) Length() const + { + const char_type* data; + return NS_CStringGetData(*this, &data); + } + + NS_HIDDEN_(bool) IsEmpty() const { return Length() == 0; } + + NS_HIDDEN_(void) SetIsVoid(bool aVal) { NS_CStringSetIsVoid(*this, aVal); } + NS_HIDDEN_(bool) IsVoid() const { return NS_CStringGetIsVoid(*this); } + + NS_HIDDEN_(void) Assign(const self_type& aString) + { + NS_CStringCopy(*this, aString); + } + NS_HIDDEN_(void) Assign(const char_type* aData, size_type aLength = UINT32_MAX) + { + NS_CStringSetData(*this, aData, aLength); + } + NS_HIDDEN_(void) Assign(char_type aChar) + { + NS_CStringSetData(*this, &aChar, 1); + } + NS_HIDDEN_(void) AssignLiteral(const char_type* aData) + { + Assign(aData); + } + NS_HIDDEN_(void) AssignASCII(const char_type* aData) + { + Assign(aData); + } + + NS_HIDDEN_(self_type&) operator=(const self_type& aString) + { + Assign(aString); + return *this; + } + NS_HIDDEN_(self_type&) operator=(const char_type* aPtr) + { + Assign(aPtr); + return *this; + } + NS_HIDDEN_(self_type&) operator=(char_type aChar) + { + Assign(aChar); + return *this; + } + + NS_HIDDEN_(void) Replace(index_type aCutStart, size_type aCutLength, + const char_type* aData, + size_type aLength = size_type(-1)) + { + NS_CStringSetDataRange(*this, aCutStart, aCutLength, aData, aLength); + } + NS_HIDDEN_(void) Replace(index_type aCutStart, size_type aCutLength, + char_type aChar) + { + Replace(aCutStart, aCutLength, &aChar, 1); + } + NS_HIDDEN_(void) Replace(index_type aCutStart, size_type aCutLength, + const self_type& aReadable) + { + const char_type* data; + uint32_t dataLen = NS_CStringGetData(aReadable, &data); + NS_CStringSetDataRange(*this, aCutStart, aCutLength, data, dataLen); + } + NS_HIDDEN_(void) SetCharAt(char_type aChar, index_type aPos) + { + Replace(aPos, 1, &aChar, 1); + } + + NS_HIDDEN_(void) Append(char_type aChar) + { + Replace(size_type(-1), 0, aChar); + } + NS_HIDDEN_(void) Append(const char_type* aData, + size_type aLength = size_type(-1)) + { + Replace(size_type(-1), 0, aData, aLength); + } + NS_HIDDEN_(void) Append(const self_type& aReadable) + { + Replace(size_type(-1), 0, aReadable); + } + NS_HIDDEN_(void) AppendLiteral(const char* aASCIIStr) + { + Append(aASCIIStr); + } + NS_HIDDEN_(void) AppendASCII(const char* aASCIIStr) + { + Append(aASCIIStr); + } + + NS_HIDDEN_(self_type&) operator+=(char_type aChar) + { + Append(aChar); + return *this; + } + NS_HIDDEN_(self_type&) operator+=(const char_type* aData) + { + Append(aData); + return *this; + } + NS_HIDDEN_(self_type&) operator+=(const self_type& aReadable) + { + Append(aReadable); + return *this; + } + + NS_HIDDEN_(void) Insert(char_type aChar, index_type aPos) + { + Replace(aPos, 0, aChar); + } + NS_HIDDEN_(void) Insert(const char_type* aData, index_type aPos, + size_type aLength = size_type(-1)) + { + Replace(aPos, 0, aData, aLength); + } + NS_HIDDEN_(void) Insert(const self_type& aReadable, index_type aPos) + { + Replace(aPos, 0, aReadable); + } + + NS_HIDDEN_(void) Cut(index_type aCutStart, size_type aCutLength) + { + Replace(aCutStart, aCutLength, nullptr, 0); + } + + NS_HIDDEN_(void) Truncate(size_type aNewLength = 0) + { + NS_ASSERTION(aNewLength <= Length(), "Truncate cannot make string longer"); + SetLength(aNewLength); + } + + /** + * Remove all occurences of characters in aSet from the string. + */ + NS_HIDDEN_(void) StripChars(const char* aSet); + + /** + * Strip whitespace characters from the string. + */ + NS_HIDDEN_(void) StripWhitespace() { StripChars("\b\t\r\n "); } + + NS_HIDDEN_(void) Trim(const char* aSet, bool aLeading = true, + bool aTrailing = true); + + /** + * Compare strings of characters. Return 0 if the characters are equal, + */ + typedef int32_t (*ComparatorFunc)(const char_type* a, + const char_type* b, + uint32_t length); + + static NS_HIDDEN_(int32_t) DefaultComparator(const char_type* aStrA, + const char_type* aStrB, + uint32_t aLength); + + NS_HIDDEN_(int32_t) Compare(const char_type* aOther, + ComparatorFunc aComparator = DefaultComparator) const; + + NS_HIDDEN_(int32_t) Compare(const self_type& aOther, + ComparatorFunc aComparator = DefaultComparator) const; + + NS_HIDDEN_(bool) Equals(const char_type* aOther, + ComparatorFunc aComparator = DefaultComparator) const; + + NS_HIDDEN_(bool) Equals(const self_type& aOther, + ComparatorFunc aComparator = DefaultComparator) const; + + NS_HIDDEN_(bool) operator<(const self_type& aOther) const + { + return Compare(aOther) < 0; + } + NS_HIDDEN_(bool) operator<(const char_type* aOther) const + { + return Compare(aOther) < 0; + } + + NS_HIDDEN_(bool) operator<=(const self_type& aOther) const + { + return Compare(aOther) <= 0; + } + NS_HIDDEN_(bool) operator<=(const char_type* aOther) const + { + return Compare(aOther) <= 0; + } + + NS_HIDDEN_(bool) operator==(const self_type& aOther) const + { + return Equals(aOther); + } + NS_HIDDEN_(bool) operator==(const char_type* aOther) const + { + return Equals(aOther); + } + + NS_HIDDEN_(bool) operator>=(const self_type& aOther) const + { + return Compare(aOther) >= 0; + } + NS_HIDDEN_(bool) operator>=(const char_type* aOther) const + { + return Compare(aOther) >= 0; + } + + NS_HIDDEN_(bool) operator>(const self_type& aOther) const + { + return Compare(aOther) > 0; + } + NS_HIDDEN_(bool) operator>(const char_type* aOther) const + { + return Compare(aOther) > 0; + } + + NS_HIDDEN_(bool) operator!=(const self_type& aOther) const + { + return !Equals(aOther); + } + NS_HIDDEN_(bool) operator!=(const char_type* aOther) const + { + return !Equals(aOther); + } + + NS_HIDDEN_(bool) EqualsLiteral(const char_type* aOther) const + { + return Equals(aOther); + } + NS_HIDDEN_(bool) EqualsASCII(const char_type* aOther) const + { + return Equals(aOther); + } + + /** + * Case-insensitive match this string to a lowercase ASCII string. + */ + NS_HIDDEN_(bool) LowerCaseEqualsLiteral(const char* aASCIIString) const + { + return Equals(aASCIIString, CaseInsensitiveCompare); + } + + /** + * Find the first occurrence of aStr in this string. + * + * @return the offset of aStr, or -1 if not found + */ + NS_HIDDEN_(int32_t) Find(const self_type& aStr, + ComparatorFunc aComparator = DefaultComparator) const + { + return Find(aStr, 0, aComparator); + } + + /** + * Find the first occurrence of aStr in this string, beginning at aOffset. + * + * @return the offset of aStr, or -1 if not found + */ + NS_HIDDEN_(int32_t) Find(const self_type& aStr, uint32_t aOffset, + ComparatorFunc aComparator = DefaultComparator) const; + + /** + * Find the first occurrence of aStr in this string. + * + * @return the offset of aStr, or -1 if not found + */ + NS_HIDDEN_(int32_t) Find(const char_type* aStr, + ComparatorFunc aComparator = DefaultComparator) const; + + NS_HIDDEN_(int32_t) Find(const char_type* aStr, uint32_t aLen, + ComparatorFunc aComparator = DefaultComparator) const; + + /** + * Find the last occurrence of aStr in this string. + * + * @return The offset of the character from the beginning of the string, + * or -1 if not found. + */ + NS_HIDDEN_(int32_t) RFind(const self_type& aStr, + ComparatorFunc aComparator = DefaultComparator) const + { + return RFind(aStr, -1, aComparator); + } + + /** + * Find the last occurrence of aStr in this string, beginning at aOffset. + * + * @param aOffset the offset from the beginning of the string to begin + * searching. If aOffset < 0, search from end of this string. + * @return The offset of aStr from the beginning of the string, + * or -1 if not found. + */ + NS_HIDDEN_(int32_t) RFind(const self_type& aStr, int32_t aOffset, + ComparatorFunc aComparator = DefaultComparator) const; + + /** + * Find the last occurrence of aStr in this string. + * + * @return The offset of aStr from the beginning of the string, + * or -1 if not found. + */ + NS_HIDDEN_(int32_t) RFind(const char_type* aStr, + ComparatorFunc aComparator = DefaultComparator) const; + + /** + * Find the last occurrence of an ASCII string in this string, + * beginning at aOffset. + * + * @param aLen is the length of aStr + * @return The offset of aStr from the beginning of the string, + * or -1 if not found. + */ + NS_HIDDEN_(int32_t) RFind(const char_type* aStr, int32_t aLen, + ComparatorFunc aComparator = DefaultComparator) const; + + /** + * Search for the offset of the first occurrence of a character in a + * string. + * + * @param aOffset the offset from the beginning of the string to begin + * searching + * @return The offset of the character from the beginning of the string, + * or -1 if not found. + */ + NS_HIDDEN_(int32_t) FindChar(char_type aChar, uint32_t aOffset = 0) const; + + /** + * Search for the offset of the last occurrence of a character in a + * string. + * + * @return The offset of the character from the beginning of the string, + * or -1 if not found. + */ + NS_HIDDEN_(int32_t) RFindChar(char_type aChar) const; + + /** + * Append a string representation of a number. + */ + NS_HIDDEN_(void) AppendInt(int aInt, int32_t aRadix = 10); + +#ifndef XPCOM_GLUE_AVOID_NSPR + /** + * Convert this string to an integer. + * + * @param aErrorCode pointer to contain result code. + * @param aRadix must be 10 or 16 + */ + NS_HIDDEN_(int32_t) ToInteger(nsresult* aErrorCode, + uint32_t aRadix = 10) const; + /** + * Convert this string to a 64-bit integer. + * + * @param aErrorCode pointer to contain result code. + * @param aRadix must be 10 or 16 + */ + NS_HIDDEN_(int64_t) ToInteger64(nsresult* aErrorCode, + uint32_t aRadix = 10) const; +#endif // XPCOM_GLUE_AVOID_NSPR + +protected: + // Prevent people from allocating a nsAString directly. + ~nsACString() {} +}; + +/* ------------------------------------------------------------------------- */ + +/** + * Below we define nsStringContainer and nsCStringContainer. These classes + * have unspecified structure. In most cases, your code should use + * nsString/nsCString instead of these classes; if you prefer C-style + * programming, then look no further. + */ + +class nsStringContainer + : public nsAString + , private nsStringContainer_base +{ +}; + +class nsCStringContainer + : public nsACString + , private nsStringContainer_base +{ +}; + +/** + * The following classes are C++ helper classes that make the frozen string + * API easier to use. + */ + +/** + * Rename symbols to avoid conflicting with internal versions. + */ +#define nsString nsString_external +#define nsCString nsCString_external +#define nsDependentString nsDependentString_external +#define nsDependentCString nsDependentCString_external +#define NS_ConvertASCIItoUTF16 NS_ConvertASCIItoUTF16_external +#define NS_ConvertUTF8toUTF16 NS_ConvertUTF8toUTF16_external +#define NS_ConvertUTF16toUTF8 NS_ConvertUTF16toUTF8_external +#define NS_LossyConvertUTF16toASCII NS_LossyConvertUTF16toASCII_external +#define nsGetterCopies nsGetterCopies_external +#define nsCGetterCopies nsCGetterCopies_external +#define nsDependentSubstring nsDependentSubstring_external +#define nsDependentCSubstring nsDependentCSubstring_external + +/** + * basic strings + */ + +class nsString : public nsStringContainer +{ +public: + typedef nsString self_type; + typedef nsAString abstract_string_type; + + nsString() + { + NS_StringContainerInit(*this); + } + + nsString(const self_type& aString) + { + NS_StringContainerInit(*this); + NS_StringCopy(*this, aString); + } + + explicit nsString(const abstract_string_type& aReadable) + { + NS_StringContainerInit(*this); + NS_StringCopy(*this, aReadable); + } + + explicit nsString(const char_type* aData, size_type aLength = UINT32_MAX) + { + NS_StringContainerInit2(*this, aData, aLength, 0); + } + +#ifdef MOZ_USE_CHAR16_WRAPPER + explicit nsString(char16ptr_t aData, size_type aLength = UINT32_MAX) + : nsString(static_cast(aData), aLength) + { + } +#endif + + ~nsString() + { + NS_StringContainerFinish(*this); + } + + char16ptr_t get() const + { + return char16ptr_t(BeginReading()); + } + + self_type& operator=(const self_type& aString) + { + Assign(aString); + return *this; + } + self_type& operator=(const abstract_string_type& aReadable) + { + Assign(aReadable); + return *this; + } + self_type& operator=(const char_type* aPtr) + { + Assign(aPtr); + return *this; + } + self_type& operator=(char_type aChar) + { + Assign(aChar); + return *this; + } + + void Adopt(const char_type* aData, size_type aLength = UINT32_MAX) + { + NS_StringContainerFinish(*this); + NS_StringContainerInit2(*this, aData, aLength, + NS_STRING_CONTAINER_INIT_ADOPT); + } + +protected: + nsString(const char_type* aData, size_type aLength, uint32_t aFlags) + { + NS_StringContainerInit2(*this, aData, aLength, aFlags); + } +}; + +class nsCString : public nsCStringContainer +{ +public: + typedef nsCString self_type; + typedef nsACString abstract_string_type; + + nsCString() + { + NS_CStringContainerInit(*this); + } + + nsCString(const self_type& aString) + { + NS_CStringContainerInit(*this); + NS_CStringCopy(*this, aString); + } + + explicit nsCString(const abstract_string_type& aReadable) + { + NS_CStringContainerInit(*this); + NS_CStringCopy(*this, aReadable); + } + + explicit nsCString(const char_type* aData, size_type aLength = UINT32_MAX) + { + NS_CStringContainerInit(*this); + NS_CStringSetData(*this, aData, aLength); + } + + ~nsCString() + { + NS_CStringContainerFinish(*this); + } + + const char_type* get() const + { + return BeginReading(); + } + + self_type& operator=(const self_type& aString) + { + Assign(aString); + return *this; + } + self_type& operator=(const abstract_string_type& aReadable) + { + Assign(aReadable); + return *this; + } + self_type& operator=(const char_type* aPtr) + { + Assign(aPtr); + return *this; + } + self_type& operator=(char_type aChar) + { + Assign(aChar); + return *this; + } + + void Adopt(const char_type* aData, size_type aLength = UINT32_MAX) + { + NS_CStringContainerFinish(*this); + NS_CStringContainerInit2(*this, aData, aLength, + NS_CSTRING_CONTAINER_INIT_ADOPT); + } + +protected: + nsCString(const char_type* aData, size_type aLength, uint32_t aFlags) + { + NS_CStringContainerInit2(*this, aData, aLength, aFlags); + } +}; + + +/** + * dependent strings + */ + +class nsDependentString : public nsString +{ +public: + typedef nsDependentString self_type; + + nsDependentString() {} + + explicit nsDependentString(const char_type* aData, + size_type aLength = UINT32_MAX) + : nsString(aData, aLength, NS_CSTRING_CONTAINER_INIT_DEPEND) + { + } + +#ifdef MOZ_USE_CHAR16_WRAPPER + explicit nsDependentString(char16ptr_t aData, size_type aLength = UINT32_MAX) + : nsDependentString(static_cast(aData), aLength) + { + } +#endif + + void Rebind(const char_type* aData, size_type aLength = UINT32_MAX) + { + NS_StringContainerFinish(*this); + NS_StringContainerInit2(*this, aData, aLength, + NS_STRING_CONTAINER_INIT_DEPEND); + } + +private: + self_type& operator=(const self_type& aString) = delete; +}; + +class nsDependentCString : public nsCString +{ +public: + typedef nsDependentCString self_type; + + nsDependentCString() {} + + explicit nsDependentCString(const char_type* aData, + size_type aLength = UINT32_MAX) + : nsCString(aData, aLength, NS_CSTRING_CONTAINER_INIT_DEPEND) + { + } + + void Rebind(const char_type* aData, size_type aLength = UINT32_MAX) + { + NS_CStringContainerFinish(*this); + NS_CStringContainerInit2(*this, aData, aLength, + NS_CSTRING_CONTAINER_INIT_DEPEND); + } + +private: + self_type& operator=(const self_type& aString) = delete; +}; + + +/** + * conversion classes + */ + +inline void +CopyUTF16toUTF8(const nsAString& aSource, nsACString& aDest) +{ + NS_UTF16ToCString(aSource, NS_CSTRING_ENCODING_UTF8, aDest); +} + +inline void +CopyUTF8toUTF16(const nsACString& aSource, nsAString& aDest) +{ + NS_CStringToUTF16(aSource, NS_CSTRING_ENCODING_UTF8, aDest); +} + +inline void +LossyCopyUTF16toASCII(const nsAString& aSource, nsACString& aDest) +{ + NS_UTF16ToCString(aSource, NS_CSTRING_ENCODING_ASCII, aDest); +} + +inline void +CopyASCIItoUTF16(const nsACString& aSource, nsAString& aDest) +{ + NS_CStringToUTF16(aSource, NS_CSTRING_ENCODING_ASCII, aDest); +} + +char* +ToNewUTF8String(const nsAString& aSource); + +class NS_ConvertASCIItoUTF16 : public nsString +{ +public: + typedef NS_ConvertASCIItoUTF16 self_type; + + explicit NS_ConvertASCIItoUTF16(const nsACString& aStr) + { + NS_CStringToUTF16(aStr, NS_CSTRING_ENCODING_ASCII, *this); + } + + explicit NS_ConvertASCIItoUTF16(const char* aData, + uint32_t aLength = UINT32_MAX) + { + NS_CStringToUTF16(nsDependentCString(aData, aLength), + NS_CSTRING_ENCODING_ASCII, *this); + } + +private: + self_type& operator=(const self_type& aString) = delete; +}; + +class NS_ConvertUTF8toUTF16 : public nsString +{ +public: + typedef NS_ConvertUTF8toUTF16 self_type; + + explicit NS_ConvertUTF8toUTF16(const nsACString& aStr) + { + NS_CStringToUTF16(aStr, NS_CSTRING_ENCODING_UTF8, *this); + } + + explicit NS_ConvertUTF8toUTF16(const char* aData, + uint32_t aLength = UINT32_MAX) + { + NS_CStringToUTF16(nsDependentCString(aData, aLength), + NS_CSTRING_ENCODING_UTF8, *this); + } + +private: + self_type& operator=(const self_type& aString) = delete; +}; + +class NS_ConvertUTF16toUTF8 : public nsCString +{ +public: + typedef NS_ConvertUTF16toUTF8 self_type; + + explicit NS_ConvertUTF16toUTF8(const nsAString& aStr) + { + NS_UTF16ToCString(aStr, NS_CSTRING_ENCODING_UTF8, *this); + } + + explicit NS_ConvertUTF16toUTF8(const char16ptr_t aData, + uint32_t aLength = UINT32_MAX) + { + NS_UTF16ToCString(nsDependentString(aData, aLength), + NS_CSTRING_ENCODING_UTF8, *this); + } + +private: + self_type& operator=(const self_type& aString) = delete; +}; + +class NS_LossyConvertUTF16toASCII : public nsCString +{ +public: + typedef NS_LossyConvertUTF16toASCII self_type; + + explicit NS_LossyConvertUTF16toASCII(const nsAString& aStr) + { + NS_UTF16ToCString(aStr, NS_CSTRING_ENCODING_ASCII, *this); + } + + explicit NS_LossyConvertUTF16toASCII(const char16ptr_t aData, + uint32_t aLength = UINT32_MAX) + { + NS_UTF16ToCString(nsDependentString(aData, aLength), + NS_CSTRING_ENCODING_ASCII, *this); + } + +private: + self_type& operator=(const self_type& aString) = delete; +}; + + +/** + * literal strings + */ +static_assert(sizeof(char16_t) == 2, "size of char16_t must be 2"); +static_assert(char16_t(-1) > char16_t(0), "char16_t must be unsigned"); + +#define NS_MULTILINE_LITERAL_STRING(s) \ + nsDependentString(reinterpret_cast(s), \ + uint32_t((sizeof(s) / 2) - 1)) +#define NS_MULTILINE_LITERAL_STRING_INIT(n, s) \ + n(reinterpret_cast(s), \ + uint32_t((sizeof(s) / 2) - 1)) +#define NS_NAMED_MULTILINE_LITERAL_STRING(n,s) \ + const nsDependentString n(reinterpret_cast(s), \ + uint32_t((sizeof(s) / 2) - 1)) +typedef nsDependentString nsLiteralString; + +#define NS_LITERAL_STRING(s) \ + static_cast(NS_MULTILINE_LITERAL_STRING(u"" s)) +#define NS_LITERAL_STRING_INIT(n, s) \ + NS_MULTILINE_LITERAL_STRING_INIT(n, (u"" s)) +#define NS_NAMED_LITERAL_STRING(n, s) \ + NS_NAMED_MULTILINE_LITERAL_STRING(n, (u"" s)) + +#define NS_LITERAL_CSTRING(s) \ + static_cast(nsDependentCString(s, uint32_t(sizeof(s) - 1))) +#define NS_LITERAL_CSTRING_INIT(n, s) \ + n(s, uint32_t(sizeof(s)-1)) +#define NS_NAMED_LITERAL_CSTRING(n, s) \ + const nsDependentCString n(s, uint32_t(sizeof(s)-1)) + +typedef nsDependentCString nsLiteralCString; + + +/** + * getter_Copies support + * + * NS_IMETHOD GetBlah(char16_t**); + * + * void some_function() + * { + * nsString blah; + * GetBlah(getter_Copies(blah)); + * // ... + * } + */ + +class nsGetterCopies +{ +public: + typedef char16_t char_type; + + explicit nsGetterCopies(nsString& aStr) + : mString(aStr) + , mData(nullptr) + { + } + + ~nsGetterCopies() { mString.Adopt(mData); } + + operator char_type**() { return &mData; } + +private: + nsString& mString; + char_type* mData; +}; + +inline nsGetterCopies +getter_Copies(nsString& aString) +{ + return nsGetterCopies(aString); +} + +class nsCGetterCopies +{ +public: + typedef char char_type; + + explicit nsCGetterCopies(nsCString& aStr) + : mString(aStr) + , mData(nullptr) + { + } + + ~nsCGetterCopies() { mString.Adopt(mData); } + + operator char_type**() { return &mData; } + +private: + nsCString& mString; + char_type* mData; +}; + +inline nsCGetterCopies +getter_Copies(nsCString& aString) +{ + return nsCGetterCopies(aString); +} + + +/** +* substrings +*/ + +class nsDependentSubstring : public nsStringContainer +{ +public: + typedef nsDependentSubstring self_type; + typedef nsAString abstract_string_type; + + ~nsDependentSubstring() { NS_StringContainerFinish(*this); } + nsDependentSubstring() { NS_StringContainerInit(*this); } + + nsDependentSubstring(const char_type* aStart, uint32_t aLength) + { + NS_StringContainerInit2(*this, aStart, aLength, + NS_STRING_CONTAINER_INIT_DEPEND | + NS_STRING_CONTAINER_INIT_SUBSTRING); + } + + nsDependentSubstring(const abstract_string_type& aStr, + uint32_t aStartPos); + nsDependentSubstring(const abstract_string_type& aStr, + uint32_t aStartPos, uint32_t aLength); + + void Rebind(const char_type* aStart, uint32_t aLength) + { + NS_StringContainerFinish(*this); + NS_StringContainerInit2(*this, aStart, aLength, + NS_STRING_CONTAINER_INIT_DEPEND | + NS_STRING_CONTAINER_INIT_SUBSTRING); + } + +private: + self_type& operator=(const self_type& aString) = delete; +}; + +class nsDependentCSubstring : public nsCStringContainer +{ +public: + typedef nsDependentCSubstring self_type; + typedef nsACString abstract_string_type; + + ~nsDependentCSubstring() { NS_CStringContainerFinish(*this); } + nsDependentCSubstring() { NS_CStringContainerInit(*this); } + + nsDependentCSubstring(const char_type* aStart, uint32_t aLength) + { + NS_CStringContainerInit2(*this, aStart, aLength, + NS_CSTRING_CONTAINER_INIT_DEPEND | + NS_CSTRING_CONTAINER_INIT_SUBSTRING); + } + + nsDependentCSubstring(const abstract_string_type& aStr, + uint32_t aStartPos); + nsDependentCSubstring(const abstract_string_type& aStr, + uint32_t aStartPos, uint32_t aLength); + + void Rebind(const char_type* aStart, uint32_t aLength) + { + NS_CStringContainerFinish(*this); + NS_CStringContainerInit2(*this, aStart, aLength, + NS_CSTRING_CONTAINER_INIT_DEPEND | + NS_CSTRING_CONTAINER_INIT_SUBSTRING); + } + +private: + self_type& operator=(const self_type& aString) = delete; +}; + + +/** + * Various nsDependentC?Substring constructor functions + */ + +// char16_t +inline const nsDependentSubstring +Substring(const nsAString& aStr, uint32_t aStartPos) +{ + return nsDependentSubstring(aStr, aStartPos); +} + +inline const nsDependentSubstring +Substring(const nsAString& aStr, uint32_t aStartPos, uint32_t aLength) +{ + return nsDependentSubstring(aStr, aStartPos, aLength); +} + +inline const nsDependentSubstring +Substring(const char16_t* aStart, const char16_t* aEnd) +{ + MOZ_ASSERT(uint32_t(aEnd - aStart) == uintptr_t(aEnd - aStart), + "string too long"); + return nsDependentSubstring(aStart, uint32_t(aEnd - aStart)); +} + +inline const nsDependentSubstring +Substring(const char16_t* aStart, uint32_t aLength) +{ + return nsDependentSubstring(aStart, aLength); +} + +inline const nsDependentSubstring +StringHead(const nsAString& aStr, uint32_t aCount) +{ + return nsDependentSubstring(aStr, 0, aCount); +} + +inline const nsDependentSubstring +StringTail(const nsAString& aStr, uint32_t aCount) +{ + return nsDependentSubstring(aStr, aStr.Length() - aCount, aCount); +} + +// char +inline const nsDependentCSubstring +Substring(const nsACString& aStr, uint32_t aStartPos) +{ + return nsDependentCSubstring(aStr, aStartPos); +} + +inline const nsDependentCSubstring +Substring(const nsACString& aStr, uint32_t aStartPos, uint32_t aLength) +{ + return nsDependentCSubstring(aStr, aStartPos, aLength); +} + +inline const nsDependentCSubstring +Substring(const char* aStart, const char* aEnd) +{ + MOZ_ASSERT(uint32_t(aEnd - aStart) == uintptr_t(aEnd - aStart), + "string too long"); + return nsDependentCSubstring(aStart, uint32_t(aEnd - aStart)); +} + +inline const nsDependentCSubstring +Substring(const char* aStart, uint32_t aLength) +{ + return nsDependentCSubstring(aStart, aLength); +} + +inline const nsDependentCSubstring +StringHead(const nsACString& aStr, uint32_t aCount) +{ + return nsDependentCSubstring(aStr, 0, aCount); +} + +inline const nsDependentCSubstring +StringTail(const nsACString& aStr, uint32_t aCount) +{ + return nsDependentCSubstring(aStr, aStr.Length() - aCount, aCount); +} + + +inline bool +StringBeginsWith(const nsAString& aSource, const nsAString& aSubstring, + nsAString::ComparatorFunc aComparator = nsAString::DefaultComparator) +{ + return aSubstring.Length() <= aSource.Length() && + StringHead(aSource, aSubstring.Length()).Equals(aSubstring, aComparator); +} + +inline bool +StringEndsWith(const nsAString& aSource, const nsAString& aSubstring, + nsAString::ComparatorFunc aComparator = nsAString::DefaultComparator) +{ + return aSubstring.Length() <= aSource.Length() && + StringTail(aSource, aSubstring.Length()).Equals(aSubstring, aComparator); +} + +inline bool +StringBeginsWith(const nsACString& aSource, const nsACString& aSubstring, + nsACString::ComparatorFunc aComparator = nsACString::DefaultComparator) +{ + return aSubstring.Length() <= aSource.Length() && + StringHead(aSource, aSubstring.Length()).Equals(aSubstring, aComparator); +} + +inline bool +StringEndsWith(const nsACString& aSource, const nsACString& aSubstring, + nsACString::ComparatorFunc aComparator = nsACString::DefaultComparator) +{ + return aSubstring.Length() <= aSource.Length() && + StringTail(aSource, aSubstring.Length()).Equals(aSubstring, aComparator); +} + +/** + * Trim whitespace from the beginning and end of a string; then compress + * remaining runs of whitespace characters to a single space. + */ +NS_HIDDEN_(void) CompressWhitespace(nsAString& aString); + +#define EmptyCString() nsCString() +#define EmptyString() nsString() + +/** + * Convert an ASCII string to all upper/lowercase (a-z,A-Z only). As a bonus, + * returns the string length. + */ +NS_HIDDEN_(uint32_t) ToLowerCase(nsACString& aStr); + +NS_HIDDEN_(uint32_t) ToUpperCase(nsACString& aStr); + +NS_HIDDEN_(uint32_t) ToLowerCase(const nsACString& aSrc, nsACString& aDest); + +NS_HIDDEN_(uint32_t) ToUpperCase(const nsACString& aSrc, nsACString& aDest); + +/** + * The following declarations are *deprecated*, and are included here only + * to make porting from existing code that doesn't use the frozen string API + * easier. They may disappear in the future. + */ + +inline char* +ToNewCString(const nsACString& aStr) +{ + return NS_CStringCloneData(aStr); +} + +inline char16_t* +ToNewUnicode(const nsAString& aStr) +{ + return NS_StringCloneData(aStr); +} + +typedef nsString PromiseFlatString; +typedef nsCString PromiseFlatCString; + +typedef nsCString nsAutoCString; +typedef nsString nsAutoString; + +NS_HIDDEN_(bool) ParseString(const nsACString& aAstring, char aDelimiter, + nsTArray& aArray); + +#endif // nsStringAPI_h__ diff --git a/xpcom/glue/nsStringGlue.h b/xpcom/glue/nsStringGlue.h new file mode 100644 index 000000000..a444eb8a1 --- /dev/null +++ b/xpcom/glue/nsStringGlue.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/. */ +// IWYU pragma: private, include "nsString.h" + +/** + * @file nsStringGlue.h + * This header exists solely to #include the proper internal/frozen string + * headers, depending on whether MOZILLA_INTERNAL_API is defined. + */ + +#ifndef nsStringGlue_h__ +#define nsStringGlue_h__ + +#ifdef MOZILLA_INTERNAL_API +#include "nsString.h" +#include "nsReadableUtils.h" +#else +#include "nsStringAPI.h" +#endif + +#endif // nsStringGlue_h__ diff --git a/xpcom/glue/nsTArray-inl.h b/xpcom/glue/nsTArray-inl.h new file mode 100644 index 000000000..af57c9866 --- /dev/null +++ b/xpcom/glue/nsTArray-inl.h @@ -0,0 +1,463 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 + +template +nsTArray_base::nsTArray_base() + : mHdr(EmptyHdr()) +{ + MOZ_COUNT_CTOR(nsTArray_base); +} + +template +nsTArray_base::~nsTArray_base() +{ + if (mHdr != EmptyHdr() && !UsesAutoArrayBuffer()) { + Alloc::Free(mHdr); + } + MOZ_COUNT_DTOR(nsTArray_base); +} + +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 elem_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 elem_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::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 (mHdr == EmptyHdr()) { + // 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() || !Copy::allowRealloc) { + // Malloc() and copy + header = static_cast(ActualAlloc::Malloc(bytesToAlloc)); + if (!header) { + return ActualAlloc::FailureResult(); + } + + Copy::MoveNonOverlappingRegionWithHeader(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 (mHdr == EmptyHdr() || 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; + Copy::MoveNonOverlappingRegion(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 size = sizeof(Header) + length * aElemSize; + void* ptr = nsTArrayFallibleAllocator::Realloc(mHdr, size); + if (!ptr) { + return; + } + mHdr = static_cast(ptr); + mHdr->mCapacity = length; +} + +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) { + ShrinkCapacity(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; + Copy::MoveOverlappingRegion(baseAddr + aNewLen, baseAddr + aOldLen, num, aElemSize); + } +} + +template +template +bool +nsTArray_base::InsertSlotsAt(index_type aIndex, size_type aCount, + size_type aElemSize, + size_t aElemAlign) +{ + MOZ_ASSERT(aIndex <= Length(), "Bogus insertion index"); + size_type newLen = Length() + aCount; + + EnsureCapacity(newLen, aElemSize); + + // Check for out of memory conditions + if (Capacity() < newLen) { + return false; + } + + // 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 true; +} + +// 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 sEmptyHdr, +// 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 sEmptyHdr. + if (mIsAuto && mArray.mHdr == mArray.EmptyHdr()) { + // 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.mHdr != mArray.EmptyHdr()) { + 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 = sEmptyHdr 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, 64> temp; + if (!ActualAlloc::Successful(temp.template EnsureCapacity(smallerLength, + aElemSize))) { + return ActualAlloc::FailureResult(); + } + + Copy::MoveNonOverlappingRegion(temp.Elements(), smallerElements, smallerLength, aElemSize); + Copy::MoveNonOverlappingRegion(smallerElements, largerElements, largerLength, aElemSize); + Copy::MoveNonOverlappingRegion(largerElements, temp.Elements(), smallerLength, aElemSize); + + // Swap the arrays' lengths. + MOZ_ASSERT((aOther.Length() == 0 || mHdr != EmptyHdr()) && + (Length() == 0 || aOther.mHdr != EmptyHdr()), + "Don't set sEmptyHdr's length."); + size_type tempLength = Length(); + + // Avoid writing to EmptyHdr, since it can trigger false + // positives with TSan. + if (mHdr != EmptyHdr()) { + mHdr->mLength = aOther.Length(); + } + if (aOther.mHdr != EmptyHdr()) { + aOther.mHdr->mLength = tempLength; + } + + return ActualAlloc::SuccessResult(); +} + +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 + // sEmptyHdr, 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; + } + + Copy::MoveNonOverlappingRegionWithHeader(header, mHdr, Length(), aElemSize); + header->mCapacity = Length(); + mHdr = header; + } + + return true; +} diff --git a/xpcom/glue/nsTArray.cpp b/xpcom/glue/nsTArray.cpp new file mode 100644 index 000000000..fd8422ec7 --- /dev/null +++ b/xpcom/glue/nsTArray.cpp @@ -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 +#include "nsTArray.h" +#include "nsXPCOM.h" +#include "nsDebug.h" +#include "mozilla/CheckedInt.h" +#include "mozilla/IntegerPrintfMacros.h" + +nsTArrayHeader nsTArrayHeader::sEmptyHdr = { 0, 0, 0 }; + +bool +IsTwiceTheRequiredBytesRepresentableAsUint32(size_t aCapacity, size_t aElemSize) +{ + using mozilla::CheckedUint32; + return ((CheckedUint32(aCapacity) * aElemSize) * 2).isValid(); +} + +MOZ_NORETURN MOZ_COLD void +InvalidArrayIndex_CRASH(size_t aIndex, size_t aLength) +{ + MOZ_CRASH_UNSAFE_PRINTF( + "ElementAt(aIndex = %" PRIu64 ", aLength = %" PRIu64 ")", + static_cast(aIndex), static_cast(aLength)); +} diff --git a/xpcom/glue/nsTArray.h b/xpcom/glue/nsTArray.h new file mode 100644 index 000000000..ca74a41f7 --- /dev/null +++ b/xpcom/glue/nsTArray.h @@ -0,0 +1,2371 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 "nsTArrayForwardDeclare.h" +#include "mozilla/Alignment.h" +#include "mozilla/Assertions.h" +#include "mozilla/Attributes.h" +#include "mozilla/BinarySearch.h" +#include "mozilla/fallible.h" +#include "mozilla/Function.h" +#include "mozilla/MathAlgorithms.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/Move.h" +#include "mozilla/ReverseIterator.h" +#include "mozilla/TypeTraits.h" + +#include + +#include "nsCycleCollectionNoteChild.h" +#include "nsAlgorithm.h" +#include "nscore.h" +#include "nsQuickSort.h" +#include "nsDebug.h" +#include "nsISupportsImpl.h" +#include "nsRegionFwd.h" +#include +#include + +namespace JS { +template +class Heap; +class ObjectPtr; +} /* namespace JS */ + +class nsRegion; +namespace mozilla { +namespace layers { +struct TileClient; +} // namespace layers +} // namespace mozilla + +namespace mozilla { +struct SerializedStructuredCloneBuffer; +} // namespace mozilla + +namespace mozilla { +namespace dom { +namespace ipc { +class StructuredCloneData; +} // namespace ipc +} // namespace dom +} // namespace mozilla + +namespace mozilla { +namespace dom { +class ClonedMessageData; +class MessagePortMessage; +namespace indexedDB { +struct StructuredCloneReadInfo; +class SerializedStructuredCloneReadInfo; +class ObjectStoreCursorResponse; +} // namespace indexedDB +} // namespace dom +} // namespace mozilla + +class JSStructuredCloneData; + +// +// nsTArray is a resizable array class, like std::vector. +// +// Unlike std::vector, which follows C++'s construction/destruction rules, +// nsTArray assumes that your "T" can be memmoved()'ed safely. +// +// The public classes defined in this header are +// +// nsTArray, +// FallibleTArray, +// AutoTArray, and +// +// nsTArray and AutoTArray are infallible by default. To opt-in to fallible +// behaviour, use the `mozilla::fallible` parameter and check the return value. +// +// 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 (i.e., T in nsTArray) specifies the type of the +// elements and has the following requirements: +// +// T MUST be safely memmove()'able. +// T MUST define a copy-constructor. +// T MAY define operator< for sorting. +// T MAY define operator== for searching. +// +// (Note that the memmove requirement may be relaxed for certain types - see +// nsTArray_CopyChooser below.) +// +// For methods taking a Comparator instance, the Comparator must be a class +// defining the following methods: +// +// class Comparator { +// public: +// /** @return True if the elements are equals; false otherwise. */ +// bool Equals(const elem_type& a, const Item& b) const; +// +// /** @return True if (a < b); false otherwise. */ +// bool LessThan(const elem_type& a, const Item& b) const; +// }; +// +// The Equals method is used for searching, and the LessThan method is used for +// searching and sorting. The |Item| type above can be arbitrary, but must +// match the Item type passed to the sort or search function. +// + + +// +// 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 nsTArrayFallibleResult(bool aResult) : mResult(aResult) {} + + MOZ_IMPLICIT 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 ResultType Result(ResultTypeProxy aResult) { return aResult; } + static bool Successful(ResultTypeProxy aResult) { return aResult; } + static ResultTypeProxy SuccessResult() { return true; } + static ResultTypeProxy FailureResult() { return false; } + static ResultType ConvertBoolToResultType(bool aValue) { return aValue; } +}; + +struct nsTArrayInfallibleAllocatorBase +{ + typedef void ResultType; + typedef nsTArrayInfallibleResult ResultTypeProxy; + + static ResultType Result(ResultTypeProxy aResult) {} + static bool Successful(ResultTypeProxy) { return true; } + static ResultTypeProxy SuccessResult() { return ResultTypeProxy(); } + + static ResultTypeProxy FailureResult() + { + NS_RUNTIMEABORT("Infallible nsTArray should never fail"); + return ResultTypeProxy(); + } + + static ResultType ConvertBoolToResultType(bool aValue) + { + if (!aValue) { + NS_RUNTIMEABORT("infallible nsTArray should never convert false to ResultType"); + } + } +}; + +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) {} +}; + +#if defined(MOZALLOC_HAVE_XMALLOC) +#include "mozilla/mozalloc_abort.h" + +struct nsTArrayInfallibleAllocator : nsTArrayInfallibleAllocatorBase +{ + static void* Malloc(size_t aSize) { return moz_xmalloc(aSize); } + static void* Realloc(void* aPtr, size_t aSize) + { + return moz_xrealloc(aPtr, aSize); + } + + static void Free(void* aPtr) { free(aPtr); } + static void SizeTooBig(size_t aSize) { NS_ABORT_OOM(aSize); } +}; + +#else +#include + +struct nsTArrayInfallibleAllocator : nsTArrayInfallibleAllocatorBase +{ + static void* Malloc(size_t aSize) + { + void* ptr = malloc(aSize); + if (MOZ_UNLIKELY(!ptr)) { + NS_ABORT_OOM(aSize); + } + return ptr; + } + + static void* Realloc(void* aPtr, size_t aSize) + { + void* newptr = realloc(aPtr, aSize); + if (MOZ_UNLIKELY(!newptr && aSize)) { + NS_ABORT_OOM(aSize); + } + return newptr; + } + + static void Free(void* aPtr) { free(aPtr); } + static void SizeTooBig(size_t aSize) { NS_ABORT_OOM(aSize); } +}; + +#endif + +// 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 +{ + static nsTArrayHeader sEmptyHdr; + + uint32_t mLength; + uint32_t mCapacity : 31; + uint32_t mIsAutoArray : 1; +}; + +// This class provides a SafeElementAt method to nsTArray which does +// not take a second default value parameter. +template +struct nsTArray_SafeElementAtHelper +{ + 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 +{ + 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 the base type that the smart pointer is templated over; the +// smart pointer can act as E*. +template +struct nsTArray_SafeElementAtSmartPtrHelper +{ + typedef E* elem_type; + typedef const E* const_elem_type; + 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. + elem_type SafeElementAt(index_type aIndex) const + { + return static_cast(this)->SafeElementAt(aIndex, nullptr); + } +}; + +template class nsCOMPtr; + +template +struct nsTArray_SafeElementAtHelper, Derived> + : public nsTArray_SafeElementAtSmartPtrHelper +{ +}; + +template +struct nsTArray_SafeElementAtHelper, Derived> + : public nsTArray_SafeElementAtSmartPtrHelper +{ +}; + +namespace mozilla { +template class OwningNonNull; +} // namespace mozilla + +template +struct nsTArray_SafeElementAtHelper, Derived> +{ + typedef E* elem_type; + typedef const E* const_elem_type; + typedef size_t index_type; + + elem_type SafeElementAt(index_type aIndex) + { + if (aIndex < static_cast(this)->Length()) { + return static_cast(this)->ElementAt(aIndex); + } + return nullptr; + } + + // XXX: Probably should return const_elem_type, but callsites must be fixed. + elem_type SafeElementAt(index_type aIndex) const + { + if (aIndex < static_cast(this)->Length()) { + return static_cast(this)->ElementAt(aIndex); + } + return nullptr; + } +}; + +// 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); + +MOZ_NORETURN MOZ_COLD void +InvalidArrayIndex_CRASH(size_t aIndex, size_t aLength); + +// +// 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; + 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(); + + // 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); + + // 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); + + // 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 increments the length member of the array's header. + // Note that mHdr may actually be sEmptyHdr 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 (mHdr == EmptyHdr()) { + 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 + bool 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); + + // 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 sEmptyHdr. + Header* mHdr; + + Header* Hdr() const { return mHdr; } + Header** PtrToHdr() { return &mHdr; } + static Header* EmptyHdr() { return &Header::sEmptyHdr; } +}; + +// +// 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) + { + typedef typename mozilla::RemoveCV::Type E_NoCV; + typedef typename mozilla::RemoveCV::Type A_NoCV; + static_assert(!mozilla::IsSame::value, + "For safety, we disallow constructing nsTArray elements " + "from E* pointers. See bug 960591."); + new (static_cast(aE)) E(mozilla::Forward(aArg)); + } + // 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) + { + 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_CopyChooser 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_CopyWithMemutils +{ + const static bool allowRealloc = true; + + static void MoveNonOverlappingRegionWithHeader(void* aDest, const void* aSrc, + size_t aCount, size_t aElemSize) + { + memcpy(aDest, aSrc, sizeof(nsTArrayHeader) + aCount * aElemSize); + } + + static void MoveOverlappingRegion(void* aDest, void* aSrc, size_t aCount, + size_t aElemSize) + { + memmove(aDest, aSrc, aCount * aElemSize); + } + + static void MoveNonOverlappingRegion(void* aDest, void* aSrc, size_t aCount, + size_t aElemSize) + { + memcpy(aDest, aSrc, aCount * aElemSize); + } +}; + +// +// A template class that defines how to copy elements calling their constructors +// and destructors appropriately. +// +template +struct nsTArray_CopyWithConstructors +{ + typedef nsTArrayElementTraits traits; + + const static bool allowRealloc = false; + + static void MoveNonOverlappingRegionWithHeader(void* aDest, void* aSrc, size_t aCount, + size_t aElemSize) + { + nsTArrayHeader* destHeader = static_cast(aDest); + nsTArrayHeader* srcHeader = static_cast(aSrc); + *destHeader = *srcHeader; + MoveNonOverlappingRegion(static_cast(aDest) + sizeof(nsTArrayHeader), + static_cast(aSrc) + sizeof(nsTArrayHeader), + aCount, aElemSize); + } + + // These functions are defined by analogy with memmove and memcpy. + // What they actually do is slightly different: MoveOverlappingRegion + // 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. MoveNonOverlappingRegion assumes that moving + // front-to-back is always valid. So they're really more like + // std::move{_backward,} in that respect. We keep these names because + // we think they read slightly better, and MoveNonOverlappingRegion is + // only ever called on overlapping regions from MoveOverlappingRegion. + static void MoveOverlappingRegion(void* aDest, void* aSrc, size_t aCount, + size_t aElemSize) + { + ElemType* destElem = static_cast(aDest); + ElemType* srcElem = static_cast(aSrc); + ElemType* destElemEnd = destElem + aCount; + ElemType* srcElemEnd = srcElem + aCount; + if (destElem == srcElem) { + return; // In practice, we don't do this. + } + + // Figure out whether to copy back-to-front or front-to-back. + if (srcElemEnd > destElem && srcElemEnd < destElemEnd) { + while (destElemEnd != destElem) { + --destElemEnd; + --srcElemEnd; + traits::Construct(destElemEnd, mozilla::Move(*srcElemEnd)); + traits::Destruct(srcElemEnd); + } + } else { + MoveNonOverlappingRegion(aDest, aSrc, aCount, aElemSize); + } + } + + static void MoveNonOverlappingRegion(void* aDest, void* aSrc, size_t aCount, + size_t aElemSize) + { + ElemType* destElem = static_cast(aDest); + ElemType* srcElem = static_cast(aSrc); + ElemType* destElemEnd = destElem + aCount; +#ifdef DEBUG + ElemType* srcElemEnd = srcElem + aCount; + MOZ_ASSERT(srcElemEnd <= destElem || srcElemEnd > destElemEnd); +#endif + while (destElem != destElemEnd) { + traits::Construct(destElem, mozilla::Move(*srcElem)); + traits::Destruct(srcElem); + ++destElem; + ++srcElem; + } + } +}; + +// +// The default behaviour is to use memcpy/memmove for everything. +// +template +struct MOZ_NEEDS_MEMMOVABLE_TYPE nsTArray_CopyChooser +{ + using Type = nsTArray_CopyWithMemutils; +}; + +// +// Some classes require constructors/destructors to be called, so they are +// specialized here. +// +#define DECLARE_USE_COPY_CONSTRUCTORS(T) \ + template<> \ + struct nsTArray_CopyChooser \ + { \ + using Type = nsTArray_CopyWithConstructors; \ + }; + +#define DECLARE_USE_COPY_CONSTRUCTORS_FOR_TEMPLATE(T) \ + template \ + struct nsTArray_CopyChooser> \ + { \ + using Type = nsTArray_CopyWithConstructors>; \ + }; + +DECLARE_USE_COPY_CONSTRUCTORS_FOR_TEMPLATE(JS::Heap) + +DECLARE_USE_COPY_CONSTRUCTORS(nsRegion) +DECLARE_USE_COPY_CONSTRUCTORS(nsIntRegion) +DECLARE_USE_COPY_CONSTRUCTORS(mozilla::layers::TileClient) +DECLARE_USE_COPY_CONSTRUCTORS(mozilla::SerializedStructuredCloneBuffer) +DECLARE_USE_COPY_CONSTRUCTORS(mozilla::dom::ipc::StructuredCloneData) +DECLARE_USE_COPY_CONSTRUCTORS(mozilla::dom::ClonedMessageData) +DECLARE_USE_COPY_CONSTRUCTORS(mozilla::dom::indexedDB::StructuredCloneReadInfo); +DECLARE_USE_COPY_CONSTRUCTORS(mozilla::dom::indexedDB::ObjectStoreCursorResponse) +DECLARE_USE_COPY_CONSTRUCTORS(mozilla::dom::indexedDB::SerializedStructuredCloneReadInfo); +DECLARE_USE_COPY_CONSTRUCTORS(JSStructuredCloneData) +DECLARE_USE_COPY_CONSTRUCTORS(mozilla::dom::MessagePortMessage) +DECLARE_USE_COPY_CONSTRUCTORS(JS::ObjectPtr) + + +// +// 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 { + +template +struct ItemComparatorEq +{ + const Item& mItem; + const Comparator& mComp; + ItemComparatorEq(const Item& aItem, const Comparator& aComp) + : mItem(aItem) + , mComp(aComp) + {} + template + int operator()(const T& aElement) const { + if (mComp.Equals(aElement, mItem)) { + return 0; + } + + return mComp.LessThan(aElement, mItem) ? 1 : -1; + } +}; + +template +struct ItemComparatorFirstElementGT +{ + const Item& mItem; + const Comparator& mComp; + ItemComparatorFirstElementGT(const Item& aItem, const Comparator& aComp) + : mItem(aItem) + , mComp(aComp) + {} + template + int operator()(const T& aElement) const { + if (mComp.LessThan(aElement, mItem) || + mComp.Equals(aElement, mItem)) { + return 1; + } else { + return -1; + } + } +}; + +} // 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: + typedef nsTArrayFallibleAllocator FallibleAlloc; + typedef nsTArrayInfallibleAllocator InfallibleAlloc; + +public: + typedef typename nsTArray_CopyChooser::Type copy_type; + typedef nsTArray_base base_type; + typedef typename base_type::size_type size_type; + typedef typename base_type::index_type index_type; + typedef E elem_type; + typedef nsTArray_Impl self_type; + typedef nsTArrayElementTraits elem_traits; + typedef nsTArray_SafeElementAtHelper safeelementat_helper_type; + typedef elem_type* iterator; + typedef const elem_type* const_iterator; + typedef mozilla::ReverseIterator reverse_iterator; + typedef mozilla::ReverseIterator const_reverse_iterator; + + using safeelementat_helper_type::SafeElementAt; + using base_type::EmptyHdr; + + // 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() { Clear(); } + + // + // Initialization methods + // + + nsTArray_Impl() {} + + // 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) + { + SwapElements(aOther); + } + + // 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&|. + explicit nsTArray_Impl(const self_type& aOther) { AppendElements(aOther); } + + explicit nsTArray_Impl(std::initializer_list aIL) { AppendElements(aIL.begin(), aIL.size()); } + // Allow converting to a const array with a different kind of allocator, + // Since the allocator doesn't matter for const arrays + template + operator const nsTArray_Impl&() const + { + return *reinterpret_cast*>(this); + } + // And we have to do this for our subclasses too + operator const nsTArray&() const + { + return *reinterpret_cast*>(this); + } + 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. + self_type& operator=(const self_type& aOther) + { + if (this != &aOther) { + ReplaceElementsAt(0, Length(), aOther.Elements(), aOther.Length()); + } + return *this; + } + + // 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(); + SwapElements(aOther); + } + return *this; + } + + // Return true if this array has the same length and the same + // elements as |aOther|. + template + 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|. + bool operator!=(const self_type& aOther) const { return !operator==(aOther); } + + template + self_type& operator=(const nsTArray_Impl& aOther) + { + ReplaceElementsAt(0, Length(), aOther.Elements(), aOther.Length()); + return *this; + } + + template + self_type& operator=(nsTArray_Impl&& aOther) + { + Clear(); + SwapElements(aOther); + 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. + size_t ShallowSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const + { + if (this->UsesAutoArrayBuffer() || Hdr() == EmptyHdr()) { + 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. + 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. + elem_type* Elements() { 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. + const elem_type* Elements() const + { + 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. + elem_type& ElementAt(index_type aIndex) + { + if (MOZ_UNLIKELY(aIndex >= Length())) { + 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. + const elem_type& ElementAt(index_type aIndex) const + { + if (MOZ_UNLIKELY(aIndex >= Length())) { + 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. + elem_type& SafeElementAt(index_type aIndex, elem_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. + const elem_type& SafeElementAt(index_type aIndex, const elem_type& aDef) const + { + return aIndex < Length() ? Elements()[aIndex] : aDef; + } + + // Shorthand for ElementAt(aIndex) + elem_type& operator[](index_type aIndex) { return ElementAt(aIndex); } + + // Shorthand for ElementAt(aIndex) + const elem_type& operator[](index_type aIndex) const { return ElementAt(aIndex); } + + // Shorthand for ElementAt(length - 1) + elem_type& LastElement() { return ElementAt(Length() - 1); } + + // Shorthand for ElementAt(length - 1) + const elem_type& LastElement() const { return ElementAt(Length() - 1); } + + // Shorthand for SafeElementAt(length - 1, def) + elem_type& SafeLastElement(elem_type& aDef) + { + return SafeElementAt(Length() - 1, aDef); + } + + // Shorthand for SafeElementAt(length - 1, def) + const elem_type& SafeLastElement(const elem_type& aDef) const + { + return SafeElementAt(Length() - 1, aDef); + } + + // Methods for range-based for loops. + iterator begin() { return Elements(); } + const_iterator begin() const { return Elements(); } + const_iterator cbegin() const { return begin(); } + iterator end() { return Elements() + Length(); } + const_iterator end() const { return Elements() + 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(); } + + // + // 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 + bool Contains(const Item& aItem, const Comparator& aComp) const + { + return IndexOf(aItem, 0, 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 elem_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) != 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 + index_type IndexOf(const Item& aItem, index_type aStart, + const Comparator& aComp) const + { + const elem_type* iter = Elements() + aStart; + const elem_type* iend = Elements() + Length(); + for (; iter != iend; ++iter) { + if (aComp.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 elem_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 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 + index_type LastIndexOf(const Item& aItem, index_type aStart, + const Comparator& aComp) const + { + size_type endOffset = aStart >= Length() ? Length() : aStart + 1; + const elem_type* iend = Elements() - 1; + const elem_type* iter = iend + endOffset; + for (; iter != iend; --iter) { + if (aComp.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 elem_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 + 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 + index_type BinaryIndexOf(const Item& aItem, const Comparator& aComp) const + { + using mozilla::BinarySearchIf; + typedef ::detail::ItemComparatorEq Cmp; + + size_t index; + bool found = BinarySearchIf(*this, 0, Length(), Cmp(aItem, aComp), &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 + index_type BinaryIndexOf(const Item& aItem) const + { + return BinaryIndexOf(aItem, nsDefaultComparator()); + } + + // + // Mutation methods + // + + template + typename ActualAlloc::ResultType Assign( + const nsTArray_Impl& aOther) + { + return ActualAlloc::ConvertBoolToResultType( + !!ReplaceElementsAt(0, Length(), + aOther.Elements(), aOther.Length())); + } + + template + MOZ_MUST_USE + bool Assign(const nsTArray_Impl& aOther, + const mozilla::fallible_t&) + { + return Assign(aOther); + } + + template + void Assign(nsTArray_Impl&& aOther) + { + Clear(); + SwapElements(aOther); + } + + // 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 (base_type::mHdr == EmptyHdr()) { + 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 elem_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) { + InsertElementsAt(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. +protected: + template + elem_type* ReplaceElementsAt(index_type aStart, size_type aCount, + const Item* aArray, size_type aArrayLen); + +public: + + template + MOZ_MUST_USE + elem_type* ReplaceElementsAt(index_type aStart, size_type aCount, + const Item* aArray, size_type aArrayLen, + const mozilla::fallible_t&) + { + return ReplaceElementsAt(aStart, aCount, + aArray, aArrayLen); + } + + // A variation on the ReplaceElementsAt method defined above. +protected: + template + elem_type* ReplaceElementsAt(index_type aStart, size_type aCount, + const nsTArray& aArray) + { + return ReplaceElementsAt( + aStart, aCount, aArray.Elements(), aArray.Length()); + } +public: + + template + MOZ_MUST_USE + elem_type* ReplaceElementsAt(index_type aStart, size_type aCount, + const nsTArray& aArray, + const mozilla::fallible_t&) + { + return ReplaceElementsAt(aStart, aCount, aArray); + } + + // A variation on the ReplaceElementsAt method defined above. +protected: + template + elem_type* ReplaceElementsAt(index_type aStart, size_type aCount, + const Item& aItem) + { + return ReplaceElementsAt(aStart, aCount, &aItem, 1); + } +public: + + template + MOZ_MUST_USE + elem_type* ReplaceElementsAt(index_type aStart, size_type aCount, + const Item& aItem, const mozilla::fallible_t&) + { + return ReplaceElementsAt(aStart, aCount, aItem); + } + + // A variation on the ReplaceElementsAt method defined above. + template + elem_type* ReplaceElementAt(index_type aIndex, const Item& aItem) + { + return ReplaceElementsAt(aIndex, 1, &aItem, 1); + } + + // A variation on the ReplaceElementsAt method defined above. +protected: + template + elem_type* InsertElementsAt(index_type aIndex, const Item* aArray, + size_type aArrayLen) + { + return ReplaceElementsAt(aIndex, 0, aArray, aArrayLen); + } +public: + + template + MOZ_MUST_USE + elem_type* InsertElementsAt(index_type aIndex, const Item* aArray, + size_type aArrayLen, const mozilla::fallible_t&) + { + return InsertElementsAt(aIndex, aArray, aArrayLen); + } + + // A variation on the ReplaceElementsAt method defined above. +protected: + template + elem_type* InsertElementsAt(index_type aIndex, + const nsTArray_Impl& aArray) + { + return ReplaceElementsAt( + aIndex, 0, aArray.Elements(), aArray.Length()); + } +public: + + template + MOZ_MUST_USE + elem_type* InsertElementsAt(index_type aIndex, + const nsTArray_Impl& aArray, + const mozilla::fallible_t&) + { + return InsertElementsAt(aIndex, aArray); + } + + // 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. +protected: + template + elem_type* InsertElementAt(index_type aIndex); + +public: + + MOZ_MUST_USE + elem_type* InsertElementAt(index_type aIndex, const mozilla::fallible_t&) + { + return InsertElementAt(aIndex); + } + + // Insert a new element, move constructing if possible. +protected: + template + elem_type* InsertElementAt(index_type aIndex, Item&& aItem); + +public: + + template + MOZ_MUST_USE + elem_type* InsertElementAt(index_type aIndex, Item&& aItem, + const mozilla::fallible_t&) + { + return InsertElementAt(aIndex, + mozilla::Forward(aItem)); + } + + // 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 + index_type IndexOfFirstElementGt(const Item& aItem, + const Comparator& aComp) const + { + using mozilla::BinarySearchIf; + typedef ::detail::ItemComparatorFirstElementGT Cmp; + + size_t index; + BinarySearchIf(*this, 0, Length(), Cmp(aItem, aComp), &index); + return index; + } + + // A variation on the IndexOfFirstElementGt method defined above. + template + index_type + IndexOfFirstElementGt(const Item& aItem) const + { + return IndexOfFirstElementGt(aItem, nsDefaultComparator()); + } + + // Inserts |aItem| at such an index to guarantee that if the array + // was previously sorted, it will remain sorted after this + // insertion. +protected: + template + elem_type* InsertElementSorted(Item&& aItem, const Comparator& aComp) + { + index_type index = IndexOfFirstElementGt(aItem, aComp); + return InsertElementAt( + index, mozilla::Forward(aItem)); + } +public: + + template + MOZ_MUST_USE + elem_type* InsertElementSorted(Item&& aItem, const Comparator& aComp, + const mozilla::fallible_t&) + { + return InsertElementSorted( + mozilla::Forward(aItem), aComp); + } + + // A variation on the InsertElementSorted method defined above. +protected: + template + elem_type* InsertElementSorted(Item&& aItem) + { + nsDefaultComparator comp; + return InsertElementSorted( + mozilla::Forward(aItem), comp); + } +public: + + template + MOZ_MUST_USE + elem_type* InsertElementSorted(Item&& aItem, const mozilla::fallible_t&) + { + return InsertElementSorted( + mozilla::Forward(aItem)); + } + + // 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. +protected: + template + elem_type* AppendElements(const Item* aArray, size_type aArrayLen); + +public: + + template + /* MOZ_MUST_USE */ + elem_type* AppendElements(const Item* aArray, size_type aArrayLen, + const mozilla::fallible_t&) + { + return AppendElements(aArray, aArrayLen); + } + + // A variation on the AppendElements method defined above. +protected: + template + elem_type* AppendElements(const nsTArray_Impl& aArray) + { + return AppendElements(aArray.Elements(), aArray.Length()); + } +public: + + template + /* MOZ_MUST_USE */ + elem_type* AppendElements(const nsTArray_Impl& aArray, + const mozilla::fallible_t&) + { + return AppendElements(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. +protected: + template + elem_type* AppendElements(nsTArray_Impl&& aArray); + +public: + + template + /* MOZ_MUST_USE */ + elem_type* AppendElements(nsTArray_Impl&& aArray, + const mozilla::fallible_t&) + { + return AppendElements(mozilla::Move(aArray)); + } + + // Append a new element, move constructing if possible. +protected: + template + elem_type* AppendElement(Item&& aItem); + +public: + + template + /* MOZ_MUST_USE */ + elem_type* AppendElement(Item&& aItem, + const mozilla::fallible_t&) + { + return AppendElement(mozilla::Forward(aItem)); + } + + // Append new elements without copy-constructing. This is useful to avoid + // temporaries. + // @return A pointer to the newly appended elements, or null on OOM. +protected: + template + elem_type* AppendElements(size_type aCount) { + if (!ActualAlloc::Successful(this->template EnsureCapacity( + Length() + aCount, sizeof(elem_type)))) { + return nullptr; + } + elem_type* elems = Elements() + Length(); + size_type i; + for (i = 0; i < aCount; ++i) { + elem_traits::Construct(elems + i); + } + this->IncrementLength(aCount); + return elems; + } +public: + + /* MOZ_MUST_USE */ + elem_type* AppendElements(size_type aCount, + const mozilla::fallible_t&) + { + return AppendElements(aCount); + } + + // 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. +protected: + template + elem_type* AppendElement() + { + return AppendElements(1); + } +public: + + /* MOZ_MUST_USE */ + elem_type* AppendElement(const mozilla::fallible_t&) + { + return AppendElement(); + } + + // 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); + + // A variation on the RemoveElementsAt method defined above. + void RemoveElementAt(index_type aIndex) { RemoveElementsAt(aIndex, 1); } + + // A variation on the RemoveElementsAt method defined above. + void Clear() { RemoveElementsAt(0, Length()); } + + // 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. + template + void 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; + } + + RemoveElementAt(i); + return true; + } + + // A variation on the RemoveElement method defined above that assumes + // that 'operator==' is defined for elem_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)) { + RemoveElementAt(index - 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 + typename Alloc::ResultType SwapElements(nsTArray_Impl& aOther) + { + return Alloc::Result(this->template SwapArrayElements( + aOther, sizeof(elem_type), MOZ_ALIGNOF(elem_type))); + } + + // + // Allocation + // + + // This method may increase the capacity of this array object by 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(elem_type))); + } +public: + + MOZ_MUST_USE + 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 elem_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 if the new length is guaranteed to be smaller than + // the old. +protected: + template + typename ActualAlloc::ResultType SetLength(size_type aNewLen) + { + size_type oldLen = Length(); + if (aNewLen > oldLen) { + return ActualAlloc::ConvertBoolToResultType( + InsertElementsAt(oldLen, aNewLen - oldLen) != nullptr); + } + + TruncateLength(aNewLen); + return ActualAlloc::ConvertBoolToResultType(true); + } +public: + + MOZ_MUST_USE + 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 elem_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) + { + size_type oldLen = Length(); + MOZ_ASSERT(aNewLen <= oldLen, + "caller should use SetLength instead"); + RemoveElementsAt(aNewLen, oldLen - 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 elem_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( + !!InsertElementsAt(oldLen, aMinLen - oldLen)); + } + return ActualAlloc::ConvertBoolToResultType(true); + } +public: + + MOZ_MUST_USE + bool EnsureLengthAtLeast(size_type aMinLen, const mozilla::fallible_t&) + { + return EnsureLengthAtLeast(aMinLen); + } + + // This method inserts elements into the array, constructing + // them using elem_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 +protected: + template + elem_type* InsertElementsAt(index_type aIndex, size_type aCount) + { + if (!base_type::template InsertSlotsAt(aIndex, aCount, + sizeof(elem_type), + MOZ_ALIGNOF(elem_type))) { + return nullptr; + } + + // Initialize the extra array elements + elem_type* iter = Elements() + aIndex; + elem_type* iend = iter + aCount; + for (; iter != iend; ++iter) { + elem_traits::Construct(iter); + } + + return Elements() + aIndex; + } +public: + + MOZ_MUST_USE + elem_type* InsertElementsAt(index_type aIndex, size_type aCount, + const mozilla::fallible_t&) + { + return InsertElementsAt(aIndex, aCount); + } + + // This method inserts elements into the array, constructing them + // elem_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. +protected: + template + elem_type* InsertElementsAt(index_type aIndex, size_type aCount, + const Item& aItem); + +public: + + template + MOZ_MUST_USE + elem_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(elem_type), MOZ_ALIGNOF(elem_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 elem_type* a = static_cast(aE1); + const elem_type* b = static_cast(aE2); + return c->LessThan(*a, *b) ? -1 : (c->Equals(*a, *b) ? 0 : 1); + } + + // 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) + { + NS_QuickSort(Elements(), Length(), sizeof(elem_type), + Compare, const_cast(&aComp)); + } + + // A variation on the Sort method defined above that assumes that + // 'operator<' is defined for elem_type. + void Sort() { Sort(nsDefaultComparator()); } + +protected: + using base_type::Hdr; + using base_type::ShrinkCapacity; + + // This method invokes elem_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) + { + elem_type* iter = Elements() + aStart; + elem_type *iend = iter + aCount; + for (; iter != iend; ++iter) { + elem_traits::Destruct(iter); + } + } + + // This method invokes elem_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::value, + mozilla::IsSame::value> + ::implementation(Elements(), aStart, aCount, aValues); + } +}; + +template +template +auto +nsTArray_Impl::ReplaceElementsAt(index_type aStart, size_type aCount, + const Item* aArray, size_type aArrayLen) -> elem_type* +{ + // Adjust memory allocation up-front to catch errors. + if (!ActualAlloc::Successful(this->template EnsureCapacity( + Length() + aArrayLen - aCount, sizeof(elem_type)))) { + return nullptr; + } + DestructRange(aStart, aCount); + this->template ShiftData(aStart, aCount, aArrayLen, + sizeof(elem_type), + MOZ_ALIGNOF(elem_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"); + MOZ_ASSERT(aStart + aCount <= Length(), "Invalid length"); + // Check that the previous assert didn't overflow + MOZ_ASSERT(aStart <= aStart + aCount, "Start index plus length overflows"); + DestructRange(aStart, aCount); + this->template ShiftData(aStart, aCount, 0, + sizeof(elem_type), + MOZ_ALIGNOF(elem_type)); +} + +template +template +void +nsTArray_Impl::RemoveElementsBy(Predicate aPredicate) +{ + if (base_type::mHdr == EmptyHdr()) { + return; + } + + index_type j = 0; + index_type len = Length(); + for (index_type i = 0; i < len; ++i) { + if (aPredicate(Elements()[i])) { + elem_traits::Destruct(Elements() + i); + } else { + if (j < i) { + copy_type::MoveNonOverlappingRegion(Elements() + j, Elements() + i, + 1, sizeof(elem_type)); + } + ++j; + } + } + base_type::mHdr->mLength = j; +} + +template +template +auto +nsTArray_Impl::InsertElementsAt(index_type aIndex, size_type aCount, + const Item& aItem) -> elem_type* +{ + if (!base_type::template InsertSlotsAt(aIndex, aCount, + sizeof(elem_type), + MOZ_ALIGNOF(elem_type))) { + return nullptr; + } + + // Initialize the extra array elements + elem_type* iter = Elements() + aIndex; + elem_type* iend = iter + aCount; + for (; iter != iend; ++iter) { + elem_traits::Construct(iter, aItem); + } + + return Elements() + aIndex; +} + +template +template +auto +nsTArray_Impl::InsertElementAt(index_type aIndex) -> elem_type* +{ + if (!ActualAlloc::Successful(this->template EnsureCapacity( + Length() + 1, sizeof(elem_type)))) { + return nullptr; + } + this->template ShiftData(aIndex, 0, 1, sizeof(elem_type), + MOZ_ALIGNOF(elem_type)); + elem_type* elem = Elements() + aIndex; + elem_traits::Construct(elem); + return elem; +} + +template +template +auto +nsTArray_Impl::InsertElementAt(index_type aIndex, Item&& aItem) -> elem_type* +{ + if (!ActualAlloc::Successful(this->template EnsureCapacity( + Length() + 1, sizeof(elem_type)))) { + return nullptr; + } + this->template ShiftData(aIndex, 0, 1, sizeof(elem_type), + MOZ_ALIGNOF(elem_type)); + elem_type* elem = Elements() + aIndex; + elem_traits::Construct(elem, mozilla::Forward(aItem)); + return elem; +} + +template +template +auto +nsTArray_Impl::AppendElements(const Item* aArray, size_type aArrayLen) -> elem_type* +{ + if (!ActualAlloc::Successful(this->template EnsureCapacity( + Length() + aArrayLen, sizeof(elem_type)))) { + return nullptr; + } + index_type len = Length(); + AssignRange(len, aArrayLen, aArray); + this->IncrementLength(aArrayLen); + return Elements() + len; +} + +template +template +auto +nsTArray_Impl::AppendElements(nsTArray_Impl&& aArray) -> elem_type* +{ + MOZ_ASSERT(&aArray != this, "argument must be different aArray"); + if (Length() == 0) { + SwapElements(aArray); + return Elements(); + } + + index_type len = Length(); + index_type otherLen = aArray.Length(); + if (!Alloc::Successful(this->template EnsureCapacity( + len + otherLen, sizeof(elem_type)))) { + return nullptr; + } + copy_type::MoveNonOverlappingRegion(Elements() + len, aArray.Elements(), otherLen, + sizeof(elem_type)); + this->IncrementLength(otherLen); + aArray.template ShiftData(0, otherLen, 0, sizeof(elem_type), + MOZ_ALIGNOF(elem_type)); + return Elements() + len; +} + +template +template +auto +nsTArray_Impl::AppendElement(Item&& aItem) -> elem_type* +{ + if (!ActualAlloc::Successful(this->template EnsureCapacity( + Length() + 1, sizeof(elem_type)))) { + return nullptr; + } + elem_type* elem = Elements() + Length(); + elem_traits::Construct(elem, mozilla::Forward(aItem)); + this->IncrementLength(1); + return elem; +} + +template +inline void +ImplCycleCollectionUnlink(nsTArray_Impl& aField) +{ + aField.Clear(); +} + +template +inline void +ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& aCallback, + nsTArray_Impl& 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[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: + typedef nsTArray_Impl base_type; + typedef nsTArray self_type; + typedef typename base_type::size_type size_type; + + nsTArray() {} + explicit nsTArray(size_type aCapacity) : base_type(aCapacity) {} + explicit nsTArray(const nsTArray& aOther) : base_type(aOther) {} + MOZ_IMPLICIT nsTArray(nsTArray&& aOther) : base_type(mozilla::Move(aOther)) {} + MOZ_IMPLICIT nsTArray(std::initializer_list aIL) : base_type(aIL) {} + + template + explicit nsTArray(const nsTArray_Impl& aOther) + : base_type(aOther) + { + } + template + MOZ_IMPLICIT nsTArray(nsTArray_Impl&& aOther) + : base_type(mozilla::Move(aOther)) + { + } + + self_type& operator=(const self_type& aOther) + { + base_type::operator=(aOther); + return *this; + } + template + self_type& operator=(const nsTArray_Impl& aOther) + { + base_type::operator=(aOther); + return *this; + } + self_type& operator=(self_type&& aOther) + { + base_type::operator=(mozilla::Move(aOther)); + return *this; + } + template + self_type& operator=(nsTArray_Impl&& aOther) + { + base_type::operator=(mozilla::Move(aOther)); + return *this; + } + + using base_type::AppendElement; + using base_type::AppendElements; + 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; +}; + +// +// 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() {} + explicit FallibleTArray(size_type aCapacity) : base_type(aCapacity) {} + explicit FallibleTArray(const FallibleTArray& aOther) : base_type(aOther) {} + FallibleTArray(FallibleTArray&& aOther) + : base_type(mozilla::Move(aOther)) + { + } + + template + explicit FallibleTArray(const nsTArray_Impl& aOther) + : base_type(aOther) + { + } + template + explicit FallibleTArray(nsTArray_Impl&& aOther) + : base_type(mozilla::Move(aOther)) + { + } + + self_type& operator=(const self_type& aOther) + { + base_type::operator=(aOther); + return *this; + } + template + self_type& operator=(const nsTArray_Impl& aOther) + { + base_type::operator=(aOther); + return *this; + } + self_type& operator=(self_type&& aOther) + { + base_type::operator=(mozilla::Move(aOther)); + return *this; + } + template + self_type& operator=(nsTArray_Impl&& aOther) + { + base_type::operator=(mozilla::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::elem_type elem_type; + + AutoTArray() + { + Init(); + } + + AutoTArray(const self_type& aOther) + { + Init(); + this->AppendElements(aOther); + } + + explicit AutoTArray(const base_type& aOther) + { + Init(); + this->AppendElements(aOther); + } + + explicit AutoTArray(base_type&& aOther) + { + Init(); + this->SwapElements(aOther); + } + + template + explicit AutoTArray(nsTArray_Impl&& aOther) + { + Init(); + this->SwapElements(aOther); + } + + MOZ_IMPLICIT AutoTArray(std::initializer_list aIL) + { + Init(); + this->AppendElements(aIL.begin(), aIL.size()); + } + + self_type& operator=(const self_type& aOther) + { + base_type::operator=(aOther); + return *this; + } + + template + self_type& operator=(const nsTArray_Impl& aOther) + { + base_type::operator=(aOther); + return *this; + } + +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(elem_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(elem_type)) == + reinterpret_cast(&mAutoBuf), + "GetAutoArrayBuffer needs to be fixed"); + } + + // Declare mAutoBuf aligned to the maximum of the header's alignment and + // elem_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(elem_type)]; + // Do the max operation inline to ensure that it is a compile-time constant. + mozilla::AlignedElem<(MOZ_ALIGNOF(Header) > MOZ_ALIGNOF(elem_type)) ? + MOZ_ALIGNOF(Header) : MOZ_ALIGNOF(elem_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 +{ +}; + +template +struct nsTArray_CopyChooser> +{ + typedef nsTArray_CopyWithConstructors> Type; +}; + +// 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/glue/nsTArrayForwardDeclare.h b/xpcom/glue/nsTArrayForwardDeclare.h new file mode 100644 index 000000000..f63ef5f59 --- /dev/null +++ b/xpcom/glue/nsTArrayForwardDeclare.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 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 AutoTArray; + +template +using InfallibleTArray = nsTArray; + +#endif diff --git a/xpcom/glue/nsTHashtable.h b/xpcom/glue/nsTHashtable.h new file mode 100644 index 000000000..705b0294e --- /dev/null +++ b/xpcom/glue/nsTHashtable.h @@ -0,0 +1,577 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 nsTHashtable_h__ +#define nsTHashtable_h__ + +#include "PLDHashTable.h" +#include "nsPointerHashKeys.h" +#include "mozilla/Assertions.h" +#include "mozilla/Attributes.h" +#include "mozilla/fallible.h" +#include "mozilla/MemoryChecking.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/Move.h" +#include "mozilla/OperatorNewExtensions.h" +#include "mozilla/PodOperations.h" +#include "mozilla/TypeTraits.h" + +#include + +/** + * 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 nsDataHashtable + * @see nsClassHashtable + * @author "Benjamin Smedberg " + */ + +template +class MOZ_NEEDS_NO_VTABLE_TYPE nsTHashtable +{ + typedef mozilla::fallible_t fallible_t; + static_assert(mozilla::IsPointer::value, + "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(); + + nsTHashtable(nsTHashtable&& aOther); + + /** + * 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( + const_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); } + + /** + * Get the entry associated with a key, or create a new entry, + * @param aKey the key to retrieve + * @return pointer to the entry class retreived; nullptr only if memory + can't be allocated + */ + EntryType* PutEntry(KeyType aKey) + { + // infallible add + return static_cast(mTable.Add(EntryType::KeyToPointer(aKey))); + } + + MOZ_MUST_USE + EntryType* PutEntry(KeyType aKey, const fallible_t&) + { + return static_cast(mTable.Add(EntryType::KeyToPointer(aKey), + mozilla::fallible)); + } + + /** + * Remove the entry associated with a key. + * @param aKey of the entry to remove + */ + void RemoveEntry(KeyType aKey) + { + mTable.Remove(EntryType::KeyToPointer(aKey)); + } + + /** + * 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); + } + + // 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 : public PLDHashTable::Iterator + { + public: + typedef PLDHashTable::Iterator Base; + + explicit Iterator(nsTHashtable* aTable) : Base(&aTable->mTable) {} + Iterator(Iterator&& aOther) : Base(aOther.mTable) {} + ~Iterator() {} + + EntryType* Get() const { return static_cast(Base::Get()); } + + private: + Iterator() = delete; + Iterator(const Iterator&) = delete; + Iterator& operator=(const Iterator&) = delete; + Iterator& operator=(const Iterator&&) = delete; + }; + + Iterator Iter() { return Iterator(this); } + + Iterator ConstIter() const + { + return Iterator(const_cast(this)); + } + + /** + * 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()); + mozilla::Swap(this->mTable, aOther.mTable); + } + +#ifdef DEBUG + /** + * 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(); + } +#endif + +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); + + static void s_InitEntry(PLDHashEntryHdr* aEntry, const void* aKey); + +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; +}; + +// +// template definitions +// + +template +nsTHashtable::nsTHashtable(nsTHashtable&& aOther) + : mTable(mozilla::Move(aOther.mTable)) +{ + // aOther shouldn't touch mTable after this, because we've stolen the table's + // pointers but not overwitten them. + MOZ_MAKE_MEM_UNDEFINED(&aOther.mTable, sizeof(aOther.mTable)); +} + +template +nsTHashtable::~nsTHashtable() +{ +} + +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 ? PLDHashTable::MoveEntryStub : s_CopyEntry, + s_ClearEntry, + s_InitEntry + }; + 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 ((const EntryType*)aEntry)->KeyEquals( + static_cast(aKey)); +} + +template +void +nsTHashtable::s_CopyEntry(PLDHashTable* aTable, + const PLDHashEntryHdr* aFrom, + PLDHashEntryHdr* aTo) +{ + EntryType* fromEntry = + const_cast(static_cast(aFrom)); + + new (mozilla::KnownNotNull, aTo) EntryType(mozilla::Move(*fromEntry)); + + fromEntry->~EntryType(); +} + +template +void +nsTHashtable::s_ClearEntry(PLDHashTable* aTable, + PLDHashEntryHdr* aEntry) +{ + static_cast(aEntry)->~EntryType(); +} + +template +void +nsTHashtable::s_InitEntry(PLDHashEntryHdr* aEntry, + const void* aKey) +{ + new (mozilla::KnownNotNull, aEntry) EntryType(static_cast(aKey)); +} + +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; + + /* Wrapper functions */ + using Base::GetGeneration; + using Base::Count; + using Base::IsEmpty; + using Base::Clear; + + using Base::ShallowSizeOfExcludingThis; + using Base::ShallowSizeOfIncludingThis; + +#ifdef DEBUG + using Base::MarkImmutable; +#endif + + 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)); + } + + MOZ_MUST_USE + EntryType* PutEntry(T* aKey, const mozilla::fallible_t&) + { + return reinterpret_cast( + Base::PutEntry(aKey, mozilla::fallible)); + } + + void RemoveEntry(T* aKey) + { + Base::RemoveEntry(aKey); + } + + void RemoveEntry(EntryType* aEntry) + { + Base::RemoveEntry(reinterpret_cast<::detail::VoidPtrHashKey*>(aEntry)); + } + + void RawRemoveEntry(EntryType* aEntry) + { + Base::RawRemoveEntry(reinterpret_cast<::detail::VoidPtrHashKey*>(aEntry)); + } + + class Iterator : public Base::Iterator + { + public: + typedef nsTHashtable::Base::Iterator Base; + + explicit Iterator(nsTHashtable* aTable) : Base(aTable) {} + Iterator(Iterator&& aOther) : Base(mozilla::Move(aOther)) {} + ~Iterator() = default; + + EntryType* Get() const { return reinterpret_cast(Base::Get()); } + + private: + Iterator() = delete; + Iterator(const Iterator&) = delete; + Iterator& operator=(const Iterator&) = delete; + Iterator& operator=(Iterator&&) = delete; + }; + + Iterator Iter() { return Iterator(this); } + + Iterator ConstIter() const + { + return Iterator(const_cast(this)); + } + + void SwapElements(nsTHashtable& aOther) + { + Base::SwapElements(aOther); + } +}; + +#endif // nsTHashtable_h__ diff --git a/xpcom/glue/nsTObserverArray.cpp b/xpcom/glue/nsTObserverArray.cpp new file mode 100644 index 000000000..dd68631f4 --- /dev/null +++ b/xpcom/glue/nsTObserverArray.cpp @@ -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/. */ + +#include "nsTObserverArray.h" + +void +nsTObserverArray_base::AdjustIterators(index_type aModPos, + diff_type aAdjustment) +{ + NS_PRECONDITION(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/glue/nsTObserverArray.h b/xpcom/glue/nsTObserverArray.h new file mode 100644 index 000000000..e9966d080 --- /dev/null +++ b/xpcom/glue/nsTObserverArray.h @@ -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/. */ + +#ifndef nsTObserverArray_h___ +#define nsTObserverArray_h___ + +#include "mozilla/MemoryReporting.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 + { + 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 elem_type; + typedef nsTArray array_type; + + nsAutoTObserverArray() {} + + // + // 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 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. + elem_type& ElementAt(index_type aIndex) + { + return mArray.ElementAt(aIndex); + } + + // Same as above, but readonly. + const elem_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. + elem_type& SafeElementAt(index_type aIndex, elem_type& aDef) + { + return mArray.SafeElementAt(aIndex, aDef); + } + + // Same as above, but readonly. + const elem_type& SafeElementAt(index_type aIndex, const elem_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 elem_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 elem_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, + // @return A pointer to the newly inserted element, or a null on DOM + template + elem_type* InsertElementAt(index_type aIndex, const Item& aItem) + { + elem_type* item = mArray.InsertElementAt(aIndex, aItem); + AdjustIterators(aIndex, 1); + return item; + } + + // Same as above but without copy constructing. + // This is useful to avoid temporaries. + elem_type* InsertElementAt(index_type aIndex) + { + elem_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 elem_type. + // @param aItem The item to prepend. + // @return true if the element was found, or inserted successfully. + template + bool PrependElementUnlessExists(const Item& aItem) + { + if (Contains(aItem)) { + return true; + } + + bool inserted = mArray.InsertElementAt(0, aItem) != nullptr; + AdjustIterators(0, 1); + return inserted; + } + + // Append an element to the array. + // @param aItem The item to append. + // @return A pointer to the newly appended element, or null on OOM. + template + elem_type* AppendElement(const Item& aItem) + { + return mArray.AppendElement(aItem); + } + + // Same as above, but without copy-constructing. This is useful to avoid + // temporaries. + elem_type* AppendElement() + { + return mArray.AppendElement(); + } + + // Append an element to the array unless it already exists in the array. + // 'operator==' must be defined for elem_type. + // @param aItem The item to append. + // @return true if the element was found, or inserted successfully. + template + bool AppendElementUnlessExists(const Item& aItem) + { + return Contains(aItem) || AppendElement(aItem) != nullptr; + } + + // 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 elem_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. + void RemoveElementsBy(mozilla::function aPredicate) + { + index_type i = 0; + mArray.RemoveElementsBy([&](const elem_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(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. + elem_type& GetNext() + { + NS_ASSERTION(HasMore(), "iterating beyond end of array"); + return base_type::mArray.ElementAt(base_type::mPosition++); + } + }; + + // 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. + elem_type& GetNext() + { + NS_ASSERTION(HasMore(), "iterating beyond end of array"); + return base_type::mArray.ElementAt(base_type::mPosition++); + } + + 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. + elem_type& GetNext() + { + NS_ASSERTION(HasMore(), "iterating beyond start of array"); + return base_type::mArray.ElementAt(--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); + } + }; + +protected: + AutoTArray mArray; +}; + +template +class nsTObserverArray : public nsAutoTObserverArray +{ +public: + typedef nsAutoTObserverArray base_type; + typedef nsTObserverArray_base::size_type size_type; + + // + // Initialization methods + // + + nsTObserverArray() {} + + // Initialize this array and pre-allocate some number of elements. + explicit nsTObserverArray(size_type aCapacity) + { + base_type::mArray.SetCapacity(aCapacity); + } +}; + +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); + } +} + +// XXXbz I wish I didn't have to pass in the observer type, but I +// don't see a way to get it out of array_. +// Note that this macro only works if the array holds pointers to XPCOM objects. +#define NS_OBSERVER_ARRAY_NOTIFY_XPCOM_OBSERVERS(array_, obstype_, func_, params_) \ + PR_BEGIN_MACRO \ + nsTObserverArray::ForwardIterator iter_(array_); \ + RefPtr obs_; \ + while (iter_.HasMore()) { \ + obs_ = iter_.GetNext(); \ + obs_ -> func_ params_ ; \ + } \ + PR_END_MACRO + +// Note that this macro only works if the array holds pointers to XPCOM objects. +#define NS_OBSERVER_ARRAY_NOTIFY_OBSERVERS(array_, obstype_, func_, params_) \ + PR_BEGIN_MACRO \ + nsTObserverArray::ForwardIterator iter_(array_); \ + obstype_* obs_; \ + while (iter_.HasMore()) { \ + obs_ = iter_.GetNext(); \ + obs_ -> func_ params_ ; \ + } \ + PR_END_MACRO + +#define NS_OBSERVER_ARRAY_NOTIFY_OBSERVERS_WITH_QI(array_, basetype_, obstype_, func_, params_) \ + PR_BEGIN_MACRO \ + nsTObserverArray::ForwardIterator iter_(array_); \ + basetype_* obsbase_; \ + while (iter_.HasMore()) { \ + obsbase_ = iter_.GetNext(); \ + nsCOMPtr obs_ = do_QueryInterface(obsbase_); \ + if (obs_) { \ + obs_ -> func_ params_ ; \ + } \ + } \ + PR_END_MACRO +#endif // nsTObserverArray_h___ diff --git a/xpcom/glue/nsTPriorityQueue.h b/xpcom/glue/nsTPriorityQueue.h new file mode 100644 index 000000000..20a0fc8a5 --- /dev/null +++ b/xpcom/glue/nsTPriorityQueue.h @@ -0,0 +1,161 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 "nsTArray.h" +#include "nsDebug.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) {} + + /** + * Copy constructor + */ + nsTPriorityQueue(const nsTPriorityQueue& aOther) + : mElements(aOther.mElements) + , mCompare(aOther.mCompare) + { + } + + /** + * @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. + */ + bool Push(const T& aElement) + { + T* elem = mElements.AppendElement(aElement); + if (!elem) { + return false; // Out of memory + } + + // 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; + } + Swap(i, parent); + i = parent; + } + + return true; + } + + /** + * 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 = mElements[0]; + + // Move last to front + mElements[0] = mElements[mElements.Length() - 1]; + mElements.TruncateLength(mElements.Length() - 1); + + // Sift down + size_type i = 0; + while (2 * i + 1 < mElements.Length()) { + 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 < mElements.Length() && + mCompare.LessThan(mElements[r_child], mElements[swap])) { + swap = r_child; + } + if (swap == i) { + break; + } + Swap(i, 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(); } + +protected: + /** + * Swaps the elements at the specified indices. + */ + void Swap(size_type aIndexA, size_type aIndexB) + { + T temp = mElements[aIndexA]; + mElements[aIndexA] = mElements[aIndexB]; + mElements[aIndexB] = temp; + } + + nsTArray mElements; + Compare mCompare; // Comparator object +}; + +#endif // NS_TPRIORITY_QUEUE_H_ diff --git a/xpcom/glue/nsTWeakRef.h b/xpcom/glue/nsTWeakRef.h new file mode 100644 index 000000000..6c9a5e8eb --- /dev/null +++ b/xpcom/glue/nsTWeakRef.h @@ -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/. */ + +#ifndef nsTWeakRef_h__ +#define nsTWeakRef_h__ + +#ifndef nsDebug_h___ +#include "nsDebug.h" +#endif + +/** + * A weak reference class for use with generic C++ objects. NOT THREADSAFE! + * + * Example usage: + * + * class A { + * public: + * A() : mWeakSelf(this) { + * } + * ~A() { + * mWeakSelf.forget(); + * } + * void Bar() { printf("Bar!\n"); } + * const nsTWeakRef
&AsWeakRef() const { return mWeakSelf; } + * private: + * nsTWeakRef mWeakSelf; + * }; + * + * class B { + * public: + * void SetA(const nsTWeakRef &a) { + * mA = a; + * } + * void Foo() { + * if (mA) + * mA->Bar(); + * } + * private: + * nsTWeakRef mA; + * }; + * + * void Test() { + * B b; + * { + * A a; + * b.SetA(a.AsWeakRef()); + * b.Foo(); // prints "Bar!" + * } + * b.Foo(); // prints nothing because |a| has already been destroyed + * } + * + * One can imagine much more complex examples, especially when asynchronous + * event processing is involved. + * + * Keep in mind that you should only ever need a class like this when you have + * multiple instances of B, such that it is not possible for A and B to simply + * have pointers to one another. + */ +template +class nsTWeakRef +{ +public: + ~nsTWeakRef() + {} + + /** + * Construct from an object pointer (may be null). + */ + explicit nsTWeakRef(Type* aObj = nullptr) + { + if (aObj) { + mRef = new Inner(aObj); + } else { + mRef = nullptr; + } + } + + /** + * Construct from another weak reference object. + */ + explicit nsTWeakRef(const nsTWeakRef& aOther) : mRef(aOther.mRef) + {} + + /** + * Assign from an object pointer. + */ + nsTWeakRef& operator=(Type* aObj) + { + if (aObj) { + mRef = new Inner(aObj); + } else { + mRef = nullptr; + } + return *this; + } + + /** + * Assign from another weak reference object. + */ + nsTWeakRef& operator=(const nsTWeakRef& aOther) + { + mRef = aOther.mRef; + return *this; + } + + /** + * Get the referenced object. This method may return null if the reference + * has been cleared or if an out-of-memory error occurred at assignment. + */ + Type* get() const { return mRef ? mRef->mObj : nullptr; } + + /** + * Called to "null out" the weak reference. Typically, the object referenced + * by this weak reference calls this method when it is being destroyed. + * @returns The former referenced object. + */ + Type* forget() + { + Type* obj; + if (mRef) { + obj = mRef->mObj; + mRef->mObj = nullptr; + mRef = nullptr; + } else { + obj = nullptr; + } + return obj; + } + + /** + * Allow |*this| to be treated as a |Type*| for convenience. + */ + operator Type*() const { return get(); } + + /** + * Allow |*this| to be treated as a |Type*| for convenience. Use with + * caution since this method will crash if the referenced object is null. + */ + Type* operator->() const MOZ_NO_ADDREF_RELEASE_ON_RETURN + { + NS_ASSERTION(mRef && mRef->mObj, + "You can't dereference a null weak reference with operator->()."); + return get(); + } + +private: + + struct Inner + { + int mCnt; + Type* mObj; + + explicit Inner(Type* aObj) + : mCnt(1) + , mObj(aObj) + { + } + void AddRef() + { + ++mCnt; + } + void Release() + { + if (--mCnt == 0) { + delete this; + } + } + }; + + RefPtr mRef; +}; + +#endif // nsTWeakRef_h__ diff --git a/xpcom/glue/nsTextFormatter.cpp b/xpcom/glue/nsTextFormatter.cpp new file mode 100644 index 000000000..ab9941d15 --- /dev/null +++ b/xpcom/glue/nsTextFormatter.cpp @@ -0,0 +1,1394 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 + */ + +/* + * Copied from xpcom/ds/nsTextFormatter.cpp r1.22 + * Changed to use nsMemory and Frozen linkage + * -- Prasad + */ + +#include +#include +#include +#include +#include "prdtoa.h" +#include "mozilla/Logging.h" +#include "mozilla/Sprintf.h" +#include "prmem.h" +#include "nsCRTGlue.h" +#include "nsTextFormatter.h" +#include "nsMemory.h" + +/* +** Note: on some platforms va_list is defined as an array, +** and requires array notation. +*/ + +#ifdef HAVE_VA_COPY +#define VARARGS_ASSIGN(foo, bar) VA_COPY(foo,bar) +#elif defined(HAVE_VA_LIST_AS_ARRAY) +#define VARARGS_ASSIGN(foo, bar) foo[0] = bar[0] +#else +#define VARARGS_ASSIGN(foo, bar) (foo) = (bar) +#endif + +typedef struct SprintfStateStr SprintfState; + +struct SprintfStateStr +{ + int (*stuff)(SprintfState* aState, const char16_t* aStr, uint32_t aLen); + + char16_t* base; + char16_t* cur; + uint32_t maxlen; + + void* stuffclosure; +}; + +/* +** Numbered Arguement State +*/ +struct NumArgState +{ + int type; /* type of the current ap */ + va_list ap; /* point to the corresponding position on ap */ + + enum Type + { + INT16, + UINT16, + INTN, + UINTN, + INT32, + UINT32, + INT64, + UINT64, + STRING, + DOUBLE, + INTSTR, + UNISTRING, + UNKNOWN + }; +}; + +#define NAS_DEFAULT_NUM 20 /* default number of NumberedArgumentState array */ + +#define _LEFT 0x1 +#define _SIGNED 0x2 +#define _SPACED 0x4 +#define _ZEROS 0x8 +#define _NEG 0x10 + +#define ELEMENTS_OF(array_) (sizeof(array_) / sizeof(array_[0])) + +#define PR_CHECK_DELETE(nas) if (nas && (nas != nasArray)) { PR_DELETE(nas); } + +/* +** Fill into the buffer using the data in src +*/ +static int +fill2(SprintfState* 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 +*/ +static int +fill_n(SprintfState* aState, const char16_t* aSrc, int aSrcLen, int aWidth, + int aPrec, int aType, 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 ((aType & 1) == 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 long into its printable form +*/ +static int +cvt_l(SprintfState* aState, long aNum, int aWidth, int aPrec, int aRadix, + int aType, 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) { + int digit = (((unsigned long)aNum) % aRadix) & 0xF; + *--cvt = aHexStr[digit]; + digits++; + aNum = (long)(((unsigned long)aNum) / aRadix); + } + 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, aType, aFlags); +} + +/* +** Convert a 64-bit integer into its printable form +*/ +static int +cvt_ll(SprintfState* aState, int64_t aNum, int aWidth, int aPrec, int aRadix, + int aType, int aFlags, const char16_t* aHexStr) +{ + char16_t cvtbuf[100]; + char16_t* cvt; + int digits; + int64_t rad; + + /* 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. + */ + rad = aRadix; + cvt = &cvtbuf[0] + ELEMENTS_OF(cvtbuf); + digits = 0; + while (aNum != 0) { + *--cvt = aHexStr[int32_t(aNum % rad) & 0xf]; + digits++; + aNum /= rad; + } + 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, aType, aFlags); +} + +/* +** Convert a double precision floating point number into its printable +** form. +*/ +static int +cvt_f(SprintfState* 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'; + MOZ_FALLTHROUGH; + case 'e': + numdigits = aPrec + 1; + mode = 2; + break; + case 'G': + exp = 'E'; + MOZ_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. +*/ +static int +cvt_S(SprintfState* 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. +*/ +static int +cvt_s(SprintfState* aState, const char* aStr, int aWidth, int aPrec, int aFlags) +{ + NS_ConvertUTF8toUTF16 utf16Val(aStr); + return cvt_S(aState, utf16Val.get(), aWidth, aPrec, aFlags); +} + +/* +** BuildArgArray stands for Numbered Argument list Sprintf +** for example, +** fmp = "%4$i, %2$d, %3s, %1d"; +** the number must start from 1, and no gap among them +*/ + +static struct NumArgState* +BuildArgArray(const char16_t* aFmt, va_list aAp, int* aRv, + struct NumArgState* aNasArray) +{ + int number = 0, cn = 0, i; + const char16_t* p; + char16_t c; + struct NumArgState* nas; + + /* + ** first pass: + ** detemine how many legal % I have got, then allocate space + */ + p = aFmt; + *aRv = 0; + i = 0; + while ((c = *p++) != 0) { + if (c != '%') { + continue; + } + /* skip %% case */ + if ((c = *p++) == '%') { + continue; + } + + while (c != 0) { + if (c > '9' || c < '0') { + /* numbered argument csae */ + if (c == '$') { + if (i > 0) { + *aRv = -1; + return nullptr; + } + number++; + break; + + } else { + /* non-numbered argument case */ + if (number > 0) { + *aRv = -1; + return nullptr; + } + i = 1; + break; + } + } + c = *p++; + } + } + + if (number == 0) { + return nullptr; + } + + if (number > NAS_DEFAULT_NUM) { + nas = (struct NumArgState*)moz_xmalloc(number * sizeof(struct NumArgState)); + if (!nas) { + *aRv = -1; + return nullptr; + } + } else { + nas = aNasArray; + } + + for (i = 0; i < number; i++) { + nas[i].type = NumArgState::UNKNOWN; + } + + /* + ** second pass: + ** set nas[].type + */ + p = aFmt; + while ((c = *p++) != 0) { + if (c != '%') { + continue; + } + c = *p++; + if (c == '%') { + continue; + } + cn = 0; + /* should improve error check later */ + while (c && c != '$') { + cn = cn * 10 + c - '0'; + c = *p++; + } + + if (!c || cn < 1 || cn > number) { + *aRv = -1; + break; + } + + /* nas[cn] starts from 0, and make sure + nas[cn].type is not assigned */ + cn--; + if (nas[cn].type != NumArgState::UNKNOWN) { + continue; + } + + c = *p++; + + /* width */ + if (c == '*') { + /* not supported feature, for the argument is not numbered */ + *aRv = -1; + break; + } else { + while ((c >= '0') && (c <= '9')) { + c = *p++; + } + } + + /* precision */ + if (c == '.') { + c = *p++; + if (c == '*') { + /* not supported feature, for the argument is not numbered */ + *aRv = -1; + break; + } else { + while ((c >= '0') && (c <= '9')) { + c = *p++; + } + } + } + + /* size */ + nas[cn].type = NumArgState::INTN; + if (c == 'h') { + nas[cn].type = NumArgState::INT16; + c = *p++; + } else if (c == 'L') { + /* XXX not quite sure here */ + nas[cn].type = NumArgState::INT64; + c = *p++; + } else if (c == 'l') { + nas[cn].type = NumArgState::INT32; + c = *p++; + if (c == 'l') { + nas[cn].type = NumArgState::INT64; + c = *p++; + } + } + + /* format */ + switch (c) { + case 'd': + case 'c': + case 'i': + case 'o': + case 'u': + case 'x': + case 'X': + break; + + case 'e': + case 'f': + case 'g': + nas[cn].type = NumArgState::DOUBLE; + break; + + case 'p': + /* XXX should use cpp */ + if (sizeof(void*) == sizeof(int32_t)) { + nas[cn].type = NumArgState::UINT32; + } else if (sizeof(void*) == sizeof(int64_t)) { + nas[cn].type = NumArgState::UINT64; + } else if (sizeof(void*) == sizeof(int)) { + nas[cn].type = NumArgState::UINTN; + } else { + nas[cn].type = NumArgState::UNKNOWN; + } + break; + + case 'C': + /* XXX not supported I suppose */ + PR_ASSERT(0); + nas[cn].type = NumArgState::UNKNOWN; + break; + + case 'S': + nas[cn].type = NumArgState::UNISTRING; + break; + + case 's': + nas[cn].type = NumArgState::STRING; + break; + + case 'n': + nas[cn].type = NumArgState::INTSTR; + break; + + default: + PR_ASSERT(0); + nas[cn].type = NumArgState::UNKNOWN; + break; + } + + /* get a legal para. */ + if (nas[cn].type == NumArgState::UNKNOWN) { + *aRv = -1; + break; + } + } + + + /* + ** third pass + ** fill the nas[cn].ap + */ + if (*aRv < 0) { + if (nas != aNasArray) { + PR_DELETE(nas); + } + return nullptr; + } + + cn = 0; + while (cn < number) { + if (nas[cn].type == NumArgState::UNKNOWN) { + cn++; + continue; + } + + VARARGS_ASSIGN(nas[cn].ap, aAp); + + switch (nas[cn].type) { + case NumArgState::INT16: + case NumArgState::UINT16: + case NumArgState::INTN: + case NumArgState::UINTN: (void)va_arg(aAp, int); break; + + case NumArgState::INT32: (void)va_arg(aAp, int32_t); break; + + case NumArgState::UINT32: (void)va_arg(aAp, uint32_t); break; + + case NumArgState::INT64: (void)va_arg(aAp, int64_t); break; + + case NumArgState::UINT64: (void)va_arg(aAp, uint64_t); break; + + case NumArgState::STRING: (void)va_arg(aAp, char*); break; + + case NumArgState::INTSTR: (void)va_arg(aAp, int*); break; + + case NumArgState::DOUBLE: (void)va_arg(aAp, double); break; + + case NumArgState::UNISTRING: (void)va_arg(aAp, char16_t*); break; + + default: + if (nas != aNasArray) { + PR_DELETE(nas); + } + *aRv = -1; + va_end(aAp); + return nullptr; + } + cn++; + } + va_end(aAp); + return nas; +} + + +/* +** The workhorse sprintf code. +*/ +static int +dosprintf(SprintfState* aState, const char16_t* aFmt, va_list aAp) +{ + char16_t c; + int flags, width, prec, radix, type; + union + { + char16_t ch; + int i; + long l; + int64_t ll; + double d; + const char* s; + const char16_t* S; + int* ip; + } u; + char16_t space = ' '; + + nsAutoString hex; + hex.AssignLiteral("0123456789abcdef"); + + nsAutoString HEX; + HEX.AssignLiteral("0123456789ABCDEF"); + + const char16_t* hexp; + int rv, i; + struct NumArgState* nas = nullptr; + struct NumArgState nasArray[NAS_DEFAULT_NUM]; + + + /* + ** build an argument array, IF the aFmt is numbered argument + ** list style, to contain the Numbered Argument list pointers + */ + nas = BuildArgArray(aFmt, aAp, &rv, nasArray); + if (rv < 0) { + /* the aFmt contains error Numbered Argument format, jliu@netscape.com */ + PR_ASSERT(0); + return rv; + } + + while ((c = *aFmt++) != 0) { + if (c != '%') { + rv = (*aState->stuff)(aState, aFmt - 1, 1); + if (rv < 0) { + va_end(aAp); + PR_CHECK_DELETE(nas); + return rv; + } + continue; + } + + /* + ** 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) { + va_end(aAp); + PR_CHECK_DELETE(nas); + return rv; + } + continue; + } + + if (nas) { + /* the aFmt contains the Numbered Arguments feature */ + i = 0; + /* should improve error check later */ + while (c && c != '$') { + i = (i * 10) + (c - '0'); + c = *aFmt++; + } + + if (nas[i - 1].type == NumArgState::UNKNOWN) { + if (nas != nasArray) { + PR_DELETE(nas); + } + va_end(aAp); + return -1; + } + + VARARGS_ASSIGN(aAp, nas[i - 1].ap); + c = *aFmt++; + } + + /* + * 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 == '*') { + c = *aFmt++; + width = va_arg(aAp, int); + } else { + width = 0; + while ((c >= '0') && (c <= '9')) { + width = (width * 10) + (c - '0'); + c = *aFmt++; + } + } + + /* precision */ + prec = -1; + if (c == '.') { + c = *aFmt++; + if (c == '*') { + c = *aFmt++; + prec = va_arg(aAp, int); + } else { + prec = 0; + while ((c >= '0') && (c <= '9')) { + prec = (prec * 10) + (c - '0'); + c = *aFmt++; + } + } + } + + /* size */ + type = NumArgState::INTN; + if (c == 'h') { + type = NumArgState::INT16; + c = *aFmt++; + } else if (c == 'L') { + /* XXX not quite sure here */ + type = NumArgState::INT64; + c = *aFmt++; + } else if (c == 'l') { + type = NumArgState::INT32; + c = *aFmt++; + if (c == 'l') { + type = NumArgState::INT64; + c = *aFmt++; + } + } + + /* format */ + hexp = hex.get(); + switch (c) { + case 'd': + case 'i': /* decimal/integer */ + radix = 10; + goto fetch_and_convert; + + case 'o': /* octal */ + radix = 8; + type |= 1; + goto fetch_and_convert; + + case 'u': /* unsigned decimal */ + radix = 10; + type |= 1; + goto fetch_and_convert; + + case 'x': /* unsigned hex */ + radix = 16; + type |= 1; + goto fetch_and_convert; + + case 'X': /* unsigned HEX */ + radix = 16; + hexp = HEX.get(); + type |= 1; + goto fetch_and_convert; + + fetch_and_convert: + switch (type) { + case NumArgState::INT16: + u.l = va_arg(aAp, int); + if (u.l < 0) { + u.l = -u.l; + flags |= _NEG; + } + goto do_long; + case NumArgState::UINT16: + u.l = va_arg(aAp, int) & 0xffff; + goto do_long; + case NumArgState::INTN: + u.l = va_arg(aAp, int); + if (u.l < 0) { + u.l = -u.l; + flags |= _NEG; + } + goto do_long; + case NumArgState::UINTN: + u.l = (long)va_arg(aAp, unsigned int); + goto do_long; + + case NumArgState::INT32: + u.l = va_arg(aAp, int32_t); + if (u.l < 0) { + u.l = -u.l; + flags |= _NEG; + } + goto do_long; + case NumArgState::UINT32: + u.l = (long)va_arg(aAp, uint32_t); + do_long: + rv = cvt_l(aState, u.l, width, prec, radix, type, flags, hexp); + if (rv < 0) { + va_end(aAp); + PR_CHECK_DELETE(nas); + return rv; + } + break; + + case NumArgState::INT64: + u.ll = va_arg(aAp, int64_t); + if (u.ll < 0) { + u.ll = -u.ll; + flags |= _NEG; + } + goto do_longlong; + case NumArgState::UINT64: + u.ll = va_arg(aAp, uint64_t); + do_longlong: + rv = cvt_ll(aState, u.ll, width, prec, radix, type, flags, hexp); + if (rv < 0) { + va_end(aAp); + PR_CHECK_DELETE(nas); + return rv; + } + break; + } + break; + + case 'e': + case 'E': + case 'f': + case 'g': + case 'G': + u.d = va_arg(aAp, double); + rv = cvt_f(aState, u.d, width, prec, c, flags); + if (rv < 0) { + return rv; + } + break; + + case 'c': + u.ch = va_arg(aAp, int); + if ((flags & _LEFT) == 0) { + while (width-- > 1) { + rv = (*aState->stuff)(aState, &space, 1); + if (rv < 0) { + va_end(aAp); + PR_CHECK_DELETE(nas); + return rv; + } + } + } + rv = (*aState->stuff)(aState, &u.ch, 1); + if (rv < 0) { + va_end(aAp); + PR_CHECK_DELETE(nas); + return rv; + } + if (flags & _LEFT) { + while (width-- > 1) { + rv = (*aState->stuff)(aState, &space, 1); + if (rv < 0) { + va_end(aAp); + PR_CHECK_DELETE(nas); + return rv; + } + } + } + break; + + case 'p': + if (sizeof(void*) == sizeof(int32_t)) { + type = NumArgState::UINT32; + } else if (sizeof(void*) == sizeof(int64_t)) { + type = NumArgState::UINT64; + } else if (sizeof(void*) == sizeof(int)) { + type = NumArgState::UINTN; + } else { + PR_ASSERT(0); + break; + } + radix = 16; + goto fetch_and_convert; + +#if 0 + case 'C': + /* XXX not supported I suppose */ + PR_ASSERT(0); + break; +#endif + + case 'S': + u.S = va_arg(aAp, const char16_t*); + rv = cvt_S(aState, u.S, width, prec, flags); + if (rv < 0) { + va_end(aAp); + PR_CHECK_DELETE(nas); + return rv; + } + break; + + case 's': + u.s = va_arg(aAp, const char*); + rv = cvt_s(aState, u.s, width, prec, flags); + if (rv < 0) { + va_end(aAp); + PR_CHECK_DELETE(nas); + return rv; + } + break; + + case 'n': + u.ip = va_arg(aAp, int*); + if (u.ip) { + *u.ip = aState->cur - aState->base; + } + break; + + default: + /* Not a % token after all... skip it */ +#if 0 + PR_ASSERT(0); +#endif + char16_t perct = '%'; + rv = (*aState->stuff)(aState, &perct, 1); + if (rv < 0) { + va_end(aAp); + PR_CHECK_DELETE(nas); + return rv; + } + rv = (*aState->stuff)(aState, aFmt - 1, 1); + if (rv < 0) { + va_end(aAp); + PR_CHECK_DELETE(nas); + return rv; + } + } + } + + /* Stuff trailing NUL */ + char16_t null = '\0'; + + rv = (*aState->stuff)(aState, &null, 1); + + va_end(aAp); + PR_CHECK_DELETE(nas); + + return rv; +} + +/************************************************************************/ + +static int +StringStuff(SprintfState* aState, const char16_t* aStr, uint32_t aLen) +{ + if (*aStr == '\0') { + return 0; + } + + 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; +} + +/* +** Stuff routine that automatically grows the malloc'd output buffer +** before it overflows. +*/ +static int +GrowStuff(SprintfState* aState, const char16_t* aStr, uint32_t aLen) +{ + ptrdiff_t off; + char16_t* newbase; + uint32_t newlen; + + off = aState->cur - aState->base; + if (off + aLen >= aState->maxlen) { + /* Grow the buffer */ + newlen = aState->maxlen + ((aLen > 32) ? aLen : 32); + if (aState->base) { + newbase = (char16_t*)moz_xrealloc(aState->base, + newlen * sizeof(char16_t)); + } else { + newbase = (char16_t*)moz_xmalloc(newlen * sizeof(char16_t)); + } + if (!newbase) { + /* Ran out of memory */ + return -1; + } + aState->base = newbase; + aState->maxlen = newlen; + aState->cur = aState->base + off; + } + + /* Copy data */ + while (aLen) { + --aLen; + *aState->cur++ = *aStr++; + } + MOZ_ASSERT((uint32_t)(aState->cur - aState->base) <= aState->maxlen); + return 0; +} + +/* +** sprintf into a malloc'd buffer +*/ +char16_t* +nsTextFormatter::smprintf(const char16_t* aFmt, ...) +{ + va_list ap; + char16_t* rv; + + va_start(ap, aFmt); + rv = nsTextFormatter::vsmprintf(aFmt, ap); + va_end(ap); + return rv; +} + +uint32_t +nsTextFormatter::ssprintf(nsAString& aOut, const char16_t* aFmt, ...) +{ + va_list ap; + uint32_t rv; + + va_start(ap, aFmt); + rv = nsTextFormatter::vssprintf(aOut, aFmt, ap); + va_end(ap); + return rv; +} + +uint32_t +nsTextFormatter::vssprintf(nsAString& aOut, const char16_t* aFmt, va_list aAp) +{ + SprintfState ss; + ss.stuff = StringStuff; + ss.base = 0; + ss.cur = 0; + ss.maxlen = 0; + ss.stuffclosure = &aOut; + + aOut.Truncate(); + int n = dosprintf(&ss, aFmt, aAp); + return n ? n - 1 : n; +} + +char16_t* +nsTextFormatter::vsmprintf(const char16_t* aFmt, va_list aAp) +{ + SprintfState ss; + int rv; + + ss.stuff = GrowStuff; + ss.base = 0; + ss.cur = 0; + ss.maxlen = 0; + rv = dosprintf(&ss, aFmt, aAp); + if (rv < 0) { + if (ss.base) { + PR_DELETE(ss.base); + } + return 0; + } + return ss.base; +} + +/* +** Stuff routine that discards overflow data +*/ +static int +LimitStuff(SprintfState* 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; +} + +/* +** sprintf into a fixed size buffer. Make sure there is a NUL at the end +** when finished. +*/ +uint32_t +nsTextFormatter::snprintf(char16_t* aOut, uint32_t aOutLen, + const char16_t* aFmt, ...) +{ + va_list ap; + uint32_t rv; + + MOZ_ASSERT((int32_t)aOutLen > 0); + if ((int32_t)aOutLen <= 0) { + return 0; + } + + va_start(ap, aFmt); + rv = nsTextFormatter::vsnprintf(aOut, aOutLen, aFmt, ap); + va_end(ap); + return rv; +} + +uint32_t +nsTextFormatter::vsnprintf(char16_t* aOut, uint32_t aOutLen, + const char16_t* aFmt, va_list aAp) +{ + SprintfState ss; + uint32_t n; + + 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; + (void) dosprintf(&ss, aFmt, aAp); + + /* If we added chars, and we didn't append a null, do it now. */ + if ((ss.cur != ss.base) && (*(ss.cur - 1) != '\0')) { + *(--ss.cur) = '\0'; + } + + n = ss.cur - ss.base; + return n ? n - 1 : n; +} + +/* + * Free memory allocated, for the caller, by smprintf + */ +void +nsTextFormatter::smprintf_free(char16_t* aMem) +{ + free(aMem); +} + diff --git a/xpcom/glue/nsTextFormatter.h b/xpcom/glue/nsTextFormatter.h new file mode 100644 index 000000000..4cd44be32 --- /dev/null +++ b/xpcom/glue/nsTextFormatter.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/. */ + +/* + * 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 "prio.h" +#include +#include +#include "nscore.h" +#include "nsStringGlue.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. + */ + static uint32_t snprintf(char16_t* aOut, uint32_t aOutLen, + const char16_t* aFmt, ...); + + /* + * sprintf into a moz_xmalloc'd buffer. Return a pointer to + * buffer on success, nullptr on failure. + */ + static char16_t* smprintf(const char16_t* aFmt, ...); + + static uint32_t ssprintf(nsAString& aOut, const char16_t* aFmt, ...); + + /* + * va_list forms of the above. + */ + static uint32_t vsnprintf(char16_t* aOut, uint32_t aOutLen, const char16_t* aFmt, + va_list aAp); + static char16_t* vsmprintf(const char16_t* aFmt, va_list aAp); + static uint32_t vssprintf(nsAString& aOut, const char16_t* aFmt, va_list aAp); + + /* + * Free the memory allocated, for the caller, by smprintf. + * -- Deprecated -- + * Callers can substitute calling smprintf_free with free + */ + static void smprintf_free(char16_t* aMem); + +}; + +#endif /* nsTextFormatter_h___ */ diff --git a/xpcom/glue/nsThreadUtils.cpp b/xpcom/glue/nsThreadUtils.cpp new file mode 100644 index 000000000..287ada7be --- /dev/null +++ b/xpcom/glue/nsThreadUtils.cpp @@ -0,0 +1,472 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. 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/Attributes.h" +#include "mozilla/Likely.h" +#include "mozilla/TimeStamp.h" +#include "LeakRefPtr.h" + +#ifdef MOZILLA_INTERNAL_API +# include "nsThreadManager.h" +#else +# include "nsXPCOMCIDInternal.h" +# include "nsIThreadManager.h" +# include "nsServiceManagerUtils.h" +#endif + +#ifdef XP_WIN +#include +#include "mozilla/WindowsVersion.h" +using mozilla::IsVistaOrLater; +#elif defined(XP_MACOSX) +#include +#endif + +#include +#include + +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_ISUPPORTS(Runnable, nsIRunnable) + +NS_IMETHODIMP +Runnable::Run() +{ + // Do nothing + return NS_OK; +} + +NS_IMPL_ISUPPORTS_INHERITED(CancelableRunnable, Runnable, + nsICancelableRunnable) + +nsresult +CancelableRunnable::Cancel() +{ + // Do nothing + return NS_OK; +} + +NS_IMPL_ISUPPORTS_INHERITED(IncrementalRunnable, CancelableRunnable, + nsIIncrementalRunnable) + +void +IncrementalRunnable::SetDeadline(TimeStamp aDeadline) +{ + // Do nothing +} + +#endif // XPCOM_GLUE_AVOID_NSPR + +//----------------------------------------------------------------------------- + +nsresult +NS_NewThread(nsIThread** aResult, nsIRunnable* aEvent, uint32_t aStackSize) +{ + nsCOMPtr thread; +#ifdef MOZILLA_INTERNAL_API + nsresult rv = + nsThreadManager::get().nsThreadManager::NewThread(0, aStackSize, + getter_AddRefs(thread)); +#else + nsresult rv; + nsCOMPtr mgr = + do_GetService(NS_THREADMANAGER_CONTRACTID, &rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = mgr->NewThread(0, aStackSize, getter_AddRefs(thread)); +#endif + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (aEvent) { + rv = thread->Dispatch(aEvent, NS_DISPATCH_NORMAL); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + + *aResult = nullptr; + thread.swap(*aResult); + return NS_OK; +} + +nsresult +NS_GetCurrentThread(nsIThread** aResult) +{ +#ifdef MOZILLA_INTERNAL_API + return nsThreadManager::get().nsThreadManager::GetCurrentThread(aResult); +#else + nsresult rv; + nsCOMPtr mgr = + do_GetService(NS_THREADMANAGER_CONTRACTID, &rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + return mgr->GetCurrentThread(aResult); +#endif +} + +nsresult +NS_GetMainThread(nsIThread** aResult) +{ +#ifdef MOZILLA_INTERNAL_API + return nsThreadManager::get().nsThreadManager::GetMainThread(aResult); +#else + nsresult rv; + nsCOMPtr mgr = + do_GetService(NS_THREADMANAGER_CONTRACTID, &rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + return mgr->GetMainThread(aResult); +#endif +} + +#ifndef MOZILLA_INTERNAL_API +bool +NS_IsMainThread() +{ + bool result = false; + nsCOMPtr mgr = + do_GetService(NS_THREADMANAGER_CONTRACTID); + if (mgr) { + mgr->GetIsMainThread(&result); + } + return bool(result); +} +#endif + +nsresult +NS_DispatchToCurrentThread(already_AddRefed&& aEvent) +{ + nsresult rv; + nsCOMPtr event(aEvent); +#ifdef MOZILLA_INTERNAL_API + nsIThread* thread = NS_GetCurrentThread(); + if (!thread) { + return NS_ERROR_UNEXPECTED; + } +#else + nsCOMPtr thread; + rv = NS_GetCurrentThread(getter_AddRefs(thread)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } +#endif + // 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(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); +#ifdef MOZILLA_INTERNAL_API + nsIThread* thread = NS_GetCurrentThread(); + if (!thread) { + return NS_ERROR_UNEXPECTED; + } +#else + nsresult rv; + nsCOMPtr thread; + rv = NS_GetCurrentThread(getter_AddRefs(thread)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } +#endif + + return thread->DelayedDispatch(event.forget(), aDelayMs); +} + +nsresult +NS_IdleDispatchToCurrentThread(already_AddRefed&& aEvent) +{ + nsresult rv; + nsCOMPtr event(aEvent); +#ifdef MOZILLA_INTERNAL_API + nsIThread* thread = NS_GetCurrentThread(); + if (!thread) { + return NS_ERROR_UNEXPECTED; + } +#else + nsCOMPtr thread; + rv = NS_GetCurrentThread(getter_AddRefs(thread)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } +#endif + // 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->IdleDispatch(event.forget()); + 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; +} + +#ifndef XPCOM_GLUE_AVOID_NSPR +nsresult +NS_ProcessPendingEvents(nsIThread* aThread, PRIntervalTime aTimeout) +{ + nsresult rv = NS_OK; + +#ifdef MOZILLA_INTERNAL_API + if (!aThread) { + aThread = NS_GetCurrentThread(); + if (NS_WARN_IF(!aThread)) { + return NS_ERROR_UNEXPECTED; + } + } +#else + nsCOMPtr current; + if (!aThread) { + rv = NS_GetCurrentThread(getter_AddRefs(current)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + aThread = current.get(); + } +#endif + + 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) { +#ifndef MOZILLA_INTERNAL_API + nsCOMPtr current; + NS_GetCurrentThread(getter_AddRefs(current)); + return hasPendingEvents(current); +#else + aThread = NS_GetCurrentThread(); + if (NS_WARN_IF(!aThread)) { + return false; + } +#endif + } + return hasPendingEvents(aThread); +} + +bool +NS_ProcessNextEvent(nsIThread* aThread, bool aMayWait) +{ +#ifdef MOZILLA_INTERNAL_API + if (!aThread) { + aThread = NS_GetCurrentThread(); + if (NS_WARN_IF(!aThread)) { + return false; + } + } +#else + nsCOMPtr current; + if (!aThread) { + NS_GetCurrentThread(getter_AddRefs(current)); + if (NS_WARN_IF(!current)) { + return false; + } + aThread = current.get(); + } +#endif + bool val; + return NS_SUCCEEDED(aThread->ProcessNextEvent(aMayWait, &val)) && val; +} + +#ifndef XPCOM_GLUE_AVOID_NSPR + +namespace { + +class nsNameThreadRunnable final : public nsIRunnable +{ + ~nsNameThreadRunnable() {} + +public: + explicit nsNameThreadRunnable(const nsACString& aName) : mName(aName) {} + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIRUNNABLE + +protected: + const nsCString mName; +}; + +NS_IMPL_ISUPPORTS(nsNameThreadRunnable, nsIRunnable) + +NS_IMETHODIMP +nsNameThreadRunnable::Run() +{ + PR_SetCurrentThreadName(mName.BeginReading()); + return NS_OK; +} + +} // namespace + +void +NS_SetThreadName(nsIThread* aThread, const nsACString& aName) +{ + if (!aThread) { + return; + } + + aThread->Dispatch(new nsNameThreadRunnable(aName), + nsIEventTarget::DISPATCH_NORMAL); +} + +#else // !XPCOM_GLUE_AVOID_NSPR + +void +NS_SetThreadName(nsIThread* aThread, const nsACString& aName) +{ + // No NSPR, no love. +} + +#endif + +#ifdef MOZILLA_INTERNAL_API +nsIThread* +NS_GetCurrentThread() +{ + return nsThreadManager::get().GetCurrentThread(); +} +#endif + +// nsThreadPoolNaming +void +nsThreadPoolNaming::SetThreadPoolName(const nsACString& aPoolName, + nsIThread* aThread) +{ + nsCString name(aPoolName); + name.AppendLiteral(" #"); + name.AppendInt(++mCounter, 10); // The counter is declared as volatile + + if (aThread) { + // Set on the target thread + NS_SetThreadName(aThread, name); + } else { + // Set on the current thread +#ifndef XPCOM_GLUE_AVOID_NSPR + PR_SetCurrentThreadName(name.BeginReading()); +#endif + } +} + +// nsAutoLowPriorityIO +nsAutoLowPriorityIO::nsAutoLowPriorityIO() +{ +#if defined(XP_WIN) + lowIOPrioritySet = IsVistaOrLater() && + 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 +} diff --git a/xpcom/glue/nsThreadUtils.h b/xpcom/glue/nsThreadUtils.h new file mode 100644 index 000000000..cbd7f7842 --- /dev/null +++ b/xpcom/glue/nsThreadUtils.h @@ -0,0 +1,1049 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 "prthread.h" +#include "prinrval.h" +#include "MainThreadUtils.h" +#include "nsIThreadManager.h" +#include "nsIThread.h" +#include "nsIRunnable.h" +#include "nsICancelableRunnable.h" +#include "nsIIdlePeriod.h" +#include "nsIIncrementalRunnable.h" +#include "nsStringGlue.h" +#include "nsCOMPtr.h" +#include "nsAutoPtr.h" +#include "mozilla/Atomics.h" +#include "mozilla/IndexSequence.h" +#include "mozilla/Likely.h" +#include "mozilla/Move.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/Tuple.h" +#include "mozilla/TypeTraits.h" + +//----------------------------------------------------------------------------- +// These methods are alternatives to the methods on nsIThreadManager, provided +// for convenience. + +/** + * Set name of the target thread. This operation is asynchronous. + */ +extern void NS_SetThreadName(nsIThread* aThread, const nsACString& aName); + +/** + * Static length version of the above function checking length of the + * name at compile time. + */ +template +inline void +NS_SetThreadName(nsIThread* aThread, const char (&aName)[LEN]) +{ + static_assert(LEN <= 16, + "Thread name must be no more than 16 characters"); + NS_SetThreadName(aThread, nsDependentCString(aName)); +} + +/** + * Create a new thread, and optionally provide an initial event for the thread. + * + * @param aResult + * The resulting nsIThread object. + * @param aInitialEvent + * The initial event to run on this thread. This parameter may be null. + * @param aStackSize + * The size in bytes to reserve for the thread's stack. + * + * @returns NS_ERROR_INVALID_ARG + * Indicates that the given name is not unique. + */ +extern nsresult +NS_NewThread(nsIThread** aResult, + nsIRunnable* aInitialEvent = nullptr, + uint32_t aStackSize = nsIThreadManager::DEFAULT_STACK_SIZE); + +/** + * Creates a named thread, otherwise the same as NS_NewThread + */ +template +inline nsresult +NS_NewNamedThread(const char (&aName)[LEN], + nsIThread** aResult, + nsIRunnable* aInitialEvent = nullptr, + uint32_t aStackSize = nsIThreadManager::DEFAULT_STACK_SIZE) +{ + // Hold a ref while dispatching the initial event to match NS_NewThread() + nsCOMPtr thread; + nsresult rv = NS_NewThread(getter_AddRefs(thread), nullptr, aStackSize); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + NS_SetThreadName(thread, aName); + if (aInitialEvent) { + rv = thread->Dispatch(aInitialEvent, NS_DISPATCH_NORMAL); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Initial event dispatch failed"); + } + + *aResult = nullptr; + thread.swap(*aResult); + return rv; +} + +/** + * Get a reference to the current thread. + * + * @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); + +extern nsresult +NS_IdleDispatchToCurrentThread(already_AddRefed&& aEvent); + +#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); + +//----------------------------------------------------------------------------- +// 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); +} + +//----------------------------------------------------------------------------- + +#ifdef MOZILLA_INTERNAL_API +// Fast access to the current thread. 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(); +#endif + +//----------------------------------------------------------------------------- + +#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() {} + +protected: + virtual ~IdlePeriod() {} +private: + IdlePeriod(const IdlePeriod&) = delete; + IdlePeriod& operator=(const IdlePeriod&) = delete; + IdlePeriod& operator=(const IdlePeriod&&) = delete; +}; + +// This class is designed to be subclassed. +class Runnable : public nsIRunnable +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIRUNNABLE + + Runnable() {} + +protected: + virtual ~Runnable() {} +private: + Runnable(const Runnable&) = delete; + Runnable& operator=(const Runnable&) = delete; + Runnable& operator=(const Runnable&&) = delete; +}; + +// This class is designed to be subclassed. +class CancelableRunnable : public Runnable, + public nsICancelableRunnable +{ +public: + NS_DECL_ISUPPORTS_INHERITED + // nsICancelableRunnable + virtual nsresult Cancel() override; + + CancelableRunnable() {} + +protected: + virtual ~CancelableRunnable() {} +private: + CancelableRunnable(const CancelableRunnable&) = delete; + CancelableRunnable& operator=(const CancelableRunnable&) = delete; + CancelableRunnable& operator=(const CancelableRunnable&&) = delete; +}; + +// This class is designed to be subclassed. +class IncrementalRunnable : public CancelableRunnable, + public nsIIncrementalRunnable +{ +public: + NS_DECL_ISUPPORTS_INHERITED + // nsIIncrementalRunnable + virtual void SetDeadline(TimeStamp aDeadline) override; + + IncrementalRunnable() {} + +protected: + virtual ~IncrementalRunnable() {} +private: + IncrementalRunnable(const IncrementalRunnable&) = delete; + IncrementalRunnable& operator=(const IncrementalRunnable&) = delete; + IncrementalRunnable& operator=(const IncrementalRunnable&&) = delete; +}; + +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(F&& aFunction) + : mFunction(Forward(aFunction)) + { } + + NS_IMETHOD Run() override { + static_assert(IsVoid::value, + "The lambda must return void!"); + mFunction(); + return NS_OK; + } +private: + StoredFunction mFunction; +}; + +} // namespace detail + +} // namespace mozilla + +template +already_AddRefed +NS_NewRunnableFunction(Function&& aFunction) +{ + return do_AddRef(new mozilla::detail::RunnableFunction + // Make sure we store a non-reference in nsRunnableFunction. + ::Type> + // But still forward aFunction to move if possible. + (mozilla::Forward(aFunction))); +} + +// 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 mozilla::Conditional::Type +{ +public: + virtual void Revoke() = 0; + + // These ReturnTypeEnforcer classes set up a blacklist for 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) {} + ~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; } +}; + +template struct nsRunnableMethodTraits; + +template +struct nsRunnableMethodTraits +{ + typedef C class_type; + typedef R return_type; + typedef nsRunnableMethod base_type; + static const bool can_cancel = Cancelable; +}; + +template +struct nsRunnableMethodTraits +{ + typedef const C class_type; + typedef R return_type; + typedef nsRunnableMethod base_type; + static const bool can_cancel = Cancelable; +}; + +#ifdef NS_HAVE_STDCALL +template +struct nsRunnableMethodTraits +{ + typedef C class_type; + typedef R return_type; + typedef nsRunnableMethod base_type; + static const bool can_cancel = Cancelable; +}; + +template +struct nsRunnableMethodTraits +{ + typedef C class_type; + typedef R return_type; + typedef nsRunnableMethod base_type; + static const bool can_cancel = Cancelable; +}; +template +struct nsRunnableMethodTraits +{ + typedef const C class_type; + typedef R return_type; + typedef nsRunnableMethod base_type; + static const bool can_cancel = Cancelable; +}; + +template +struct nsRunnableMethodTraits +{ + typedef const C class_type; + typedef R return_type; + typedef nsRunnableMethod base_type; + static const bool can_cancel = 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 mozilla::FalseType {}; + +// StoreXPassByY structs used to inform nsRunnableMethodArguments how to +// store arguments, and how to pass them to the target method. + +template +struct StoreCopyPassByValue +{ + typedef T stored_type; + typedef T passed_type; + stored_type m; + template + MOZ_IMPLICIT StoreCopyPassByValue(A&& a) : m(mozilla::Forward(a)) {} + passed_type PassAsParameter() { return m; } +}; +template +struct IsParameterStorageClass> + : public mozilla::TrueType {}; + +template +struct StoreCopyPassByConstLRef +{ + typedef T stored_type; + typedef const T& passed_type; + stored_type m; + template + MOZ_IMPLICIT StoreCopyPassByConstLRef(A&& a) : m(mozilla::Forward(a)) {} + passed_type PassAsParameter() { return m; } +}; +template +struct IsParameterStorageClass> + : public mozilla::TrueType {}; + +template +struct StoreCopyPassByLRef +{ + typedef T stored_type; + typedef T& passed_type; + stored_type m; + template + MOZ_IMPLICIT StoreCopyPassByLRef(A&& a) : m(mozilla::Forward(a)) {} + passed_type PassAsParameter() { return m; } +}; +template +struct IsParameterStorageClass> + : public mozilla::TrueType {}; + +template +struct StoreCopyPassByRRef +{ + typedef T stored_type; + typedef T&& passed_type; + stored_type m; + template + MOZ_IMPLICIT StoreCopyPassByRRef(A&& a) : m(mozilla::Forward(a)) {} + passed_type PassAsParameter() { return mozilla::Move(m); } +}; +template +struct IsParameterStorageClass> + : public mozilla::TrueType {}; + +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 mozilla::TrueType {}; + +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 mozilla::TrueType {}; + +template +struct StorensRefPtrPassByPtr +{ + typedef RefPtr stored_type; + typedef T* passed_type; + stored_type m; + template + MOZ_IMPLICIT StorensRefPtrPassByPtr(A&& a) : m(mozilla::Forward(a)) {} + passed_type PassAsParameter() { return m.get(); } +}; +template +struct IsParameterStorageClass> + : public mozilla::TrueType {}; + +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 mozilla::TrueType {}; + +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 mozilla::TrueType {}; + +template +struct StoreCopyPassByConstPtr +{ + typedef T stored_type; + typedef const T* passed_type; + stored_type m; + template + MOZ_IMPLICIT StoreCopyPassByConstPtr(A&& a) : m(mozilla::Forward(a)) {} + passed_type PassAsParameter() { return &m; } +}; +template +struct IsParameterStorageClass> + : public mozilla::TrueType {}; + +template +struct StoreCopyPassByPtr +{ + typedef T stored_type; + typedef T* passed_type; + stored_type m; + template + MOZ_IMPLICIT StoreCopyPassByPtr(A&& a) : m(mozilla::Forward(a)) {} + passed_type PassAsParameter() { return &m; } +}; +template +struct IsParameterStorageClass> + : public mozilla::TrueType {}; + +namespace detail { + +template +struct NonnsISupportsPointerStorageClass + : mozilla::Conditional::value, + StoreConstPtrPassByConstPtr< + typename mozilla::RemoveConst::Type>, + StorePtrPassByPtr> +{}; + +template +struct SFINAE1True : mozilla::TrueType +{}; + +template +static auto HasRefCountMethodsTest(int) + -> SFINAE1True().AddRef(), + mozilla::DeclVal().Release())>; +template +static auto HasRefCountMethodsTest(long) -> mozilla::FalseType; + +template +struct HasRefCountMethods : decltype(HasRefCountMethodsTest(0)) +{}; + +template +struct IsRefcountedSmartPointer : public mozilla::FalseType +{}; + +template +struct IsRefcountedSmartPointer> : public mozilla::TrueType +{}; + +template +struct IsRefcountedSmartPointer> : public mozilla::TrueType +{}; + +template +struct StripSmartPointer +{ + typedef void Type; +}; + +template +struct StripSmartPointer> +{ + typedef T Type; +}; + +template +struct StripSmartPointer> +{ + typedef T Type; +}; + +template +struct PointerStorageClass + : mozilla::Conditional::value, + StorensRefPtrPassByPtr, + typename NonnsISupportsPointerStorageClass< + TWithoutPointer + >::Type> +{}; + +template +struct LValueReferenceStorageClass + : mozilla::Conditional::value, + StoreConstRefPassByConstLRef< + typename mozilla::RemoveConst::Type>, + StoreRefPassByLRef> +{}; + +template +struct SmartPointerStorageClass + : mozilla::Conditional::value, + StorensRefPtrPassByPtr< + typename StripSmartPointer::Type>, + StoreCopyPassByValue> +{}; + +template +struct NonLValueReferenceStorageClass + : mozilla::Conditional::value, + StoreCopyPassByRRef< + typename mozilla::RemoveReference::Type>, + typename SmartPointerStorageClass::Type> +{}; + +template +struct NonPointerStorageClass + : mozilla::Conditional::value, + typename LValueReferenceStorageClass< + typename mozilla::RemoveReference::Type + >::Type, + typename NonLValueReferenceStorageClass::Type> +{}; + +template +struct NonParameterStorageClass + : mozilla::Conditional::value, + typename PointerStorageClass< + typename mozilla::RemovePointer::Type + >::Type, + typename NonPointerStorageClass::Type> +{}; + +// Choose storage&passing strategy based on preferred storage type: +// - If IsParameterStorageClass::value is true, use as-is. +// - RC* -> StorensRefPtrPassByPtr : 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 Move(T). +// - RefPtr, nsCOMPtr +// -> StorensRefPtrPassByPtr : Store RefPtr, pass T* +// - Other T -> StoreCopyPassByValue : Store T, pass T. +// Other available explicit options: +// - StoreCopyPassByConstLRef : Store T, pass const 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 + : mozilla::Conditional::value, + T, + typename NonParameterStorageClass::Type> +{}; + +} /* namespace detail */ + +namespace mozilla { + +namespace detail { + +// struct used to store arguments and later apply them to a method. +template +struct RunnableMethodArguments final +{ + Tuple::Type...> mArguments; + template + explicit RunnableMethodArguments(As&&... aArguments) + : mArguments(Forward(aArguments)...) + {} + template + static auto + applyImpl(C* o, M m, Tuple& args, IndexSequence) + -> decltype(((*o).*m)(Get(args).PassAsParameter()...)) + { + return ((*o).*m)(Get(args).PassAsParameter()...); + } + template auto apply(C* o, M m) + -> decltype(applyImpl(o, m, mArguments, + typename IndexSequenceFor::Type())) + { + return applyImpl(o, m, mArguments, + typename IndexSequenceFor::Type()); + } +}; + +template +class RunnableMethodImpl final + : public ::nsRunnableMethodTraits::base_type +{ + typedef typename ::nsRunnableMethodTraits::class_type + ClassType; + ::nsRunnableMethodReceiver mReceiver; + Method mMethod; + RunnableMethodArguments mArgs; +private: + virtual ~RunnableMethodImpl() { Revoke(); }; +public: + template + explicit RunnableMethodImpl(ClassType* aObj, Method aMethod, + Args&&... aArgs) + : mReceiver(aObj) + , mMethod(aMethod) + , mArgs(Forward(aArgs)...) + { + static_assert(sizeof...(Storages) == sizeof...(Args), "Storages and Args should have equal sizes"); + } + NS_IMETHOD Run() + { + if (MOZ_LIKELY(mReceiver.Get())) { + mArgs.apply(mReceiver.Get(), mMethod); + } + return NS_OK; + } + nsresult Cancel() { + static_assert(Cancelable, "Don't use me!"); + Revoke(); + return NS_OK; + } + void Revoke() { mReceiver.Revoke(); } +}; + +} // namespace detail + +// Use this template function like so: +// +// nsCOMPtr event = +// mozilla::NewRunnableMethod(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 +// + +template +already_AddRefed::base_type> +NewRunnableMethod(PtrType aPtr, Method aMethod) +{ + return do_AddRef(new detail::RunnableMethodImpl(aPtr, aMethod)); +} + +template +already_AddRefed::base_type> +NewCancelableRunnableMethod(PtrType aPtr, Method aMethod) +{ + return do_AddRef(new detail::RunnableMethodImpl(aPtr, aMethod)); +} + +template +already_AddRefed::base_type> +NewNonOwningRunnableMethod(PtrType&& aPtr, Method aMethod) +{ + return do_AddRef(new detail::RunnableMethodImpl(aPtr, aMethod)); +} + +template +already_AddRefed::base_type> +NewNonOwningCancelableRunnableMethod(PtrType&& aPtr, Method aMethod) +{ + return do_AddRef(new detail::RunnableMethodImpl(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::base_type> +NewRunnableMethod(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::RunnableMethodImpl( + aPtr, aMethod, mozilla::Forward(aArgs)...)); +} + +template +already_AddRefed::base_type> +NewNonOwningRunnableMethod(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::RunnableMethodImpl( + aPtr, aMethod, mozilla::Forward(aArgs)...)); +} + +template +already_AddRefed::base_type> +NewCancelableRunnableMethod(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::RunnableMethodImpl( + aPtr, aMethod, mozilla::Forward(aArgs)...)); +} + +template +already_AddRefed::base_type> +NewNonOwningCancelableRunnableMethod(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::RunnableMethodImpl( + aPtr, aMethod, mozilla::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=(T* aEvent) + { + if (mEvent != aEvent) { + Revoke(); + mEvent = aEvent; + } + return *this; + } + + const nsRevocableEventPtr& operator=(already_AddRefed aEvent) + { + RefPtr event = aEvent; + if (mEvent != event) { + Revoke(); + mEvent = event.forget(); + } + 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; +}; + +/** + * A simple helper to suffix thread pool name + * with incremental numbers. + */ +class nsThreadPoolNaming +{ +public: + nsThreadPoolNaming() : mCounter(0) {} + + /** + * Creates and sets next thread name as " #" + * on the specified thread. If no thread is specified (aThread + * is null) then the name is synchronously set on the current thread. + */ + void SetThreadPoolName(const nsACString& aPoolName, + nsIThread* aThread = nullptr); + +private: + mozilla::Atomic mCounter; + + 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(); + +#endif // nsThreadUtils_h__ diff --git a/xpcom/glue/nsVersionComparator.cpp b/xpcom/glue/nsVersionComparator.cpp new file mode 100644 index 000000000..6be32b974 --- /dev/null +++ b/xpcom/glue/nsVersionComparator.cpp @@ -0,0 +1,379 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. 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 +#if defined(XP_WIN) && !defined(UPDATER_NO_STRING_GLUE_STL) +#include +#include "nsStringGlue.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 + +/** + * 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 = strtol(aPart, const_cast(&aResult.strB), 10); + } + + 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 = strtol(numstart, &aResult.extraD, 10); + 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 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') { + aResult.numA = INT32_MAX; + aResult.strB = L""; + } else { + aResult.numA = wcstol(aPart, const_cast(&aResult.strB), 10); + } + + 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 = wcstol(numstart, &aResult.extraD, 10); + 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/glue/nsVersionComparator.h b/xpcom/glue/nsVersionComparator.h new file mode 100644 index 000000000..0dbf8532b --- /dev/null +++ b/xpcom/glue/nsVersionComparator.h @@ -0,0 +1,174 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 nsVersionComparator_h__ +#define nsVersionComparator_h__ + +#include "nscore.h" +#include +#include +#include +#if defined(XP_WIN) && !defined(UPDATER_NO_STRING_GLUE_STL) +#include +#include "nsStringGlue.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; + * } + * + * On Windows, if your version strings are wide characters, you should use the + * mozilla::VersionW variant instead. The semantics of that class is the same + * as Version. + */ + +namespace mozilla { + +int32_t CompareVersions(const char* aStrA, const char* aStrB); + +#ifdef XP_WIN +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()) == -1; + } + bool operator<=(const Version& aRhs) const + { + return CompareVersions(versionContent, aRhs.ReadContent()) < 1; + } + bool operator>(const Version& aRhs) const + { + return CompareVersions(versionContent, aRhs.ReadContent()) == 1; + } + 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) == -1; + } + bool operator<=(const char* aRhs) const + { + return CompareVersions(versionContent, aRhs) < 1; + } + bool operator>(const char* aRhs) const + { + return CompareVersions(versionContent, aRhs) == 1; + } + 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; +}; + +#ifdef XP_WIN +struct VersionW +{ + VersionW(const char16_t* aVersionStringW) + { + versionContentW = + reinterpret_cast(wcsdup(char16ptr_t(aVersionStringW))); + } + + const char16_t* ReadContentW() const + { + return versionContentW; + } + + ~VersionW() + { + free(versionContentW); + } + + bool operator<(const VersionW& aRhs) const + { + return CompareVersions(versionContentW, aRhs.ReadContentW()) == -1; + } + bool operator<=(const VersionW& aRhs) const + { + return CompareVersions(versionContentW, aRhs.ReadContentW()) < 1; + } + bool operator>(const VersionW& aRhs) const + { + return CompareVersions(versionContentW, aRhs.ReadContentW()) == 1; + } + bool operator>=(const VersionW& aRhs) const + { + return CompareVersions(versionContentW, aRhs.ReadContentW()) > -1; + } + bool operator==(const VersionW& aRhs) const + { + return CompareVersions(versionContentW, aRhs.ReadContentW()) == 0; + } + bool operator!=(const VersionW& aRhs) const + { + return CompareVersions(versionContentW, aRhs.ReadContentW()) != 0; + } + +private: + char16_t* versionContentW; +}; +#endif + +} // namespace mozilla + +#endif // nsVersionComparator_h__ + diff --git a/xpcom/glue/nsWeakReference.cpp b/xpcom/glue/nsWeakReference.cpp new file mode 100644 index 000000000..57f372641 --- /dev/null +++ b/xpcom/glue/nsWeakReference.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/. */ + +// nsWeakReference.cpp + +#include "mozilla/Attributes.h" + +#include "nsWeakReference.h" +#include "nsCOMPtr.h" +#include "nsDebug.h" + +#ifdef MOZ_THREAD_SAFETY_OWNERSHIP_CHECKS_SUPPORTED + +#define MOZ_WEAKREF_DECL_OWNINGTHREAD nsAutoOwningThread _mWeakRefOwningThread; +#define MOZ_WEAKREF_ASSERT_OWNINGTHREAD \ + NS_CheckThreadSafe(_mWeakRefOwningThread.GetThread(), "nsWeakReference not thread-safe") +#define MOZ_WEAKREF_ASSERT_OWNINGTHREAD_DELEGATED(that) \ + NS_CheckThreadSafe((that)->_mWeakRefOwningThread.GetThread(), "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 + +class nsWeakReference final : public nsIWeakReference +{ +public: + // nsISupports... + NS_DECL_ISUPPORTS + + // nsIWeakReference... + NS_DECL_NSIWEAKREFERENCE + virtual size_t SizeOfOnlyThis(mozilla::MallocSizeOf aMallocSizeOf) const override; + +private: + MOZ_WEAKREF_DECL_OWNINGTHREAD + + friend class nsSupportsWeakReference; + + explicit nsWeakReference(nsSupportsWeakReference* aReferent) + : mReferent(aReferent) + // ...I can only be constructed by an |nsSupportsWeakReference| + { + } + + ~nsWeakReference() + // ...I will only be destroyed by calling |delete| myself. + { + MOZ_WEAKREF_ASSERT_OWNINGTHREAD; + if (mReferent) { + mReferent->NoticeProxyDestruction(); + } + } + + void + NoticeReferentDestruction() + // ...called (only) by an |nsSupportsWeakReference| from _its_ dtor. + { + MOZ_WEAKREF_ASSERT_OWNINGTHREAD; + mReferent = nullptr; + } + + nsSupportsWeakReference* MOZ_NON_OWNING_REF mReferent; +}; + +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* // 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); + } + *aInstancePtr = mProxy; + + nsresult status; + if (!*aInstancePtr) { + status = NS_ERROR_OUT_OF_MEMORY; + } else { + NS_ADDREF(*aInstancePtr); + status = NS_OK; + } + + return status; +} + +NS_IMPL_ISUPPORTS(nsWeakReference, nsIWeakReference) + +NS_IMETHODIMP +nsWeakReference::QueryReferent(const nsIID& aIID, void** aInstancePtr) +{ + MOZ_WEAKREF_ASSERT_OWNINGTHREAD; + + return mReferent ? mReferent->QueryInterface(aIID, aInstancePtr) : + NS_ERROR_NULL_POINTER; +} + +size_t +nsWeakReference::SizeOfOnlyThis(mozilla::MallocSizeOf aMallocSizeOf) const +{ + return aMallocSizeOf(this); +} + +void +nsSupportsWeakReference::ClearWeakReferences() +{ + if (mProxy) { + mProxy->NoticeReferentDestruction(); + mProxy = nullptr; + } +} + diff --git a/xpcom/glue/nsWeakReference.h b/xpcom/glue/nsWeakReference.h new file mode 100644 index 000000000..fc875ba96 --- /dev/null +++ b/xpcom/glue/nsWeakReference.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 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(); +} + +#endif diff --git a/xpcom/glue/nsXPTCUtils.h b/xpcom/glue/nsXPTCUtils.h new file mode 100644 index 000000000..8922aca16 --- /dev/null +++ b/xpcom/glue/nsXPTCUtils.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 nsXPTCUtils_h__ +#define nsXPTCUtils_h__ + +#include "xptcall.h" +#include "mozilla/MemoryReporting.h" + +/** + * A helper class that initializes an xptcall helper at construction + * and releases it at destruction. + */ +class nsAutoXPTCStub : protected nsIXPTCProxy +{ +public: + nsISomeInterface* mXPTCStub; + +protected: + nsAutoXPTCStub() : mXPTCStub(nullptr) {} + + nsresult + InitStub(const nsIID& aIID) + { + return NS_GetXPTCallStub(aIID, this, &mXPTCStub); + } + + ~nsAutoXPTCStub() + { + if (mXPTCStub) { + NS_DestroyXPTCallStub(mXPTCStub); + } + } + + size_t + SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const + { + return mXPTCStub ? NS_SizeOfIncludingThisXPTCallStub(mXPTCStub, aMallocSizeOf) : 0; + } +}; + +#endif // nsXPTCUtils_h__ diff --git a/xpcom/glue/objs.mozbuild b/xpcom/glue/objs.mozbuild new file mode 100644 index 000000000..8161e1ebc --- /dev/null +++ b/xpcom/glue/objs.mozbuild @@ -0,0 +1,48 @@ +# -*- 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/. + +xpcom_glue_src_lcppsrcs = [ + 'AppData.cpp', + 'FileUtils.cpp', + 'nsArrayEnumerator.cpp', + 'nsArrayUtils.cpp', + 'nsCategoryCache.cpp', + 'nsClassInfoImpl.cpp', + 'nsCOMArray.cpp', + 'nsComponentManagerUtils.cpp', + 'nsCOMPtr.cpp', + 'nsCRTGlue.cpp', + 'nsCycleCollectionParticipant.cpp', + 'nsDeque.cpp', + 'nsEnumeratorUtils.cpp', + 'nsID.cpp', + 'nsIInterfaceRequestorUtils.cpp', + 'nsINIParser.cpp', + 'nsISupportsImpl.cpp', + 'nsMemory.cpp', + 'nsQuickSort.cpp', + 'nsTArray.cpp', + 'nsThreadUtils.cpp', + 'nsTObserverArray.cpp', + 'nsVersionComparator.cpp', + 'nsWeakReference.cpp', + 'PLDHashTable.cpp', +] + +xpcom_glue_src_cppsrcs = [ + '/xpcom/glue/%s' % s for s in xpcom_glue_src_lcppsrcs +] + +xpcom_gluens_src_lcppsrcs = [ + 'BlockingResourceBase.cpp', + 'GenericFactory.cpp', + 'nsProxyRelease.cpp', + 'nsTextFormatter.cpp', +] + +xpcom_gluens_src_cppsrcs = [ + '/xpcom/glue/%s' % s for s in xpcom_gluens_src_lcppsrcs +] diff --git a/xpcom/glue/standalone/moz.build b/xpcom/glue/standalone/moz.build new file mode 100644 index 000000000..fd91fa0bb --- /dev/null +++ b/xpcom/glue/standalone/moz.build @@ -0,0 +1,58 @@ +# -*- 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/. + +# On win we build two glue libs - glue linked to crt dlls here and in staticruntime we build +# a statically linked glue lib. +if CONFIG['OS_ARCH'] == 'WINNT': + DIRS += ['staticruntime'] + +include('../objs.mozbuild') + +SOURCES += xpcom_glue_src_cppsrcs + +SOURCES += [ + '../nsStringAPI.cpp', + 'nsXPCOMGlue.cpp', +] + +Library('xpcomglue') + +EXPORTS += [ + 'nsXPCOMGlue.h', +] + +SDK_LIBRARY = True + +FORCE_STATIC_LIB = True + +if CONFIG['_MSC_VER']: + DEFINES['_USE_ANSI_CPP'] = True + # Don't include directives about which CRT to use + CFLAGS += ['-Zl'] + CXXFLAGS += ['-Zl'] + +DEFINES['XPCOM_GLUE'] = True + +LOCAL_INCLUDES += [ + '../../build', + '../../threads', +] + +# Don't use STL wrappers here (i.e. wrapped ); they require mozalloc +DISABLE_STL_WRAPPING = True + +# Include fallible for third party code using the xpcom glue +USE_LIBS += [ + 'fallible', +] + +# Force to build a static library only +NO_EXPAND_LIBS = True + +DIST_INSTALL = True + +if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('gtk2', 'gtk3'): + CXXFLAGS += CONFIG['GLIB_CFLAGS'] diff --git a/xpcom/glue/standalone/nsXPCOMGlue.cpp b/xpcom/glue/standalone/nsXPCOMGlue.cpp new file mode 100644 index 000000000..b657d7050 --- /dev/null +++ b/xpcom/glue/standalone/nsXPCOMGlue.cpp @@ -0,0 +1,927 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsXPCOMGlue.h" + +#include "nspr.h" +#include "nsDebug.h" +#include "nsIServiceManager.h" +#include "nsXPCOMPrivate.h" +#include "nsCOMPtr.h" +#include +#include + +#include "mozilla/FileUtils.h" +#include "mozilla/Sprintf.h" + +using namespace mozilla; + +#define XPCOM_DEPENDENT_LIBS_LIST "dependentlibs.list" + +static XPCOMFunctions xpcomFunctions; +static bool do_preload = false; + +#if defined(XP_WIN) +#define READ_TEXTMODE L"rt" +#else +#define READ_TEXTMODE "r" +#endif + +#if defined(XP_WIN) +#include +#include + +typedef HINSTANCE LibHandleType; + +static LibHandleType +GetLibHandle(pathstr_t aDependentLib) +{ + LibHandleType libHandle = + LoadLibraryExW(aDependentLib, nullptr, LOAD_WITH_ALTERED_SEARCH_PATH); + +#ifdef DEBUG + if (!libHandle) { + DWORD err = GetLastError(); + LPWSTR lpMsgBuf; + FormatMessageW( + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + nullptr, + err, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPWSTR)&lpMsgBuf, + 0, + nullptr + ); + wprintf(L"Error loading %ls: %s\n", aDependentLib, lpMsgBuf); + LocalFree(lpMsgBuf); + } +#endif + + return libHandle; +} + +static NSFuncPtr +GetSymbol(LibHandleType aLibHandle, const char* aSymbol) +{ + return (NSFuncPtr)GetProcAddress(aLibHandle, aSymbol); +} + +static void +CloseLibHandle(LibHandleType aLibHandle) +{ + FreeLibrary(aLibHandle); +} + +#else +#include + +#if defined(MOZ_LINKER) && !defined(ANDROID) +extern "C" { +NS_HIDDEN __typeof(dlopen) __wrap_dlopen; +NS_HIDDEN __typeof(dlsym) __wrap_dlsym; +NS_HIDDEN __typeof(dlclose) __wrap_dlclose; +} + +#define dlopen __wrap_dlopen +#define dlsym __wrap_dlsym +#define dlclose __wrap_dlclose +#endif + +typedef void* LibHandleType; + +static LibHandleType +GetLibHandle(pathstr_t aDependentLib) +{ + LibHandleType libHandle = dlopen(aDependentLib, + RTLD_GLOBAL | RTLD_LAZY +#ifdef XP_MACOSX + | RTLD_FIRST +#endif + ); + if (!libHandle) { + fprintf(stderr, "XPCOMGlueLoad error for file %s:\n%s\n", aDependentLib, + dlerror()); + } + return libHandle; +} + +static NSFuncPtr +GetSymbol(LibHandleType aLibHandle, const char* aSymbol) +{ + return (NSFuncPtr)dlsym(aLibHandle, aSymbol); +} + +static void +CloseLibHandle(LibHandleType aLibHandle) +{ + dlclose(aLibHandle); +} +#endif + +struct DependentLib +{ + LibHandleType libHandle; + DependentLib* next; +}; + +static DependentLib* sTop; + +static void +AppendDependentLib(LibHandleType aLibHandle) +{ + DependentLib* d = new DependentLib; + if (!d) { + return; + } + + d->next = sTop; + d->libHandle = aLibHandle; + + sTop = d; +} + +static bool +ReadDependentCB(pathstr_t aDependentLib, bool aDoPreload) +{ + if (aDoPreload) { + ReadAheadLib(aDependentLib); + } + LibHandleType libHandle = GetLibHandle(aDependentLib); + if (libHandle) { + AppendDependentLib(libHandle); + } + + return libHandle; +} + +#ifdef XP_WIN +static bool +ReadDependentCB(const char* aDependentLib, bool do_preload) +{ + wchar_t wideDependentLib[MAX_PATH]; + MultiByteToWideChar(CP_UTF8, 0, aDependentLib, -1, wideDependentLib, MAX_PATH); + return ReadDependentCB(wideDependentLib, do_preload); +} + +inline FILE* +TS_tfopen(const char* path, const wchar_t* mode) +{ + wchar_t wPath[MAX_PATH]; + MultiByteToWideChar(CP_UTF8, 0, path, -1, wPath, MAX_PATH); + return _wfopen(wPath, mode); +} +#else +inline FILE* +TS_tfopen(const char* aPath, const char* aMode) +{ + return fopen(aPath, aMode); +} +#endif + +/* RAII wrapper for FILE descriptors */ +struct ScopedCloseFileTraits +{ + typedef FILE* type; + static type empty() { return nullptr; } + static void release(type aFile) + { + if (aFile) { + fclose(aFile); + } + } +}; +typedef Scoped ScopedCloseFile; + +static void +XPCOMGlueUnload() +{ + while (sTop) { + CloseLibHandle(sTop->libHandle); + + DependentLib* temp = sTop; + sTop = sTop->next; + + delete temp; + } +} + +#if defined(XP_WIN) +// like strpbrk but finds the *last* char, not the first +static const char* +ns_strrpbrk(const char* string, const char* strCharSet) +{ + const char* found = nullptr; + for (; *string; ++string) { + for (const char* search = strCharSet; *search; ++search) { + if (*search == *string) { + found = string; + // Since we're looking for the last char, we save "found" + // until we're at the end of the string. + } + } + } + + return found; +} +#endif + +static GetFrozenFunctionsFunc +XPCOMGlueLoad(const char* aXPCOMFile) +{ + char xpcomDir[MAXPATHLEN]; +#ifdef XP_WIN + const char* lastSlash = ns_strrpbrk(aXPCOMFile, "/\\"); +#elif XP_MACOSX + // On OSX, the dependentlibs.list file lives under Contents/Resources. + // However, the actual libraries listed in dependentlibs.list live under + // Contents/MacOS. We want to read the list from Contents/Resources, then + // load the libraries from Contents/MacOS. + const char *tempSlash = strrchr(aXPCOMFile, '/'); + size_t tempLen = size_t(tempSlash - aXPCOMFile); + if (tempLen > MAXPATHLEN) { + return nullptr; + } + char tempBuffer[MAXPATHLEN]; + memcpy(tempBuffer, aXPCOMFile, tempLen); + tempBuffer[tempLen] = '\0'; + const char *slash = strrchr(tempBuffer, '/'); + tempLen = size_t(slash - tempBuffer); + const char *lastSlash = aXPCOMFile + tempLen; +#else + const char* lastSlash = strrchr(aXPCOMFile, '/'); +#endif + char* cursor; + if (lastSlash) { + size_t len = size_t(lastSlash - aXPCOMFile); + + if (len > MAXPATHLEN - sizeof(XPCOM_FILE_PATH_SEPARATOR +#ifdef XP_MACOSX + "Resources" + XPCOM_FILE_PATH_SEPARATOR +#endif + XPCOM_DEPENDENT_LIBS_LIST)) { + return nullptr; + } + memcpy(xpcomDir, aXPCOMFile, len); + strcpy(xpcomDir + len, XPCOM_FILE_PATH_SEPARATOR +#ifdef XP_MACOSX + "Resources" + XPCOM_FILE_PATH_SEPARATOR +#endif + XPCOM_DEPENDENT_LIBS_LIST); + cursor = xpcomDir + len + 1; + } else { + strcpy(xpcomDir, XPCOM_DEPENDENT_LIBS_LIST); + cursor = xpcomDir; + } + + if (getenv("MOZ_RUN_GTEST")) { + strcat(xpcomDir, ".gtest"); + } + + ScopedCloseFile flist; + flist = TS_tfopen(xpcomDir, READ_TEXTMODE); + if (!flist) { + return nullptr; + } + +#ifdef XP_MACOSX + tempLen = size_t(cursor - xpcomDir); + if (tempLen > MAXPATHLEN - sizeof("MacOS" XPCOM_FILE_PATH_SEPARATOR) - 1) { + return nullptr; + } + strcpy(cursor, "MacOS" XPCOM_FILE_PATH_SEPARATOR); + cursor += strlen(cursor); +#endif + *cursor = '\0'; + + char buffer[MAXPATHLEN]; + + while (fgets(buffer, sizeof(buffer), flist)) { + int l = strlen(buffer); + + // ignore empty lines and comments + if (l == 0 || *buffer == '#') { + continue; + } + + // cut the trailing newline, if present + if (buffer[l - 1] == '\n') { + buffer[l - 1] = '\0'; + } + + if (l + size_t(cursor - xpcomDir) > MAXPATHLEN) { + return nullptr; + } + + strcpy(cursor, buffer); + if (!ReadDependentCB(xpcomDir, do_preload)) { + XPCOMGlueUnload(); + return nullptr; + } + } + + GetFrozenFunctionsFunc sym = + (GetFrozenFunctionsFunc)GetSymbol(sTop->libHandle, + "NS_GetFrozenFunctions"); + + if (!sym) { // No symbol found. + XPCOMGlueUnload(); + return nullptr; + } + + return sym; +} + +nsresult +XPCOMGlueLoadXULFunctions(const nsDynamicFunctionLoad* aSymbols) +{ + // We don't null-check sXULLibHandle because this might work even + // if it is null (same as RTLD_DEFAULT) + + nsresult rv = NS_OK; + while (aSymbols->functionName) { + char buffer[512]; + SprintfLiteral(buffer, "%s", aSymbols->functionName); + + *aSymbols->function = (NSFuncPtr)GetSymbol(sTop->libHandle, buffer); + if (!*aSymbols->function) { + rv = NS_ERROR_LOSS_OF_SIGNIFICANT_DATA; + } + + ++aSymbols; + } + return rv; +} + +void +XPCOMGlueEnablePreload() +{ + do_preload = true; +} + +#if defined(MOZ_WIDGET_GTK) && (defined(MOZ_MEMORY) || defined(__FreeBSD__) || defined(__NetBSD__)) +#define MOZ_GSLICE_INIT +#endif + +#ifdef MOZ_GSLICE_INIT +#include + +class GSliceInit { +public: + GSliceInit() { + mHadGSlice = bool(getenv("G_SLICE")); + if (!mHadGSlice) { + // Disable the slice allocator, since jemalloc already uses similar layout + // algorithms, and using a sub-allocator tends to increase fragmentation. + // This must be done before g_thread_init() is called. + // glib >= 2.36 initializes g_slice as a side effect of its various static + // initializers, so this needs to happen before glib is loaded, which is + // this is hooked in XPCOMGlueStartup before libxul is loaded. This + // relies on the main executable not depending on glib. + setenv("G_SLICE", "always-malloc", 1); + } + } + + ~GSliceInit() { +#if MOZ_WIDGET_GTK == 2 + if (sTop) { + auto XRE_GlibInit = (void (*)(void)) GetSymbol(sTop->libHandle, + "XRE_GlibInit"); + // Initialize glib enough for G_SLICE to have an effect before it is unset. + // unset. + XRE_GlibInit(); + } +#endif + if (!mHadGSlice) { + unsetenv("G_SLICE"); + } + } + +private: + bool mHadGSlice; +}; +#endif + +nsresult +XPCOMGlueStartup(const char* aXPCOMFile) +{ +#ifdef MOZ_GSLICE_INIT + GSliceInit gSliceInit; +#endif + xpcomFunctions.version = XPCOM_GLUE_VERSION; + xpcomFunctions.size = sizeof(XPCOMFunctions); + + if (!aXPCOMFile) { + aXPCOMFile = XPCOM_DLL; + } + + GetFrozenFunctionsFunc func = XPCOMGlueLoad(aXPCOMFile); + if (!func) { + return NS_ERROR_NOT_AVAILABLE; + } + + nsresult rv = (*func)(&xpcomFunctions, nullptr); + if (NS_FAILED(rv)) { + XPCOMGlueUnload(); + return rv; + } + + return NS_OK; +} + +XPCOM_API(nsresult) +NS_InitXPCOM2(nsIServiceManager** aResult, + nsIFile* aBinDirectory, + nsIDirectoryServiceProvider* aAppFileLocationProvider) +{ + if (!xpcomFunctions.init) { + return NS_ERROR_NOT_INITIALIZED; + } + return xpcomFunctions.init(aResult, aBinDirectory, aAppFileLocationProvider); +} + +XPCOM_API(nsresult) +NS_ShutdownXPCOM(nsIServiceManager* aServMgr) +{ + if (!xpcomFunctions.shutdown) { + return NS_ERROR_NOT_INITIALIZED; + } + return xpcomFunctions.shutdown(aServMgr); +} + +XPCOM_API(nsresult) +NS_GetServiceManager(nsIServiceManager** aResult) +{ + if (!xpcomFunctions.getServiceManager) { + return NS_ERROR_NOT_INITIALIZED; + } + return xpcomFunctions.getServiceManager(aResult); +} + +XPCOM_API(nsresult) +NS_GetComponentManager(nsIComponentManager** aResult) +{ + if (!xpcomFunctions.getComponentManager) { + return NS_ERROR_NOT_INITIALIZED; + } + return xpcomFunctions.getComponentManager(aResult); +} + +XPCOM_API(nsresult) +NS_GetComponentRegistrar(nsIComponentRegistrar** aResult) +{ + if (!xpcomFunctions.getComponentRegistrar) { + return NS_ERROR_NOT_INITIALIZED; + } + return xpcomFunctions.getComponentRegistrar(aResult); +} + +XPCOM_API(nsresult) +NS_GetMemoryManager(nsIMemory** aResult) +{ + if (!xpcomFunctions.getMemoryManager) { + return NS_ERROR_NOT_INITIALIZED; + } + return xpcomFunctions.getMemoryManager(aResult); +} + +XPCOM_API(nsresult) +NS_NewLocalFile(const nsAString& aPath, bool aFollowLinks, nsIFile** aResult) +{ + if (!xpcomFunctions.newLocalFile) { + return NS_ERROR_NOT_INITIALIZED; + } + return xpcomFunctions.newLocalFile(aPath, aFollowLinks, aResult); +} + +XPCOM_API(nsresult) +NS_NewNativeLocalFile(const nsACString& aPath, bool aFollowLinks, + nsIFile** aResult) +{ + if (!xpcomFunctions.newNativeLocalFile) { + return NS_ERROR_NOT_INITIALIZED; + } + return xpcomFunctions.newNativeLocalFile(aPath, aFollowLinks, aResult); +} + +XPCOM_API(nsresult) +NS_GetDebug(nsIDebug2** aResult) +{ + if (!xpcomFunctions.getDebug) { + return NS_ERROR_NOT_INITIALIZED; + } + return xpcomFunctions.getDebug(aResult); +} + + +XPCOM_API(nsresult) +NS_StringContainerInit(nsStringContainer& aStr) +{ + if (!xpcomFunctions.stringContainerInit) { + return NS_ERROR_NOT_INITIALIZED; + } + return xpcomFunctions.stringContainerInit(aStr); +} + +XPCOM_API(nsresult) +NS_StringContainerInit2(nsStringContainer& aStr, const char16_t* aData, + uint32_t aDataLength, uint32_t aFlags) +{ + if (!xpcomFunctions.stringContainerInit2) { + return NS_ERROR_NOT_INITIALIZED; + } + return xpcomFunctions.stringContainerInit2(aStr, aData, aDataLength, aFlags); +} + +XPCOM_API(void) +NS_StringContainerFinish(nsStringContainer& aStr) +{ + if (xpcomFunctions.stringContainerFinish) { + xpcomFunctions.stringContainerFinish(aStr); + } +} + +XPCOM_API(uint32_t) +NS_StringGetData(const nsAString& aStr, const char16_t** aBuf, bool* aTerm) +{ + if (!xpcomFunctions.stringGetData) { + *aBuf = nullptr; + return 0; + } + return xpcomFunctions.stringGetData(aStr, aBuf, aTerm); +} + +XPCOM_API(uint32_t) +NS_StringGetMutableData(nsAString& aStr, uint32_t aLen, char16_t** aBuf) +{ + if (!xpcomFunctions.stringGetMutableData) { + *aBuf = nullptr; + return 0; + } + return xpcomFunctions.stringGetMutableData(aStr, aLen, aBuf); +} + +XPCOM_API(char16_t*) +NS_StringCloneData(const nsAString& aStr) +{ + if (!xpcomFunctions.stringCloneData) { + return nullptr; + } + return xpcomFunctions.stringCloneData(aStr); +} + +XPCOM_API(nsresult) +NS_StringSetData(nsAString& aStr, const char16_t* aBuf, uint32_t aCount) +{ + if (!xpcomFunctions.stringSetData) { + return NS_ERROR_NOT_INITIALIZED; + } + + return xpcomFunctions.stringSetData(aStr, aBuf, aCount); +} + +XPCOM_API(nsresult) +NS_StringSetDataRange(nsAString& aStr, uint32_t aCutStart, uint32_t aCutLength, + const char16_t* aBuf, uint32_t aCount) +{ + if (!xpcomFunctions.stringSetDataRange) { + return NS_ERROR_NOT_INITIALIZED; + } + return xpcomFunctions.stringSetDataRange(aStr, aCutStart, aCutLength, aBuf, + aCount); +} + +XPCOM_API(nsresult) +NS_StringCopy(nsAString& aDest, const nsAString& aSrc) +{ + if (!xpcomFunctions.stringCopy) { + return NS_ERROR_NOT_INITIALIZED; + } + return xpcomFunctions.stringCopy(aDest, aSrc); +} + +XPCOM_API(void) +NS_StringSetIsVoid(nsAString& aStr, const bool aIsVoid) +{ + if (xpcomFunctions.stringSetIsVoid) { + xpcomFunctions.stringSetIsVoid(aStr, aIsVoid); + } +} + +XPCOM_API(bool) +NS_StringGetIsVoid(const nsAString& aStr) +{ + if (!xpcomFunctions.stringGetIsVoid) { + return false; + } + return xpcomFunctions.stringGetIsVoid(aStr); +} + +XPCOM_API(nsresult) +NS_CStringContainerInit(nsCStringContainer& aStr) +{ + if (!xpcomFunctions.cstringContainerInit) { + return NS_ERROR_NOT_INITIALIZED; + } + return xpcomFunctions.cstringContainerInit(aStr); +} + +XPCOM_API(nsresult) +NS_CStringContainerInit2(nsCStringContainer& aStr, const char* aData, + uint32_t aDataLength, uint32_t aFlags) +{ + if (!xpcomFunctions.cstringContainerInit2) { + return NS_ERROR_NOT_INITIALIZED; + } + return xpcomFunctions.cstringContainerInit2(aStr, aData, aDataLength, aFlags); +} + +XPCOM_API(void) +NS_CStringContainerFinish(nsCStringContainer& aStr) +{ + if (xpcomFunctions.cstringContainerFinish) { + xpcomFunctions.cstringContainerFinish(aStr); + } +} + +XPCOM_API(uint32_t) +NS_CStringGetData(const nsACString& aStr, const char** aBuf, bool* aTerm) +{ + if (!xpcomFunctions.cstringGetData) { + *aBuf = nullptr; + return 0; + } + return xpcomFunctions.cstringGetData(aStr, aBuf, aTerm); +} + +XPCOM_API(uint32_t) +NS_CStringGetMutableData(nsACString& aStr, uint32_t aLen, char** aBuf) +{ + if (!xpcomFunctions.cstringGetMutableData) { + *aBuf = nullptr; + return 0; + } + return xpcomFunctions.cstringGetMutableData(aStr, aLen, aBuf); +} + +XPCOM_API(char*) +NS_CStringCloneData(const nsACString& aStr) +{ + if (!xpcomFunctions.cstringCloneData) { + return nullptr; + } + return xpcomFunctions.cstringCloneData(aStr); +} + +XPCOM_API(nsresult) +NS_CStringSetData(nsACString& aStr, const char* aBuf, uint32_t aCount) +{ + if (!xpcomFunctions.cstringSetData) { + return NS_ERROR_NOT_INITIALIZED; + } + return xpcomFunctions.cstringSetData(aStr, aBuf, aCount); +} + +XPCOM_API(nsresult) +NS_CStringSetDataRange(nsACString& aStr, uint32_t aCutStart, + uint32_t aCutLength, const char* aBuf, uint32_t aCount) +{ + if (!xpcomFunctions.cstringSetDataRange) { + return NS_ERROR_NOT_INITIALIZED; + } + return xpcomFunctions.cstringSetDataRange(aStr, aCutStart, aCutLength, aBuf, + aCount); +} + +XPCOM_API(nsresult) +NS_CStringCopy(nsACString& aDest, const nsACString& aSrc) +{ + if (!xpcomFunctions.cstringCopy) { + return NS_ERROR_NOT_INITIALIZED; + } + return xpcomFunctions.cstringCopy(aDest, aSrc); +} + +XPCOM_API(void) +NS_CStringSetIsVoid(nsACString& aStr, const bool aIsVoid) +{ + if (xpcomFunctions.cstringSetIsVoid) { + xpcomFunctions.cstringSetIsVoid(aStr, aIsVoid); + } +} + +XPCOM_API(bool) +NS_CStringGetIsVoid(const nsACString& aStr) +{ + if (!xpcomFunctions.cstringGetIsVoid) { + return false; + } + return xpcomFunctions.cstringGetIsVoid(aStr); +} + +XPCOM_API(nsresult) +NS_CStringToUTF16(const nsACString& aSrc, nsCStringEncoding aSrcEncoding, + nsAString& aDest) +{ + if (!xpcomFunctions.cstringToUTF16) { + return NS_ERROR_NOT_INITIALIZED; + } + return xpcomFunctions.cstringToUTF16(aSrc, aSrcEncoding, aDest); +} + +XPCOM_API(nsresult) +NS_UTF16ToCString(const nsAString& aSrc, nsCStringEncoding aDestEncoding, + nsACString& aDest) +{ + if (!xpcomFunctions.utf16ToCString) { + return NS_ERROR_NOT_INITIALIZED; + } + return xpcomFunctions.utf16ToCString(aSrc, aDestEncoding, aDest); +} + +XPCOM_API(void*) +NS_Alloc(size_t aSize) +{ + if (!xpcomFunctions.allocFunc) { + return nullptr; + } + return xpcomFunctions.allocFunc(aSize); +} + +XPCOM_API(void*) +NS_Realloc(void* aPtr, size_t aSize) +{ + if (!xpcomFunctions.reallocFunc) { + return nullptr; + } + return xpcomFunctions.reallocFunc(aPtr, aSize); +} + +XPCOM_API(void) +NS_Free(void* aPtr) +{ + if (xpcomFunctions.freeFunc) { + xpcomFunctions.freeFunc(aPtr); + } +} + +XPCOM_API(void) +NS_DebugBreak(uint32_t aSeverity, const char* aStr, const char* aExpr, + const char* aFile, int32_t aLine) +{ + if (xpcomFunctions.debugBreakFunc) { + xpcomFunctions.debugBreakFunc(aSeverity, aStr, aExpr, aFile, aLine); + } +} + +XPCOM_API(void) +NS_LogInit() +{ + if (xpcomFunctions.logInitFunc) { + xpcomFunctions.logInitFunc(); + } +} + +XPCOM_API(void) +NS_LogTerm() +{ + if (xpcomFunctions.logTermFunc) { + xpcomFunctions.logTermFunc(); + } +} + +XPCOM_API(void) +NS_LogAddRef(void* aPtr, nsrefcnt aNewRefCnt, + const char* aTypeName, uint32_t aInstanceSize) +{ + if (xpcomFunctions.logAddRefFunc) + xpcomFunctions.logAddRefFunc(aPtr, aNewRefCnt, + aTypeName, aInstanceSize); +} + +XPCOM_API(void) +NS_LogRelease(void* aPtr, nsrefcnt aNewRefCnt, const char* aTypeName) +{ + if (xpcomFunctions.logReleaseFunc) { + xpcomFunctions.logReleaseFunc(aPtr, aNewRefCnt, aTypeName); + } +} + +XPCOM_API(void) +NS_LogCtor(void* aPtr, const char* aTypeName, uint32_t aInstanceSize) +{ + if (xpcomFunctions.logCtorFunc) { + xpcomFunctions.logCtorFunc(aPtr, aTypeName, aInstanceSize); + } +} + +XPCOM_API(void) +NS_LogDtor(void* aPtr, const char* aTypeName, uint32_t aInstanceSize) +{ + if (xpcomFunctions.logDtorFunc) { + xpcomFunctions.logDtorFunc(aPtr, aTypeName, aInstanceSize); + } +} + +XPCOM_API(void) +NS_LogCOMPtrAddRef(void* aCOMPtr, nsISupports* aObject) +{ + if (xpcomFunctions.logCOMPtrAddRefFunc) { + xpcomFunctions.logCOMPtrAddRefFunc(aCOMPtr, aObject); + } +} + +XPCOM_API(void) +NS_LogCOMPtrRelease(void* aCOMPtr, nsISupports* aObject) +{ + if (xpcomFunctions.logCOMPtrReleaseFunc) { + xpcomFunctions.logCOMPtrReleaseFunc(aCOMPtr, aObject); + } +} + +XPCOM_API(nsresult) +NS_GetXPTCallStub(REFNSIID aIID, nsIXPTCProxy* aOuter, + nsISomeInterface** aStub) +{ + if (!xpcomFunctions.getXPTCallStubFunc) { + return NS_ERROR_NOT_INITIALIZED; + } + + return xpcomFunctions.getXPTCallStubFunc(aIID, aOuter, aStub); +} + +XPCOM_API(void) +NS_DestroyXPTCallStub(nsISomeInterface* aStub) +{ + if (xpcomFunctions.destroyXPTCallStubFunc) { + xpcomFunctions.destroyXPTCallStubFunc(aStub); + } +} + +XPCOM_API(nsresult) +NS_InvokeByIndex(nsISupports* aThat, uint32_t aMethodIndex, + uint32_t aParamCount, nsXPTCVariant* aParams) +{ + if (!xpcomFunctions.invokeByIndexFunc) { + return NS_ERROR_NOT_INITIALIZED; + } + + return xpcomFunctions.invokeByIndexFunc(aThat, aMethodIndex, + aParamCount, aParams); +} + +XPCOM_API(bool) +NS_CycleCollectorSuspect(nsISupports* aObj) +{ + if (!xpcomFunctions.cycleSuspectFunc) { + return false; + } + + return xpcomFunctions.cycleSuspectFunc(aObj); +} + +XPCOM_API(bool) +NS_CycleCollectorForget(nsISupports* aObj) +{ + if (!xpcomFunctions.cycleForgetFunc) { + return false; + } + + return xpcomFunctions.cycleForgetFunc(aObj); +} + +XPCOM_API(nsPurpleBufferEntry*) +NS_CycleCollectorSuspect2(void* aObj, nsCycleCollectionParticipant* aCp) +{ + if (!xpcomFunctions.cycleSuspect2Func) { + return nullptr; + } + + return xpcomFunctions.cycleSuspect2Func(aObj, aCp); +} + +XPCOM_API(void) +NS_CycleCollectorSuspect3(void* aObj, nsCycleCollectionParticipant* aCp, + nsCycleCollectingAutoRefCnt* aRefCnt, + bool* aShouldDelete) +{ + if (xpcomFunctions.cycleSuspect3Func) { + xpcomFunctions.cycleSuspect3Func(aObj, aCp, aRefCnt, aShouldDelete); + } +} + +XPCOM_API(bool) +NS_CycleCollectorForget2(nsPurpleBufferEntry* aEntry) +{ + if (!xpcomFunctions.cycleForget2Func) { + return false; + } + + return xpcomFunctions.cycleForget2Func(aEntry); +} diff --git a/xpcom/glue/standalone/nsXPCOMGlue.h b/xpcom/glue/standalone/nsXPCOMGlue.h new file mode 100644 index 000000000..e23bfa498 --- /dev/null +++ b/xpcom/glue/standalone/nsXPCOMGlue.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 nsXPCOMGlue_h__ +#define nsXPCOMGlue_h__ + +#include "nscore.h" + +#ifdef XPCOM_GLUE + +/** + * The following functions are only available in the standalone glue. + */ + +/** + * Enabled preloading of dynamically loaded libraries + */ +extern "C" NS_HIDDEN_(void) XPCOMGlueEnablePreload(); + +/** + * Initialize the XPCOM glue by dynamically linking against the XPCOM + * shared library indicated by xpcomFile. + */ +extern "C" NS_HIDDEN_(nsresult) XPCOMGlueStartup(const char* aXPCOMFile); + +typedef void (*NSFuncPtr)(); + +struct nsDynamicFunctionLoad +{ + const char* functionName; + NSFuncPtr* function; +}; + +/** + * Dynamically load functions from libxul. + * + * @throws NS_ERROR_NOT_INITIALIZED if XPCOMGlueStartup() was not called or + * if the libxul DLL was not found. + * @throws NS_ERROR_LOSS_OF_SIGNIFICANT_DATA if only some of the required + * functions were found. + */ +extern "C" NS_HIDDEN_(nsresult) +XPCOMGlueLoadXULFunctions(const nsDynamicFunctionLoad* aSymbols); + +#endif // XPCOM_GLUE +#endif // nsXPCOMGlue_h__ diff --git a/xpcom/glue/standalone/staticruntime/moz.build b/xpcom/glue/standalone/staticruntime/moz.build new file mode 100644 index 000000000..735086ab0 --- /dev/null +++ b/xpcom/glue/standalone/staticruntime/moz.build @@ -0,0 +1,50 @@ +# -*- 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/. + +include('../../objs.mozbuild') + +SOURCES += xpcom_glue_src_cppsrcs + +SOURCES += [ + '../../nsStringAPI.cpp', + '../nsXPCOMGlue.cpp', +] + +Library('xpcomglue_staticruntime') + +SDK_LIBRARY = True + +# create a static lib +FORCE_STATIC_LIB = True + +if CONFIG['_MSC_VER']: + DEFINES['_USE_ANSI_CPP'] = True + # Don't include directives about which CRT to use + CFLAGS += ['-Zl'] + CXXFLAGS += ['-Zl'] + +DEFINES['XPCOM_GLUE'] = True + +LOCAL_INCLUDES += [ + '../../../build', + '../../../threads', +] + +# Statically link to the CRT on Windows +USE_STATIC_LIBS = True + +# Don't use STL wrappers here (i.e. wrapped ); they require mozalloc +DISABLE_STL_WRAPPING = True + +# Include fallible for third party code using the xpcom glue +USE_LIBS += [ + 'fallible', +] + +# Force to build a static library only +NO_EXPAND_LIBS = True + +DIST_INSTALL = True diff --git a/xpcom/glue/staticruntime/moz.build b/xpcom/glue/staticruntime/moz.build new file mode 100644 index 000000000..384bc6878 --- /dev/null +++ b/xpcom/glue/staticruntime/moz.build @@ -0,0 +1,48 @@ +# -*- 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/. + +include('../objs.mozbuild') + +UNIFIED_SOURCES += xpcom_gluens_src_cppsrcs +UNIFIED_SOURCES += xpcom_glue_src_cppsrcs + +UNIFIED_SOURCES += [ + '../GenericModule.cpp', + '../nsStringAPI.cpp', +] + +Library('xpcomglue_staticruntime_s') + +SDK_LIBRARY = True + +FORCE_STATIC_LIB = True + +if CONFIG['_MSC_VER']: + DEFINES['_USE_ANSI_CPP'] = True + # Don't include directives about which CRT to use + CFLAGS += ['-Zl'] + CXXFLAGS += ['-Zl'] + +LOCAL_INCLUDES += [ + '../../build', + '../../threads', +] + +# Statically link to the CRT on Windows +USE_STATIC_LIBS = True + +# Don't use STL wrappers here (i.e. wrapped ); they require mozalloc +DISABLE_STL_WRAPPING = True + +# Include fallible for third party code using the xpcom glue +USE_LIBS += [ + 'fallible', +] + +# Force to build a static library only +NO_EXPAND_LIBS = True + +DIST_INSTALL = True diff --git a/xpcom/glue/tests/gtest/TestArray.cpp b/xpcom/glue/tests/gtest/TestArray.cpp new file mode 100644 index 000000000..72d28b4df --- /dev/null +++ b/xpcom/glue/tests/gtest/TestArray.cpp @@ -0,0 +1,169 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. 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 "gtest/gtest.h" + +// Disable deprecation warnings generated by nsISupportsArray and associated +// classes. +#if defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#elif defined(_MSC_VER) +#pragma warning (push) +#pragma warning (disable : 4996) +#endif + +#include "nsISupportsArray.h" + +// {9e70a320-be02-11d1-8031-006008159b5a} +#define NS_IFOO_IID \ + {0x9e70a320, 0xbe02, 0x11d1, \ + {0x80, 0x31, 0x00, 0x60, 0x08, 0x15, 0x9b, 0x5a}} + +namespace TestArray { + +class IFoo : 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(IFoo, NS_IFOO_IID) + +class Foo final : public IFoo { +public: + + explicit Foo(int32_t aID); + + // nsISupports implementation + NS_DECL_ISUPPORTS + + // IFoo implementation + NS_IMETHOD_(nsrefcnt) RefCnt() override { return mRefCnt; } + NS_IMETHOD_(int32_t) ID() override { return mID; } + + static int32_t gCount; + + int32_t mID; + +private: + ~Foo(); +}; + +int32_t Foo::gCount; + +Foo::Foo(int32_t aID) +{ + mID = aID; + ++gCount; +} + +Foo::~Foo() +{ + --gCount; +} + +NS_IMPL_ISUPPORTS(Foo, IFoo) + +void CheckArray(nsISupportsArray* aArray, int32_t aExpectedCount, int32_t aElementIDs[], int32_t aExpectedTotal) +{ + uint32_t cnt = 0; +#ifdef DEBUG + nsresult rv = +#endif + aArray->Count(&cnt); + NS_ASSERTION(NS_SUCCEEDED(rv), "Count failed"); + int32_t count = cnt; + int32_t index; + + EXPECT_EQ(Foo::gCount, aExpectedTotal); + EXPECT_EQ(count, aExpectedCount); + + for (index = 0; (index < count) && (index < aExpectedCount); index++) { + nsCOMPtr foo = do_QueryElementAt(aArray, index); + EXPECT_EQ(foo->ID(), aElementIDs[index]); + } +} + +void FillArray(nsISupportsArray* aArray, int32_t aCount) +{ + int32_t index; + for (index = 0; index < aCount; index++) { + nsCOMPtr foo = new Foo(index); + aArray->AppendElement(foo); + } +} + +} // namespace TestArray + +using namespace TestArray; + +TEST(Array, main) +{ + nsISupportsArray* array; + nsresult rv; + + if (NS_OK == (rv = NS_NewISupportsArray(&array))) { + FillArray(array, 10); + int32_t fillResult[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + CheckArray(array, 10, fillResult, 10); + + // test insert + nsCOMPtr foo = do_QueryElementAt(array, 3); + array->InsertElementAt(foo, 5); + int32_t insertResult[11] = {0, 1, 2, 3, 4, 3, 5, 6, 7, 8, 9}; + CheckArray(array, 11, insertResult, 10); + array->InsertElementAt(foo, 0); + int32_t insertResult2[12] = {3, 0, 1, 2, 3, 4, 3, 5, 6, 7, 8, 9}; + CheckArray(array, 12, insertResult2, 10); + array->AppendElement(foo); + int32_t appendResult[13] = {3, 0, 1, 2, 3, 4, 3, 5, 6, 7, 8, 9, 3}; + CheckArray(array, 13, appendResult, 10); + + + // test IndexOf + int32_t expectedIndex = 0; + int32_t index = array->IndexOf(foo); + EXPECT_EQ(index, expectedIndex); + + // test ReplaceElementAt + array->ReplaceElementAt(foo, 8); + int32_t replaceResult[13] = {3, 0, 1, 2, 3, 4, 3, 5, 3, 7, 8, 9, 3}; + CheckArray(array, 13, replaceResult, 9); + + // test RemoveElementAt, RemoveElement + array->RemoveElementAt(0); + int32_t removeResult[12] = {0, 1, 2, 3, 4, 3, 5, 3, 7, 8, 9, 3}; + CheckArray(array, 12, removeResult, 9); + array->RemoveElementAt(7); + int32_t removeResult2[11] = {0, 1, 2, 3, 4, 3, 5, 7, 8, 9, 3}; + CheckArray(array, 11, removeResult2, 9); + array->RemoveElement(foo); + int32_t removeResult3[10] = {0, 1, 2, 4, 3, 5, 7, 8, 9, 3}; + CheckArray(array, 10, removeResult3, 9); + + foo = nullptr; + + // test clear + array->Clear(); + FillArray(array, 4); + CheckArray(array, 4, fillResult, 4); + + // test delete + NS_RELEASE(array); + } +} + +#if defined(__GNUC__) +#pragma GCC diagnostic pop +#elif defined(_MSC_VER) +#pragma warning (pop) +#endif diff --git a/xpcom/glue/tests/gtest/TestFileUtils.cpp b/xpcom/glue/tests/gtest/TestFileUtils.cpp new file mode 100644 index 000000000..55106c6c5 --- /dev/null +++ b/xpcom/glue/tests/gtest/TestFileUtils.cpp @@ -0,0 +1,283 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +/* Test ReadSysFile() */ + +#include +#include + +#include +#include +#include +#include + +#include "FileUtils.h" + +#include "gtest/gtest.h" + +namespace mozilla { + +#ifdef ReadSysFile_PRESENT + +/** + * Create a file with the specified contents. + */ +static bool +WriteFile( + const char* aFilename, + const void* aContents, + size_t aContentsLen) +{ + int fd; + ssize_t ret; + size_t offt; + + fd = MOZ_TEMP_FAILURE_RETRY( + open(aFilename, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR)); + if (fd == -1) { + fprintf(stderr, "open(): %s: %s\n", aFilename, strerror(errno)); + return false; + } + + offt = 0; + do { + ret = MOZ_TEMP_FAILURE_RETRY( + write(fd, (char*)aContents + offt, aContentsLen - offt)); + if (ret == -1) { + fprintf(stderr, "write(): %s: %s\n", aFilename, strerror(errno)); + close(fd); + return false; + } + offt += ret; + } while (offt < aContentsLen); + + ret = MOZ_TEMP_FAILURE_RETRY(close(fd)); + if (ret == -1) { + fprintf(stderr, "close(): %s: %s\n", aFilename, strerror(errno)); + return false; + } + return true; +} + +TEST(ReadSysFile, Nonexistent) { + bool ret; + int errno_saved; + + ret = ReadSysFile("/nonexistent", nullptr, 0); + errno_saved = errno; + + ASSERT_FALSE(ret); + ASSERT_EQ(errno_saved, ENOENT); +} + +TEST(ReadSysFile, Main) { + /* Use a different file name for each test since different tests could be + executed concurrently. */ + static const char* fn = "TestReadSysFileMain"; + /* If we have a file which contains "abcd" and we read it with ReadSysFile(), + providing a buffer of size 10 bytes, we would expect 5 bytes to be written + to that buffer: "abcd\0". */ + struct + { + /* input (file contents), e.g. "abcd" */ + const char* input; + /* pretended output buffer size, e.g. 10; the actual buffer is larger + and we check if anything was written past the end of the allowed length */ + size_t output_size; + /* expected number of bytes written to the output buffer, including the + terminating '\0', e.g. 5 */ + size_t output_len; + /* expected output buffer contents, e.g. "abcd\0", the first output_len + bytes of the output buffer should match the first 'output_len' bytes from + 'output', the rest of the output buffer should be untouched. */ + const char* output; + } tests[] = { + /* No new lines */ + {"", 0, 0, ""}, + {"", 1, 1, "\0"}, /* \0 is redundant, but we write it for clarity */ + {"", 9, 1, "\0"}, + + {"a", 0, 0, ""}, + {"a", 1, 1, "\0"}, + {"a", 2, 2, "a\0"}, + {"a", 9, 2, "a\0"}, + + {"abcd", 0, 0, ""}, + {"abcd", 1, 1, "\0"}, + {"abcd", 2, 2, "a\0"}, + {"abcd", 3, 3, "ab\0"}, + {"abcd", 4, 4, "abc\0"}, + {"abcd", 5, 5, "abcd\0"}, + {"abcd", 9, 5, "abcd\0"}, + + /* A single trailing new line */ + {"\n", 0, 0, ""}, + {"\n", 1, 1, "\0"}, + {"\n", 2, 1, "\0"}, + {"\n", 9, 1, "\0"}, + + {"a\n", 0, 0, ""}, + {"a\n", 1, 1, "\0"}, + {"a\n", 2, 2, "a\0"}, + {"a\n", 3, 2, "a\0"}, + {"a\n", 9, 2, "a\0"}, + + {"abcd\n", 0, 0, ""}, + {"abcd\n", 1, 1, "\0"}, + {"abcd\n", 2, 2, "a\0"}, + {"abcd\n", 3, 3, "ab\0"}, + {"abcd\n", 4, 4, "abc\0"}, + {"abcd\n", 5, 5, "abcd\0"}, + {"abcd\n", 6, 5, "abcd\0"}, + {"abcd\n", 9, 5, "abcd\0"}, + + /* Multiple trailing new lines */ + {"\n\n", 0, 0, ""}, + {"\n\n", 1, 1, "\0"}, + {"\n\n", 2, 2, "\n\0"}, + {"\n\n", 3, 2, "\n\0"}, + {"\n\n", 9, 2, "\n\0"}, + + {"a\n\n", 0, 0, ""}, + {"a\n\n", 1, 1, "\0"}, + {"a\n\n", 2, 2, "a\0"}, + {"a\n\n", 3, 3, "a\n\0"}, + {"a\n\n", 4, 3, "a\n\0"}, + {"a\n\n", 9, 3, "a\n\0"}, + + {"abcd\n\n", 0, 0, ""}, + {"abcd\n\n", 1, 1, "\0"}, + {"abcd\n\n", 2, 2, "a\0"}, + {"abcd\n\n", 3, 3, "ab\0"}, + {"abcd\n\n", 4, 4, "abc\0"}, + {"abcd\n\n", 5, 5, "abcd\0"}, + {"abcd\n\n", 6, 6, "abcd\n\0"}, + {"abcd\n\n", 7, 6, "abcd\n\0"}, + {"abcd\n\n", 9, 6, "abcd\n\0"}, + + /* New line in the middle */ + {"ab\ncd", 9, 6, "ab\ncd\0"}, + }; + + for (size_t i = 0; i < sizeof(tests) / sizeof(tests[0]); i++) { + ASSERT_TRUE(WriteFile(fn, tests[i].input, strlen(tests[i].input))); + /* Leave the file to exist if some of the assertions fail. */ + + char buf[128]; + static const char unmodified = 'X'; + + memset(buf, unmodified, sizeof(buf)); + + ASSERT_TRUE(ReadSysFile(fn, buf, tests[i].output_size)); + + if (tests[i].output_size == 0) { + /* The buffer must be unmodified. We check only the first byte. */ + ASSERT_EQ(unmodified, buf[0]); + } else { + ASSERT_EQ(tests[i].output_len, strlen(buf) + 1); + ASSERT_STREQ(tests[i].output, buf); + /* Check that the first byte after the trailing '\0' has not been + modified. */ + ASSERT_EQ(unmodified, buf[tests[i].output_len]); + } + } + + unlink(fn); +} + +TEST(ReadSysFile, Int) { + static const char* fn = "TestReadSysFileInt"; + struct + { + /* input (file contents), e.g. "5" */ + const char* input; + /* expected return value, if false, then the output is not checked */ + bool ret; + /* expected result */ + int output; + } tests[] = { + {"0", true, 0}, + {"00", true, 0}, + {"1", true, 1}, + {"5", true, 5}, + {"55", true, 55}, + + {" 123", true, 123}, + {"123 ", true, 123}, + {" 123 ", true, 123}, + {"123\n", true, 123}, + + {"", false, 0}, + {" ", false, 0}, + {"a", false, 0}, + + {"-1", true, -1}, + {" -456 ", true, -456}, + {" -78.9 ", true, -78}, + }; + + for (size_t i = 0; i < sizeof(tests) / sizeof(tests[0]); i++) { + ASSERT_TRUE(WriteFile(fn, tests[i].input, strlen(tests[i].input))); + /* Leave the file to exist if some of the assertions fail. */ + + bool ret; + int output = 424242; + + ret = ReadSysFile(fn, &output); + + ASSERT_EQ(tests[i].ret, ret); + + if (ret) { + ASSERT_EQ(tests[i].output, output); + } + } + + unlink(fn); +} + +TEST(ReadSysFile, Bool) { + static const char* fn = "TestReadSysFileBool"; + struct + { + /* input (file contents), e.g. "1" */ + const char* input; + /* expected return value */ + bool ret; + /* expected result */ + bool output; + } tests[] = { + {"0", true, false}, + {"00", true, false}, + {"1", true, true}, + {"5", true, true}, + {"23", true, true}, + {"-1", true, true}, + + {"", false, true /* unused */}, + }; + + for (size_t i = 0; i < sizeof(tests) / sizeof(tests[0]); i++) { + ASSERT_TRUE(WriteFile(fn, tests[i].input, strlen(tests[i].input))); + /* Leave the file to exist if some of the assertions fail. */ + + bool ret; + bool output; + + ret = ReadSysFile(fn, &output); + + ASSERT_EQ(tests[i].ret, ret); + + if (ret) { + ASSERT_EQ(tests[i].output, output); + } + } + + unlink(fn); +} + +#endif /* ReadSysFile_PRESENT */ + +} // namespace mozilla diff --git a/xpcom/glue/tests/gtest/TestGCPostBarriers.cpp b/xpcom/glue/tests/gtest/TestGCPostBarriers.cpp new file mode 100644 index 000000000..5bf10ab05 --- /dev/null +++ b/xpcom/glue/tests/gtest/TestGCPostBarriers.cpp @@ -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/. */ + +/* + * Tests that generational garbage collection post-barriers are correctly + * implemented for nsTArrays that contain JavaScript Values. + */ + +#include "jsapi.h" +#include "nsTArray.h" + +#include "gtest/gtest.h" + +#include "js/TracingAPI.h" +#include "js/HeapAPI.h" + +#include "mozilla/CycleCollectedJSContext.h" + +using namespace JS; +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 +RunTest(JSContext* cx, ArrayT* array) +{ + JS_GC(cx); + + ASSERT_TRUE(array != nullptr); + JS_AddExtraGCRootsTracer(cx, TraceArray, array); + + /* + * Create the array and fill it with new JS objects. With GGC these will be + * allocated in the nursery. + */ + RootedValue value(cx); + const char* property = "foo"; + for (size_t i = 0; i < ElementCount; ++i) { + RootedObject obj(cx, JS_NewPlainObject(cx)); + ASSERT_FALSE(JS::ObjectIsTenured(obj)); + value = Int32Value(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. + */ + for (size_t i = 0; i < ElementCount; ++i) { + 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); +} + +static void +CreateGlobalAndRunTest(JSContext* cx) +{ + static const JSClassOps GlobalClassOps = { + nullptr, nullptr, nullptr, nullptr, + nullptr, nullptr, nullptr, nullptr, + nullptr, nullptr, nullptr, + JS_GlobalObjectTraceHook + }; + + static const JSClass GlobalClass = { + "global", JSCLASS_GLOBAL_FLAGS, + &GlobalClassOps + }; + + JS::CompartmentOptions options; + options.behaviors().setVersion(JSVERSION_LATEST); + JS::PersistentRootedObject global(cx); + global = JS_NewGlobalObject(cx, &GlobalClass, nullptr, JS::FireOnNewGlobalHook, options); + ASSERT_TRUE(global != nullptr); + + JSCompartment *oldCompartment = JS_EnterCompartment(cx, global); + + typedef Heap ElementT; + + { + nsTArray* array = new nsTArray(InitialElements); + RunTest(cx, array); + delete array; + } + + { + FallibleTArray* array = new FallibleTArray(InitialElements); + RunTest(cx, array); + delete array; + } + + { + AutoTArray array; + RunTest(cx, &array); + } + + JS_LeaveCompartment(cx, oldCompartment); +} + +TEST(GCPostBarriers, nsTArray) { + CycleCollectedJSContext* ccjscx = CycleCollectedJSContext::Get(); + ASSERT_TRUE(ccjscx != nullptr); + JSContext* cx = ccjscx->Context(); + ASSERT_TRUE(cx != nullptr); + + JS_BeginRequest(cx); + + CreateGlobalAndRunTest(cx); + + JS_EndRequest(cx); +} diff --git a/xpcom/glue/tests/gtest/TestNsDeque.cpp b/xpcom/glue/tests/gtest/TestNsDeque.cpp new file mode 100644 index 000000000..b84e1b781 --- /dev/null +++ b/xpcom/glue/tests/gtest/TestNsDeque.cpp @@ -0,0 +1,342 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. 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 + +/************************************************************** + Now define the token deallocator class... + **************************************************************/ +namespace TestNsDeque { + + class _Dealloc: public nsDequeFunctor + { + virtual void* operator()(void* aObject) { + return 0; + } + }; + + static bool VerifyContents(const nsDeque& aDeque, const int* aContents, size_t aLength) + { + for (size_t i=0; i(i); + } + // queue = [0...69] + for (i=0;i<70;i++) { + theDeque.Push(&ints[i]); + temp=*(int*)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=*(int*)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=*(int*)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=*(int*)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 = *(int*)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=*(int*)theDeque.Pop(); + EXPECT_EQ(34 - static_cast(i), temp) << "Verify end after pop # 3"; + } + + // queue = [0...14] + for (i=0;i<20;i++) { + temp=*(int*)theDeque.Pop(); + EXPECT_EQ(19 - static_cast(i),temp) << "Verify end after pop # 4"; + } + + // queue = [] + for (i=0;i<15;i++) { + temp=*(int*)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 = *(int*)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=*(int*)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=*(int*)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(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"; +} + +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((void*)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((void*)testArray[i]); + } + + ForEachAdder adder; + d.ForEach(adder); + EXPECT_EQ(sum, adder.GetSum()) << "For each should iterate over values"; + + d.Erase(); +} diff --git a/xpcom/glue/tests/gtest/TestThreadUtils.cpp b/xpcom/glue/tests/gtest/TestThreadUtils.cpp new file mode 100644 index 000000000..728bae612 --- /dev/null +++ b/xpcom/glue/tests/gtest/TestThreadUtils.cpp @@ -0,0 +1,937 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. 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 "nsThreadUtils.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}} + +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 ThreadUtilsObject : public IThreadUtilsObject +{ + // 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 = mozilla::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() {} + // '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() {} + // '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) +{ +#ifndef XPCOM_GLUE_AVOID_NSPR + 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, + "StorensRefPtrPassByPtr 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(rpt, &ThreadUtilsObject::Test0); + r1->Run(); + EXPECT_EQ(count += 1, rpt->mCount); + + r1 = NewRunnableMethod(rpt, &ThreadUtilsObject::Test1i, 11); + r1->Run(); + EXPECT_EQ(count += 2, rpt->mCount); + EXPECT_EQ(11, rpt->mA0); + + // Test variadic function with simple POD arguments. + + r1 = NewRunnableMethod(rpt, &ThreadUtilsObject::Test0); + r1->Run(); + EXPECT_EQ(count += 1, rpt->mCount); + + static_assert( + mozilla::IsSame< ::detail::ParameterStorage::Type, + StoreCopyPassByValue>::value, + "detail::ParameterStorage::Type should be StoreCopyPassByValue"); + static_assert( + mozilla::IsSame< ::detail::ParameterStorage>::Type, + StoreCopyPassByValue>::value, + "detail::ParameterStorage>::Type should be StoreCopyPassByValue"); + + r1 = NewRunnableMethod(rpt, &ThreadUtilsObject::Test1i, 12); + r1->Run(); + EXPECT_EQ(count += 2, rpt->mCount); + EXPECT_EQ(12, rpt->mA0); + + r1 = NewRunnableMethod( + 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( + 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( + 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(rpt, &ThreadUtilsObject::Test1i, si); + r1->Run(); + EXPECT_EQ(count += 2, rpt->mCount); + EXPECT_EQ(si, rpt->mA0); + + // Raw pointer, possible cv-qualified. + static_assert(mozilla::IsSame< ::detail::ParameterStorage::Type, + StorePtrPassByPtr>::value, + "detail::ParameterStorage::Type should be StorePtrPassByPtr"); + static_assert(mozilla::IsSame< ::detail::ParameterStorage::Type, + StorePtrPassByPtr>::value, + "detail::ParameterStorage::Type should be StorePtrPassByPtr"); + static_assert(mozilla::IsSame< ::detail::ParameterStorage::Type, + StorePtrPassByPtr>::value, + "detail::ParameterStorage::Type should be StorePtrPassByPtr"); + static_assert(mozilla::IsSame< ::detail::ParameterStorage::Type, + StorePtrPassByPtr>::value, + "detail::ParameterStorage::Type should be StorePtrPassByPtr"); + static_assert(mozilla::IsSame< ::detail::ParameterStorage::Type::stored_type, + int*>::value, + "detail::ParameterStorage::Type::stored_type should be int*"); + static_assert(mozilla::IsSame< ::detail::ParameterStorage::Type::passed_type, + int*>::value, + "detail::ParameterStorage::Type::passed_type should be int*"); + { + int i = 12; + r1 = NewRunnableMethod(rpt, &ThreadUtilsObject::Test1pi, &i); + r1->Run(); + EXPECT_EQ(count += 2, rpt->mCount); + EXPECT_EQ(i, rpt->mA0); + } + + // Raw pointer to const. + static_assert(mozilla::IsSame< ::detail::ParameterStorage::Type, + StoreConstPtrPassByConstPtr>::value, + "detail::ParameterStorage::Type should be StoreConstPtrPassByConstPtr"); + static_assert(mozilla::IsSame< ::detail::ParameterStorage::Type, + StoreConstPtrPassByConstPtr>::value, + "detail::ParameterStorage::Type should be StoreConstPtrPassByConstPtr"); + static_assert(mozilla::IsSame< ::detail::ParameterStorage::Type, + StoreConstPtrPassByConstPtr>::value, + "detail::ParameterStorage::Type should be StoreConstPtrPassByConstPtr"); + static_assert(mozilla::IsSame< ::detail::ParameterStorage::Type, + StoreConstPtrPassByConstPtr>::value, + "detail::ParameterStorage::Type should be StoreConstPtrPassByConstPtr"); + static_assert(mozilla::IsSame< ::detail::ParameterStorage::Type::stored_type, + const int*>::value, + "detail::ParameterStorage::Type::stored_type should be const int*"); + static_assert(mozilla::IsSame< ::detail::ParameterStorage::Type::passed_type, + const int*>::value, + "detail::ParameterStorage::Type::passed_type should be const int*"); + { + int i = 1201; + r1 = NewRunnableMethod(rpt, &ThreadUtilsObject::Test1pci, &i); + r1->Run(); + EXPECT_EQ(count += 2, rpt->mCount); + EXPECT_EQ(i, rpt->mA0); + } + + // Raw pointer to copy. + static_assert(mozilla::IsSame::stored_type, + int>::value, + "StoreCopyPassByPtr::stored_type should be int"); + static_assert(mozilla::IsSame::passed_type, + int*>::value, + "StoreCopyPassByPtr::passed_type should be int*"); + { + int i = 1202; + r1 = NewRunnableMethod>( + rpt, &ThreadUtilsObject::Test1pi, i); + r1->Run(); + EXPECT_EQ(count += 2, rpt->mCount); + EXPECT_EQ(i, rpt->mA0); + } + + // Raw pointer to const copy. + static_assert(mozilla::IsSame::stored_type, + int>::value, + "StoreCopyPassByConstPtr::stored_type should be int"); + static_assert(mozilla::IsSame::passed_type, + const int*>::value, + "StoreCopyPassByConstPtr::passed_type should be const int*"); + { + int i = 1203; + r1 = NewRunnableMethod>( + rpt, &ThreadUtilsObject::Test1pci, i); + r1->Run(); + EXPECT_EQ(count += 2, rpt->mCount); + EXPECT_EQ(i, rpt->mA0); + } + + // nsRefPtr to pointer. + static_assert(mozilla::IsSame< ::detail::ParameterStorage>::Type, + StorensRefPtrPassByPtr>::value, + "ParameterStorage>::Type should be StorensRefPtrPassByPtr"); + static_assert(mozilla::IsSame< ::detail::ParameterStorage::Type, + StorensRefPtrPassByPtr>::value, + "ParameterStorage::Type should be StorensRefPtrPassByPtr"); + static_assert(mozilla::IsSame::stored_type, + RefPtr>::value, + "StorensRefPtrPassByPtr::stored_type should be RefPtr"); + static_assert(mozilla::IsSame::passed_type, + SpyWithISupports*>::value, + "StorensRefPtrPassByPtr::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(mozilla::IsSame< ::detail::ParameterStorage::Type, + StorensRefPtrPassByPtr>::value, + "ParameterStorage::Type should be StorensRefPtrPassByPtr"); + static_assert(::detail::HasRefCountMethods::value, + "ThreadUtilsRefCountedBase has AddRef() and Release()"); + static_assert(mozilla::IsSame< ::detail::ParameterStorage::Type, + StorensRefPtrPassByPtr>::value, + "ParameterStorage::Type should be StorensRefPtrPassByPtr"); + static_assert(::detail::HasRefCountMethods::value, + "ThreadUtilsRefCountedDerived has AddRef() and Release()"); + static_assert(mozilla::IsSame< ::detail::ParameterStorage::Type, + StorensRefPtrPassByPtr>::value, + "ParameterStorage::Type should be StorensRefPtrPassByPtr"); + + static_assert(!::detail::HasRefCountMethods::value, + "ThreadUtilsNonRefCounted doesn't have AddRef() and Release()"); + static_assert(!mozilla::IsSame< ::detail::ParameterStorage::Type, + StorensRefPtrPassByPtr>::value, + "ParameterStorage::Type should NOT be StorensRefPtrPassByPtr"); + + // Lvalue reference. + static_assert(mozilla::IsSame< ::detail::ParameterStorage::Type, + StoreRefPassByLRef>::value, + "ParameterStorage::Type should be StoreRefPassByLRef"); + static_assert(mozilla::IsSame< ::detail::ParameterStorage::Type::stored_type, + StoreRefPassByLRef::stored_type>::value, + "ParameterStorage::Type::stored_type should be StoreRefPassByLRef::stored_type"); + static_assert(mozilla::IsSame< ::detail::ParameterStorage::Type::stored_type, + int&>::value, + "ParameterStorage::Type::stored_type should be int&"); + static_assert(mozilla::IsSame< ::detail::ParameterStorage::Type::passed_type, + int&>::value, + "ParameterStorage::Type::passed_type should be int&"); + { + int i = 13; + r1 = NewRunnableMethod(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(mozilla::IsSame< ::detail::ParameterStorage::Type, + StoreCopyPassByRRef>::value, + "ParameterStorage::Type should be StoreCopyPassByRRef"); + static_assert(mozilla::IsSame< ::detail::ParameterStorage::Type::stored_type, + StoreCopyPassByRRef::stored_type>::value, + "ParameterStorage::Type::stored_type should be StoreCopyPassByRRef::stored_type"); + static_assert(mozilla::IsSame< ::detail::ParameterStorage::Type::stored_type, + int>::value, + "ParameterStorage::Type::stored_type should be int"); + static_assert(mozilla::IsSame< ::detail::ParameterStorage::Type::passed_type, + int&&>::value, + "ParameterStorage::Type::passed_type should be int&&"); + { + int i = 14; + r1 = NewRunnableMethod( + rpt, &ThreadUtilsObject::Test1rri, mozilla::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(mozilla::IsSame< ::detail::ParameterStorage&&>::Type, + StoreCopyPassByRRef>>::value, + "ParameterStorage&&>::Type should be StoreCopyPassByRRef>"); + static_assert(mozilla::IsSame< ::detail::ParameterStorage&&>::Type::stored_type, + StoreCopyPassByRRef>::stored_type>::value, + "ParameterStorage&&>::Type::stored_type should be StoreCopyPassByRRef>::stored_type"); + static_assert(mozilla::IsSame< ::detail::ParameterStorage&&>::Type::stored_type, + mozilla::UniquePtr>::value, + "ParameterStorage&&>::Type::stored_type should be UniquePtr"); + static_assert(mozilla::IsSame< ::detail::ParameterStorage&&>::Type::passed_type, + mozilla::UniquePtr&&>::value, + "ParameterStorage&&>::Type::passed_type should be UniquePtr&&"); + { + mozilla::UniquePtr upi; + r1 = NewRunnableMethod&&>( + rpt, &ThreadUtilsObject::Test1upi, mozilla::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(mozilla::IsSame< ::detail::ParameterStorage>>::Type::stored_type, + StoreCopyPassByRRef>::stored_type>::value, + "ParameterStorage>>::Type::stored_type should be StoreCopyPassByRRef>::stored_type"); + static_assert(mozilla::IsSame< ::detail::ParameterStorage>>::Type::stored_type, + StoreCopyPassByRRef>::stored_type>::value, + "ParameterStorage>>::Type::stored_type should be StoreCopyPassByRRef>::stored_type"); + static_assert(mozilla::IsSame< ::detail::ParameterStorage>>::Type::stored_type, + mozilla::UniquePtr>::value, + "ParameterStorage>>::Type::stored_type should be UniquePtr"); + static_assert(mozilla::IsSame< ::detail::ParameterStorage>>::Type::passed_type, + mozilla::UniquePtr&&>::value, + "ParameterStorage>>::Type::passed_type should be UniquePtr&&"); + { + mozilla::UniquePtr upi; + r1 = NewRunnableMethod + >>( + rpt, &ThreadUtilsObject::Test1upi, mozilla::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&&>( + rpt, &ThreadUtilsObject::Test1upi, mozilla::Move(upi)); + } + r1->Run(); + EXPECT_EQ(count += 2, rpt->mCount); + EXPECT_EQ(1, rpt->mA0); + + { + mozilla::UniquePtr upi = mozilla::MakeUnique(1); + r1 = NewRunnableMethod + >> + (rpt, &ThreadUtilsObject::Test1upi, mozilla::Move(upi)); + } + r1->Run(); + EXPECT_EQ(count += 2, rpt->mCount); + EXPECT_EQ(1, rpt->mA0); + + // Unique pointer as prvalue. + r1 = NewRunnableMethod&&>( + 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&>( + 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>( + 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>( + 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>( + rpt, &ThreadUtilsObject::TestByValue, mozilla::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>( + 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>( + 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>( + 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>( + 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( + 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>( + 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( + 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( + 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); +#endif // XPCOM_GLUE_AVOID_NSPR +} diff --git a/xpcom/glue/tests/gtest/moz.build b/xpcom/glue/tests/gtest/moz.build new file mode 100644 index 000000000..9f4d83a3e --- /dev/null +++ b/xpcom/glue/tests/gtest/moz.build @@ -0,0 +1,22 @@ +# -*- 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 += [ + 'TestArray.cpp', + 'TestFileUtils.cpp', + 'TestGCPostBarriers.cpp', + 'TestNsDeque.cpp', + 'TestThreadUtils.cpp', +] + +LOCAL_INCLUDES = [ + '../..', +] + +FINAL_LIBRARY = 'xul-gtest' + +if CONFIG['GNU_CXX']: + CXXFLAGS += ['-Wno-error=shadow'] diff --git a/xpcom/idl-parser/setup.py b/xpcom/idl-parser/setup.py new file mode 100644 index 000000000..f42edbb99 --- /dev/null +++ b/xpcom/idl-parser/setup.py @@ -0,0 +1,15 @@ +from setuptools import setup, find_packages + + +setup( + name='xpidl', + version='1.0', + description='Parser and header generator for xpidl files.', + author='Mozilla Foundation', + license='MPL 2.0', + packages=find_packages(), + install_requires=['ply>=3.6,<4.0'], + url='https://github.com/pelmers/xpidl', + entry_points={'console_scripts': ['header.py = xpidl.header:main']}, + keywords=['xpidl', 'parser'] +) diff --git a/xpcom/idl-parser/xpidl/__init__.py b/xpcom/idl-parser/xpidl/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/xpcom/idl-parser/xpidl/header.py b/xpcom/idl-parser/xpidl/header.py new file mode 100644 index 000000000..8e02a7d11 --- /dev/null +++ b/xpcom/idl-parser/xpidl/header.py @@ -0,0 +1,566 @@ +#!/usr/bin/env python +# header.py - Generate C++ header files from IDL. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +"""Print a C++ header file for the IDL files specified on the command line""" + +import sys +import os.path +import re +import xpidl +import itertools +import glob + +printdoccomments = False + +if printdoccomments: + def printComments(fd, clist, indent): + for c in clist: + fd.write("%s%s\n" % (indent, c)) +else: + def printComments(fd, clist, indent): + pass + + +def firstCap(str): + return str[0].upper() + str[1:] + + +def attributeParamName(a): + return "a" + firstCap(a.name) + + +def attributeParamNames(a): + l = [attributeParamName(a)] + if a.implicit_jscontext: + l.insert(0, "cx") + return ", ".join(l) + + +def attributeNativeName(a, getter): + binaryname = a.binaryname is not None and a.binaryname or firstCap(a.name) + return "%s%s" % (getter and 'Get' or 'Set', binaryname) + + +def attributeReturnType(a, macro): + """macro should be NS_IMETHOD or NS_IMETHODIMP""" + if a.nostdcall: + ret = macro == "NS_IMETHOD" and "virtual nsresult" or "nsresult" + else: + ret = macro + if a.must_use: + ret = "MOZ_MUST_USE " + ret + return ret + + +def attributeParamlist(a, getter): + l = ["%s%s" % (a.realtype.nativeType(getter and 'out' or 'in'), + attributeParamName(a))] + if a.implicit_jscontext: + l.insert(0, "JSContext* cx") + + return ", ".join(l) + + +def attributeAsNative(a, getter, declType = 'NS_IMETHOD'): + deprecated = a.deprecated and "NS_DEPRECATED " or "" + params = {'deprecated': deprecated, + 'returntype': attributeReturnType(a, declType), + 'binaryname': attributeNativeName(a, getter), + 'paramlist': attributeParamlist(a, getter)} + return "%(deprecated)s%(returntype)s %(binaryname)s(%(paramlist)s)" % params + + +def methodNativeName(m): + return m.binaryname is not None and m.binaryname or firstCap(m.name) + + +def methodReturnType(m, macro): + """macro should be NS_IMETHOD or NS_IMETHODIMP""" + if m.nostdcall and m.notxpcom: + ret = "%s%s" % (macro == "NS_IMETHOD" and "virtual " or "", + m.realtype.nativeType('in').strip()) + elif m.nostdcall: + ret = "%snsresult" % (macro == "NS_IMETHOD" and "virtual " or "") + elif m.notxpcom: + ret = "%s_(%s)" % (macro, m.realtype.nativeType('in').strip()) + else: + ret = macro + if m.must_use: + ret = "MOZ_MUST_USE " + ret + return ret + + +def methodAsNative(m, declType = 'NS_IMETHOD'): + return "%s %s(%s)" % (methodReturnType(m, declType), + methodNativeName(m), + paramlistAsNative(m)) + + +def paramlistAsNative(m, empty='void'): + l = [paramAsNative(p) for p in m.params] + + if m.implicit_jscontext: + l.append("JSContext* cx") + + if m.optional_argc: + l.append('uint8_t _argc') + + if not m.notxpcom and m.realtype.name != 'void': + l.append(paramAsNative(xpidl.Param(paramtype='out', + type=None, + name='_retval', + attlist=[], + location=None, + realtype=m.realtype))) + + # Set any optional out params to default to nullptr. Skip if we just added + # extra non-optional args to l. + if len(l) == len(m.params): + paramIter = len(m.params) - 1 + while (paramIter >= 0 and m.params[paramIter].optional and + m.params[paramIter].paramtype == "out"): + t = m.params[paramIter].type + # Strings can't be optional, so this shouldn't happen, but let's make sure: + if t == "AString" or t == "ACString" or t == "DOMString" or t == "AUTF8String": + break + l[paramIter] += " = nullptr" + paramIter -= 1 + + if len(l) == 0: + return empty + + return ", ".join(l) + + +def paramAsNative(p): + return "%s%s" % (p.nativeType(), + p.name) + + +def paramlistNames(m): + names = [p.name for p in m.params] + + if m.implicit_jscontext: + names.append('cx') + + if m.optional_argc: + names.append('_argc') + + if not m.notxpcom and m.realtype.name != 'void': + names.append('_retval') + + if len(names) == 0: + return '' + return ', '.join(names) + +header = """/* + * DO NOT EDIT. THIS FILE IS GENERATED FROM %(filename)s + */ + +#ifndef __gen_%(basename)s_h__ +#define __gen_%(basename)s_h__ +""" + +include = """ +#ifndef __gen_%(basename)s_h__ +#include "%(basename)s.h" +#endif +""" + +jsvalue_include = """ +#include "js/Value.h" +""" + +infallible_includes = """ +#include "mozilla/Assertions.h" +#include "mozilla/DebugOnly.h" +""" + +header_end = """/* For IDL files that don't want to include root IDL files. */ +#ifndef NS_NO_VTABLE +#define NS_NO_VTABLE +#endif +""" + +footer = """ +#endif /* __gen_%(basename)s_h__ */ +""" + +forward_decl = """class %(name)s; /* forward declaration */ + +""" + + +def idl_basename(f): + """returns the base name of a file with the last extension stripped""" + return os.path.basename(f).rpartition('.')[0] + + +def print_header(idl, fd, filename): + fd.write(header % {'filename': filename, + 'basename': idl_basename(filename)}) + + foundinc = False + for inc in idl.includes(): + if not foundinc: + foundinc = True + fd.write('\n') + fd.write(include % {'basename': idl_basename(inc.filename)}) + + if idl.needsJSTypes(): + fd.write(jsvalue_include) + + # Include some extra files if any attributes are infallible. + for iface in [p for p in idl.productions if p.kind == 'interface']: + for attr in [m for m in iface.members if isinstance(m, xpidl.Attribute)]: + if attr.infallible: + fd.write(infallible_includes) + break + + fd.write('\n') + fd.write(header_end) + + for p in idl.productions: + if p.kind == 'include': + continue + if p.kind == 'cdata': + fd.write(p.data) + continue + + if p.kind == 'forward': + fd.write(forward_decl % {'name': p.name}) + continue + if p.kind == 'interface': + write_interface(p, fd) + continue + if p.kind == 'typedef': + printComments(fd, p.doccomments, '') + fd.write("typedef %s %s;\n\n" % (p.realtype.nativeType('in'), + p.name)) + + fd.write(footer % {'basename': idl_basename(filename)}) + +iface_header = r""" +/* starting interface: %(name)s */ +#define %(defname)s_IID_STR "%(iid)s" + +#define %(defname)s_IID \ + {0x%(m0)s, 0x%(m1)s, 0x%(m2)s, \ + { %(m3joined)s }} + +""" + +uuid_decoder = re.compile(r"""(?P[a-f0-9]{8})- + (?P[a-f0-9]{4})- + (?P[a-f0-9]{4})- + (?P[a-f0-9]{4})- + (?P[a-f0-9]{12})$""", re.X) + +iface_prolog = """ { + public: + + NS_DECLARE_STATIC_IID_ACCESSOR(%(defname)s_IID) + +""" + +iface_epilog = """}; + + NS_DEFINE_STATIC_IID_ACCESSOR(%(name)s, %(defname)s_IID) + +/* Use this macro when declaring classes that implement this interface. */ +#define NS_DECL_%(macroname)s """ + +iface_nonvirtual = """ + +/* Use this macro when declaring the members of this interface when the + class doesn't implement the interface. This is useful for forwarding. */ +#define NS_DECL_NON_VIRTUAL_%(macroname)s """ + +iface_forward = """ + +/* Use this macro to declare functions that forward the behavior of this interface to another object. */ +#define NS_FORWARD_%(macroname)s(_to) """ + +iface_forward_safe = """ + +/* Use this macro to declare functions that forward the behavior of this interface to another object in a safe way. */ +#define NS_FORWARD_SAFE_%(macroname)s(_to) """ + +iface_template_prolog = """ + +#if 0 +/* Use the code below as a template for the implementation class for this interface. */ + +/* Header file */ +class %(implclass)s : public %(name)s +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_%(macroname)s + + %(implclass)s(); + +private: + ~%(implclass)s(); + +protected: + /* additional members */ +}; + +/* Implementation file */ +NS_IMPL_ISUPPORTS(%(implclass)s, %(name)s) + +%(implclass)s::%(implclass)s() +{ + /* member initializers and constructor code */ +} + +%(implclass)s::~%(implclass)s() +{ + /* destructor code */ +} + +""" + +example_tmpl = """%(returntype)s %(implclass)s::%(nativeName)s(%(paramList)s) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} +""" + +iface_template_epilog = """/* End of implementation class template. */ +#endif + +""" + +attr_infallible_tmpl = """\ + inline %(realtype)s%(nativename)s(%(args)s) + { + %(realtype)sresult; + mozilla::DebugOnly rv = %(nativename)s(%(argnames)s&result); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + return result; + } +""" + + +def write_interface(iface, fd): + if iface.namemap is None: + raise Exception("Interface was not resolved.") + + # Confirm that no names of methods will overload in this interface + names = set() + def record_name(name): + if name in names: + raise Exception("Unexpected overloaded virtual method %s in interface %s" + % (name, iface.name)) + names.add(name) + for m in iface.members: + if type(m) == xpidl.Attribute: + record_name(attributeNativeName(m, getter=True)) + if not m.readonly: + record_name(attributeNativeName(m, getter=False)) + elif type(m) == xpidl.Method: + record_name(methodNativeName(m)) + + def write_const_decls(g): + fd.write(" enum {\n") + enums = [] + for c in g: + printComments(fd, c.doccomments, ' ') + basetype = c.basetype + value = c.getValue() + enums.append(" %(name)s = %(value)s%(signed)s" % { + 'name': c.name, + 'value': value, + 'signed': (not basetype.signed) and 'U' or ''}) + fd.write(",\n".join(enums)) + fd.write("\n };\n\n") + + def write_method_decl(m): + printComments(fd, m.doccomments, ' ') + + fd.write(" /* %s */\n" % m.toIDL()) + fd.write(" %s = 0;\n\n" % methodAsNative(m)) + + def write_attr_decl(a): + printComments(fd, a.doccomments, ' ') + + fd.write(" /* %s */\n" % a.toIDL()) + + fd.write(" %s = 0;\n" % attributeAsNative(a, True)) + if a.infallible: + fd.write(attr_infallible_tmpl % + {'realtype': a.realtype.nativeType('in'), + 'nativename': attributeNativeName(a, getter=True), + 'args': '' if not a.implicit_jscontext else 'JSContext* cx', + 'argnames': '' if not a.implicit_jscontext else 'cx, '}) + + if not a.readonly: + fd.write(" %s = 0;\n" % attributeAsNative(a, False)) + fd.write("\n") + + defname = iface.name.upper() + if iface.name[0:2] == 'ns': + defname = 'NS_' + defname[2:] + + names = uuid_decoder.match(iface.attributes.uuid).groupdict() + m3str = names['m3'] + names['m4'] + names['m3joined'] = ", ".join(["0x%s" % m3str[i:i+2] for i in xrange(0, 16, 2)]) + + if iface.name[2] == 'I': + implclass = iface.name[:2] + iface.name[3:] + else: + implclass = '_MYCLASS_' + + names.update({'defname': defname, + 'macroname': iface.name.upper(), + 'name': iface.name, + 'iid': iface.attributes.uuid, + 'implclass': implclass}) + + fd.write(iface_header % names) + + printComments(fd, iface.doccomments, '') + + fd.write("class ") + foundcdata = False + for m in iface.members: + if isinstance(m, xpidl.CDATA): + foundcdata = True + + if not foundcdata: + fd.write("NS_NO_VTABLE ") + + if iface.attributes.deprecated: + fd.write("MOZ_DEPRECATED ") + fd.write(iface.name) + if iface.base: + fd.write(" : public %s" % iface.base) + fd.write(iface_prolog % names) + + for key, group in itertools.groupby(iface.members, key=type): + if key == xpidl.ConstMember: + write_const_decls(group) # iterator of all the consts + else: + for member in group: + if key == xpidl.Attribute: + write_attr_decl(member) + elif key == xpidl.Method: + write_method_decl(member) + elif key == xpidl.CDATA: + fd.write(" %s" % member.data) + else: + raise Exception("Unexpected interface member: %s" % member) + + fd.write(iface_epilog % names) + + def writeDeclaration(fd, iface, virtual): + declType = "NS_IMETHOD" if virtual else "NS_METHOD" + suffix = " override" if virtual else "" + for member in iface.members: + if isinstance(member, xpidl.Attribute): + if member.infallible: + fd.write("\\\n using %s::%s; " % (iface.name, attributeNativeName(member, True))) + fd.write("\\\n %s%s; " % (attributeAsNative(member, True, declType), suffix)) + if not member.readonly: + fd.write("\\\n %s%s; " % (attributeAsNative(member, False, declType), suffix)) + elif isinstance(member, xpidl.Method): + fd.write("\\\n %s%s; " % (methodAsNative(member, declType), suffix)) + if len(iface.members) == 0: + fd.write('\\\n /* no methods! */') + elif not member.kind in ('attribute', 'method'): + fd.write('\\') + + writeDeclaration(fd, iface, True); + fd.write(iface_nonvirtual % names) + writeDeclaration(fd, iface, False); + fd.write(iface_forward % names) + + def emitTemplate(forward_infallible, tmpl, tmpl_notxpcom=None): + if tmpl_notxpcom is None: + tmpl_notxpcom = tmpl + for member in iface.members: + if isinstance(member, xpidl.Attribute): + if forward_infallible and member.infallible: + fd.write("\\\n using %s::%s; " % (iface.name, attributeNativeName(member, True))) + fd.write(tmpl % {'asNative': attributeAsNative(member, True), + 'nativeName': attributeNativeName(member, True), + 'paramList': attributeParamNames(member)}) + if not member.readonly: + fd.write(tmpl % {'asNative': attributeAsNative(member, False), + 'nativeName': attributeNativeName(member, False), + 'paramList': attributeParamNames(member)}) + elif isinstance(member, xpidl.Method): + if member.notxpcom: + fd.write(tmpl_notxpcom % {'asNative': methodAsNative(member), + 'nativeName': methodNativeName(member), + 'paramList': paramlistNames(member)}) + else: + fd.write(tmpl % {'asNative': methodAsNative(member), + 'nativeName': methodNativeName(member), + 'paramList': paramlistNames(member)}) + if len(iface.members) == 0: + fd.write('\\\n /* no methods! */') + elif not member.kind in ('attribute', 'method'): + fd.write('\\') + + emitTemplate(True, + "\\\n %(asNative)s override { return _to %(nativeName)s(%(paramList)s); } ") + + fd.write(iface_forward_safe % names) + + # Don't try to safely forward notxpcom functions, because we have no + # sensible default error return. Instead, the caller will have to + # implement them. + emitTemplate(False, + "\\\n %(asNative)s override { return !_to ? NS_ERROR_NULL_POINTER : _to->%(nativeName)s(%(paramList)s); } ", + "\\\n %(asNative)s override; ") + + fd.write(iface_template_prolog % names) + + for member in iface.members: + if isinstance(member, xpidl.ConstMember) or isinstance(member, xpidl.CDATA): + continue + fd.write("/* %s */\n" % member.toIDL()) + if isinstance(member, xpidl.Attribute): + fd.write(example_tmpl % {'implclass': implclass, + 'returntype': attributeReturnType(member, 'NS_IMETHODIMP'), + 'nativeName': attributeNativeName(member, True), + 'paramList': attributeParamlist(member, True)}) + if not member.readonly: + fd.write(example_tmpl % {'implclass': implclass, + 'returntype': attributeReturnType(member, 'NS_IMETHODIMP'), + 'nativeName': attributeNativeName(member, False), + 'paramList': attributeParamlist(member, False)}) + elif isinstance(member, xpidl.Method): + fd.write(example_tmpl % {'implclass': implclass, + 'returntype': methodReturnType(member, 'NS_IMETHODIMP'), + 'nativeName': methodNativeName(member), + 'paramList': paramlistAsNative(member, empty='')}) + fd.write('\n') + + fd.write(iface_template_epilog) + + +def main(outputfile): + cachedir = '.' + if not os.path.isdir(cachedir): + os.mkdir(cachedir) + sys.path.append(cachedir) + + # Delete the lex/yacc files. Ply is too stupid to regenerate them + # properly + for fileglobs in [os.path.join(cachedir, f) for f in ["xpidllex.py*", "xpidlyacc.py*"]]: + for filename in glob.glob(fileglobs): + os.remove(filename) + + # Instantiate the parser. + p = xpidl.IDLParser(outputdir=cachedir) + +if __name__ == '__main__': + main(None) diff --git a/xpcom/idl-parser/xpidl/moz.build b/xpcom/idl-parser/xpidl/moz.build new file mode 100644 index 000000000..33c1e7b13 --- /dev/null +++ b/xpcom/idl-parser/xpidl/moz.build @@ -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/. + +PYTHON_UNIT_TESTS += [ + 'runtests.py', +] + +GENERATED_FILES += [ + ('xpidl.stub', 'xpidllex.py', 'xpidlyacc.py'), +] + +GENERATED_FILES[('xpidl.stub', 'xpidllex.py', 'xpidlyacc.py')].script = 'header.py:main' + +SDK_FILES.bin += [ + '!xpidllex.py', + '!xpidlyacc.py', + 'header.py', + 'typelib.py', + 'xpidl.py', +] + +SDK_FILES.bin.ply += [ + '/other-licenses/ply/ply/__init__.py', + '/other-licenses/ply/ply/lex.py', + '/other-licenses/ply/ply/yacc.py', +] diff --git a/xpcom/idl-parser/xpidl/runtests.py b/xpcom/idl-parser/xpidl/runtests.py new file mode 100644 index 000000000..89222d546 --- /dev/null +++ b/xpcom/idl-parser/xpidl/runtests.py @@ -0,0 +1,114 @@ +#!/usr/bin/env python +# +# Any copyright is dedicated to the Public Domain. +# http://creativecommons.org/publicdomain/zero/1.0/ +# +# Unit tests for xpidl.py + +import mozunit +import unittest +import xpidl +import header + + +class TestParser(unittest.TestCase): + def setUp(self): + self.p = xpidl.IDLParser() + + def testEmpty(self): + i = self.p.parse("", filename='f') + self.assertTrue(isinstance(i, xpidl.IDL)) + self.assertEqual([], i.productions) + + def testForwardInterface(self): + i = self.p.parse("interface foo;", filename='f') + self.assertTrue(isinstance(i, xpidl.IDL)) + self.assertTrue(isinstance(i.productions[0], xpidl.Forward)) + self.assertEqual("foo", i.productions[0].name) + + def testInterface(self): + i = self.p.parse("[uuid(abc)] interface foo {};", filename='f') + self.assertTrue(isinstance(i, xpidl.IDL)) + self.assertTrue(isinstance(i.productions[0], xpidl.Interface)) + self.assertEqual("foo", i.productions[0].name) + + def testAttributes(self): + i = self.p.parse("[scriptable, builtinclass, function, deprecated, uuid(abc)] interface foo {};", filename='f') + self.assertTrue(isinstance(i, xpidl.IDL)) + self.assertTrue(isinstance(i.productions[0], xpidl.Interface)) + iface = i.productions[0] + self.assertEqual("foo", iface.name) + self.assertTrue(iface.attributes.scriptable) + self.assertTrue(iface.attributes.builtinclass) + self.assertTrue(iface.attributes.function) + self.assertTrue(iface.attributes.deprecated) + + i = self.p.parse("[noscript, uuid(abc)] interface foo {};", filename='f') + self.assertTrue(isinstance(i, xpidl.IDL)) + self.assertTrue(isinstance(i.productions[0], xpidl.Interface)) + iface = i.productions[0] + self.assertEqual("foo", iface.name) + self.assertTrue(iface.attributes.noscript) + + def testMethod(self): + i = self.p.parse("""[uuid(abc)] interface foo { +void bar(); +};""", filename='f') + self.assertTrue(isinstance(i, xpidl.IDL)) + self.assertTrue(isinstance(i.productions[0], xpidl.Interface)) + iface = i.productions[0] + m = iface.members[0] + self.assertTrue(isinstance(m, xpidl.Method)) + self.assertEqual("bar", m.name) + self.assertEqual("void", m.type) + + def testMethodParams(self): + i = self.p.parse("""[uuid(abc)] interface foo { +long bar(in long a, in float b, [array] in long c); +};""", filename='f') + i.resolve([], self.p) + self.assertTrue(isinstance(i, xpidl.IDL)) + self.assertTrue(isinstance(i.productions[0], xpidl.Interface)) + iface = i.productions[0] + m = iface.members[0] + self.assertTrue(isinstance(m, xpidl.Method)) + self.assertEqual("bar", m.name) + self.assertEqual("long", m.type) + self.assertEqual(3, len(m.params)) + self.assertEqual("long", m.params[0].type) + self.assertEqual("in", m.params[0].paramtype) + self.assertEqual("float", m.params[1].type) + self.assertEqual("in", m.params[1].paramtype) + self.assertEqual("long", m.params[2].type) + self.assertEqual("in", m.params[2].paramtype) + self.assertTrue(isinstance(m.params[2].realtype, xpidl.Array)) + self.assertEqual("long", m.params[2].realtype.type.name) + + def testAttribute(self): + i = self.p.parse("""[uuid(abc)] interface foo { +attribute long bar; +};""", filename='f') + self.assertTrue(isinstance(i, xpidl.IDL)) + self.assertTrue(isinstance(i.productions[0], xpidl.Interface)) + iface = i.productions[0] + a = iface.members[0] + self.assertTrue(isinstance(a, xpidl.Attribute)) + self.assertEqual("bar", a.name) + self.assertEqual("long", a.type) + + def testOverloadedVirtual(self): + i = self.p.parse("""[uuid(abc)] interface foo { +attribute long bar; +void getBar(); +};""", filename='f') + self.assertTrue(isinstance(i, xpidl.IDL)) + class FdMock: + def write(self, s): + pass + try: + header.print_header(i, FdMock(), filename='f') + except Exception as e: + self.assertEqual(e.args[0], "Unexpected overloaded virtual method GetBar in interface foo") + +if __name__ == '__main__': + mozunit.main() diff --git a/xpcom/idl-parser/xpidl/typelib.py b/xpcom/idl-parser/xpidl/typelib.py new file mode 100644 index 000000000..911e3873d --- /dev/null +++ b/xpcom/idl-parser/xpidl/typelib.py @@ -0,0 +1,307 @@ +#!/usr/bin/env python +# typelib.py - Generate XPCOM typelib files from IDL. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +"""Generate an XPIDL typelib for the IDL files specified on the command line""" + +import os +import sys +import xpidl +import xpt + +# A map of xpidl.py types to xpt.py types +TypeMap = { + # nsresult is not strictly an xpidl.py type, but it's useful here + 'nsresult': xpt.Type.Tags.uint32, + # builtins + 'boolean': xpt.Type.Tags.boolean, + 'void': xpt.Type.Tags.void, + 'int16_t': xpt.Type.Tags.int16, + 'int32_t': xpt.Type.Tags.int32, + 'int64_t': xpt.Type.Tags.int64, + 'uint8_t': xpt.Type.Tags.uint8, + 'uint16_t': xpt.Type.Tags.uint16, + 'uint32_t': xpt.Type.Tags.uint32, + 'uint64_t': xpt.Type.Tags.uint64, + 'octet': xpt.Type.Tags.uint8, + 'short': xpt.Type.Tags.int16, + 'long': xpt.Type.Tags.int32, + 'long long': xpt.Type.Tags.int64, + 'unsigned short': xpt.Type.Tags.uint16, + 'unsigned long': xpt.Type.Tags.uint32, + 'unsigned long long': xpt.Type.Tags.uint64, + 'float': xpt.Type.Tags.float, + 'double': xpt.Type.Tags.double, + 'char': xpt.Type.Tags.char, + 'string': xpt.Type.Tags.char_ptr, + 'wchar': xpt.Type.Tags.wchar_t, + 'wstring': xpt.Type.Tags.wchar_t_ptr, + # special types + 'nsid': xpt.Type.Tags.nsIID, + 'domstring': xpt.Type.Tags.DOMString, + 'astring': xpt.Type.Tags.AString, + 'utf8string': xpt.Type.Tags.UTF8String, + 'cstring': xpt.Type.Tags.CString, + 'jsval': xpt.Type.Tags.jsval +} + + +# XXXkhuey dipper types should go away (bug 677784) +def isDipperType(type): + return type == xpt.Type.Tags.DOMString or type == xpt.Type.Tags.AString or type == xpt.Type.Tags.CString or type == xpt.Type.Tags.UTF8String + + +def build_interface(iface, ifaces): + def get_type(type, calltype, iid_is=None, size_is=None): + """ Return the appropriate xpt.Type object for this param """ + + while isinstance(type, xpidl.Typedef): + type = type.realtype + + if isinstance(type, xpidl.Builtin): + if type.name == 'string' and size_is is not None: + return xpt.StringWithSizeType(size_is, size_is) + elif type.name == 'wstring' and size_is is not None: + return xpt.WideStringWithSizeType(size_is, size_is) + else: + tag = TypeMap[type.name] + isPtr = (tag == xpt.Type.Tags.char_ptr or tag == xpt.Type.Tags.wchar_t_ptr) + return xpt.SimpleType(tag, + pointer=isPtr, + reference=False) + + if isinstance(type, xpidl.Array): + # NB: For an Array we pass down the iid_is to get the type of T. + # This allows Arrays of InterfaceIs types to work. + return xpt.ArrayType(get_type(type.type, calltype, iid_is), size_is, + #XXXkhuey length_is duplicates size_is (bug 677788), + size_is) + + if isinstance(type, xpidl.Interface) or isinstance(type, xpidl.Forward): + xptiface = None + for i in ifaces: + if i.name == type.name: + xptiface = i + + if not xptiface: + xptiface = xpt.Interface(name=type.name) + ifaces.append(xptiface) + + return xpt.InterfaceType(xptiface) + + if isinstance(type, xpidl.Native): + if type.specialtype: + # XXXkhuey jsval is marked differently in the typelib and in the headers :-( + isPtr = (type.isPtr(calltype) or type.isRef(calltype)) and not type.specialtype == 'jsval' + isRef = type.isRef(calltype) and not type.specialtype == 'jsval' + return xpt.SimpleType(TypeMap[type.specialtype], + pointer=isPtr, + reference=isRef) + elif iid_is is not None: + return xpt.InterfaceIsType(iid_is) + else: + # void ptr + return xpt.SimpleType(TypeMap['void'], + pointer=True, + reference=False) + + raise Exception("Unknown type!") + + def get_nsresult(): + return xpt.SimpleType(TypeMap['nsresult']) + + def build_nsresult_param(): + return xpt.Param(get_nsresult()) + + def get_result_type(m): + if not m.notxpcom: + return get_nsresult() + + return get_type(m.realtype, '') + + def build_result_param(m): + return xpt.Param(get_result_type(m)) + + def build_retval_param(m): + type = get_type(m.realtype, 'out') + if isDipperType(type.tag): + # NB: The retval bit needs to be set here, contrary to what the + # xpt spec says. + return xpt.Param(type, in_=True, retval=True, dipper=True) + return xpt.Param(type, in_=False, out=True, retval=True) + + def build_attr_param(a, getter=False, setter=False): + if not (getter or setter): + raise Exception("Attribute param must be for a getter or a setter!") + + type = get_type(a.realtype, getter and 'out' or 'in') + if setter: + return xpt.Param(type) + else: + if isDipperType(type.tag): + # NB: The retval bit needs to be set here, contrary to what the + # xpt spec says. + return xpt.Param(type, in_=True, retval=True, dipper=True) + return xpt.Param(type, in_=False, out=True, retval=True) + + if iface.namemap is None: + raise Exception("Interface was not resolved.") + + consts = [] + methods = [] + + def build_const(c): + consts.append(xpt.Constant(c.name, get_type(c.basetype, ''), c.getValue())) + + def build_method(m): + params = [] + + def build_param(p): + def findattr(p, attr): + if hasattr(p, attr) and getattr(p, attr): + for i, param in enumerate(m.params): + if param.name == getattr(p, attr): + return i + return None + + iid_is = findattr(p, 'iid_is') + size_is = findattr(p, 'size_is') + + in_ = p.paramtype.count("in") + out = p.paramtype.count("out") + dipper = False + type = get_type(p.realtype, p.paramtype, iid_is=iid_is, size_is=size_is) + if out and isDipperType(type.tag): + out = False + dipper = True + + return xpt.Param(type, in_, out, p.retval, p.shared, dipper, p.optional) + + for p in m.params: + params.append(build_param(p)) + + if not m.notxpcom and m.realtype.name != 'void': + params.append(build_retval_param(m)) + + methods.append(xpt.Method(m.name, build_result_param(m), params, + getter=False, setter=False, notxpcom=m.notxpcom, + constructor=False, hidden=m.noscript, + optargc=m.optional_argc, + implicit_jscontext=m.implicit_jscontext)) + + def build_attr(a): + # Write the getter + methods.append(xpt.Method(a.name, build_nsresult_param(), + [build_attr_param(a, getter=True)], + getter=True, setter=False, + constructor=False, hidden=a.noscript, + optargc=False, + implicit_jscontext=a.implicit_jscontext)) + + # And maybe the setter + if not a.readonly: + methods.append(xpt.Method(a.name, build_nsresult_param(), + [build_attr_param(a, setter=True)], + getter=False, setter=True, + constructor=False, hidden=a.noscript, + optargc=False, + implicit_jscontext=a.implicit_jscontext)) + + for member in iface.members: + if isinstance(member, xpidl.ConstMember): + build_const(member) + elif isinstance(member, xpidl.Attribute): + build_attr(member) + elif isinstance(member, xpidl.Method): + build_method(member) + elif isinstance(member, xpidl.CDATA): + pass + else: + raise Exception("Unexpected interface member: %s" % member) + + parent = None + if iface.base: + for i in ifaces: + if i.name == iface.base: + parent = i + if not parent: + parent = xpt.Interface(name=iface.base) + ifaces.append(parent) + + return xpt.Interface(iface.name, iface.attributes.uuid, methods=methods, + constants=consts, resolved=True, parent=parent, + scriptable=iface.attributes.scriptable, + function=iface.attributes.function, + builtinclass=iface.attributes.builtinclass, + main_process_scriptable_only=iface.attributes.main_process_scriptable_only) + + +def write_typelib(idl, fd, filename): + """ Generate the typelib. """ + + # We only care about interfaces that are scriptable. + ifaces = [] + for p in idl.productions: + if p.kind == 'interface' and p.attributes.scriptable: + ifaces.append(build_interface(p, ifaces)) + + typelib = xpt.Typelib(interfaces=ifaces) + typelib.writefd(fd) + +if __name__ == '__main__': + from optparse import OptionParser + o = OptionParser() + o.add_option('-I', action='append', dest='incdirs', default=['.'], + help="Directory to search for imported files") + o.add_option('--cachedir', dest='cachedir', default=None, + help="Directory in which to cache lex/parse tables.") + o.add_option('-o', dest='outfile', default=None, + help="Output file") + o.add_option('-d', dest='depfile', default=None, + help="Generate a make dependency file") + o.add_option('--regen', action='store_true', dest='regen', default=False, + help="Regenerate IDL Parser cache") + options, args = o.parse_args() + file = args[0] if args else None + + if options.cachedir is not None: + if not os.path.isdir(options.cachedir): + os.mkdir(options.cachedir) + sys.path.append(options.cachedir) + + if options.regen: + if options.cachedir is None: + print >>sys.stderr, "--regen requires --cachedir" + sys.exit(1) + + p = xpidl.IDLParser(outputdir=options.cachedir, regen=True) + sys.exit(0) + + if options.depfile is not None and options.outfile is None: + print >>sys.stderr, "-d requires -o" + sys.exit(1) + + if options.outfile is not None: + outfd = open(options.outfile, 'wb') + closeoutfd = True + else: + raise "typelib generation requires an output file" + + p = xpidl.IDLParser(outputdir=options.cachedir) + idl = p.parse(open(file).read(), filename=file) + idl.resolve(options.incdirs, p) + write_typelib(idl, outfd, file) + + if closeoutfd: + outfd.close() + + if options.depfile is not None: + depfd = open(options.depfile, 'w') + deps = [dep.replace('\\', '/') for dep in idl.deps] + + print >>depfd, "%s: %s" % (options.outfile, " ".join(deps)) + for dep in deps: + print >>depfd, "%s:" % dep diff --git a/xpcom/idl-parser/xpidl/xpidl.py b/xpcom/idl-parser/xpidl/xpidl.py new file mode 100755 index 000000000..8b2d8c884 --- /dev/null +++ b/xpcom/idl-parser/xpidl/xpidl.py @@ -0,0 +1,1465 @@ +#!/usr/bin/env python +# xpidl.py - A parser for cross-platform IDL (XPIDL) files. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.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 parser for cross-platform IDL (XPIDL) files.""" + +import sys +import os.path +import re +from ply import lex +from ply import yacc + +"""A type conforms to the following pattern: + + def isScriptable(self): + 'returns True or False' + + def nativeType(self, calltype): + 'returns a string representation of the native type + calltype must be 'in', 'out', or 'inout' + +Interface members const/method/attribute conform to the following pattern: + + name = 'string' + + def toIDL(self): + 'returns the member signature as IDL' +""" + + +def attlistToIDL(attlist): + if len(attlist) == 0: + return '' + + attlist = list(attlist) + attlist.sort(cmp=lambda a, b: cmp(a[0], b[0])) + + return '[%s] ' % ','.join(["%s%s" % (name, value is not None and '(%s)' % value or '') + for name, value, aloc in attlist]) + +_paramsHardcode = { + 2: ('array', 'shared', 'iid_is', 'size_is', 'retval'), + 3: ('array', 'size_is', 'const'), +} + + +def paramAttlistToIDL(attlist): + if len(attlist) == 0: + return '' + + # Hack alert: g_hash_table_foreach is pretty much unimitatable... hardcode + # quirk + attlist = list(attlist) + sorted = [] + if len(attlist) in _paramsHardcode: + for p in _paramsHardcode[len(attlist)]: + i = 0 + while i < len(attlist): + if attlist[i][0] == p: + sorted.append(attlist[i]) + del attlist[i] + continue + + i += 1 + + sorted.extend(attlist) + + return '[%s] ' % ', '.join(["%s%s" % (name, value is not None and ' (%s)' % value or '') + for name, value, aloc in sorted]) + + +def unaliasType(t): + while t.kind == 'typedef': + t = t.realtype + assert t is not None + return t + + +def getBuiltinOrNativeTypeName(t): + t = unaliasType(t) + if t.kind == 'builtin': + return t.name + elif t.kind == 'native': + assert t.specialtype is not None + return '[%s]' % t.specialtype + else: + return None + + +class BuiltinLocation(object): + def get(self): + return "" + + def __str__(self): + return self.get() + + +class Builtin(object): + kind = 'builtin' + location = BuiltinLocation + + def __init__(self, name, nativename, signed=False, maybeConst=False): + self.name = name + self.nativename = nativename + self.signed = signed + self.maybeConst = maybeConst + + def isScriptable(self): + return True + + def nativeType(self, calltype, shared=False, const=False): + if const: + print >>sys.stderr, IDLError("[const] doesn't make sense on builtin types.", self.location, warning=True) + const = 'const ' + elif calltype == 'in' and self.nativename.endswith('*'): + const = 'const ' + elif shared: + if not self.nativename.endswith('*'): + raise IDLError("[shared] not applicable to non-pointer types.", self.location) + const = 'const ' + else: + const = '' + return "%s%s %s" % (const, self.nativename, + calltype != 'in' and '*' or '') + +builtinNames = [ + Builtin('boolean', 'bool'), + Builtin('void', 'void'), + Builtin('octet', 'uint8_t'), + Builtin('short', 'int16_t', True, True), + Builtin('long', 'int32_t', True, True), + Builtin('long long', 'int64_t', True, False), + Builtin('unsigned short', 'uint16_t', False, True), + Builtin('unsigned long', 'uint32_t', False, True), + Builtin('unsigned long long', 'uint64_t', False, False), + Builtin('float', 'float', True, False), + Builtin('double', 'double', True, False), + Builtin('char', 'char', True, False), + Builtin('string', 'char *', False, False), + Builtin('wchar', 'char16_t', False, False), + Builtin('wstring', 'char16_t *', False, False), +] + +builtinMap = {} +for b in builtinNames: + builtinMap[b.name] = b + + +class Location(object): + _line = None + + def __init__(self, lexer, lineno, lexpos): + self._lineno = lineno + self._lexpos = lexpos + self._lexdata = lexer.lexdata + self._file = getattr(lexer, 'filename', "") + + def __eq__(self, other): + return (self._lexpos == other._lexpos and + self._file == other._file) + + def resolve(self): + if self._line: + return + + startofline = self._lexdata.rfind('\n', 0, self._lexpos) + 1 + endofline = self._lexdata.find('\n', self._lexpos, self._lexpos + 80) + self._line = self._lexdata[startofline:endofline] + self._colno = self._lexpos - startofline + + def pointerline(self): + def i(): + for i in xrange(0, self._colno): + yield " " + yield "^" + + return "".join(i()) + + def get(self): + self.resolve() + return "%s line %s:%s" % (self._file, self._lineno, self._colno) + + def __str__(self): + self.resolve() + return "%s line %s:%s\n%s\n%s" % (self._file, self._lineno, self._colno, + self._line, self.pointerline()) + + +class NameMap(object): + """Map of name -> object. Each object must have a .name and .location property. + Setting the same name twice throws an error.""" + def __init__(self): + self._d = {} + + def __getitem__(self, key): + if key in builtinMap: + return builtinMap[key] + return self._d[key] + + def __iter__(self): + return self._d.itervalues() + + def __contains__(self, key): + return key in builtinMap or key in self._d + + def set(self, object): + if object.name in builtinMap: + raise IDLError("name '%s' is a builtin and cannot be redeclared" % (object.name), object.location) + if object.name.startswith("_"): + object.name = object.name[1:] + if object.name in self._d: + old = self._d[object.name] + if old == object: + return + if isinstance(old, Forward) and isinstance(object, Interface): + self._d[object.name] = object + elif isinstance(old, Interface) and isinstance(object, Forward): + pass + else: + raise IDLError("name '%s' specified twice. Previous location: %s" % (object.name, self._d[object.name].location), object.location) + else: + self._d[object.name] = object + + def get(self, id, location): + try: + return self[id] + except KeyError: + raise IDLError("Name '%s' not found", location) + + +class IDLError(Exception): + def __init__(self, message, location, warning=False): + self.message = message + self.location = location + self.warning = warning + + def __str__(self): + return "%s: %s, %s" % (self.warning and 'warning' or 'error', + self.message, self.location) + + +class Include(object): + kind = 'include' + + def __init__(self, filename, location): + self.filename = filename + self.location = location + + def __str__(self): + return "".join(["include '%s'\n" % self.filename]) + + def resolve(self, parent): + def incfiles(): + yield self.filename + for dir in parent.incdirs: + yield os.path.join(dir, self.filename) + + for file in incfiles(): + if not os.path.exists(file): + continue + + self.IDL = parent.parser.parse(open(file).read(), filename=file) + self.IDL.resolve(parent.incdirs, parent.parser) + for type in self.IDL.getNames(): + parent.setName(type) + parent.deps.extend(self.IDL.deps) + return + + raise IDLError("File '%s' not found" % self.filename, self.location) + + +class IDL(object): + def __init__(self, productions): + self.productions = productions + self.deps = [] + + def setName(self, object): + self.namemap.set(object) + + def getName(self, id, location): + try: + return self.namemap[id] + except KeyError: + raise IDLError("type '%s' not found" % id, location) + + def hasName(self, id): + return id in self.namemap + + def getNames(self): + return iter(self.namemap) + + def __str__(self): + return "".join([str(p) for p in self.productions]) + + def resolve(self, incdirs, parser): + self.namemap = NameMap() + self.incdirs = incdirs + self.parser = parser + for p in self.productions: + p.resolve(self) + + def includes(self): + for p in self.productions: + if p.kind == 'include': + yield p + + def needsJSTypes(self): + for p in self.productions: + if p.kind == 'interface' and p.needsJSTypes(): + return True + return False + + +class CDATA(object): + kind = 'cdata' + _re = re.compile(r'\n+') + + def __init__(self, data, location): + self.data = self._re.sub('\n', data) + self.location = location + + def resolve(self, parent): + pass + + def __str__(self): + return "cdata: %s\n\t%r\n" % (self.location.get(), self.data) + + def count(self): + return 0 + + +class Typedef(object): + kind = 'typedef' + + def __init__(self, type, name, location, doccomments): + self.type = type + self.name = name + self.location = location + self.doccomments = doccomments + + def __eq__(self, other): + return self.name == other.name and self.type == other.type + + def resolve(self, parent): + parent.setName(self) + self.realtype = parent.getName(self.type, self.location) + + def isScriptable(self): + return self.realtype.isScriptable() + + def nativeType(self, calltype): + return "%s %s" % (self.name, + calltype != 'in' and '*' or '') + + def __str__(self): + return "typedef %s %s\n" % (self.type, self.name) + + +class Forward(object): + kind = 'forward' + + def __init__(self, name, location, doccomments): + self.name = name + self.location = location + self.doccomments = doccomments + + def __eq__(self, other): + return other.kind == 'forward' and other.name == self.name + + def resolve(self, parent): + # Hack alert: if an identifier is already present, move the doccomments + # forward. + if parent.hasName(self.name): + for i in xrange(0, len(parent.productions)): + if parent.productions[i] is self: + break + for i in xrange(i + 1, len(parent.productions)): + if hasattr(parent.productions[i], 'doccomments'): + parent.productions[i].doccomments[0:0] = self.doccomments + break + + parent.setName(self) + + def isScriptable(self): + return True + + def nativeType(self, calltype): + return "%s %s" % (self.name, + calltype != 'in' and '* *' or '*') + + def __str__(self): + return "forward-declared %s\n" % self.name + + +class Native(object): + kind = 'native' + + modifier = None + specialtype = None + + specialtypes = { + 'nsid': None, + 'domstring': 'nsAString', + 'utf8string': 'nsACString', + 'cstring': 'nsACString', + 'astring': 'nsAString', + 'jsval': 'JS::Value' + } + + def __init__(self, name, nativename, attlist, location): + self.name = name + self.nativename = nativename + self.location = location + + for name, value, aloc in attlist: + if value is not None: + raise IDLError("Unexpected attribute value", aloc) + if name in ('ptr', 'ref'): + if self.modifier is not None: + raise IDLError("More than one ptr/ref modifier", aloc) + self.modifier = name + elif name in self.specialtypes.keys(): + if self.specialtype is not None: + raise IDLError("More than one special type", aloc) + self.specialtype = name + if self.specialtypes[name] is not None: + self.nativename = self.specialtypes[name] + else: + raise IDLError("Unexpected attribute", aloc) + + def __eq__(self, other): + return (self.name == other.name and + self.nativename == other.nativename and + self.modifier == other.modifier and + self.specialtype == other.specialtype) + + def resolve(self, parent): + parent.setName(self) + + def isScriptable(self): + if self.specialtype is None: + return False + + if self.specialtype == 'nsid': + return self.modifier is not None + + return self.modifier == 'ref' + + def isPtr(self, calltype): + return self.modifier == 'ptr' + + def isRef(self, calltype): + return self.modifier == 'ref' + + def nativeType(self, calltype, const=False, shared=False): + if shared: + if calltype != 'out': + raise IDLError("[shared] only applies to out parameters.") + const = True + + if self.specialtype is not None and calltype == 'in': + const = True + + if self.specialtype == 'jsval': + if calltype == 'out' or calltype == 'inout': + return "JS::MutableHandleValue " + return "JS::HandleValue " + + if self.isRef(calltype): + m = '& ' + elif self.isPtr(calltype): + m = '*' + ((self.modifier == 'ptr' and calltype != 'in') and '*' or '') + else: + m = calltype != 'in' and '*' or '' + return "%s%s %s" % (const and 'const ' or '', self.nativename, m) + + def __str__(self): + return "native %s(%s)\n" % (self.name, self.nativename) + + +class Interface(object): + kind = 'interface' + + def __init__(self, name, attlist, base, members, location, doccomments): + self.name = name + self.attributes = InterfaceAttributes(attlist, location) + self.base = base + self.members = members + self.location = location + self.namemap = NameMap() + self.doccomments = doccomments + self.nativename = name + + for m in members: + if not isinstance(m, CDATA): + self.namemap.set(m) + + def __eq__(self, other): + return self.name == other.name and self.location == other.location + + def resolve(self, parent): + self.idl = parent + + # Hack alert: if an identifier is already present, libIDL assigns + # doc comments incorrectly. This is quirks-mode extraordinaire! + if parent.hasName(self.name): + for member in self.members: + if hasattr(member, 'doccomments'): + member.doccomments[0:0] = self.doccomments + break + self.doccomments = parent.getName(self.name, None).doccomments + + if self.attributes.function: + has_method = False + for member in self.members: + if member.kind is 'method': + if has_method: + raise IDLError("interface '%s' has multiple methods, but marked 'function'" % self.name, self.location) + else: + has_method = True + + parent.setName(self) + if self.base is not None: + realbase = parent.getName(self.base, self.location) + if realbase.kind != 'interface': + raise IDLError("interface '%s' inherits from non-interface type '%s'" % (self.name, self.base), self.location) + + if self.attributes.scriptable and not realbase.attributes.scriptable: + print >>sys.stderr, IDLError("interface '%s' is scriptable but derives from non-scriptable '%s'" % (self.name, self.base), self.location, warning=True) + + if self.attributes.scriptable and realbase.attributes.builtinclass and not self.attributes.builtinclass: + raise IDLError("interface '%s' is not builtinclass but derives from builtinclass '%s'" % (self.name, self.base), self.location) + + for member in self.members: + member.resolve(self) + + # The number 250 is NOT arbitrary; this number is the maximum number of + # stub entries defined in xpcom/reflect/xptcall/genstubs.pl + # Do not increase this value without increasing the number in that + # location, or you WILL cause otherwise unknown problems! + if self.countEntries() > 250 and not self.attributes.builtinclass: + raise IDLError("interface '%s' has too many entries" % self.name, self.location) + + def isScriptable(self): + # NOTE: this is not whether *this* interface is scriptable... it's + # whether, when used as a type, it's scriptable, which is true of all + # interfaces. + return True + + def nativeType(self, calltype, const=False): + return "%s%s %s" % (const and 'const ' or '', + self.name, + calltype != 'in' and '* *' or '*') + + def __str__(self): + l = ["interface %s\n" % self.name] + if self.base is not None: + l.append("\tbase %s\n" % self.base) + l.append(str(self.attributes)) + if self.members is None: + l.append("\tincomplete type\n") + else: + for m in self.members: + l.append(str(m)) + return "".join(l) + + def getConst(self, name, location): + # The constant may be in a base class + iface = self + while name not in iface.namemap and iface is not None: + iface = self.idl.getName(self.base, self.location) + if iface is None: + raise IDLError("cannot find symbol '%s'" % name) + c = iface.namemap.get(name, location) + if c.kind != 'const': + raise IDLError("symbol '%s' is not a constant", c.location) + + return c.getValue() + + def needsJSTypes(self): + for m in self.members: + if m.kind == "attribute" and m.type == "jsval": + return True + if m.kind == "method" and m.needsJSTypes(): + return True + return False + + def countEntries(self): + ''' Returns the number of entries in the vtable for this interface. ''' + total = sum(member.count() for member in self.members) + if self.base is not None: + realbase = self.idl.getName(self.base, self.location) + total += realbase.countEntries() + return total + + +class InterfaceAttributes(object): + uuid = None + scriptable = False + builtinclass = False + function = False + deprecated = False + noscript = False + main_process_scriptable_only = False + + def setuuid(self, value): + self.uuid = value.lower() + + def setscriptable(self): + self.scriptable = True + + def setfunction(self): + self.function = True + + def setnoscript(self): + self.noscript = True + + def setbuiltinclass(self): + self.builtinclass = True + + def setdeprecated(self): + self.deprecated = True + + def setmain_process_scriptable_only(self): + self.main_process_scriptable_only = True + + actions = { + 'uuid': (True, setuuid), + 'scriptable': (False, setscriptable), + 'builtinclass': (False, setbuiltinclass), + 'function': (False, setfunction), + 'noscript': (False, setnoscript), + 'deprecated': (False, setdeprecated), + 'object': (False, lambda self: True), + 'main_process_scriptable_only': (False, setmain_process_scriptable_only), + } + + def __init__(self, attlist, location): + def badattribute(self): + raise IDLError("Unexpected interface attribute '%s'" % name, location) + + for name, val, aloc in attlist: + hasval, action = self.actions.get(name, (False, badattribute)) + if hasval: + if val is None: + raise IDLError("Expected value for attribute '%s'" % name, + aloc) + + action(self, val) + else: + if val is not None: + raise IDLError("Unexpected value for attribute '%s'" % name, + aloc) + + action(self) + + if self.uuid is None: + raise IDLError("interface has no uuid", location) + + def __str__(self): + l = [] + if self.uuid: + l.append("\tuuid: %s\n" % self.uuid) + if self.scriptable: + l.append("\tscriptable\n") + if self.builtinclass: + l.append("\tbuiltinclass\n") + if self.function: + l.append("\tfunction\n") + if self.main_process_scriptable_only: + l.append("\tmain_process_scriptable_only\n") + return "".join(l) + + +class ConstMember(object): + kind = 'const' + + def __init__(self, type, name, value, location, doccomments): + self.type = type + self.name = name + self.value = value + self.location = location + self.doccomments = doccomments + + def resolve(self, parent): + self.realtype = parent.idl.getName(self.type, self.location) + self.iface = parent + basetype = self.realtype + while isinstance(basetype, Typedef): + basetype = basetype.realtype + if not isinstance(basetype, Builtin) or not basetype.maybeConst: + raise IDLError("const may only be a short or long type, not %s" % self.type, self.location) + + self.basetype = basetype + + def getValue(self): + return self.value(self.iface) + + def __str__(self): + return "\tconst %s %s = %s\n" % (self.type, self.name, self.getValue()) + + def count(self): + return 0 + + +class Attribute(object): + kind = 'attribute' + noscript = False + readonly = False + implicit_jscontext = False + nostdcall = False + must_use = False + binaryname = None + null = None + undefined = None + deprecated = False + infallible = False + + def __init__(self, type, name, attlist, readonly, location, doccomments): + self.type = type + self.name = name + self.attlist = attlist + self.readonly = readonly + self.location = location + self.doccomments = doccomments + + for name, value, aloc in attlist: + if name == 'binaryname': + if value is None: + raise IDLError("binaryname attribute requires a value", + aloc) + + self.binaryname = value + continue + + if name == 'Null': + if value is None: + raise IDLError("'Null' attribute requires a value", aloc) + if readonly: + raise IDLError("'Null' attribute only makes sense for setters", + aloc) + if value not in ('Empty', 'Null', 'Stringify'): + raise IDLError("'Null' attribute value must be 'Empty', 'Null' or 'Stringify'", + aloc) + self.null = value + elif name == 'Undefined': + if value is None: + raise IDLError("'Undefined' attribute requires a value", aloc) + if readonly: + raise IDLError("'Undefined' attribute only makes sense for setters", + aloc) + if value not in ('Empty', 'Null'): + raise IDLError("'Undefined' attribute value must be 'Empty' or 'Null'", + aloc) + self.undefined = value + else: + if value is not None: + raise IDLError("Unexpected attribute value", aloc) + + if name == 'noscript': + self.noscript = True + elif name == 'implicit_jscontext': + self.implicit_jscontext = True + elif name == 'deprecated': + self.deprecated = True + elif name == 'nostdcall': + self.nostdcall = True + elif name == 'must_use': + self.must_use = True + elif name == 'infallible': + self.infallible = True + else: + raise IDLError("Unexpected attribute '%s'" % name, aloc) + + def resolve(self, iface): + self.iface = iface + self.realtype = iface.idl.getName(self.type, self.location) + if (self.null is not None and + getBuiltinOrNativeTypeName(self.realtype) != '[domstring]'): + raise IDLError("'Null' attribute can only be used on DOMString", + self.location) + if (self.undefined is not None and + getBuiltinOrNativeTypeName(self.realtype) != '[domstring]'): + raise IDLError("'Undefined' attribute can only be used on DOMString", + self.location) + if self.infallible and not self.realtype.kind == 'builtin': + raise IDLError('[infallible] only works on builtin types ' + '(numbers, booleans, and raw char types)', + self.location) + if self.infallible and not iface.attributes.builtinclass: + raise IDLError('[infallible] attributes are only allowed on ' + '[builtinclass] interfaces', + self.location) + + def toIDL(self): + attribs = attlistToIDL(self.attlist) + readonly = self.readonly and 'readonly ' or '' + return "%s%sattribute %s %s;" % (attribs, readonly, self.type, self.name) + + def isScriptable(self): + if not self.iface.attributes.scriptable: + return False + return not self.noscript + + def __str__(self): + return "\t%sattribute %s %s\n" % (self.readonly and 'readonly ' or '', + self.type, self.name) + + def count(self): + return self.readonly and 1 or 2 + + +class Method(object): + kind = 'method' + noscript = False + notxpcom = False + binaryname = None + implicit_jscontext = False + nostdcall = False + must_use = False + optional_argc = False + deprecated = False + + def __init__(self, type, name, attlist, paramlist, location, doccomments, raises): + self.type = type + self.name = name + self.attlist = attlist + self.params = paramlist + self.location = location + self.doccomments = doccomments + self.raises = raises + + for name, value, aloc in attlist: + if name == 'binaryname': + if value is None: + raise IDLError("binaryname attribute requires a value", + aloc) + + self.binaryname = value + continue + + if value is not None: + raise IDLError("Unexpected attribute value", aloc) + + if name == 'noscript': + self.noscript = True + elif name == 'notxpcom': + self.notxpcom = True + elif name == 'implicit_jscontext': + self.implicit_jscontext = True + elif name == 'optional_argc': + self.optional_argc = True + elif name == 'deprecated': + self.deprecated = True + elif name == 'nostdcall': + self.nostdcall = True + elif name == 'must_use': + self.must_use = True + else: + raise IDLError("Unexpected attribute '%s'" % name, aloc) + + self.namemap = NameMap() + for p in paramlist: + self.namemap.set(p) + + def resolve(self, iface): + self.iface = iface + self.realtype = self.iface.idl.getName(self.type, self.location) + for p in self.params: + p.resolve(self) + for p in self.params: + if p.retval and p != self.params[-1]: + raise IDLError("'retval' parameter '%s' is not the last parameter" % p.name, self.location) + if p.size_is: + found_size_param = False + for size_param in self.params: + if p.size_is == size_param.name: + found_size_param = True + if getBuiltinOrNativeTypeName(size_param.realtype) != 'unsigned long': + raise IDLError("is_size parameter must have type 'unsigned long'", self.location) + if not found_size_param: + raise IDLError("could not find is_size parameter '%s'" % p.size_is, self.location) + + def isScriptable(self): + if not self.iface.attributes.scriptable: + return False + return not (self.noscript or self.notxpcom) + + def __str__(self): + return "\t%s %s(%s)\n" % (self.type, self.name, ", ".join([p.name for p in self.params])) + + def toIDL(self): + if len(self.raises): + raises = ' raises (%s)' % ','.join(self.raises) + else: + raises = '' + + return "%s%s %s (%s)%s;" % (attlistToIDL(self.attlist), + self.type, + self.name, + ", ".join([p.toIDL() + for p in self.params]), + raises) + + def needsJSTypes(self): + if self.implicit_jscontext: + return True + if self.type == "jsval": + return True + for p in self.params: + t = p.realtype + if isinstance(t, Native) and t.specialtype == "jsval": + return True + return False + + def count(self): + return 1 + + +class Param(object): + size_is = None + iid_is = None + const = False + array = False + retval = False + shared = False + optional = False + null = None + undefined = None + + def __init__(self, paramtype, type, name, attlist, location, realtype=None): + self.paramtype = paramtype + self.type = type + self.name = name + self.attlist = attlist + self.location = location + self.realtype = realtype + + for name, value, aloc in attlist: + # Put the value-taking attributes first! + if name == 'size_is': + if value is None: + raise IDLError("'size_is' must specify a parameter", aloc) + self.size_is = value + elif name == 'iid_is': + if value is None: + raise IDLError("'iid_is' must specify a parameter", aloc) + self.iid_is = value + elif name == 'Null': + if value is None: + raise IDLError("'Null' must specify a parameter", aloc) + if value not in ('Empty', 'Null', 'Stringify'): + raise IDLError("'Null' parameter value must be 'Empty', 'Null', or 'Stringify'", + aloc) + self.null = value + elif name == 'Undefined': + if value is None: + raise IDLError("'Undefined' must specify a parameter", aloc) + if value not in ('Empty', 'Null'): + raise IDLError("'Undefined' parameter value must be 'Empty' or 'Null'", + aloc) + self.undefined = value + else: + if value is not None: + raise IDLError("Unexpected value for attribute '%s'" % name, + aloc) + + if name == 'const': + self.const = True + elif name == 'array': + self.array = True + elif name == 'retval': + self.retval = True + elif name == 'shared': + self.shared = True + elif name == 'optional': + self.optional = True + else: + raise IDLError("Unexpected attribute '%s'" % name, aloc) + + def resolve(self, method): + self.realtype = method.iface.idl.getName(self.type, self.location) + if self.array: + self.realtype = Array(self.realtype) + if (self.null is not None and + getBuiltinOrNativeTypeName(self.realtype) != '[domstring]'): + raise IDLError("'Null' attribute can only be used on DOMString", + self.location) + if (self.undefined is not None and + getBuiltinOrNativeTypeName(self.realtype) != '[domstring]'): + raise IDLError("'Undefined' attribute can only be used on DOMString", + self.location) + + def nativeType(self): + kwargs = {} + if self.shared: + kwargs['shared'] = True + if self.const: + kwargs['const'] = True + + try: + return self.realtype.nativeType(self.paramtype, **kwargs) + except IDLError, e: + raise IDLError(e.message, self.location) + except TypeError, e: + raise IDLError("Unexpected parameter attribute", self.location) + + def toIDL(self): + return "%s%s %s %s" % (paramAttlistToIDL(self.attlist), + self.paramtype, + self.type, + self.name) + + +class Array(object): + def __init__(self, basetype): + self.type = basetype + + def isScriptable(self): + return self.type.isScriptable() + + def nativeType(self, calltype, const=False): + return "%s%s*" % (const and 'const ' or '', + self.type.nativeType(calltype)) + + +class IDLParser(object): + keywords = { + 'const': 'CONST', + 'interface': 'INTERFACE', + 'in': 'IN', + 'inout': 'INOUT', + 'out': 'OUT', + 'attribute': 'ATTRIBUTE', + 'raises': 'RAISES', + 'readonly': 'READONLY', + 'native': 'NATIVE', + 'typedef': 'TYPEDEF', + } + + tokens = [ + 'IDENTIFIER', + 'CDATA', + 'INCLUDE', + 'IID', + 'NUMBER', + 'HEXNUM', + 'LSHIFT', + 'RSHIFT', + 'NATIVEID', + ] + + tokens.extend(keywords.values()) + + states = ( + ('nativeid', 'exclusive'), + ) + + hexchar = r'[a-fA-F0-9]' + + t_NUMBER = r'-?\d+' + t_HEXNUM = r'0x%s+' % hexchar + t_LSHIFT = r'<<' + t_RSHIFT = r'>>' + + literals = '"(){}[],;:=|+-*' + + t_ignore = ' \t' + + def t_multilinecomment(self, t): + r'/\*(?s).*?\*/' + t.lexer.lineno += t.value.count('\n') + if t.value.startswith("/**"): + self._doccomments.append(t.value) + + def t_singlelinecomment(self, t): + r'(?m)//.*?$' + + def t_IID(self, t): + return t + t_IID.__doc__ = r'%(c)s{8}-%(c)s{4}-%(c)s{4}-%(c)s{4}-%(c)s{12}' % {'c': hexchar} + + def t_IDENTIFIER(self, t): + r'(unsigned\ long\ long|unsigned\ short|unsigned\ long|long\ long)(?!_?[A-Za-z][A-Za-z_0-9])|_?[A-Za-z][A-Za-z_0-9]*' + t.type = self.keywords.get(t.value, 'IDENTIFIER') + return t + + def t_LCDATA(self, t): + r'(?s)%\{[ ]*C\+\+[ ]*\n(?P.*?\n?)%\}[ ]*(C\+\+)?' + t.type = 'CDATA' + t.value = t.lexer.lexmatch.group('cdata') + t.lexer.lineno += t.value.count('\n') + return t + + def t_INCLUDE(self, t): + r'\#include[ \t]+"[^"\n]+"' + inc, value, end = t.value.split('"') + t.value = value + return t + + def t_directive(self, t): + r'\#(?P[a-zA-Z]+)[^\n]+' + raise IDLError("Unrecognized directive %s" % t.lexer.lexmatch.group('directive'), + Location(lexer=self.lexer, lineno=self.lexer.lineno, + lexpos=self.lexer.lexpos)) + + def t_newline(self, t): + r'\n+' + t.lexer.lineno += len(t.value) + + def t_nativeid_NATIVEID(self, t): + r'[^()\n]+(?=\))' + t.lexer.begin('INITIAL') + return t + + t_nativeid_ignore = '' + + def t_ANY_error(self, t): + raise IDLError("unrecognized input", + Location(lexer=self.lexer, + lineno=self.lexer.lineno, + lexpos=self.lexer.lexpos)) + + precedence = ( + ('left', '|'), + ('left', 'LSHIFT', 'RSHIFT'), + ('left', '+', '-'), + ('left', '*'), + ('left', 'UMINUS'), + ) + + def p_idlfile(self, p): + """idlfile : productions""" + p[0] = IDL(p[1]) + + def p_productions_start(self, p): + """productions : """ + p[0] = [] + + def p_productions_cdata(self, p): + """productions : CDATA productions""" + p[0] = list(p[2]) + p[0].insert(0, CDATA(p[1], self.getLocation(p, 1))) + + def p_productions_include(self, p): + """productions : INCLUDE productions""" + p[0] = list(p[2]) + p[0].insert(0, Include(p[1], self.getLocation(p, 1))) + + def p_productions_interface(self, p): + """productions : interface productions + | typedef productions + | native productions""" + p[0] = list(p[2]) + p[0].insert(0, p[1]) + + def p_typedef(self, p): + """typedef : TYPEDEF IDENTIFIER IDENTIFIER ';'""" + p[0] = Typedef(type=p[2], + name=p[3], + location=self.getLocation(p, 1), + doccomments=p.slice[1].doccomments) + + def p_native(self, p): + """native : attributes NATIVE IDENTIFIER afternativeid '(' NATIVEID ')' ';'""" + p[0] = Native(name=p[3], + nativename=p[6], + attlist=p[1]['attlist'], + location=self.getLocation(p, 2)) + + def p_afternativeid(self, p): + """afternativeid : """ + # this is a place marker: we switch the lexer into literal identifier + # mode here, to slurp up everything until the closeparen + self.lexer.begin('nativeid') + + def p_anyident(self, p): + """anyident : IDENTIFIER + | CONST""" + p[0] = {'value': p[1], + 'location': self.getLocation(p, 1)} + + def p_attributes(self, p): + """attributes : '[' attlist ']' + | """ + if len(p) == 1: + p[0] = {'attlist': []} + else: + p[0] = {'attlist': p[2], + 'doccomments': p.slice[1].doccomments} + + def p_attlist_start(self, p): + """attlist : attribute""" + p[0] = [p[1]] + + def p_attlist_continue(self, p): + """attlist : attribute ',' attlist""" + p[0] = list(p[3]) + p[0].insert(0, p[1]) + + def p_attribute(self, p): + """attribute : anyident attributeval""" + p[0] = (p[1]['value'], p[2], p[1]['location']) + + def p_attributeval(self, p): + """attributeval : '(' IDENTIFIER ')' + | '(' IID ')' + | """ + if len(p) > 1: + p[0] = p[2] + + def p_interface(self, p): + """interface : attributes INTERFACE IDENTIFIER ifacebase ifacebody ';'""" + atts, INTERFACE, name, base, body, SEMI = p[1:] + attlist = atts['attlist'] + doccomments = [] + if 'doccomments' in atts: + doccomments.extend(atts['doccomments']) + doccomments.extend(p.slice[2].doccomments) + + l = lambda: self.getLocation(p, 2) + + if body is None: + # forward-declared interface... must not have attributes! + if len(attlist) != 0: + raise IDLError("Forward-declared interface must not have attributes", + list[0][3]) + + if base is not None: + raise IDLError("Forward-declared interface must not have a base", + l()) + p[0] = Forward(name=name, location=l(), doccomments=doccomments) + else: + p[0] = Interface(name=name, + attlist=attlist, + base=base, + members=body, + location=l(), + doccomments=doccomments) + + def p_ifacebody(self, p): + """ifacebody : '{' members '}' + | """ + if len(p) > 1: + p[0] = p[2] + + def p_ifacebase(self, p): + """ifacebase : ':' IDENTIFIER + | """ + if len(p) == 3: + p[0] = p[2] + + def p_members_start(self, p): + """members : """ + p[0] = [] + + def p_members_continue(self, p): + """members : member members""" + p[0] = list(p[2]) + p[0].insert(0, p[1]) + + def p_member_cdata(self, p): + """member : CDATA""" + p[0] = CDATA(p[1], self.getLocation(p, 1)) + + def p_member_const(self, p): + """member : CONST IDENTIFIER IDENTIFIER '=' number ';' """ + p[0] = ConstMember(type=p[2], name=p[3], + value=p[5], location=self.getLocation(p, 1), + doccomments=p.slice[1].doccomments) + +# All "number" products return a function(interface) + + def p_number_decimal(self, p): + """number : NUMBER""" + n = int(p[1]) + p[0] = lambda i: n + + def p_number_hex(self, p): + """number : HEXNUM""" + n = int(p[1], 16) + p[0] = lambda i: n + + def p_number_identifier(self, p): + """number : IDENTIFIER""" + id = p[1] + loc = self.getLocation(p, 1) + p[0] = lambda i: i.getConst(id, loc) + + def p_number_paren(self, p): + """number : '(' number ')'""" + p[0] = p[2] + + def p_number_neg(self, p): + """number : '-' number %prec UMINUS""" + n = p[2] + p[0] = lambda i: - n(i) + + def p_number_add(self, p): + """number : number '+' number + | number '-' number + | number '*' number""" + n1 = p[1] + n2 = p[3] + if p[2] == '+': + p[0] = lambda i: n1(i) + n2(i) + elif p[2] == '-': + p[0] = lambda i: n1(i) - n2(i) + else: + p[0] = lambda i: n1(i) * n2(i) + + def p_number_shift(self, p): + """number : number LSHIFT number + | number RSHIFT number""" + n1 = p[1] + n2 = p[3] + if p[2] == '<<': + p[0] = lambda i: n1(i) << n2(i) + else: + p[0] = lambda i: n1(i) >> n2(i) + + def p_number_bitor(self, p): + """number : number '|' number""" + n1 = p[1] + n2 = p[3] + p[0] = lambda i: n1(i) | n2(i) + + def p_member_att(self, p): + """member : attributes optreadonly ATTRIBUTE IDENTIFIER IDENTIFIER ';'""" + if 'doccomments' in p[1]: + doccomments = p[1]['doccomments'] + elif p[2] is not None: + doccomments = p[2] + else: + doccomments = p.slice[3].doccomments + + p[0] = Attribute(type=p[4], + name=p[5], + attlist=p[1]['attlist'], + readonly=p[2] is not None, + location=self.getLocation(p, 3), + doccomments=doccomments) + + def p_member_method(self, p): + """member : attributes IDENTIFIER IDENTIFIER '(' paramlist ')' raises ';'""" + if 'doccomments' in p[1]: + doccomments = p[1]['doccomments'] + else: + doccomments = p.slice[2].doccomments + + p[0] = Method(type=p[2], + name=p[3], + attlist=p[1]['attlist'], + paramlist=p[5], + location=self.getLocation(p, 3), + doccomments=doccomments, + raises=p[7]) + + def p_paramlist(self, p): + """paramlist : param moreparams + | """ + if len(p) == 1: + p[0] = [] + else: + p[0] = list(p[2]) + p[0].insert(0, p[1]) + + def p_moreparams_start(self, p): + """moreparams :""" + p[0] = [] + + def p_moreparams_continue(self, p): + """moreparams : ',' param moreparams""" + p[0] = list(p[3]) + p[0].insert(0, p[2]) + + def p_param(self, p): + """param : attributes paramtype IDENTIFIER IDENTIFIER""" + p[0] = Param(paramtype=p[2], + type=p[3], + name=p[4], + attlist=p[1]['attlist'], + location=self.getLocation(p, 3)) + + def p_paramtype(self, p): + """paramtype : IN + | INOUT + | OUT""" + p[0] = p[1] + + def p_optreadonly(self, p): + """optreadonly : READONLY + | """ + if len(p) > 1: + p[0] = p.slice[1].doccomments + else: + p[0] = None + + def p_raises(self, p): + """raises : RAISES '(' idlist ')' + | """ + if len(p) == 1: + p[0] = [] + else: + p[0] = p[3] + + def p_idlist(self, p): + """idlist : IDENTIFIER""" + p[0] = [p[1]] + + def p_idlist_continue(self, p): + """idlist : IDENTIFIER ',' idlist""" + p[0] = list(p[3]) + p[0].insert(0, p[1]) + + def p_error(self, t): + if not t: + raise IDLError("Syntax Error at end of file. Possibly due to missing semicolon(;), braces(}) or both", None) + else: + location = Location(self.lexer, t.lineno, t.lexpos) + raise IDLError("invalid syntax", location) + + def __init__(self, outputdir=''): + self._doccomments = [] + self.lexer = lex.lex(object=self, + outputdir=outputdir, + lextab='xpidllex', + optimize=1) + self.parser = yacc.yacc(module=self, + outputdir=outputdir, + debug=0, + tabmodule='xpidlyacc', + optimize=1) + + def clearComments(self): + self._doccomments = [] + + def token(self): + t = self.lexer.token() + if t is not None and t.type != 'CDATA': + t.doccomments = self._doccomments + self._doccomments = [] + return t + + def parse(self, data, filename=None): + if filename is not None: + self.lexer.filename = filename + self.lexer.lineno = 1 + self.lexer.input(data) + idl = self.parser.parse(lexer=self) + if filename is not None: + idl.deps.append(filename) + return idl + + def getLocation(self, p, i): + return Location(self.lexer, p.lineno(i), p.lexpos(i)) + +if __name__ == '__main__': + p = IDLParser() + for f in sys.argv[1:]: + print "Parsing %s" % f + p.parse(open(f).read(), filename=f) diff --git a/xpcom/io/Base64.cpp b/xpcom/io/Base64.cpp new file mode 100644 index 000000000..911c0595a --- /dev/null +++ b/xpcom/io/Base64.cpp @@ -0,0 +1,645 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "Base64.h" + +#include "mozilla/UniquePtrExtensions.h" +#include "nsIInputStream.h" +#include "nsString.h" +#include "nsTArray.h" + +#include "plbase64.h" + +namespace { + +// BEGIN base64 encode code copied and modified from NSPR +const unsigned char* base = + (unsigned char*)"ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789+/"; + +template +static void +Encode3to4(const unsigned char* aSrc, T* aDest) +{ + uint32_t b32 = (uint32_t)0; + int i, j = 18; + + for (i = 0; i < 3; ++i) { + b32 <<= 8; + b32 |= (uint32_t)aSrc[i]; + } + + for (i = 0; i < 4; ++i) { + aDest[i] = base[(uint32_t)((b32 >> j) & 0x3F)]; + j -= 6; + } +} + +template +static void +Encode2to4(const unsigned char* aSrc, T* aDest) +{ + aDest[0] = base[(uint32_t)((aSrc[0] >> 2) & 0x3F)]; + aDest[1] = base[(uint32_t)(((aSrc[0] & 0x03) << 4) | ((aSrc[1] >> 4) & 0x0F))]; + aDest[2] = base[(uint32_t)((aSrc[1] & 0x0F) << 2)]; + aDest[3] = (unsigned char)'='; +} + +template +static void +Encode1to4(const unsigned char* aSrc, T* aDest) +{ + aDest[0] = base[(uint32_t)((aSrc[0] >> 2) & 0x3F)]; + aDest[1] = base[(uint32_t)((aSrc[0] & 0x03) << 4)]; + aDest[2] = (unsigned char)'='; + aDest[3] = (unsigned char)'='; +} + +template +static void +Encode(const unsigned char* aSrc, uint32_t aSrcLen, T* aDest) +{ + while (aSrcLen >= 3) { + Encode3to4(aSrc, aDest); + aSrc += 3; + aDest += 4; + aSrcLen -= 3; + } + + switch (aSrcLen) { + case 2: + Encode2to4(aSrc, aDest); + break; + case 1: + Encode1to4(aSrc, aDest); + break; + case 0: + break; + default: + NS_NOTREACHED("coding error"); + } +} + +// END base64 encode code copied and modified from NSPR. + +template +struct EncodeInputStream_State +{ + unsigned char c[3]; + uint8_t charsOnStack; + typename T::char_type* buffer; +}; + +template +nsresult +EncodeInputStream_Encoder(nsIInputStream* aStream, + void* aClosure, + const char* aFromSegment, + uint32_t aToOffset, + uint32_t aCount, + uint32_t* aWriteCount) +{ + NS_ASSERTION(aCount > 0, "Er, what?"); + + EncodeInputStream_State* state = + static_cast*>(aClosure); + + // If we have any data left from last time, encode it now. + uint32_t countRemaining = aCount; + const unsigned char* src = (const unsigned char*)aFromSegment; + if (state->charsOnStack) { + unsigned char firstSet[4]; + if (state->charsOnStack == 1) { + firstSet[0] = state->c[0]; + firstSet[1] = src[0]; + firstSet[2] = (countRemaining > 1) ? src[1] : '\0'; + firstSet[3] = '\0'; + } else /* state->charsOnStack == 2 */ { + firstSet[0] = state->c[0]; + firstSet[1] = state->c[1]; + firstSet[2] = src[0]; + firstSet[3] = '\0'; + } + Encode(firstSet, 3, state->buffer); + state->buffer += 4; + countRemaining -= (3 - state->charsOnStack); + src += (3 - state->charsOnStack); + state->charsOnStack = 0; + } + + // Encode the bulk of the + uint32_t encodeLength = countRemaining - countRemaining % 3; + MOZ_ASSERT(encodeLength % 3 == 0, + "Should have an exact number of triplets!"); + Encode(src, encodeLength, state->buffer); + state->buffer += (encodeLength / 3) * 4; + src += encodeLength; + countRemaining -= encodeLength; + + // We must consume all data, so if there's some data left stash it + *aWriteCount = aCount; + + if (countRemaining) { + // We should never have a full triplet left at this point. + MOZ_ASSERT(countRemaining < 3, "We should have encoded more!"); + state->c[0] = src[0]; + state->c[1] = (countRemaining == 2) ? src[1] : '\0'; + state->charsOnStack = countRemaining; + } + + return NS_OK; +} + +template +nsresult +EncodeInputStream(nsIInputStream* aInputStream, + T& aDest, + uint32_t aCount, + uint32_t aOffset) +{ + nsresult rv; + uint64_t count64 = aCount; + + if (!aCount) { + rv = aInputStream->Available(&count64); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + // if count64 is over 4GB, it will be failed at the below condition, + // then will return NS_ERROR_OUT_OF_MEMORY + aCount = (uint32_t)count64; + } + + uint64_t countlong = + (count64 + 2) / 3 * 4; // +2 due to integer math. + if (countlong + aOffset > UINT32_MAX) { + return NS_ERROR_OUT_OF_MEMORY; + } + + uint32_t count = uint32_t(countlong); + + if (!aDest.SetLength(count + aOffset, mozilla::fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + EncodeInputStream_State state; + state.charsOnStack = 0; + state.c[2] = '\0'; + state.buffer = aOffset + aDest.BeginWriting(); + + while (1) { + uint32_t read = 0; + + rv = aInputStream->ReadSegments(&EncodeInputStream_Encoder, + (void*)&state, + aCount, + &read); + if (NS_FAILED(rv)) { + if (rv == NS_BASE_STREAM_WOULD_BLOCK) { + NS_RUNTIMEABORT("Not implemented for async streams!"); + } + if (rv == NS_ERROR_NOT_IMPLEMENTED) { + NS_RUNTIMEABORT("Requires a stream that implements ReadSegments!"); + } + return rv; + } + + if (!read) { + break; + } + } + + // Finish encoding if anything is left + if (state.charsOnStack) { + Encode(state.c, state.charsOnStack, state.buffer); + } + + if (aDest.Length()) { + // May belong to an nsCString with an unallocated buffer, so only null + // terminate if there is a need to. + *aDest.EndWriting() = '\0'; + } + + return NS_OK; +} + +static const char kBase64URLAlphabet[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; + +// Maps an encoded character to a value in the Base64 URL alphabet, per +// RFC 4648, Table 2. Invalid input characters map to UINT8_MAX. +static const uint8_t kBase64URLDecodeTable[] = { + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, + 62 /* - */, + 255, 255, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, /* 0 - 9 */ + 255, 255, 255, 255, 255, 255, 255, + 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, /* A - Z */ + 255, 255, 255, 255, + 63 /* _ */, + 255, + 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, /* a - z */ + 255, 255, 255, 255, +}; + +bool +Base64URLCharToValue(char aChar, uint8_t* aValue) { + uint8_t index = static_cast(aChar); + *aValue = kBase64URLDecodeTable[index & 0x7f]; + return (*aValue != 255) && !(index & ~0x7f); +} + +} // namespace + +namespace mozilla { + +nsresult +Base64EncodeInputStream(nsIInputStream* aInputStream, + nsACString& aDest, + uint32_t aCount, + uint32_t aOffset) +{ + return EncodeInputStream(aInputStream, aDest, aCount, aOffset); +} + +nsresult +Base64EncodeInputStream(nsIInputStream* aInputStream, + nsAString& aDest, + uint32_t aCount, + uint32_t aOffset) +{ + return EncodeInputStream(aInputStream, aDest, aCount, aOffset); +} + +nsresult +Base64Encode(const char* aBinary, uint32_t aBinaryLen, char** aBase64) +{ + // Check for overflow. + if (aBinaryLen > (UINT32_MAX / 4) * 3) { + return NS_ERROR_FAILURE; + } + + // Don't ask PR_Base64Encode to encode empty strings. + if (aBinaryLen == 0) { + *aBase64 = (char*)moz_xmalloc(1); + (*aBase64)[0] = '\0'; + return NS_OK; + } + + *aBase64 = nullptr; + uint32_t base64Len = ((aBinaryLen + 2) / 3) * 4; + + // Add one byte for null termination. + UniqueFreePtr base64((char*)malloc(base64Len + 1)); + if (!base64) { + return NS_ERROR_OUT_OF_MEMORY; + } + + if (!PL_Base64Encode(aBinary, aBinaryLen, base64.get())) { + return NS_ERROR_INVALID_ARG; + } + + // PL_Base64Encode doesn't null terminate the buffer for us when we pass + // the buffer in. Do that manually. + base64[base64Len] = '\0'; + + *aBase64 = base64.release(); + return NS_OK; +} + +nsresult +Base64Encode(const nsACString& aBinary, nsACString& aBase64) +{ + // Check for overflow. + if (aBinary.Length() > (UINT32_MAX / 4) * 3) { + return NS_ERROR_FAILURE; + } + + // Don't ask PR_Base64Encode to encode empty strings. + if (aBinary.IsEmpty()) { + aBase64.Truncate(); + return NS_OK; + } + + uint32_t base64Len = ((aBinary.Length() + 2) / 3) * 4; + + // Add one byte for null termination. + if (!aBase64.SetCapacity(base64Len + 1, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + char* base64 = aBase64.BeginWriting(); + if (!PL_Base64Encode(aBinary.BeginReading(), aBinary.Length(), base64)) { + aBase64.Truncate(); + return NS_ERROR_INVALID_ARG; + } + + // PL_Base64Encode doesn't null terminate the buffer for us when we pass + // the buffer in. Do that manually. + base64[base64Len] = '\0'; + + aBase64.SetLength(base64Len); + return NS_OK; +} + +nsresult +Base64Encode(const nsAString& aBinary, nsAString& aBase64) +{ + NS_LossyConvertUTF16toASCII binary(aBinary); + nsAutoCString base64; + + nsresult rv = Base64Encode(binary, base64); + if (NS_SUCCEEDED(rv)) { + CopyASCIItoUTF16(base64, aBase64); + } else { + aBase64.Truncate(); + } + + return rv; +} + +static nsresult +Base64DecodeHelper(const char* aBase64, uint32_t aBase64Len, char* aBinary, + uint32_t* aBinaryLen) +{ + MOZ_ASSERT(aBinary); + if (!PL_Base64Decode(aBase64, aBase64Len, aBinary)) { + return NS_ERROR_INVALID_ARG; + } + + // PL_Base64Decode doesn't null terminate the buffer for us when we pass + // the buffer in. Do that manually, taking into account the number of '=' + // characters we were passed. + if (aBase64Len != 0 && aBase64[aBase64Len - 1] == '=') { + if (aBase64Len > 1 && aBase64[aBase64Len - 2] == '=') { + *aBinaryLen -= 2; + } else { + *aBinaryLen -= 1; + } + } + aBinary[*aBinaryLen] = '\0'; + return NS_OK; +} + +nsresult +Base64Decode(const char* aBase64, uint32_t aBase64Len, char** aBinary, + uint32_t* aBinaryLen) +{ + // Check for overflow. + if (aBase64Len > UINT32_MAX / 3) { + return NS_ERROR_FAILURE; + } + + // Don't ask PR_Base64Decode to decode the empty string. + if (aBase64Len == 0) { + *aBinary = (char*)moz_xmalloc(1); + (*aBinary)[0] = '\0'; + *aBinaryLen = 0; + return NS_OK; + } + + *aBinary = nullptr; + *aBinaryLen = (aBase64Len * 3) / 4; + + // Add one byte for null termination. + UniqueFreePtr binary((char*)malloc(*aBinaryLen + 1)); + if (!binary) { + return NS_ERROR_OUT_OF_MEMORY; + } + + nsresult rv = + Base64DecodeHelper(aBase64, aBase64Len, binary.get(), aBinaryLen); + if (NS_FAILED(rv)) { + return rv; + } + + *aBinary = binary.release(); + return NS_OK; +} + +nsresult +Base64Decode(const nsACString& aBase64, nsACString& aBinary) +{ + // Check for overflow. + if (aBase64.Length() > UINT32_MAX / 3) { + return NS_ERROR_FAILURE; + } + + // Don't ask PR_Base64Decode to decode the empty string + if (aBase64.IsEmpty()) { + aBinary.Truncate(); + return NS_OK; + } + + uint32_t binaryLen = ((aBase64.Length() * 3) / 4); + + // Add one byte for null termination. + if (!aBinary.SetCapacity(binaryLen + 1, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + char* binary = aBinary.BeginWriting(); + nsresult rv = Base64DecodeHelper(aBase64.BeginReading(), aBase64.Length(), + binary, &binaryLen); + if (NS_FAILED(rv)) { + aBinary.Truncate(); + return rv; + } + + aBinary.SetLength(binaryLen); + return NS_OK; +} + +nsresult +Base64Decode(const nsAString& aBase64, nsAString& aBinary) +{ + NS_LossyConvertUTF16toASCII base64(aBase64); + nsAutoCString binary; + + nsresult rv = Base64Decode(base64, binary); + if (NS_SUCCEEDED(rv)) { + CopyASCIItoUTF16(binary, aBinary); + } else { + aBinary.Truncate(); + } + + return rv; +} + +nsresult +Base64URLDecode(const nsACString& aBase64, + Base64URLDecodePaddingPolicy aPaddingPolicy, + FallibleTArray& aBinary) +{ + // Don't decode empty strings. + if (aBase64.IsEmpty()) { + aBinary.Clear(); + return NS_OK; + } + + // Check for overflow. + uint32_t base64Len = aBase64.Length(); + if (base64Len > UINT32_MAX / 3) { + return NS_ERROR_FAILURE; + } + const char* base64 = aBase64.BeginReading(); + + // The decoded length may be 1-2 bytes over, depending on the final quantum. + uint32_t binaryLen = (base64Len * 3) / 4; + + // Determine whether to check for and ignore trailing padding. + bool maybePadded = false; + switch (aPaddingPolicy) { + case Base64URLDecodePaddingPolicy::Require: + if (base64Len % 4) { + // Padded input length must be a multiple of 4. + return NS_ERROR_INVALID_ARG; + } + maybePadded = true; + break; + + case Base64URLDecodePaddingPolicy::Ignore: + // Check for padding only if the length is a multiple of 4. + maybePadded = !(base64Len % 4); + break; + + // If we're expecting unpadded input, no need for additional checks. + // `=` isn't in the decode table, so padded strings will fail to decode. + default: + MOZ_FALLTHROUGH_ASSERT("Invalid decode padding policy"); + case Base64URLDecodePaddingPolicy::Reject: + break; + } + if (maybePadded && base64[base64Len - 1] == '=') { + if (base64[base64Len - 2] == '=') { + base64Len -= 2; + } else { + base64Len -= 1; + } + } + + if (NS_WARN_IF(!aBinary.SetCapacity(binaryLen, mozilla::fallible))) { + return NS_ERROR_OUT_OF_MEMORY; + } + aBinary.SetLengthAndRetainStorage(binaryLen); + uint8_t* binary = aBinary.Elements(); + + for (; base64Len >= 4; base64Len -= 4) { + uint8_t w, x, y, z; + if (!Base64URLCharToValue(*base64++, &w) || + !Base64URLCharToValue(*base64++, &x) || + !Base64URLCharToValue(*base64++, &y) || + !Base64URLCharToValue(*base64++, &z)) { + return NS_ERROR_INVALID_ARG; + } + *binary++ = w << 2 | x >> 4; + *binary++ = x << 4 | y >> 2; + *binary++ = y << 6 | z; + } + + if (base64Len == 3) { + uint8_t w, x, y; + if (!Base64URLCharToValue(*base64++, &w) || + !Base64URLCharToValue(*base64++, &x) || + !Base64URLCharToValue(*base64++, &y)) { + return NS_ERROR_INVALID_ARG; + } + *binary++ = w << 2 | x >> 4; + *binary++ = x << 4 | y >> 2; + } else if (base64Len == 2) { + uint8_t w, x; + if (!Base64URLCharToValue(*base64++, &w) || + !Base64URLCharToValue(*base64++, &x)) { + return NS_ERROR_INVALID_ARG; + } + *binary++ = w << 2 | x >> 4; + } else if (base64Len) { + return NS_ERROR_INVALID_ARG; + } + + // Set the length to the actual number of decoded bytes. + aBinary.TruncateLength(binary - aBinary.Elements()); + return NS_OK; +} + +nsresult +Base64URLEncode(uint32_t aBinaryLen, const uint8_t* aBinary, + Base64URLEncodePaddingPolicy aPaddingPolicy, + nsACString& aBase64) +{ + // Don't encode empty strings. + if (aBinaryLen == 0) { + aBase64.Truncate(); + return NS_OK; + } + + // Check for overflow. + if (aBinaryLen > (UINT32_MAX / 4) * 3) { + return NS_ERROR_FAILURE; + } + + // Allocate a buffer large enough to hold the encoded string with padding. + // Add one byte for null termination. + uint32_t base64Len = ((aBinaryLen + 2) / 3) * 4; + if (NS_WARN_IF(!aBase64.SetCapacity(base64Len + 1, fallible))) { + aBase64.Truncate(); + return NS_ERROR_FAILURE; + } + + char* base64 = aBase64.BeginWriting(); + + uint32_t index = 0; + for (; index + 3 <= aBinaryLen; index += 3) { + *base64++ = kBase64URLAlphabet[aBinary[index] >> 2]; + *base64++ = kBase64URLAlphabet[((aBinary[index] & 0x3) << 4) | + (aBinary[index + 1] >> 4)]; + *base64++ = kBase64URLAlphabet[((aBinary[index + 1] & 0xf) << 2) | + (aBinary[index + 2] >> 6)]; + *base64++ = kBase64URLAlphabet[aBinary[index + 2] & 0x3f]; + } + + uint32_t remaining = aBinaryLen - index; + if (remaining == 1) { + *base64++ = kBase64URLAlphabet[aBinary[index] >> 2]; + *base64++ = kBase64URLAlphabet[((aBinary[index] & 0x3) << 4)]; + } else if (remaining == 2) { + *base64++ = kBase64URLAlphabet[aBinary[index] >> 2]; + *base64++ = kBase64URLAlphabet[((aBinary[index] & 0x3) << 4) | + (aBinary[index + 1] >> 4)]; + *base64++ = kBase64URLAlphabet[((aBinary[index + 1] & 0xf) << 2)]; + } + + uint32_t length = base64 - aBase64.BeginWriting(); + if (aPaddingPolicy == Base64URLEncodePaddingPolicy::Include) { + if (length % 4 == 2) { + *base64++ = '='; + *base64++ = '='; + length += 2; + } else if (length % 4 == 3) { + *base64++ = '='; + length += 1; + } + } else { + MOZ_ASSERT(aPaddingPolicy == Base64URLEncodePaddingPolicy::Omit, + "Invalid encode padding policy"); + } + + // Null terminate and truncate to the actual number of characters. + *base64 = '\0'; + aBase64.SetLength(length); + + return NS_OK; +} + +} // namespace mozilla diff --git a/xpcom/io/Base64.h b/xpcom/io/Base64.h new file mode 100644 index 000000000..0c3f1e140 --- /dev/null +++ b/xpcom/io/Base64.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_Base64_h__ +#define mozilla_Base64_h__ + +#include "nsString.h" + +class nsIInputStream; + +namespace mozilla { + +MOZ_MUST_USE nsresult +Base64EncodeInputStream(nsIInputStream* aInputStream, + nsACString& aDest, + uint32_t aCount, + uint32_t aOffset = 0); +MOZ_MUST_USE nsresult +Base64EncodeInputStream(nsIInputStream* aInputStream, + nsAString& aDest, + uint32_t aCount, + uint32_t aOffset = 0); + +MOZ_MUST_USE nsresult +Base64Encode(const char* aBinary, uint32_t aBinaryLen, char** aBase64); +MOZ_MUST_USE nsresult +Base64Encode(const nsACString& aBinary, nsACString& aBase64); +MOZ_MUST_USE nsresult +Base64Encode(const nsAString& aBinary, nsAString& aBase64); + +MOZ_MUST_USE nsresult +Base64Decode(const char* aBase64, uint32_t aBase64Len, char** aBinary, + uint32_t* aBinaryLen); +MOZ_MUST_USE nsresult +Base64Decode(const nsACString& aBase64, nsACString& aBinary); +MOZ_MUST_USE nsresult +Base64Decode(const nsAString& aBase64, nsAString& aBinary); + +enum class Base64URLEncodePaddingPolicy { + Include, + Omit, +}; + +/** + * Converts |aBinary| to an unpadded, Base64 URL-encoded string per RFC 4648. + * Aims to encode the data in constant time. The caller retains ownership + * of |aBinary|. + */ +MOZ_MUST_USE nsresult +Base64URLEncode(uint32_t aBinaryLen, const uint8_t* aBinary, + Base64URLEncodePaddingPolicy aPaddingPolicy, + nsACString& aBase64); + +enum class Base64URLDecodePaddingPolicy { + Require, + Ignore, + Reject, +}; + +/** + * Decodes a Base64 URL-encoded |aBase64| into |aBinary|. + */ +MOZ_MUST_USE nsresult +Base64URLDecode(const nsACString& aBase64, + Base64URLDecodePaddingPolicy aPaddingPolicy, + FallibleTArray& aBinary); + +} // namespace mozilla + +#endif diff --git a/xpcom/io/CocoaFileUtils.h b/xpcom/io/CocoaFileUtils.h new file mode 100644 index 000000000..7127cb65d --- /dev/null +++ b/xpcom/io/CocoaFileUtils.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/. */ + +// This namespace contains methods with Obj-C/Cocoa implementations. The header +// is C/C++ for inclusion in C/C++-only files. + +#ifndef CocoaFileUtils_h_ +#define CocoaFileUtils_h_ + +#include "nscore.h" +#include + +namespace CocoaFileUtils { + +nsresult RevealFileInFinder(CFURLRef aUrl); +nsresult OpenURL(CFURLRef aUrl); +nsresult GetFileCreatorCode(CFURLRef aUrl, OSType* aCreatorCode); +nsresult SetFileCreatorCode(CFURLRef aUrl, OSType aCreatorCode); +nsresult GetFileTypeCode(CFURLRef aUrl, OSType* aTypeCode); +nsresult SetFileTypeCode(CFURLRef aUrl, OSType aTypeCode); +void AddOriginMetadataToFile(const CFStringRef filePath, + const CFURLRef sourceURL, + const CFURLRef referrerURL); +void AddQuarantineMetadataToFile(const CFStringRef filePath, + const CFURLRef sourceURL, + const CFURLRef referrerURL, + const bool isFromWeb); +CFURLRef GetTemporaryFolderCFURLRef(); + +} // namespace CocoaFileUtils + +#endif diff --git a/xpcom/io/CocoaFileUtils.mm b/xpcom/io/CocoaFileUtils.mm new file mode 100644 index 000000000..a02b82ac1 --- /dev/null +++ b/xpcom/io/CocoaFileUtils.mm @@ -0,0 +1,267 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +// vim:set ts=2 sts=2 sw=2 et cin: +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "CocoaFileUtils.h" +#include "nsCocoaFeatures.h" +#include "nsCocoaUtils.h" +#include +#include "nsObjCExceptions.h" +#include "nsDebug.h" + +// Need to cope with us using old versions of the SDK and needing this on 10.10+ +#if !defined(MAC_OS_X_VERSION_10_10) || (MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_10) +const CFStringRef kCFURLQuarantinePropertiesKey = CFSTR("NSURLQuarantinePropertiesKey"); +#endif + +namespace CocoaFileUtils { + +nsresult RevealFileInFinder(CFURLRef url) +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; + + if (NS_WARN_IF(!url)) + return NS_ERROR_INVALID_ARG; + + NSAutoreleasePool* ap = [[NSAutoreleasePool alloc] init]; + BOOL success = [[NSWorkspace sharedWorkspace] selectFile:[(NSURL*)url path] inFileViewerRootedAtPath:@""]; + [ap release]; + + return (success ? NS_OK : NS_ERROR_FAILURE); + + NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; +} + +nsresult OpenURL(CFURLRef url) +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; + + if (NS_WARN_IF(!url)) + return NS_ERROR_INVALID_ARG; + + NSAutoreleasePool* ap = [[NSAutoreleasePool alloc] init]; + BOOL success = [[NSWorkspace sharedWorkspace] openURL:(NSURL*)url]; + [ap release]; + + return (success ? NS_OK : NS_ERROR_FAILURE); + + NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; +} + +nsresult GetFileCreatorCode(CFURLRef url, OSType *creatorCode) +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; + + if (NS_WARN_IF(!url) || NS_WARN_IF(!creatorCode)) + return NS_ERROR_INVALID_ARG; + + nsAutoreleasePool localPool; + + NSString *resolvedPath = [[(NSURL*)url path] stringByResolvingSymlinksInPath]; + if (!resolvedPath) { + return NS_ERROR_FAILURE; + } + + NSDictionary* dict = [[NSFileManager defaultManager] attributesOfItemAtPath:resolvedPath error:nil]; + if (!dict) { + return NS_ERROR_FAILURE; + } + + NSNumber* creatorNum = (NSNumber*)[dict objectForKey:NSFileHFSCreatorCode]; + if (!creatorNum) { + return NS_ERROR_FAILURE; + } + + *creatorCode = [creatorNum unsignedLongValue]; + return NS_OK; + + NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; +} + +nsresult SetFileCreatorCode(CFURLRef url, OSType creatorCode) +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; + + if (NS_WARN_IF(!url)) + return NS_ERROR_INVALID_ARG; + + NSAutoreleasePool* ap = [[NSAutoreleasePool alloc] init]; + NSDictionary* dict = [NSDictionary dictionaryWithObject:[NSNumber numberWithUnsignedLong:creatorCode] forKey:NSFileHFSCreatorCode]; + BOOL success = [[NSFileManager defaultManager] setAttributes:dict ofItemAtPath:[(NSURL*)url path] error:nil]; + [ap release]; + return (success ? NS_OK : NS_ERROR_FAILURE); + + NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; +} + +nsresult GetFileTypeCode(CFURLRef url, OSType *typeCode) +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; + + if (NS_WARN_IF(!url) || NS_WARN_IF(!typeCode)) + return NS_ERROR_INVALID_ARG; + + nsAutoreleasePool localPool; + + NSString *resolvedPath = [[(NSURL*)url path] stringByResolvingSymlinksInPath]; + if (!resolvedPath) { + return NS_ERROR_FAILURE; + } + + NSDictionary* dict = [[NSFileManager defaultManager] attributesOfItemAtPath:resolvedPath error:nil]; + if (!dict) { + return NS_ERROR_FAILURE; + } + + NSNumber* typeNum = (NSNumber*)[dict objectForKey:NSFileHFSTypeCode]; + if (!typeNum) { + return NS_ERROR_FAILURE; + } + + *typeCode = [typeNum unsignedLongValue]; + return NS_OK; + + NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; +} + +nsresult SetFileTypeCode(CFURLRef url, OSType typeCode) +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; + + if (NS_WARN_IF(!url)) + return NS_ERROR_INVALID_ARG; + + NSAutoreleasePool* ap = [[NSAutoreleasePool alloc] init]; + NSDictionary* dict = [NSDictionary dictionaryWithObject:[NSNumber numberWithUnsignedLong:typeCode] forKey:NSFileHFSTypeCode]; + BOOL success = [[NSFileManager defaultManager] setAttributes:dict ofItemAtPath:[(NSURL*)url path] error:nil]; + [ap release]; + return (success ? NS_OK : NS_ERROR_FAILURE); + + NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; +} + +void AddOriginMetadataToFile(const CFStringRef filePath, + const CFURLRef sourceURL, + const CFURLRef referrerURL) { + typedef OSStatus (*MDItemSetAttribute_type)(MDItemRef, CFStringRef, CFTypeRef); + static MDItemSetAttribute_type mdItemSetAttributeFunc = NULL; + + static bool did_symbol_lookup = false; + if (!did_symbol_lookup) { + did_symbol_lookup = true; + + CFBundleRef metadata_bundle = ::CFBundleGetBundleWithIdentifier(CFSTR("com.apple.Metadata")); + if (!metadata_bundle) { + return; + } + + mdItemSetAttributeFunc = (MDItemSetAttribute_type) + ::CFBundleGetFunctionPointerForName(metadata_bundle, CFSTR("MDItemSetAttribute")); + } + if (!mdItemSetAttributeFunc) { + return; + } + + MDItemRef mdItem = ::MDItemCreate(NULL, filePath); + if (!mdItem) { + return; + } + + CFMutableArrayRef list = ::CFArrayCreateMutable(kCFAllocatorDefault, 2, NULL); + if (!list) { + ::CFRelease(mdItem); + return; + } + + // The first item in the list is the source URL of the downloaded file. + if (sourceURL) { + ::CFArrayAppendValue(list, ::CFURLGetString(sourceURL)); + } + + // If the referrer is known, store that in the second position. + if (referrerURL) { + ::CFArrayAppendValue(list, ::CFURLGetString(referrerURL)); + } + + mdItemSetAttributeFunc(mdItem, kMDItemWhereFroms, list); + + ::CFRelease(list); + ::CFRelease(mdItem); +} + +void AddQuarantineMetadataToFile(const CFStringRef filePath, + const CFURLRef sourceURL, + const CFURLRef referrerURL, + const bool isFromWeb) { + CFURLRef fileURL = ::CFURLCreateWithFileSystemPath(kCFAllocatorDefault, + filePath, + kCFURLPOSIXPathStyle, + false); + + // The properties key changed in 10.10: + CFStringRef quarantinePropKey; + if (nsCocoaFeatures::OnYosemiteOrLater()) { + quarantinePropKey = kCFURLQuarantinePropertiesKey; + } else { + quarantinePropKey = kLSItemQuarantineProperties; + } + CFDictionaryRef quarantineProps = NULL; + Boolean success = ::CFURLCopyResourcePropertyForKey(fileURL, + quarantinePropKey, + &quarantineProps, + NULL); + + // If there aren't any quarantine properties then the user probably + // set up an exclusion and we don't need to add metadata. + if (!success || !quarantineProps) { + ::CFRelease(fileURL); + return; + } + + // We don't know what to do if the props aren't a dictionary. + if (::CFGetTypeID(quarantineProps) != ::CFDictionaryGetTypeID()) { + ::CFRelease(fileURL); + ::CFRelease(quarantineProps); + return; + } + + // Make a mutable copy of the properties. + CFMutableDictionaryRef mutQuarantineProps = + ::CFDictionaryCreateMutableCopy(kCFAllocatorDefault, 0, (CFDictionaryRef)quarantineProps); + ::CFRelease(quarantineProps); + + // Add metadata that the OS couldn't infer. + + if (!::CFDictionaryGetValue(mutQuarantineProps, kLSQuarantineTypeKey)) { + CFStringRef type = isFromWeb ? kLSQuarantineTypeWebDownload : kLSQuarantineTypeOtherDownload; + ::CFDictionarySetValue(mutQuarantineProps, kLSQuarantineTypeKey, type); + } + + if (!::CFDictionaryGetValue(mutQuarantineProps, kLSQuarantineOriginURLKey) && referrerURL) { + ::CFDictionarySetValue(mutQuarantineProps, kLSQuarantineOriginURLKey, referrerURL); + } + + if (!::CFDictionaryGetValue(mutQuarantineProps, kLSQuarantineDataURLKey) && sourceURL) { + ::CFDictionarySetValue(mutQuarantineProps, kLSQuarantineDataURLKey, sourceURL); + } + + // Set quarantine properties on file. + ::CFURLSetResourcePropertyForKey(fileURL, + quarantinePropKey, + mutQuarantineProps, + NULL); + + ::CFRelease(fileURL); + ::CFRelease(mutQuarantineProps); +} + +CFURLRef GetTemporaryFolderCFURLRef() +{ + NSString* tempDir = ::NSTemporaryDirectory(); + return tempDir == nil ? NULL : (CFURLRef)[NSURL fileURLWithPath:tempDir + isDirectory:YES]; +} + +} // namespace CocoaFileUtils diff --git a/xpcom/io/FileUtilsWin.cpp b/xpcom/io/FileUtilsWin.cpp new file mode 100644 index 000000000..732c074f7 --- /dev/null +++ b/xpcom/io/FileUtilsWin.cpp @@ -0,0 +1,75 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "FileUtilsWin.h" + +#include +#include + +#include "mozilla/Unused.h" +#include "nsWindowsHelpers.h" +#include "GeckoProfiler.h" + +namespace { + +// Scoped type used by HandleToFilename +struct ScopedMappedViewTraits +{ + typedef void* type; + static void* empty() + { + return nullptr; + } + static void release(void* aPtr) + { + if (aPtr) { + mozilla::Unused << UnmapViewOfFile(aPtr); + } + } +}; +typedef mozilla::Scoped ScopedMappedView; + +} // namespace + +namespace mozilla { + +bool +HandleToFilename(HANDLE aHandle, const LARGE_INTEGER& aOffset, + nsAString& aFilename) +{ + PROFILER_LABEL_FUNC(js::ProfileEntry::Category::NETWORK); + + aFilename.Truncate(); + // This implementation is nice because it uses fully documented APIs that + // are available on all Windows versions that we support. + nsAutoHandle fileMapping(CreateFileMapping(aHandle, nullptr, PAGE_READONLY, + 0, 1, nullptr)); + if (!fileMapping) { + return false; + } + ScopedMappedView view(MapViewOfFile(fileMapping, FILE_MAP_READ, + aOffset.HighPart, aOffset.LowPart, 1)); + if (!view) { + return false; + } + nsAutoString mappedFilename; + DWORD len = 0; + SetLastError(ERROR_SUCCESS); + do { + mappedFilename.SetLength(mappedFilename.Length() + MAX_PATH); + len = GetMappedFileNameW(GetCurrentProcess(), view, + wwc(mappedFilename.BeginWriting()), + mappedFilename.Length()); + } while (!len && GetLastError() == ERROR_INSUFFICIENT_BUFFER); + if (!len) { + return false; + } + mappedFilename.Truncate(len); + return NtPathToDosPath(mappedFilename, aFilename); +} + +} // namespace mozilla + diff --git a/xpcom/io/FileUtilsWin.h b/xpcom/io/FileUtilsWin.h new file mode 100644 index 000000000..e32e9fd6e --- /dev/null +++ b/xpcom/io/FileUtilsWin.h @@ -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/. */ + +#ifndef mozilla_FileUtilsWin_h +#define mozilla_FileUtilsWin_h + +#include + +#include "mozilla/Scoped.h" +#include "nsStringGlue.h" + +namespace mozilla { + +inline bool +EnsureLongPath(nsAString& aDosPath) +{ + uint32_t aDosPathOriginalLen = aDosPath.Length(); + auto inputPath = PromiseFlatString(aDosPath); + // Try to get the long path, or else get the required length of the long path + DWORD longPathLen = GetLongPathNameW(inputPath.get(), + reinterpret_cast(aDosPath.BeginWriting()), + aDosPathOriginalLen); + if (longPathLen == 0) { + return false; + } + aDosPath.SetLength(longPathLen); + if (longPathLen <= aDosPathOriginalLen) { + // Our string happened to be long enough for the first call to succeed. + return true; + } + // Now we have a large enough buffer, get the actual string + longPathLen = GetLongPathNameW(inputPath.get(), + reinterpret_cast(aDosPath.BeginWriting()), aDosPath.Length()); + if (longPathLen == 0) { + return false; + } + // This success check should always be less-than because longPathLen excludes + // the null terminator on success, but includes it in the first call that + // returned the required size. + if (longPathLen < aDosPath.Length()) { + aDosPath.SetLength(longPathLen); + return true; + } + // We shouldn't reach this, but if we do then it's a failure! + return false; +} + +inline bool +NtPathToDosPath(const nsAString& aNtPath, nsAString& aDosPath) +{ + aDosPath.Truncate(); + if (aNtPath.IsEmpty()) { + return true; + } + NS_NAMED_LITERAL_STRING(symLinkPrefix, "\\??\\"); + uint32_t ntPathLen = aNtPath.Length(); + uint32_t symLinkPrefixLen = symLinkPrefix.Length(); + if (ntPathLen >= 6 && aNtPath.CharAt(5) == L':' && + ntPathLen >= symLinkPrefixLen && + Substring(aNtPath, 0, symLinkPrefixLen).Equals(symLinkPrefix)) { + // Symbolic link for DOS device. Just strip off the prefix. + aDosPath = aNtPath; + aDosPath.Cut(0, 4); + return true; + } + nsAutoString logicalDrives; + DWORD len = 0; + while (true) { + len = GetLogicalDriveStringsW( + len, reinterpret_cast(logicalDrives.BeginWriting())); + if (!len) { + return false; + } else if (len > logicalDrives.Length()) { + logicalDrives.SetLength(len); + } else { + break; + } + } + const char16_t* cur = logicalDrives.BeginReading(); + const char16_t* end = logicalDrives.EndReading(); + nsString targetPath; + targetPath.SetLength(MAX_PATH); + wchar_t driveTemplate[] = L" :"; + do { + // Unfortunately QueryDosDevice doesn't support the idiom for querying the + // output buffer size, so it may require retries. + driveTemplate[0] = *cur; + DWORD targetPathLen = 0; + SetLastError(ERROR_SUCCESS); + while (true) { + targetPathLen = QueryDosDeviceW(driveTemplate, + reinterpret_cast(targetPath.BeginWriting()), + targetPath.Length()); + if (targetPathLen || GetLastError() != ERROR_INSUFFICIENT_BUFFER) { + break; + } + targetPath.SetLength(targetPath.Length() * 2); + } + if (targetPathLen) { + // Need to use wcslen here because targetPath contains embedded NULL chars + size_t firstTargetPathLen = wcslen(targetPath.get()); + const char16_t* pathComponent = aNtPath.BeginReading() + + firstTargetPathLen; + bool found = _wcsnicmp(char16ptr_t(aNtPath.BeginReading()), targetPath.get(), + firstTargetPathLen) == 0 && + *pathComponent == L'\\'; + if (found) { + aDosPath = driveTemplate; + aDosPath += pathComponent; + return EnsureLongPath(aDosPath); + } + } + // Advance to the next NUL character in logicalDrives + while (*cur++); + } while (cur != end); + // Try to handle UNC paths. NB: This must happen after we've checked drive + // mappings in case a UNC path is mapped to a drive! + NS_NAMED_LITERAL_STRING(uncPrefix, "\\\\"); + NS_NAMED_LITERAL_STRING(deviceMupPrefix, "\\Device\\Mup\\"); + if (StringBeginsWith(aNtPath, deviceMupPrefix)) { + aDosPath = uncPrefix; + aDosPath += Substring(aNtPath, deviceMupPrefix.Length()); + return true; + } + NS_NAMED_LITERAL_STRING(deviceLanmanRedirectorPrefix, + "\\Device\\LanmanRedirector\\"); + if (StringBeginsWith(aNtPath, deviceLanmanRedirectorPrefix)) { + aDosPath = uncPrefix; + aDosPath += Substring(aNtPath, deviceLanmanRedirectorPrefix.Length()); + return true; + } + return false; +} + +bool +HandleToFilename(HANDLE aHandle, const LARGE_INTEGER& aOffset, + nsAString& aFilename); + +} // namespace mozilla + +#endif // mozilla_FileUtilsWin_h diff --git a/xpcom/io/SlicedInputStream.cpp b/xpcom/io/SlicedInputStream.cpp new file mode 100644 index 000000000..7d5fc2b05 --- /dev/null +++ b/xpcom/io/SlicedInputStream.cpp @@ -0,0 +1,209 @@ +/* -*- 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 "SlicedInputStream.h" +#include "nsISeekableStream.h" +#include "nsStreamUtils.h" + +NS_IMPL_ISUPPORTS(SlicedInputStream, nsIInputStream, + nsICloneableInputStream, nsIAsyncInputStream) + +SlicedInputStream::SlicedInputStream(nsIInputStream* aInputStream, + uint64_t aStart, uint64_t aLength) + : mInputStream(aInputStream) + , mStart(aStart) + , mLength(aLength) + , mCurPos(0) + , mClosed(false) +{ + MOZ_ASSERT(aInputStream); +} + +SlicedInputStream::~SlicedInputStream() +{} + +NS_IMETHODIMP +SlicedInputStream::Close() +{ + mClosed = true; + return NS_OK; +} + +// nsIInputStream interface + +NS_IMETHODIMP +SlicedInputStream::Available(uint64_t* aLength) +{ + if (mClosed) { + return NS_BASE_STREAM_CLOSED; + } + + nsresult rv = mInputStream->Available(aLength); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // Let's remove extra length from the end. + if (*aLength + mCurPos > mStart + mLength) { + *aLength -= XPCOM_MIN(*aLength, (*aLength + mCurPos) - (mStart + mLength)); + } + + // Let's remove extra length from the begin. + if (mCurPos < mStart) { + *aLength -= XPCOM_MIN(*aLength, mStart - mCurPos); + } + + return NS_OK; +} + +NS_IMETHODIMP +SlicedInputStream::Read(char* aBuffer, uint32_t aCount, uint32_t* aReadCount) +{ + return ReadSegments(NS_CopySegmentToBuffer, aBuffer, aCount, aReadCount); +} + +NS_IMETHODIMP +SlicedInputStream::ReadSegments(nsWriteSegmentFun aWriter, void* aClosure, + uint32_t aCount, uint32_t *aResult) +{ + uint32_t result; + + if (!aResult) { + aResult = &result; + } + + *aResult = 0; + + if (mClosed) { + return NS_BASE_STREAM_CLOSED; + } + + if (mCurPos < mStart) { + nsCOMPtr seekableStream = + do_QueryInterface(mInputStream); + if (seekableStream) { + nsresult rv = seekableStream->Seek(nsISeekableStream::NS_SEEK_SET, + mStart); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + mCurPos = mStart; + } else { + char buf[4096]; + while (mCurPos < mStart) { + uint32_t bytesRead; + uint64_t bufCount = XPCOM_MIN(mStart - mCurPos, (uint64_t)sizeof(buf)); + nsresult rv = mInputStream->Read(buf, bufCount, &bytesRead); + if (NS_WARN_IF(NS_FAILED(rv)) || bytesRead == 0) { + return rv; + } + + mCurPos += bytesRead; + } + } + } + + // Let's reduce aCount in case it's too big. + if (mCurPos + aCount > mStart + mLength) { + aCount = mStart + mLength - mCurPos; + } + + char buf[4096]; + while (mCurPos < mStart + mLength && *aResult < aCount) { + uint32_t bytesRead; + uint64_t bufCount = XPCOM_MIN(aCount - *aResult, (uint32_t)sizeof(buf)); + nsresult rv = mInputStream->Read(buf, bufCount, &bytesRead); + if (NS_WARN_IF(NS_FAILED(rv)) || bytesRead == 0) { + return rv; + } + + mCurPos += bytesRead; + + uint32_t bytesWritten = 0; + while (bytesWritten < bytesRead) { + uint32_t writerCount = 0; + rv = aWriter(this, aClosure, buf + bytesWritten, *aResult, + bytesRead - bytesWritten, &writerCount); + if (NS_FAILED(rv) || writerCount == 0) { + return NS_OK; + } + + MOZ_ASSERT(writerCount <= bytesRead - bytesWritten); + bytesWritten += writerCount; + *aResult += writerCount; + } + } + + return NS_OK; +} + +NS_IMETHODIMP +SlicedInputStream::IsNonBlocking(bool* aNonBlocking) +{ + return mInputStream->IsNonBlocking(aNonBlocking); +} + +// nsICloneableInputStream interface + +NS_IMETHODIMP +SlicedInputStream::GetCloneable(bool* aCloneable) +{ + *aCloneable = true; + return NS_OK; +} + +NS_IMETHODIMP +SlicedInputStream::Clone(nsIInputStream** aResult) +{ + nsCOMPtr clonedStream; + nsCOMPtr replacementStream; + + nsresult rv = NS_CloneInputStream(mInputStream, getter_AddRefs(clonedStream), + getter_AddRefs(replacementStream)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (replacementStream) { + mInputStream = replacementStream.forget(); + } + + nsCOMPtr sis = + new SlicedInputStream(clonedStream, mStart, mLength); + + sis.forget(aResult); + return NS_OK; +} + +// nsIAsyncInputStream interface + +NS_IMETHODIMP +SlicedInputStream::CloseWithStatus(nsresult aStatus) +{ + nsCOMPtr asyncStream = + do_QueryInterface(mInputStream); + if (!asyncStream) { + return NS_ERROR_FAILURE; + } + + return asyncStream->CloseWithStatus(aStatus); +} + +NS_IMETHODIMP +SlicedInputStream::AsyncWait(nsIInputStreamCallback* aCallback, + uint32_t aFlags, + uint32_t aRequestedCount, + nsIEventTarget* aEventTarget) +{ + nsCOMPtr asyncStream = + do_QueryInterface(mInputStream); + if (!asyncStream) { + return NS_ERROR_FAILURE; + } + + return asyncStream->AsyncWait(aCallback, aFlags, aRequestedCount, + aEventTarget); +} diff --git a/xpcom/io/SlicedInputStream.h b/xpcom/io/SlicedInputStream.h new file mode 100644 index 000000000..6c38fc39f --- /dev/null +++ b/xpcom/io/SlicedInputStream.h @@ -0,0 +1,50 @@ +/* -*- 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/. */ + +#ifndef SlicedInputStream_h +#define SlicedInputStream_h + +#include "mozilla/Attributes.h" +#include "nsCOMPtr.h" +#include "nsIAsyncInputStream.h" +#include "nsICloneableInputStream.h" + +// A wrapper for a slice of an underlying input stream. + +class SlicedInputStream final : public nsIAsyncInputStream + , public nsICloneableInputStream +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIINPUTSTREAM + NS_DECL_NSICLONEABLEINPUTSTREAM + NS_DECL_NSIASYNCINPUTSTREAM + + // Create an input stream whose data comes from a slice of aInputStream. The + // slice begins at aStart bytes beyond aInputStream's current position, and + // extends for a maximum of aLength bytes. If aInputStream contains fewer + // than aStart bytes, reading from SlicedInputStream returns no data. If + // aInputStream contains more than aStart bytes, but fewer than aStart + + // aLength bytes, reading from SlicedInputStream returns as many bytes as can + // be consumed from aInputStream after reading aLength bytes. + // + // aInputStream should not be read from after constructing a + // SlicedInputStream wrapper around it. + + SlicedInputStream(nsIInputStream* aInputStream, + uint64_t aStart, uint64_t aLength); + +private: + ~SlicedInputStream(); + + nsCOMPtr mInputStream; + uint64_t mStart; + uint64_t mLength; + uint64_t mCurPos; + + bool mClosed; +}; + +#endif // SlicedInputStream_h diff --git a/xpcom/io/SnappyCompressOutputStream.cpp b/xpcom/io/SnappyCompressOutputStream.cpp new file mode 100644 index 000000000..89b8a0808 --- /dev/null +++ b/xpcom/io/SnappyCompressOutputStream.cpp @@ -0,0 +1,256 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. 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/SnappyCompressOutputStream.h" + +#include +#include "nsStreamUtils.h" +#include "snappy/snappy.h" + +namespace mozilla { + +NS_IMPL_ISUPPORTS(SnappyCompressOutputStream, nsIOutputStream); + +// static +const size_t +SnappyCompressOutputStream::kMaxBlockSize = snappy::kBlockSize; + +SnappyCompressOutputStream::SnappyCompressOutputStream(nsIOutputStream* aBaseStream, + size_t aBlockSize) + : mBaseStream(aBaseStream) + , mBlockSize(std::min(aBlockSize, kMaxBlockSize)) + , mNextByte(0) + , mCompressedBufferLength(0) + , mStreamIdentifierWritten(false) +{ + MOZ_ASSERT(mBlockSize > 0); + + // This implementation only supports sync base streams. Verify this in debug + // builds. Note, this can be simpler than the check in + // SnappyUncompressInputStream because we don't have to deal with the + // nsStringInputStream oddness of being non-blocking and sync. +#ifdef DEBUG + bool baseNonBlocking; + nsresult rv = mBaseStream->IsNonBlocking(&baseNonBlocking); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + MOZ_ASSERT(!baseNonBlocking); +#endif +} + +size_t +SnappyCompressOutputStream::BlockSize() const +{ + return mBlockSize; +} + +NS_IMETHODIMP +SnappyCompressOutputStream::Close() +{ + if (!mBaseStream) { + return NS_OK; + } + + nsresult rv = Flush(); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + mBaseStream->Close(); + mBaseStream = nullptr; + + mBuffer = nullptr; + mCompressedBuffer = nullptr; + + return NS_OK; +} + +NS_IMETHODIMP +SnappyCompressOutputStream::Flush() +{ + if (!mBaseStream) { + return NS_BASE_STREAM_CLOSED; + } + + nsresult rv = FlushToBaseStream(); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + mBaseStream->Flush(); + + return NS_OK; +} + +NS_IMETHODIMP +SnappyCompressOutputStream::Write(const char* aBuf, uint32_t aCount, + uint32_t* aResultOut) +{ + return WriteSegments(NS_CopySegmentToBuffer, const_cast(aBuf), aCount, + aResultOut); +} + +NS_IMETHODIMP +SnappyCompressOutputStream::WriteFrom(nsIInputStream*, uint32_t, uint32_t*) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +SnappyCompressOutputStream::WriteSegments(nsReadSegmentFun aReader, + void* aClosure, + uint32_t aCount, + uint32_t* aBytesWrittenOut) +{ + *aBytesWrittenOut = 0; + + if (!mBaseStream) { + return NS_BASE_STREAM_CLOSED; + } + + if (!mBuffer) { + mBuffer.reset(new (fallible) char[mBlockSize]); + if (NS_WARN_IF(!mBuffer)) { + return NS_ERROR_OUT_OF_MEMORY; + } + } + + while (aCount > 0) { + // Determine how much space is left in our flat, uncompressed buffer. + MOZ_ASSERT(mNextByte <= mBlockSize); + uint32_t remaining = mBlockSize - mNextByte; + + // If it is full, then compress and flush the data to the base stream. + if (remaining == 0) { + nsresult rv = FlushToBaseStream(); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + // Now the entire buffer should be available for copying. + MOZ_ASSERT(!mNextByte); + remaining = mBlockSize; + } + + uint32_t numToRead = std::min(remaining, aCount); + uint32_t numRead = 0; + + nsresult rv = aReader(this, aClosure, &mBuffer[mNextByte], + *aBytesWrittenOut, numToRead, &numRead); + + // As defined in nsIOutputStream.idl, do not pass reader func errors. + if (NS_FAILED(rv)) { + return NS_OK; + } + + // End-of-file + if (numRead == 0) { + return NS_OK; + } + + mNextByte += numRead; + *aBytesWrittenOut += numRead; + aCount -= numRead; + } + + return NS_OK; +} + +NS_IMETHODIMP +SnappyCompressOutputStream::IsNonBlocking(bool* aNonBlockingOut) +{ + *aNonBlockingOut = false; + return NS_OK; +} + +SnappyCompressOutputStream::~SnappyCompressOutputStream() +{ + Close(); +} + +nsresult +SnappyCompressOutputStream::FlushToBaseStream() +{ + MOZ_ASSERT(mBaseStream); + + // Lazily create the compressed buffer on our first flush. This + // allows us to report OOM during stream operation. This buffer + // will then get re-used until the stream is closed. + if (!mCompressedBuffer) { + mCompressedBufferLength = MaxCompressedBufferLength(mBlockSize); + mCompressedBuffer.reset(new (fallible) char[mCompressedBufferLength]); + if (NS_WARN_IF(!mCompressedBuffer)) { + return NS_ERROR_OUT_OF_MEMORY; + } + } + + // The first chunk must be a StreamIdentifier chunk. Write it out + // if we have not done so already. + nsresult rv = MaybeFlushStreamIdentifier(); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + // Compress the data to our internal compressed buffer. + size_t compressedLength; + rv = WriteCompressedData(mCompressedBuffer.get(), mCompressedBufferLength, + mBuffer.get(), mNextByte, &compressedLength); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + MOZ_ASSERT(compressedLength > 0); + + mNextByte = 0; + + // Write the compressed buffer out to the base stream. + uint32_t numWritten = 0; + rv = WriteAll(mCompressedBuffer.get(), compressedLength, &numWritten); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + MOZ_ASSERT(compressedLength == numWritten); + + return NS_OK; +} + +nsresult +SnappyCompressOutputStream::MaybeFlushStreamIdentifier() +{ + MOZ_ASSERT(mCompressedBuffer); + + if (mStreamIdentifierWritten) { + return NS_OK; + } + + // Build the StreamIdentifier in our compressed buffer. + size_t compressedLength; + nsresult rv = WriteStreamIdentifier(mCompressedBuffer.get(), + mCompressedBufferLength, + &compressedLength); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + // Write the compressed buffer out to the base stream. + uint32_t numWritten = 0; + rv = WriteAll(mCompressedBuffer.get(), compressedLength, &numWritten); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + MOZ_ASSERT(compressedLength == numWritten); + + mStreamIdentifierWritten = true; + + return NS_OK; +} + +nsresult +SnappyCompressOutputStream::WriteAll(const char* aBuf, uint32_t aCount, + uint32_t* aBytesWrittenOut) +{ + *aBytesWrittenOut = 0; + + if (!mBaseStream) { + return NS_BASE_STREAM_CLOSED; + } + + uint32_t offset = 0; + while (aCount > 0) { + uint32_t numWritten = 0; + nsresult rv = mBaseStream->Write(aBuf + offset, aCount, &numWritten); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + offset += numWritten; + aCount -= numWritten; + *aBytesWrittenOut += numWritten; + } + + return NS_OK; +} + +} // namespace mozilla diff --git a/xpcom/io/SnappyCompressOutputStream.h b/xpcom/io/SnappyCompressOutputStream.h new file mode 100644 index 000000000..36c47e66e --- /dev/null +++ b/xpcom/io/SnappyCompressOutputStream.h @@ -0,0 +1,69 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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_SnappyCompressOutputStream_h__ +#define mozilla_SnappyCompressOutputStream_h__ + +#include "mozilla/Attributes.h" +#include "mozilla/UniquePtr.h" +#include "nsCOMPtr.h" +#include "nsIOutputStream.h" +#include "nsISupportsImpl.h" +#include "SnappyFrameUtils.h" + +namespace mozilla { + +class SnappyCompressOutputStream final : public nsIOutputStream + , protected detail::SnappyFrameUtils +{ +public: + // Maximum compression block size. + static const size_t kMaxBlockSize; + + // Construct a new blocking output stream to compress data to + // the given base stream. The base stream must also be blocking. + // The compression block size may optionally be set to a value + // up to kMaxBlockSize. + explicit SnappyCompressOutputStream(nsIOutputStream* aBaseStream, + size_t aBlockSize = kMaxBlockSize); + + // The compression block size. To optimize stream performance + // try to write to the stream in segments at least this size. + size_t BlockSize() const; + +private: + virtual ~SnappyCompressOutputStream(); + + nsresult FlushToBaseStream(); + nsresult MaybeFlushStreamIdentifier(); + nsresult WriteAll(const char* aBuf, uint32_t aCount, + uint32_t* aBytesWrittenOut); + + nsCOMPtr mBaseStream; + const size_t mBlockSize; + + // Buffer holding copied uncompressed data. This must be copied here + // so that the compression can be performed on a single flat buffer. + mozilla::UniquePtr mBuffer; + + // The next byte in the uncompressed data to copy incoming data to. + size_t mNextByte; + + // Buffer holding the resulting compressed data. + mozilla::UniquePtr mCompressedBuffer; + size_t mCompressedBufferLength; + + // The first thing written to the stream must be a stream identifier. + bool mStreamIdentifierWritten; + +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIOUTPUTSTREAM +}; + +} // namespace mozilla + +#endif // mozilla_SnappyCompressOutputStream_h__ diff --git a/xpcom/io/SnappyFrameUtils.cpp b/xpcom/io/SnappyFrameUtils.cpp new file mode 100644 index 000000000..97883a362 --- /dev/null +++ b/xpcom/io/SnappyFrameUtils.cpp @@ -0,0 +1,258 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. 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/SnappyFrameUtils.h" + +#include "crc32c.h" +#include "mozilla/EndianUtils.h" +#include "nsDebug.h" +#include "snappy/snappy.h" + +namespace { + +using mozilla::detail::SnappyFrameUtils; +using mozilla::NativeEndian; + +SnappyFrameUtils::ChunkType ReadChunkType(uint8_t aByte) +{ + if (aByte == 0xff) { + return SnappyFrameUtils::StreamIdentifier; + } else if (aByte == 0x00) { + return SnappyFrameUtils::CompressedData; + } else if (aByte == 0x01) { + return SnappyFrameUtils::UncompressedData; + } else if (aByte == 0xfe) { + return SnappyFrameUtils::Padding; + } + + return SnappyFrameUtils::Reserved; +} + +void WriteChunkType(char* aDest, SnappyFrameUtils::ChunkType aType) +{ + unsigned char* dest = reinterpret_cast(aDest); + if (aType == SnappyFrameUtils::StreamIdentifier) { + *dest = 0xff; + } else if (aType == SnappyFrameUtils::CompressedData) { + *dest = 0x00; + } else if (aType == SnappyFrameUtils::UncompressedData) { + *dest = 0x01; + } else if (aType == SnappyFrameUtils::Padding) { + *dest = 0xfe; + } else { + *dest = 0x02; + } +} + +void WriteUInt24(char* aBuf, uint32_t aVal) +{ + MOZ_ASSERT(!(aVal & 0xff000000)); + uint32_t tmp = NativeEndian::swapToLittleEndian(aVal); + memcpy(aBuf, &tmp, 3); +} + +uint32_t ReadUInt24(const char* aBuf) +{ + uint32_t val = 0; + memcpy(&val, aBuf, 3); + return NativeEndian::swapFromLittleEndian(val); +} + +// This mask is explicitly defined in the snappy framing_format.txt file. +uint32_t MaskChecksum(uint32_t aValue) +{ + return ((aValue >> 15) | (aValue << 17)) + 0xa282ead8; +} + +} // namespace + +namespace mozilla { +namespace detail { + +using mozilla::LittleEndian; + +// static +nsresult +SnappyFrameUtils::WriteStreamIdentifier(char* aDest, size_t aDestLength, + size_t* aBytesWrittenOut) +{ + if (NS_WARN_IF(aDestLength < (kHeaderLength + kStreamIdentifierDataLength))) { + return NS_ERROR_NOT_AVAILABLE; + } + + WriteChunkType(aDest, StreamIdentifier); + aDest[1] = 0x06; // Data length + aDest[2] = 0x00; + aDest[3] = 0x00; + aDest[4] = 0x73; // "sNaPpY" + aDest[5] = 0x4e; + aDest[6] = 0x61; + aDest[7] = 0x50; + aDest[8] = 0x70; + aDest[9] = 0x59; + + static_assert(kHeaderLength + kStreamIdentifierDataLength == 10, + "StreamIdentifier chunk should be exactly 10 bytes long"); + *aBytesWrittenOut = kHeaderLength + kStreamIdentifierDataLength; + + return NS_OK; +} + +// static +nsresult +SnappyFrameUtils::WriteCompressedData(char* aDest, size_t aDestLength, + const char* aData, size_t aDataLength, + size_t* aBytesWrittenOut) +{ + *aBytesWrittenOut = 0; + + size_t neededLength = MaxCompressedBufferLength(aDataLength); + if (NS_WARN_IF(aDestLength < neededLength)) { + return NS_ERROR_NOT_AVAILABLE; + } + + size_t offset = 0; + + WriteChunkType(aDest, CompressedData); + offset += kChunkTypeLength; + + // Skip length for now and write it out after we know the compressed length. + size_t lengthOffset = offset; + offset += kChunkLengthLength; + + uint32_t crc = ComputeCrc32c(~0, reinterpret_cast(aData), + aDataLength); + uint32_t maskedCrc = MaskChecksum(crc); + LittleEndian::writeUint32(aDest + offset, maskedCrc); + offset += kCRCLength; + + size_t compressedLength; + snappy::RawCompress(aData, aDataLength, aDest + offset, &compressedLength); + + // Go back and write the data length. + size_t dataLength = compressedLength + kCRCLength; + WriteUInt24(aDest + lengthOffset, dataLength); + + *aBytesWrittenOut = kHeaderLength + dataLength; + + return NS_OK; +} + +// static +nsresult +SnappyFrameUtils::ParseHeader(const char* aSource, size_t aSourceLength, + ChunkType* aTypeOut, size_t* aDataLengthOut) +{ + if (NS_WARN_IF(aSourceLength < kHeaderLength)) { + return NS_ERROR_NOT_AVAILABLE; + } + + *aTypeOut = ReadChunkType(aSource[0]); + *aDataLengthOut = ReadUInt24(aSource + kChunkTypeLength); + + return NS_OK; +} + +// static +nsresult +SnappyFrameUtils::ParseData(char* aDest, size_t aDestLength, + ChunkType aType, const char* aData, + size_t aDataLength, + size_t* aBytesWrittenOut, size_t* aBytesReadOut) +{ + switch(aType) { + case StreamIdentifier: + return ParseStreamIdentifier(aDest, aDestLength, aData, aDataLength, + aBytesWrittenOut, aBytesReadOut); + + case CompressedData: + return ParseCompressedData(aDest, aDestLength, aData, aDataLength, + aBytesWrittenOut, aBytesReadOut); + + // TODO: support other snappy chunk types + default: + MOZ_ASSERT_UNREACHABLE("Unsupported snappy framing chunk type."); + return NS_ERROR_NOT_IMPLEMENTED; + } +} + +// static +nsresult +SnappyFrameUtils::ParseStreamIdentifier(char*, size_t, + const char* aData, size_t aDataLength, + size_t* aBytesWrittenOut, + size_t* aBytesReadOut) +{ + *aBytesWrittenOut = 0; + *aBytesReadOut = 0; + if (NS_WARN_IF(aDataLength != kStreamIdentifierDataLength || + aData[0] != 0x73 || + aData[1] != 0x4e || + aData[2] != 0x61 || + aData[3] != 0x50 || + aData[4] != 0x70 || + aData[5] != 0x59)) { + return NS_ERROR_CORRUPTED_CONTENT; + } + *aBytesReadOut = aDataLength; + return NS_OK; +} + +// static +nsresult +SnappyFrameUtils::ParseCompressedData(char* aDest, size_t aDestLength, + const char* aData, size_t aDataLength, + size_t* aBytesWrittenOut, + size_t* aBytesReadOut) +{ + *aBytesWrittenOut = 0; + *aBytesReadOut = 0; + size_t offset = 0; + + uint32_t readCrc = LittleEndian::readUint32(aData + offset); + offset += kCRCLength; + + size_t uncompressedLength; + if (NS_WARN_IF(!snappy::GetUncompressedLength(aData + offset, + aDataLength - offset, + &uncompressedLength))) { + return NS_ERROR_CORRUPTED_CONTENT; + } + + if (NS_WARN_IF(aDestLength < uncompressedLength)) { + return NS_ERROR_NOT_AVAILABLE; + } + + if (NS_WARN_IF(!snappy::RawUncompress(aData + offset, aDataLength - offset, + aDest))) { + return NS_ERROR_CORRUPTED_CONTENT; + } + + uint32_t crc = ComputeCrc32c(~0, reinterpret_cast(aDest), + uncompressedLength); + uint32_t maskedCrc = MaskChecksum(crc); + if (NS_WARN_IF(readCrc != maskedCrc)) { + return NS_ERROR_CORRUPTED_CONTENT; + } + + *aBytesWrittenOut = uncompressedLength; + *aBytesReadOut = aDataLength; + + return NS_OK; +} + +// static +size_t +SnappyFrameUtils::MaxCompressedBufferLength(size_t aSourceLength) +{ + size_t neededLength = kHeaderLength; + neededLength += kCRCLength; + neededLength += snappy::MaxCompressedLength(aSourceLength); + return neededLength; +} + +} // namespace detail +} // namespace mozilla diff --git a/xpcom/io/SnappyFrameUtils.h b/xpcom/io/SnappyFrameUtils.h new file mode 100644 index 000000000..41479b14d --- /dev/null +++ b/xpcom/io/SnappyFrameUtils.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/. */ + +#ifndef mozilla_SnappyFrameUtils_h__ +#define mozilla_SnappyFrameUtils_h__ + +#include "mozilla/Attributes.h" +#include "nsError.h" + +namespace mozilla { +namespace detail { + +// +// Utility class providing primitives necessary to build streams based +// on the snappy compressor. This essentially abstracts the framing format +// defined in: +// +// other-licences/snappy/src/framing_format.txt +// +// NOTE: Currently only the StreamIdentifier and CompressedData chunks are +// supported. +// +class SnappyFrameUtils +{ +public: + enum ChunkType + { + Unknown, + StreamIdentifier, + CompressedData, + UncompressedData, + Padding, + Reserved, + ChunkTypeCount + }; + + static const size_t kChunkTypeLength = 1; + static const size_t kChunkLengthLength = 3; + static const size_t kHeaderLength = kChunkTypeLength + kChunkLengthLength; + static const size_t kStreamIdentifierDataLength = 6; + static const size_t kCRCLength = 4; + + static nsresult + WriteStreamIdentifier(char* aDest, size_t aDestLength, + size_t* aBytesWrittenOut); + + static nsresult + WriteCompressedData(char* aDest, size_t aDestLength, + const char* aData, size_t aDataLength, + size_t* aBytesWrittenOut); + + static nsresult + ParseHeader(const char* aSource, size_t aSourceLength, ChunkType* aTypeOut, + size_t* aDataLengthOut); + + static nsresult + ParseData(char* aDest, size_t aDestLength, + ChunkType aType, const char* aData, size_t aDataLength, + size_t* aBytesWrittenOut, size_t* aBytesReadOut); + + static nsresult + ParseStreamIdentifier(char* aDest, size_t aDestLength, + const char* aData, size_t aDataLength, + size_t* aBytesWrittenOut, size_t* aBytesReadOut); + + static nsresult + ParseCompressedData(char* aDest, size_t aDestLength, + const char* aData, size_t aDataLength, + size_t* aBytesWrittenOut, size_t* aBytesReadOut); + + static size_t + MaxCompressedBufferLength(size_t aSourceLength); + +protected: + SnappyFrameUtils() { } + virtual ~SnappyFrameUtils() { } +}; + +} // namespace detail +} // namespace mozilla + +#endif // mozilla_SnappyFrameUtils_h__ diff --git a/xpcom/io/SnappyUncompressInputStream.cpp b/xpcom/io/SnappyUncompressInputStream.cpp new file mode 100644 index 000000000..e0a1b6f4c --- /dev/null +++ b/xpcom/io/SnappyUncompressInputStream.cpp @@ -0,0 +1,362 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. 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/SnappyUncompressInputStream.h" + +#include +#include "nsIAsyncInputStream.h" +#include "nsStreamUtils.h" +#include "snappy/snappy.h" + +namespace mozilla { + +NS_IMPL_ISUPPORTS(SnappyUncompressInputStream, + nsIInputStream); + +// Putting kCompressedBufferLength inside a function avoids a static +// constructor. +static size_t CompressedBufferLength() +{ + static size_t kCompressedBufferLength = + detail::SnappyFrameUtils::MaxCompressedBufferLength(snappy::kBlockSize); + + MOZ_ASSERT(kCompressedBufferLength > 0); + return kCompressedBufferLength; +} + +SnappyUncompressInputStream::SnappyUncompressInputStream(nsIInputStream* aBaseStream) + : mBaseStream(aBaseStream) + , mUncompressedBytes(0) + , mNextByte(0) + , mNextChunkType(Unknown) + , mNextChunkDataLength(0) + , mNeedFirstStreamIdentifier(true) +{ + // This implementation only supports sync base streams. Verify this in debug + // builds. Note, this is a bit complicated because the streams we support + // advertise different capabilities: + // - nsFileInputStream - blocking and sync + // - nsStringInputStream - non-blocking and sync + // - nsPipeInputStream - can be blocking, but provides async interface +#ifdef DEBUG + bool baseNonBlocking; + nsresult rv = mBaseStream->IsNonBlocking(&baseNonBlocking); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + if (baseNonBlocking) { + nsCOMPtr async = do_QueryInterface(mBaseStream); + MOZ_ASSERT(!async); + } +#endif +} + +NS_IMETHODIMP +SnappyUncompressInputStream::Close() +{ + if (!mBaseStream) { + return NS_OK; + } + + mBaseStream->Close(); + mBaseStream = nullptr; + + mUncompressedBuffer = nullptr; + mCompressedBuffer = nullptr; + + return NS_OK; +} + +NS_IMETHODIMP +SnappyUncompressInputStream::Available(uint64_t* aLengthOut) +{ + if (!mBaseStream) { + return NS_BASE_STREAM_CLOSED; + } + + // If we have uncompressed bytes, then we are done. + *aLengthOut = UncompressedLength(); + if (*aLengthOut > 0) { + return NS_OK; + } + + // Otherwise, attempt to uncompress bytes until we get something or the + // underlying stream is drained. We loop here because some chunks can + // be StreamIdentifiers, padding, etc with no data. + uint32_t bytesRead; + do { + nsresult rv = ParseNextChunk(&bytesRead); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + *aLengthOut = UncompressedLength(); + } while(*aLengthOut == 0 && bytesRead); + + return NS_OK; +} + +NS_IMETHODIMP +SnappyUncompressInputStream::Read(char* aBuf, uint32_t aCount, + uint32_t* aBytesReadOut) +{ + return ReadSegments(NS_CopySegmentToBuffer, aBuf, aCount, aBytesReadOut); +} + +NS_IMETHODIMP +SnappyUncompressInputStream::ReadSegments(nsWriteSegmentFun aWriter, + void* aClosure, uint32_t aCount, + uint32_t* aBytesReadOut) +{ + *aBytesReadOut = 0; + + if (!mBaseStream) { + return NS_BASE_STREAM_CLOSED; + } + + nsresult rv; + + // Do not try to use the base stream's ReadSegements here. Its very + // unlikely we will get a single buffer that contains all of the compressed + // data and therefore would have to copy into our own buffer anyways. + // Instead, focus on making efficient use of the Read() interface. + + while (aCount > 0) { + // We have some decompressed data in our buffer. Provide it to the + // callers writer function. + if (mUncompressedBytes > 0) { + MOZ_ASSERT(mUncompressedBuffer); + uint32_t remaining = UncompressedLength(); + uint32_t numToWrite = std::min(aCount, remaining); + uint32_t numWritten; + rv = aWriter(this, aClosure, &mUncompressedBuffer[mNextByte], *aBytesReadOut, + numToWrite, &numWritten); + + // As defined in nsIInputputStream.idl, do not pass writer func errors. + if (NS_FAILED(rv)) { + return NS_OK; + } + + // End-of-file + if (numWritten == 0) { + return NS_OK; + } + + *aBytesReadOut += numWritten; + mNextByte += numWritten; + MOZ_ASSERT(mNextByte <= mUncompressedBytes); + + if (mNextByte == mUncompressedBytes) { + mNextByte = 0; + mUncompressedBytes = 0; + } + + aCount -= numWritten; + + continue; + } + + // Otherwise uncompress the next chunk and loop. Any resulting data + // will set mUncompressedBytes which we check at the top of the loop. + uint32_t bytesRead; + rv = ParseNextChunk(&bytesRead); + if (NS_FAILED(rv)) { return rv; } + + // If we couldn't read anything and there is no more data to provide + // to the caller, then this is eof. + if (bytesRead == 0 && mUncompressedBytes == 0) { + return NS_OK; + } + } + + return NS_OK; +} + +NS_IMETHODIMP +SnappyUncompressInputStream::IsNonBlocking(bool* aNonBlockingOut) +{ + *aNonBlockingOut = false; + return NS_OK; +} + +SnappyUncompressInputStream::~SnappyUncompressInputStream() +{ + Close(); +} + +nsresult +SnappyUncompressInputStream::ParseNextChunk(uint32_t* aBytesReadOut) +{ + // There must not be any uncompressed data already in mUncompressedBuffer. + MOZ_ASSERT(mUncompressedBytes == 0); + MOZ_ASSERT(mNextByte == 0); + + nsresult rv; + *aBytesReadOut = 0; + + // Lazily create our two buffers so we can report OOM during stream + // operation. These allocations only happens once. The buffers are reused + // until the stream is closed. + if (!mUncompressedBuffer) { + mUncompressedBuffer.reset(new (fallible) char[snappy::kBlockSize]); + if (NS_WARN_IF(!mUncompressedBuffer)) { + return NS_ERROR_OUT_OF_MEMORY; + } + } + + if (!mCompressedBuffer) { + mCompressedBuffer.reset(new (fallible) char[CompressedBufferLength()]); + if (NS_WARN_IF(!mCompressedBuffer)) { + return NS_ERROR_OUT_OF_MEMORY; + } + } + + // We have no decompressed data and we also have not seen the start of stream + // yet. Read and validate the StreamIdentifier chunk. Also read the next + // header to determine the size of the first real data chunk. + if (mNeedFirstStreamIdentifier) { + const uint32_t firstReadLength = kHeaderLength + + kStreamIdentifierDataLength + + kHeaderLength; + MOZ_ASSERT(firstReadLength <= CompressedBufferLength()); + + rv = ReadAll(mCompressedBuffer.get(), firstReadLength, firstReadLength, + aBytesReadOut); + if (NS_WARN_IF(NS_FAILED(rv)) || *aBytesReadOut == 0) { return rv; } + + rv = ParseHeader(mCompressedBuffer.get(), kHeaderLength, + &mNextChunkType, &mNextChunkDataLength); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + if (NS_WARN_IF(mNextChunkType != StreamIdentifier || + mNextChunkDataLength != kStreamIdentifierDataLength)) { + return NS_ERROR_CORRUPTED_CONTENT; + } + size_t offset = kHeaderLength; + + mNeedFirstStreamIdentifier = false; + + size_t numRead; + size_t numWritten; + rv = ParseData(mUncompressedBuffer.get(), snappy::kBlockSize, mNextChunkType, + &mCompressedBuffer[offset], + mNextChunkDataLength, &numWritten, &numRead); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + MOZ_ASSERT(numWritten == 0); + MOZ_ASSERT(numRead == mNextChunkDataLength); + offset += numRead; + + rv = ParseHeader(&mCompressedBuffer[offset], *aBytesReadOut - offset, + &mNextChunkType, &mNextChunkDataLength); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + return NS_OK; + } + + // We have no compressed data and we don't know how big the next chunk is. + // This happens when we get an EOF pause in the middle of a stream and also + // at the end of the stream. Simply read the next header and return. The + // chunk body will be read on the next entry into this method. + if (mNextChunkType == Unknown) { + rv = ReadAll(mCompressedBuffer.get(), kHeaderLength, kHeaderLength, + aBytesReadOut); + if (NS_WARN_IF(NS_FAILED(rv)) || *aBytesReadOut == 0) { return rv; } + + rv = ParseHeader(mCompressedBuffer.get(), kHeaderLength, + &mNextChunkType, &mNextChunkDataLength); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + return NS_OK; + } + + // We have no decompressed data, but we do know the size of the next chunk. + // Read at least that much from the base stream. + uint32_t readLength = mNextChunkDataLength; + MOZ_ASSERT(readLength <= CompressedBufferLength()); + + // However, if there is enough data in the base stream, also read the next + // chunk header. This helps optimize the stream by avoiding many small reads. + uint64_t avail; + rv = mBaseStream->Available(&avail); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + if (avail >= (readLength + kHeaderLength)) { + readLength += kHeaderLength; + MOZ_ASSERT(readLength <= CompressedBufferLength()); + } + + rv = ReadAll(mCompressedBuffer.get(), readLength, mNextChunkDataLength, + aBytesReadOut); + if (NS_WARN_IF(NS_FAILED(rv)) || *aBytesReadOut == 0) { return rv; } + + size_t numRead; + size_t numWritten; + rv = ParseData(mUncompressedBuffer.get(), snappy::kBlockSize, mNextChunkType, + mCompressedBuffer.get(), mNextChunkDataLength, + &numWritten, &numRead); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + MOZ_ASSERT(numRead == mNextChunkDataLength); + + mUncompressedBytes = numWritten; + + // If we were unable to directly read the next chunk header, then clear + // our internal state. We will have to perform a small read to get the + // header the next time we enter this method. + if (*aBytesReadOut <= mNextChunkDataLength) { + mNextChunkType = Unknown; + mNextChunkDataLength = 0; + return NS_OK; + } + + // We got the next chunk header. Parse it so that we are ready to for the + // next call into this method. + rv = ParseHeader(&mCompressedBuffer[numRead], *aBytesReadOut - numRead, + &mNextChunkType, &mNextChunkDataLength); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + return NS_OK; +} + +nsresult +SnappyUncompressInputStream::ReadAll(char* aBuf, uint32_t aCount, + uint32_t aMinValidCount, + uint32_t* aBytesReadOut) +{ + MOZ_ASSERT(aCount >= aMinValidCount); + + *aBytesReadOut = 0; + + if (!mBaseStream) { + return NS_BASE_STREAM_CLOSED; + } + + uint32_t offset = 0; + while (aCount > 0) { + uint32_t bytesRead = 0; + nsresult rv = mBaseStream->Read(aBuf + offset, aCount, &bytesRead); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + // EOF, but don't immediately return. We need to validate min read bytes + // below. + if (bytesRead == 0) { + break; + } + + *aBytesReadOut += bytesRead; + offset += bytesRead; + aCount -= bytesRead; + } + + // Reading zero bytes is not an error. Its the expected EOF condition. + // Only compare to the minimum valid count if we read at least one byte. + if (*aBytesReadOut != 0 && *aBytesReadOut < aMinValidCount) { + return NS_ERROR_CORRUPTED_CONTENT; + } + + return NS_OK; +} + +size_t +SnappyUncompressInputStream::UncompressedLength() const +{ + MOZ_ASSERT(mNextByte <= mUncompressedBytes); + return mUncompressedBytes - mNextByte; +} + +} // namespace mozilla diff --git a/xpcom/io/SnappyUncompressInputStream.h b/xpcom/io/SnappyUncompressInputStream.h new file mode 100644 index 000000000..0d24c0d11 --- /dev/null +++ b/xpcom/io/SnappyUncompressInputStream.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 mozilla_SnappyUncompressInputStream_h__ +#define mozilla_SnappyUncompressInputStream_h__ + +#include "mozilla/Attributes.h" +#include "mozilla/UniquePtr.h" +#include "nsCOMPtr.h" +#include "nsIInputStream.h" +#include "nsISupportsImpl.h" +#include "SnappyFrameUtils.h" + +namespace mozilla { + +class SnappyUncompressInputStream final : public nsIInputStream + , protected detail::SnappyFrameUtils +{ +public: + // Construct a new blocking stream to uncompress the given base stream. The + // base stream must also be blocking. The base stream does not have to be + // buffered. + explicit SnappyUncompressInputStream(nsIInputStream* aBaseStream); + +private: + virtual ~SnappyUncompressInputStream(); + + // Parse the next chunk of data. This may populate mBuffer and set + // mBufferFillSize. This should not be called when mBuffer already + // contains data. + nsresult ParseNextChunk(uint32_t* aBytesReadOut); + + // Convenience routine to Read() from the base stream until we get + // the given number of bytes or reach EOF. + // + // aBuf - The buffer to write the bytes into. + // aCount - Max number of bytes to read. If the stream closes + // fewer bytes my be read. + // aMinValidCount - A minimum expected number of bytes. If we find + // fewer than this many bytes, then return + // NS_ERROR_CORRUPTED_CONTENT. If nothing was read due + // due to EOF (aBytesReadOut == 0), then NS_OK is returned. + // aBytesReadOut - An out parameter indicating how many bytes were read. + nsresult ReadAll(char* aBuf, uint32_t aCount, uint32_t aMinValidCount, + uint32_t* aBytesReadOut); + + // Convenience routine to determine how many bytes of uncompressed data + // we currently have in our buffer. + size_t UncompressedLength() const; + + nsCOMPtr mBaseStream; + + // Buffer to hold compressed data. Must copy here since we need a large + // flat buffer to run the uncompress process on. Always the same length + // of SnappyFrameUtils::MaxCompressedBufferLength(snappy::kBlockSize) + // bytes long. + mozilla::UniquePtr mCompressedBuffer; + + // Buffer storing the resulting uncompressed data. Exactly snappy::kBlockSize + // bytes long. + mozilla::UniquePtr mUncompressedBuffer; + + // Number of bytes of uncompressed data in mBuffer. + size_t mUncompressedBytes; + + // Next byte of mBuffer to return in ReadSegments(). Must be less than + // mBufferFillSize + size_t mNextByte; + + // Next chunk in the stream that has been parsed during read-ahead. + ChunkType mNextChunkType; + + // Length of next chunk's length that has been determined during read-ahead. + size_t mNextChunkDataLength; + + // The stream must begin with a StreamIdentifier chunk. Are we still + // expecting it? + bool mNeedFirstStreamIdentifier; + +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIINPUTSTREAM +}; + +} // namespace mozilla + +#endif // mozilla_SnappyUncompressInputStream_h__ diff --git a/xpcom/io/SpecialSystemDirectory.cpp b/xpcom/io/SpecialSystemDirectory.cpp new file mode 100644 index 000000000..9ce8eb85b --- /dev/null +++ b/xpcom/io/SpecialSystemDirectory.cpp @@ -0,0 +1,830 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SpecialSystemDirectory.h" +#include "nsString.h" +#include "nsDependentString.h" +#include "nsAutoPtr.h" + +#if defined(XP_WIN) + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "mozilla/WindowsVersion.h" + +using mozilla::IsWin7OrLater; + +#elif defined(XP_UNIX) + +#include +#include +#include +#include +#include "prenv.h" +#if defined(MOZ_WIDGET_COCOA) +#include "CocoaFileUtils.h" +#endif + +#endif + +#ifndef MAXPATHLEN +#ifdef PATH_MAX +#define MAXPATHLEN PATH_MAX +#elif defined(MAX_PATH) +#define MAXPATHLEN MAX_PATH +#elif defined(_MAX_PATH) +#define MAXPATHLEN _MAX_PATH +#elif defined(CCHMAXPATH) +#define MAXPATHLEN CCHMAXPATH +#else +#define MAXPATHLEN 1024 +#endif +#endif + +#ifdef XP_WIN +typedef HRESULT (WINAPI* nsGetKnownFolderPath)(GUID& rfid, + DWORD dwFlags, + HANDLE hToken, + PWSTR* ppszPath); + +static nsGetKnownFolderPath gGetKnownFolderPath = nullptr; +#endif + +void +StartupSpecialSystemDirectory() +{ +#if defined (XP_WIN) + // SHGetKnownFolderPath is only available on Windows Vista + // so that we need to use GetProcAddress to get the pointer. + HMODULE hShell32DLLInst = GetModuleHandleW(L"shell32.dll"); + if (hShell32DLLInst) { + gGetKnownFolderPath = (nsGetKnownFolderPath) + GetProcAddress(hShell32DLLInst, "SHGetKnownFolderPath"); + } +#endif +} + +#if defined (XP_WIN) + +static nsresult +GetKnownFolder(GUID* aGuid, nsIFile** aFile) +{ + if (!aGuid || !gGetKnownFolderPath) { + return NS_ERROR_FAILURE; + } + + PWSTR path = nullptr; + gGetKnownFolderPath(*aGuid, 0, nullptr, &path); + + if (!path) { + return NS_ERROR_FAILURE; + } + + nsresult rv = NS_NewLocalFile(nsDependentString(path), + true, + aFile); + + CoTaskMemFree(path); + return rv; +} + +static nsresult +GetWindowsFolder(int aFolder, nsIFile** aFile) +{ + WCHAR path_orig[MAX_PATH + 3]; + WCHAR* path = path_orig + 1; + HRESULT result = SHGetSpecialFolderPathW(nullptr, path, aFolder, true); + + if (!SUCCEEDED(result)) { + return NS_ERROR_FAILURE; + } + + // Append the trailing slash + int len = wcslen(path); + if (len > 1 && path[len - 1] != L'\\') { + path[len] = L'\\'; + path[++len] = L'\0'; + } + + return NS_NewLocalFile(nsDependentString(path, len), true, aFile); +} + +__inline HRESULT +SHLoadLibraryFromKnownFolder(REFKNOWNFOLDERID aFolderId, DWORD aMode, + REFIID riid, void** ppv) +{ + *ppv = nullptr; + IShellLibrary* plib; + HRESULT hr = CoCreateInstance(CLSID_ShellLibrary, nullptr, + CLSCTX_INPROC_SERVER, + IID_PPV_ARGS(&plib)); + if (SUCCEEDED(hr)) { + hr = plib->LoadLibraryFromKnownFolder(aFolderId, aMode); + if (SUCCEEDED(hr)) { + hr = plib->QueryInterface(riid, ppv); + } + plib->Release(); + } + return hr; +} + +/* + * Check to see if we're on Win7 and up, and if so, returns the default + * save-to location for the Windows Library passed in through aFolderId. + * Otherwise falls back on pre-win7 GetWindowsFolder. + */ +static nsresult +GetLibrarySaveToPath(int aFallbackFolderId, REFKNOWNFOLDERID aFolderId, + nsIFile** aFile) +{ + // Skip off checking for library support if the os is Vista or lower. + if (!IsWin7OrLater()) { + return GetWindowsFolder(aFallbackFolderId, aFile); + } + + RefPtr shellLib; + RefPtr savePath; + HRESULT hr = + SHLoadLibraryFromKnownFolder(aFolderId, STGM_READ, + IID_IShellLibrary, getter_AddRefs(shellLib)); + + if (shellLib && + SUCCEEDED(shellLib->GetDefaultSaveFolder(DSFT_DETECT, IID_IShellItem, + getter_AddRefs(savePath)))) { + wchar_t* str = nullptr; + if (SUCCEEDED(savePath->GetDisplayName(SIGDN_FILESYSPATH, &str))) { + nsAutoString path; + path.Assign(str); + path.Append('\\'); + nsresult rv = + NS_NewLocalFile(path, false, aFile); + CoTaskMemFree(str); + return rv; + } + } + + return GetWindowsFolder(aFallbackFolderId, aFile); +} + +/** + * Provides a fallback for getting the path to APPDATA or LOCALAPPDATA by + * querying the registry when the call to SHGetSpecialFolderPathW is unable to + * provide these paths (Bug 513958). + */ +static nsresult +GetRegWindowsAppDataFolder(bool aLocal, nsIFile** aFile) +{ + HKEY key; + NS_NAMED_LITERAL_STRING(keyName, + "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders"); + DWORD res = ::RegOpenKeyExW(HKEY_CURRENT_USER, keyName.get(), 0, KEY_READ, + &key); + if (res != ERROR_SUCCESS) { + return NS_ERROR_FAILURE; + } + + WCHAR path[MAX_PATH + 2]; + DWORD type, size; + res = RegQueryValueExW(key, (aLocal ? L"Local AppData" : L"AppData"), + nullptr, &type, (LPBYTE)&path, &size); + ::RegCloseKey(key); + // The call to RegQueryValueExW must succeed, the type must be REG_SZ, the + // buffer size must not equal 0, and the buffer size be a multiple of 2. + if (res != ERROR_SUCCESS || type != REG_SZ || size == 0 || size % 2 != 0) { + return NS_ERROR_FAILURE; + } + + // Append the trailing slash + int len = wcslen(path); + if (len > 1 && path[len - 1] != L'\\') { + path[len] = L'\\'; + path[++len] = L'\0'; + } + + return NS_NewLocalFile(nsDependentString(path, len), true, aFile); +} + +#endif // XP_WIN + +#if defined(XP_UNIX) +static nsresult +GetUnixHomeDir(nsIFile** aFile) +{ +#if defined(ANDROID) + // XXX no home dir on android; maybe we should return the sdcard if present? + return NS_ERROR_FAILURE; +#else + return NS_NewNativeLocalFile(nsDependentCString(PR_GetEnv("HOME")), + true, aFile); +#endif +} + +/* + The following license applies to the xdg_user_dir_lookup function: + + Copyright (c) 2007 Red Hat, Inc. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +static char* +xdg_user_dir_lookup(const char* aType) +{ + FILE* file; + char* home_dir; + char* config_home; + char* config_file; + char buffer[512]; + char* user_dir; + char* p; + char* d; + int len; + int relative; + + home_dir = getenv("HOME"); + + if (!home_dir) { + goto error; + } + + config_home = getenv("XDG_CONFIG_HOME"); + if (!config_home || config_home[0] == 0) { + config_file = (char*)malloc(strlen(home_dir) + + strlen("/.config/user-dirs.dirs") + 1); + if (!config_file) { + goto error; + } + + strcpy(config_file, home_dir); + strcat(config_file, "/.config/user-dirs.dirs"); + } else { + config_file = (char*)malloc(strlen(config_home) + + strlen("/user-dirs.dirs") + 1); + if (!config_file) { + goto error; + } + + strcpy(config_file, config_home); + strcat(config_file, "/user-dirs.dirs"); + } + + file = fopen(config_file, "r"); + free(config_file); + if (!file) { + goto error; + } + + user_dir = nullptr; + while (fgets(buffer, sizeof(buffer), file)) { + /* Remove newline at end */ + len = strlen(buffer); + if (len > 0 && buffer[len - 1] == '\n') { + buffer[len - 1] = 0; + } + + p = buffer; + while (*p == ' ' || *p == '\t') { + p++; + } + + if (strncmp(p, "XDG_", 4) != 0) { + continue; + } + p += 4; + if (strncmp(p, aType, strlen(aType)) != 0) { + continue; + } + p += strlen(aType); + if (strncmp(p, "_DIR", 4) != 0) { + continue; + } + p += 4; + + while (*p == ' ' || *p == '\t') { + p++; + } + + if (*p != '=') { + continue; + } + p++; + + while (*p == ' ' || *p == '\t') { + p++; + } + + if (*p != '"') { + continue; + } + p++; + + relative = 0; + if (strncmp(p, "$HOME/", 6) == 0) { + p += 6; + relative = 1; + } else if (*p != '/') { + continue; + } + + if (relative) { + user_dir = (char*)malloc(strlen(home_dir) + 1 + strlen(p) + 1); + if (!user_dir) { + goto error2; + } + + strcpy(user_dir, home_dir); + strcat(user_dir, "/"); + } else { + user_dir = (char*)malloc(strlen(p) + 1); + if (!user_dir) { + goto error2; + } + + *user_dir = 0; + } + + d = user_dir + strlen(user_dir); + while (*p && *p != '"') { + if ((*p == '\\') && (*(p + 1) != 0)) { + p++; + } + *d++ = *p++; + } + *d = 0; + } +error2: + fclose(file); + + if (user_dir) { + return user_dir; + } + +error: + return nullptr; +} + +static const char xdg_user_dirs[] = + "DESKTOP\0" + "DOCUMENTS\0" + "DOWNLOAD\0" + "MUSIC\0" + "PICTURES\0" + "PUBLICSHARE\0" + "TEMPLATES\0" + "VIDEOS"; + +static const uint8_t xdg_user_dir_offsets[] = { + 0, + 8, + 18, + 27, + 33, + 42, + 54, + 64 +}; + +static nsresult +GetUnixXDGUserDirectory(SystemDirectories aSystemDirectory, + nsIFile** aFile) +{ + char* dir = xdg_user_dir_lookup( + xdg_user_dirs + xdg_user_dir_offsets[aSystemDirectory - Unix_XDG_Desktop]); + + nsresult rv; + nsCOMPtr file; + if (dir) { + rv = NS_NewNativeLocalFile(nsDependentCString(dir), true, + getter_AddRefs(file)); + free(dir); + } else if (Unix_XDG_Desktop == aSystemDirectory) { + // for the XDG desktop dir, fall back to HOME/Desktop + // (for historical compatibility) + rv = GetUnixHomeDir(getter_AddRefs(file)); + if (NS_FAILED(rv)) { + return rv; + } + + rv = file->AppendNative(NS_LITERAL_CSTRING("Desktop")); + } else { + // no fallback for the other XDG dirs + rv = NS_ERROR_FAILURE; + } + + if (NS_FAILED(rv)) { + return rv; + } + + bool exists; + rv = file->Exists(&exists); + if (NS_FAILED(rv)) { + return rv; + } + if (!exists) { + rv = file->Create(nsIFile::DIRECTORY_TYPE, 0755); + if (NS_FAILED(rv)) { + return rv; + } + } + + *aFile = nullptr; + file.swap(*aFile); + + return NS_OK; +} +#endif + +nsresult +GetSpecialSystemDirectory(SystemDirectories aSystemSystemDirectory, + nsIFile** aFile) +{ +#if defined(XP_WIN) + WCHAR path[MAX_PATH]; +#else + char path[MAXPATHLEN]; +#endif + + switch (aSystemSystemDirectory) { + case OS_CurrentWorkingDirectory: +#if defined(XP_WIN) + if (!_wgetcwd(path, MAX_PATH)) { + return NS_ERROR_FAILURE; + } + return NS_NewLocalFile(nsDependentString(path), + true, + aFile); +#else + if (!getcwd(path, MAXPATHLEN)) { + return NS_ERROR_FAILURE; + } +#endif + +#if !defined(XP_WIN) + return NS_NewNativeLocalFile(nsDependentCString(path), + true, + aFile); +#endif + + case OS_DriveDirectory: +#if defined (XP_WIN) + { + int32_t len = ::GetWindowsDirectoryW(path, MAX_PATH); + if (len == 0) { + break; + } + if (path[1] == char16_t(':') && path[2] == char16_t('\\')) { + path[3] = 0; + } + + return NS_NewLocalFile(nsDependentString(path), + true, + aFile); + } +#else + return NS_NewNativeLocalFile(nsDependentCString("/"), + true, + aFile); + +#endif + + case OS_TemporaryDirectory: +#if defined (XP_WIN) + { + DWORD len = ::GetTempPathW(MAX_PATH, path); + if (len == 0) { + break; + } + return NS_NewLocalFile(nsDependentString(path, len), + true, + aFile); + } +#elif defined(MOZ_WIDGET_COCOA) + { + return GetOSXFolderType(kUserDomain, kTemporaryFolderType, aFile); + } + +#elif defined(XP_UNIX) + { + static const char* tPath = nullptr; + if (!tPath) { + tPath = PR_GetEnv("TMPDIR"); + if (!tPath || !*tPath) { + tPath = PR_GetEnv("TMP"); + if (!tPath || !*tPath) { + tPath = PR_GetEnv("TEMP"); + if (!tPath || !*tPath) { + tPath = "/tmp/"; + } + } + } + } + return NS_NewNativeLocalFile(nsDependentCString(tPath), + true, + aFile); + } +#else + break; +#endif +#if defined (XP_WIN) + case Win_SystemDirectory: { + int32_t len = ::GetSystemDirectoryW(path, MAX_PATH); + + // Need enough space to add the trailing backslash + if (!len || len > MAX_PATH - 2) { + break; + } + path[len] = L'\\'; + path[++len] = L'\0'; + + return NS_NewLocalFile(nsDependentString(path, len), + true, + aFile); + } + + case Win_WindowsDirectory: { + int32_t len = ::GetWindowsDirectoryW(path, MAX_PATH); + + // Need enough space to add the trailing backslash + if (!len || len > MAX_PATH - 2) { + break; + } + + path[len] = L'\\'; + path[++len] = L'\0'; + + return NS_NewLocalFile(nsDependentString(path, len), + true, + aFile); + } + + case Win_ProgramFiles: { + return GetWindowsFolder(CSIDL_PROGRAM_FILES, aFile); + } + + case Win_HomeDirectory: { + nsresult rv = GetWindowsFolder(CSIDL_PROFILE, aFile); + if (NS_SUCCEEDED(rv)) { + return rv; + } + + int32_t len; + if ((len = ::GetEnvironmentVariableW(L"HOME", path, MAX_PATH)) > 0) { + // Need enough space to add the trailing backslash + if (len > MAX_PATH - 2) { + break; + } + + path[len] = L'\\'; + path[++len] = L'\0'; + + rv = NS_NewLocalFile(nsDependentString(path, len), + true, + aFile); + if (NS_SUCCEEDED(rv)) { + return rv; + } + } + + len = ::GetEnvironmentVariableW(L"HOMEDRIVE", path, MAX_PATH); + if (0 < len && len < MAX_PATH) { + WCHAR temp[MAX_PATH]; + DWORD len2 = ::GetEnvironmentVariableW(L"HOMEPATH", temp, MAX_PATH); + if (0 < len2 && len + len2 < MAX_PATH) { + wcsncat(path, temp, len2); + } + + len = wcslen(path); + + // Need enough space to add the trailing backslash + if (len > MAX_PATH - 2) { + break; + } + + path[len] = L'\\'; + path[++len] = L'\0'; + + return NS_NewLocalFile(nsDependentString(path, len), + true, + aFile); + } + } + case Win_Desktop: { + return GetWindowsFolder(CSIDL_DESKTOP, aFile); + } + case Win_Programs: { + return GetWindowsFolder(CSIDL_PROGRAMS, aFile); + } + + case Win_Downloads: { + // Defined in KnownFolders.h. + GUID folderid_downloads = { + 0x374de290, 0x123f, 0x4565, + { 0x91, 0x64, 0x39, 0xc4, 0x92, 0x5e, 0x46, 0x7b } + }; + nsresult rv = GetKnownFolder(&folderid_downloads, aFile); + // On WinXP, there is no downloads folder, default + // to 'Desktop'. + if (NS_ERROR_FAILURE == rv) { + rv = GetWindowsFolder(CSIDL_DESKTOP, aFile); + } + return rv; + } + + case Win_Controls: { + return GetWindowsFolder(CSIDL_CONTROLS, aFile); + } + case Win_Printers: { + return GetWindowsFolder(CSIDL_PRINTERS, aFile); + } + case Win_Personal: { + return GetWindowsFolder(CSIDL_PERSONAL, aFile); + } + case Win_Favorites: { + return GetWindowsFolder(CSIDL_FAVORITES, aFile); + } + case Win_Startup: { + return GetWindowsFolder(CSIDL_STARTUP, aFile); + } + case Win_Recent: { + return GetWindowsFolder(CSIDL_RECENT, aFile); + } + case Win_Sendto: { + return GetWindowsFolder(CSIDL_SENDTO, aFile); + } + case Win_Bitbucket: { + return GetWindowsFolder(CSIDL_BITBUCKET, aFile); + } + case Win_Startmenu: { + return GetWindowsFolder(CSIDL_STARTMENU, aFile); + } + case Win_Desktopdirectory: { + return GetWindowsFolder(CSIDL_DESKTOPDIRECTORY, aFile); + } + case Win_Drives: { + return GetWindowsFolder(CSIDL_DRIVES, aFile); + } + case Win_Network: { + return GetWindowsFolder(CSIDL_NETWORK, aFile); + } + case Win_Nethood: { + return GetWindowsFolder(CSIDL_NETHOOD, aFile); + } + case Win_Fonts: { + return GetWindowsFolder(CSIDL_FONTS, aFile); + } + case Win_Templates: { + return GetWindowsFolder(CSIDL_TEMPLATES, aFile); + } + case Win_Common_Startmenu: { + return GetWindowsFolder(CSIDL_COMMON_STARTMENU, aFile); + } + case Win_Common_Programs: { + return GetWindowsFolder(CSIDL_COMMON_PROGRAMS, aFile); + } + case Win_Common_Startup: { + return GetWindowsFolder(CSIDL_COMMON_STARTUP, aFile); + } + case Win_Common_Desktopdirectory: { + return GetWindowsFolder(CSIDL_COMMON_DESKTOPDIRECTORY, aFile); + } + case Win_Common_AppData: { + return GetWindowsFolder(CSIDL_COMMON_APPDATA, aFile); + } + case Win_Printhood: { + return GetWindowsFolder(CSIDL_PRINTHOOD, aFile); + } + case Win_Cookies: { + return GetWindowsFolder(CSIDL_COOKIES, aFile); + } + case Win_Appdata: { + nsresult rv = GetWindowsFolder(CSIDL_APPDATA, aFile); + if (NS_FAILED(rv)) { + rv = GetRegWindowsAppDataFolder(false, aFile); + } + return rv; + } + case Win_LocalAppdata: { + nsresult rv = GetWindowsFolder(CSIDL_LOCAL_APPDATA, aFile); + if (NS_FAILED(rv)) { + rv = GetRegWindowsAppDataFolder(true, aFile); + } + return rv; + } +#if defined(MOZ_CONTENT_SANDBOX) + case Win_LocalAppdataLow: { + // This should only really fail on versions pre-Vista, in which case this + // shouldn't have been used in the first place. + GUID localAppDataLowGuid = FOLDERID_LocalAppDataLow; + return GetKnownFolder(&localAppDataLowGuid, aFile); + } +#endif + case Win_Documents: { + return GetLibrarySaveToPath(CSIDL_MYDOCUMENTS, + FOLDERID_DocumentsLibrary, + aFile); + } + case Win_Pictures: { + return GetLibrarySaveToPath(CSIDL_MYPICTURES, + FOLDERID_PicturesLibrary, + aFile); + } + case Win_Music: { + return GetLibrarySaveToPath(CSIDL_MYMUSIC, + FOLDERID_MusicLibrary, + aFile); + } + case Win_Videos: { + return GetLibrarySaveToPath(CSIDL_MYVIDEO, + FOLDERID_VideosLibrary, + aFile); + } +#endif // XP_WIN + +#if defined(XP_UNIX) + case Unix_LocalDirectory: + return NS_NewNativeLocalFile(nsDependentCString("/usr/local/netscape/"), + true, + aFile); + case Unix_LibDirectory: + return NS_NewNativeLocalFile(nsDependentCString("/usr/local/lib/netscape/"), + true, + aFile); + + case Unix_HomeDirectory: + return GetUnixHomeDir(aFile); + + case Unix_XDG_Desktop: + case Unix_XDG_Documents: + case Unix_XDG_Download: + case Unix_XDG_Music: + case Unix_XDG_Pictures: + case Unix_XDG_PublicShare: + case Unix_XDG_Templates: + case Unix_XDG_Videos: + return GetUnixXDGUserDirectory(aSystemSystemDirectory, aFile); +#endif + + default: + break; + } + return NS_ERROR_NOT_AVAILABLE; +} + +#if defined (MOZ_WIDGET_COCOA) +nsresult +GetOSXFolderType(short aDomain, OSType aFolderType, nsIFile** aLocalFile) +{ + nsresult rv = NS_ERROR_FAILURE; + + if (aFolderType == kTemporaryFolderType) { + NS_NewLocalFile(EmptyString(), true, aLocalFile); + nsCOMPtr localMacFile(do_QueryInterface(*aLocalFile)); + if (localMacFile) { + rv = localMacFile->InitWithCFURL( + CocoaFileUtils::GetTemporaryFolderCFURLRef()); + } + return rv; + } + + OSErr err; + FSRef fsRef; + err = ::FSFindFolder(aDomain, aFolderType, kCreateFolder, &fsRef); + if (err == noErr) { + NS_NewLocalFile(EmptyString(), true, aLocalFile); + nsCOMPtr localMacFile(do_QueryInterface(*aLocalFile)); + if (localMacFile) { + rv = localMacFile->InitWithFSRef(&fsRef); + } + } + return rv; +} +#endif + diff --git a/xpcom/io/SpecialSystemDirectory.h b/xpcom/io/SpecialSystemDirectory.h new file mode 100644 index 000000000..dd3d88379 --- /dev/null +++ b/xpcom/io/SpecialSystemDirectory.h @@ -0,0 +1,107 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 _SPECIALSYSTEMDIRECTORY_H_ +#define _SPECIALSYSTEMDIRECTORY_H_ + +#include "nscore.h" +#include "nsIFile.h" + +#ifdef MOZ_WIDGET_COCOA +#include +#include "nsILocalFileMac.h" +#include "prenv.h" +#endif + +extern void StartupSpecialSystemDirectory(); + + +enum SystemDirectories { + OS_DriveDirectory = 1, + OS_TemporaryDirectory = 2, + OS_CurrentProcessDirectory = 3, + OS_CurrentWorkingDirectory = 4, + XPCOM_CurrentProcessComponentDirectory = 5, + XPCOM_CurrentProcessComponentRegistry = 6, + + Moz_BinDirectory = 100 , + Mac_SystemDirectory = 101, + Mac_DesktopDirectory = 102, + Mac_TrashDirectory = 103, + Mac_StartupDirectory = 104, + Mac_ShutdownDirectory = 105, + Mac_AppleMenuDirectory = 106, + Mac_ControlPanelDirectory = 107, + Mac_ExtensionDirectory = 108, + Mac_FontsDirectory = 109, + Mac_ClassicPreferencesDirectory = 110, + Mac_DocumentsDirectory = 111, + Mac_InternetSearchDirectory = 112, + Mac_DefaultDownloadDirectory = 113, + Mac_UserLibDirectory = 114, + Mac_PreferencesDirectory = 115, + + Win_SystemDirectory = 201, + Win_WindowsDirectory = 202, + Win_HomeDirectory = 203, + Win_Desktop = 204, + Win_Programs = 205, + Win_Controls = 206, + Win_Printers = 207, + Win_Personal = 208, + Win_Favorites = 209, + Win_Startup = 210, + Win_Recent = 211, + Win_Sendto = 212, + Win_Bitbucket = 213, + Win_Startmenu = 214, + Win_Desktopdirectory = 215, + Win_Drives = 216, + Win_Network = 217, + Win_Nethood = 218, + Win_Fonts = 219, + Win_Templates = 220, + Win_Common_Startmenu = 221, + Win_Common_Programs = 222, + Win_Common_Startup = 223, + Win_Common_Desktopdirectory = 224, + Win_Appdata = 225, + Win_Printhood = 226, + Win_Cookies = 227, + Win_LocalAppdata = 228, + Win_ProgramFiles = 229, + Win_Downloads = 230, + Win_Common_AppData = 231, + Win_Documents = 232, + Win_Pictures = 233, + Win_Music = 234, + Win_Videos = 235, +#if defined(MOZ_CONTENT_SANDBOX) + Win_LocalAppdataLow = 236, +#endif + + Unix_LocalDirectory = 301, + Unix_LibDirectory = 302, + Unix_HomeDirectory = 303, + Unix_XDG_Desktop = 304, + Unix_XDG_Documents = 305, + Unix_XDG_Download = 306, + Unix_XDG_Music = 307, + Unix_XDG_Pictures = 308, + Unix_XDG_PublicShare = 309, + Unix_XDG_Templates = 310, + Unix_XDG_Videos = 311 +}; + +nsresult +GetSpecialSystemDirectory(SystemDirectories aSystemSystemDirectory, + nsIFile** aFile); +#ifdef MOZ_WIDGET_COCOA +nsresult +GetOSXFolderType(short aDomain, OSType aFolderType, nsIFile** aLocalFile); +#endif + +#endif diff --git a/xpcom/io/crc32c.c b/xpcom/io/crc32c.c new file mode 100644 index 000000000..3494945c0 --- /dev/null +++ b/xpcom/io/crc32c.c @@ -0,0 +1,154 @@ +/* + * Based on file found here: + * + * https://svnweb.freebsd.org/base/stable/10/sys/libkern/crc32.c?revision=256281 + */ + +/*- + * COPYRIGHT (C) 1986 Gary S. Brown. You may use this program, or + * code or tables extracted from it, as desired without restriction. + */ + +/* + * First, the polynomial itself and its table of feedback terms. The + * polynomial is + * X^32+X^26+X^23+X^22+X^16+X^12+X^11+X^10+X^8+X^7+X^5+X^4+X^2+X^1+X^0 + * + * Note that we take it "backwards" and put the highest-order term in + * the lowest-order bit. The X^32 term is "implied"; the LSB is the + * X^31 term, etc. The X^0 term (usually shown as "+1") results in + * the MSB being 1 + * + * Note that the usual hardware shift register implementation, which + * is what we're using (we're merely optimizing it by doing eight-bit + * chunks at a time) shifts bits into the lowest-order term. In our + * implementation, that means shifting towards the right. Why do we + * do it this way? Because the calculated CRC must be transmitted in + * order from highest-order term to lowest-order term. UARTs transmit + * characters in order from LSB to MSB. By storing the CRC this way + * we hand it to the UART in the order low-byte to high-byte; the UART + * sends each low-bit to hight-bit; and the result is transmission bit + * by bit from highest- to lowest-order term without requiring any bit + * shuffling on our part. Reception works similarly + * + * The feedback terms table consists of 256, 32-bit entries. Notes + * + * The table can be generated at runtime if desired; code to do so + * is shown later. It might not be obvious, but the feedback + * terms simply represent the results of eight shift/xor opera + * tions for all combinations of data and CRC register values + * + * The values must be right-shifted by eight bits by the "updcrc + * logic; the shift must be unsigned (bring in zeroes). On some + * hardware you could probably optimize the shift in assembler by + * using byte-swap instructions + * polynomial $edb88320 + * + * + * CRC32 code derived from work by Gary S. Brown. + */ + +#include "crc32c.h" + +/* CRC32C routines, these use a different polynomial */ +/*****************************************************************/ +/* */ +/* CRC LOOKUP TABLE */ +/* ================ */ +/* The following CRC lookup table was generated automagically */ +/* by the Rocksoft^tm Model CRC Algorithm Table Generation */ +/* Program V1.0 using the following model parameters: */ +/* */ +/* Width : 4 bytes. */ +/* Poly : 0x1EDC6F41L */ +/* Reverse : TRUE. */ +/* */ +/* For more information on the Rocksoft^tm Model CRC Algorithm, */ +/* see the document titled "A Painless Guide to CRC Error */ +/* Detection Algorithms" by Ross Williams */ +/* (ross@guest.adelaide.edu.au.). This document is likely to be */ +/* in the FTP archive "ftp.adelaide.edu.au/pub/rocksoft". */ +/* */ +/*****************************************************************/ + +static const uint32_t crc32Table[256] = { + 0x00000000L, 0xF26B8303L, 0xE13B70F7L, 0x1350F3F4L, + 0xC79A971FL, 0x35F1141CL, 0x26A1E7E8L, 0xD4CA64EBL, + 0x8AD958CFL, 0x78B2DBCCL, 0x6BE22838L, 0x9989AB3BL, + 0x4D43CFD0L, 0xBF284CD3L, 0xAC78BF27L, 0x5E133C24L, + 0x105EC76FL, 0xE235446CL, 0xF165B798L, 0x030E349BL, + 0xD7C45070L, 0x25AFD373L, 0x36FF2087L, 0xC494A384L, + 0x9A879FA0L, 0x68EC1CA3L, 0x7BBCEF57L, 0x89D76C54L, + 0x5D1D08BFL, 0xAF768BBCL, 0xBC267848L, 0x4E4DFB4BL, + 0x20BD8EDEL, 0xD2D60DDDL, 0xC186FE29L, 0x33ED7D2AL, + 0xE72719C1L, 0x154C9AC2L, 0x061C6936L, 0xF477EA35L, + 0xAA64D611L, 0x580F5512L, 0x4B5FA6E6L, 0xB93425E5L, + 0x6DFE410EL, 0x9F95C20DL, 0x8CC531F9L, 0x7EAEB2FAL, + 0x30E349B1L, 0xC288CAB2L, 0xD1D83946L, 0x23B3BA45L, + 0xF779DEAEL, 0x05125DADL, 0x1642AE59L, 0xE4292D5AL, + 0xBA3A117EL, 0x4851927DL, 0x5B016189L, 0xA96AE28AL, + 0x7DA08661L, 0x8FCB0562L, 0x9C9BF696L, 0x6EF07595L, + 0x417B1DBCL, 0xB3109EBFL, 0xA0406D4BL, 0x522BEE48L, + 0x86E18AA3L, 0x748A09A0L, 0x67DAFA54L, 0x95B17957L, + 0xCBA24573L, 0x39C9C670L, 0x2A993584L, 0xD8F2B687L, + 0x0C38D26CL, 0xFE53516FL, 0xED03A29BL, 0x1F682198L, + 0x5125DAD3L, 0xA34E59D0L, 0xB01EAA24L, 0x42752927L, + 0x96BF4DCCL, 0x64D4CECFL, 0x77843D3BL, 0x85EFBE38L, + 0xDBFC821CL, 0x2997011FL, 0x3AC7F2EBL, 0xC8AC71E8L, + 0x1C661503L, 0xEE0D9600L, 0xFD5D65F4L, 0x0F36E6F7L, + 0x61C69362L, 0x93AD1061L, 0x80FDE395L, 0x72966096L, + 0xA65C047DL, 0x5437877EL, 0x4767748AL, 0xB50CF789L, + 0xEB1FCBADL, 0x197448AEL, 0x0A24BB5AL, 0xF84F3859L, + 0x2C855CB2L, 0xDEEEDFB1L, 0xCDBE2C45L, 0x3FD5AF46L, + 0x7198540DL, 0x83F3D70EL, 0x90A324FAL, 0x62C8A7F9L, + 0xB602C312L, 0x44694011L, 0x5739B3E5L, 0xA55230E6L, + 0xFB410CC2L, 0x092A8FC1L, 0x1A7A7C35L, 0xE811FF36L, + 0x3CDB9BDDL, 0xCEB018DEL, 0xDDE0EB2AL, 0x2F8B6829L, + 0x82F63B78L, 0x709DB87BL, 0x63CD4B8FL, 0x91A6C88CL, + 0x456CAC67L, 0xB7072F64L, 0xA457DC90L, 0x563C5F93L, + 0x082F63B7L, 0xFA44E0B4L, 0xE9141340L, 0x1B7F9043L, + 0xCFB5F4A8L, 0x3DDE77ABL, 0x2E8E845FL, 0xDCE5075CL, + 0x92A8FC17L, 0x60C37F14L, 0x73938CE0L, 0x81F80FE3L, + 0x55326B08L, 0xA759E80BL, 0xB4091BFFL, 0x466298FCL, + 0x1871A4D8L, 0xEA1A27DBL, 0xF94AD42FL, 0x0B21572CL, + 0xDFEB33C7L, 0x2D80B0C4L, 0x3ED04330L, 0xCCBBC033L, + 0xA24BB5A6L, 0x502036A5L, 0x4370C551L, 0xB11B4652L, + 0x65D122B9L, 0x97BAA1BAL, 0x84EA524EL, 0x7681D14DL, + 0x2892ED69L, 0xDAF96E6AL, 0xC9A99D9EL, 0x3BC21E9DL, + 0xEF087A76L, 0x1D63F975L, 0x0E330A81L, 0xFC588982L, + 0xB21572C9L, 0x407EF1CAL, 0x532E023EL, 0xA145813DL, + 0x758FE5D6L, 0x87E466D5L, 0x94B49521L, 0x66DF1622L, + 0x38CC2A06L, 0xCAA7A905L, 0xD9F75AF1L, 0x2B9CD9F2L, + 0xFF56BD19L, 0x0D3D3E1AL, 0x1E6DCDEEL, 0xEC064EEDL, + 0xC38D26C4L, 0x31E6A5C7L, 0x22B65633L, 0xD0DDD530L, + 0x0417B1DBL, 0xF67C32D8L, 0xE52CC12CL, 0x1747422FL, + 0x49547E0BL, 0xBB3FFD08L, 0xA86F0EFCL, 0x5A048DFFL, + 0x8ECEE914L, 0x7CA56A17L, 0x6FF599E3L, 0x9D9E1AE0L, + 0xD3D3E1ABL, 0x21B862A8L, 0x32E8915CL, 0xC083125FL, + 0x144976B4L, 0xE622F5B7L, 0xF5720643L, 0x07198540L, + 0x590AB964L, 0xAB613A67L, 0xB831C993L, 0x4A5A4A90L, + 0x9E902E7BL, 0x6CFBAD78L, 0x7FAB5E8CL, 0x8DC0DD8FL, + 0xE330A81AL, 0x115B2B19L, 0x020BD8EDL, 0xF0605BEEL, + 0x24AA3F05L, 0xD6C1BC06L, 0xC5914FF2L, 0x37FACCF1L, + 0x69E9F0D5L, 0x9B8273D6L, 0x88D28022L, 0x7AB90321L, + 0xAE7367CAL, 0x5C18E4C9L, 0x4F48173DL, 0xBD23943EL, + 0xF36E6F75L, 0x0105EC76L, 0x12551F82L, 0xE03E9C81L, + 0x34F4F86AL, 0xC69F7B69L, 0xD5CF889DL, 0x27A40B9EL, + 0x79B737BAL, 0x8BDCB4B9L, 0x988C474DL, 0x6AE7C44EL, + 0xBE2DA0A5L, 0x4C4623A6L, 0x5F16D052L, 0xAD7D5351L +}; + +// NOTE: See source URL at top of this file for multitable implementation which +// offers a performance boost at the cost of ~8KB of static tables. + +uint32_t +ComputeCrc32c(uint32_t crc, const void *buf, size_t size) +{ + const uint8_t *p = buf; + + + while (size--) + crc = crc32Table[(crc ^ *p++) & 0xff] ^ (crc >> 8); + + return crc; +} diff --git a/xpcom/io/crc32c.h b/xpcom/io/crc32c.h new file mode 100644 index 000000000..f7035fa15 --- /dev/null +++ b/xpcom/io/crc32c.h @@ -0,0 +1,23 @@ +#ifndef crc32c_h +#define crc32c_h + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +// Compute a CRC32c as defined in RFC3720. This is a different polynomial than +// what is used in the crc for zlib, etc. Typical usage to calculate a new CRC: +// +// ComputeCrc32c(~0, buffer, bufferLength); +// +uint32_t +ComputeCrc32c(uint32_t aCrc, const void *aBuf, size_t aSize); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // crc32c_h diff --git a/xpcom/io/moz.build b/xpcom/io/moz.build new file mode 100644 index 000000000..6f21e0a72 --- /dev/null +++ b/xpcom/io/moz.build @@ -0,0 +1,140 @@ +# -*- 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 += [ + 'nsIAsyncInputStream.idl', + 'nsIAsyncOutputStream.idl', + 'nsIBinaryInputStream.idl', + 'nsIBinaryOutputStream.idl', + 'nsICloneableInputStream.idl', + 'nsIConverterInputStream.idl', + 'nsIConverterOutputStream.idl', + 'nsIDirectoryEnumerator.idl', + 'nsIDirectoryService.idl', + 'nsIFile.idl', + 'nsIInputStream.idl', + 'nsIInputStreamTee.idl', + 'nsIIOUtil.idl', + 'nsILineInputStream.idl', + 'nsILocalFile.idl', + 'nsILocalFileWin.idl', + 'nsIMultiplexInputStream.idl', + 'nsIObjectInputStream.idl', + 'nsIObjectOutputStream.idl', + 'nsIOutputStream.idl', + 'nsIPipe.idl', + 'nsISafeOutputStream.idl', + 'nsIScriptableBase64Encoder.idl', + 'nsIScriptableInputStream.idl', + 'nsISeekableStream.idl', + 'nsIStorageStream.idl', + 'nsIStreamBufferAccess.idl', + 'nsIStringStream.idl', + 'nsIUnicharInputStream.idl', + 'nsIUnicharLineInputStream.idl', + 'nsIUnicharOutputStream.idl', +] + +if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa': + XPIDL_SOURCES += [ + 'nsILocalFileMac.idl', + ] + +if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows': + EXPORTS += ['nsLocalFileWin.h'] + EXPORTS.mozilla += [ + 'FileUtilsWin.h', + ] + SOURCES += [ + 'FileUtilsWin.cpp', + 'nsLocalFileWin.cpp', + ] +else: + EXPORTS += ['nsLocalFileUnix.h'] + SOURCES += [ + 'nsLocalFileUnix.cpp', + ] + +XPIDL_MODULE = 'xpcom_io' + +EXPORTS += [ + 'nsAnonymousTemporaryFile.h', + 'nsAppDirectoryServiceDefs.h', + 'nsDirectoryService.h', + 'nsDirectoryServiceAtomList.h', + 'nsDirectoryServiceDefs.h', + 'nsDirectoryServiceUtils.h', + 'nsEscape.h', + 'nsLinebreakConverter.h', + 'nsLocalFile.h', + 'nsMultiplexInputStream.h', + 'nsNativeCharsetUtils.h', + 'nsScriptableInputStream.h', + 'nsStorageStream.h', + 'nsStreamUtils.h', + 'nsStringStream.h', + 'nsUnicharInputStream.h', + 'nsWildCard.h', + 'SlicedInputStream.h', + 'SpecialSystemDirectory.h', +] + +EXPORTS.mozilla += [ + 'Base64.h', + 'SnappyCompressOutputStream.h', + 'SnappyFrameUtils.h', + 'SnappyUncompressInputStream.h', +] + +UNIFIED_SOURCES += [ + 'Base64.cpp', + 'crc32c.c', + 'nsAnonymousTemporaryFile.cpp', + 'nsAppFileLocationProvider.cpp', + 'nsBinaryStream.cpp', + 'nsDirectoryService.cpp', + 'nsEscape.cpp', + 'nsInputStreamTee.cpp', + 'nsIOUtil.cpp', + 'nsLinebreakConverter.cpp', + 'nsLocalFileCommon.cpp', + 'nsMultiplexInputStream.cpp', + 'nsNativeCharsetUtils.cpp', + 'nsPipe3.cpp', + 'nsScriptableBase64Encoder.cpp', + 'nsScriptableInputStream.cpp', + 'nsSegmentedBuffer.cpp', + 'nsStorageStream.cpp', + 'nsStreamUtils.cpp', + 'nsStringStream.cpp', + 'nsUnicharInputStream.cpp', + 'nsWildCard.cpp', + 'SlicedInputStream.cpp', + 'SnappyCompressOutputStream.cpp', + 'SnappyFrameUtils.cpp', + 'SnappyUncompressInputStream.cpp', + 'SpecialSystemDirectory.cpp', +] + +if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa': + SOURCES += [ + 'CocoaFileUtils.mm', + ] + +include('/ipc/chromium/chromium-config.mozbuild') + +FINAL_LIBRARY = 'xul' + +if CONFIG['OS_ARCH'] == 'Linux' and 'lib64' in CONFIG['libdir']: + DEFINES['HAVE_USR_LIB64_DIR'] = True + +LOCAL_INCLUDES += ['!..'] + +if CONFIG['_MSC_VER']: + # This is intended as a temporary hack to support building with VS2015. + # '_snwprintf' : format string '%s' requires an argument of type 'wchar_t *', + # but variadic argument 3 has type 'char16ptr_t' + CXXFLAGS += ['-wd4477'] diff --git a/xpcom/io/nsAnonymousTemporaryFile.cpp b/xpcom/io/nsAnonymousTemporaryFile.cpp new file mode 100644 index 000000000..586e552c4 --- /dev/null +++ b/xpcom/io/nsAnonymousTemporaryFile.cpp @@ -0,0 +1,314 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. 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/dom/ContentChild.h" +#include "mozilla/SyncRunnable.h" +#include "nsAnonymousTemporaryFile.h" +#include "nsDirectoryServiceUtils.h" +#include "nsDirectoryServiceDefs.h" +#include "nsXULAppAPI.h" +#include "nsCOMPtr.h" +#include "nsString.h" +#include "nsAppDirectoryServiceDefs.h" +#include "prio.h" +#include "private/pprio.h" + +#ifdef XP_WIN +#include "nsIObserver.h" +#include "nsIObserverService.h" +#include "mozilla/Services.h" +#include "nsIIdleService.h" +#include "nsISimpleEnumerator.h" +#include "nsIFile.h" +#include "nsAutoPtr.h" +#include "nsITimer.h" +#include "nsCRT.h" + +#endif + +using namespace mozilla; + +// We store the temp files in the system temp dir. +// +// On Windows systems in particular we use a sub-directory of the temp +// directory, because: +// 1. DELETE_ON_CLOSE is unreliable on Windows, in particular if we power +// cycle (and perhaps if we crash) the files are not deleted. We store +// the temporary files in a known sub-dir so that we can find and delete +// them easily and quickly. +// 2. On Windows NT the system temp dir is in the user's $HomeDir/AppData, +// so we can be sure the user always has write privileges to that directory; +// if the sub-dir for our temp files was in some shared location and +// was created by a privileged user, it's possible that other users +// wouldn't have write access to that sub-dir. (Non-Windows systems +// don't store their temp files in a sub-dir, so this isn't an issue on +// those platforms). +// 3. Content processes can access the system temp dir +// (NS_GetSpecialDirectory fails on NS_APP_USER_PROFILE_LOCAL_50_DIR +// for content process for example, which is where we previously stored +// temp files on Windows). This argument applies to all platforms, not +// just Windows. +static nsresult +GetTempDir(nsIFile** aTempDir) +{ + if (NS_WARN_IF(!aTempDir)) { + return NS_ERROR_INVALID_ARG; + } + nsCOMPtr tmpFile; + nsresult rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(tmpFile)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + +#ifdef XP_WIN + // On windows DELETE_ON_CLOSE is unreliable, so we store temporary files + // in a subdir of the temp dir and delete that in an idle service observer + // to ensure it's been cleared. + rv = tmpFile->AppendNative(nsDependentCString("mozilla-temp-files")); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + rv = tmpFile->Create(nsIFile::DIRECTORY_TYPE, 0700); + if (rv != NS_ERROR_FILE_ALREADY_EXISTS && NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } +#endif + + tmpFile.forget(aTempDir); + + return NS_OK; +} + +namespace { + +class nsRemoteAnonymousTemporaryFileRunnable : public Runnable +{ +public: + dom::FileDescOrError *mResultPtr; + explicit nsRemoteAnonymousTemporaryFileRunnable(dom::FileDescOrError *aResultPtr) + : mResultPtr(aResultPtr) + { } + +protected: + NS_IMETHOD Run() override { + dom::ContentChild* child = dom::ContentChild::GetSingleton(); + MOZ_ASSERT(child); + child->SendOpenAnonymousTemporaryFile(mResultPtr); + return NS_OK; + } +}; + +} // namespace + +nsresult +NS_OpenAnonymousTemporaryFile(PRFileDesc** aOutFileDesc) +{ + if (NS_WARN_IF(!aOutFileDesc)) { + return NS_ERROR_INVALID_ARG; + } + + if (dom::ContentChild* child = dom::ContentChild::GetSingleton()) { + dom::FileDescOrError fd = NS_OK; + if (NS_IsMainThread()) { + child->SendOpenAnonymousTemporaryFile(&fd); + } else { + nsCOMPtr mainThread = do_GetMainThread(); + MOZ_ASSERT(mainThread); + SyncRunnable::DispatchToThread(mainThread, + new nsRemoteAnonymousTemporaryFileRunnable(&fd)); + } + if (fd.type() == dom::FileDescOrError::Tnsresult) { + nsresult rv = fd.get_nsresult(); + MOZ_ASSERT(NS_FAILED(rv)); + return rv; + } + auto rawFD = fd.get_FileDescriptor().ClonePlatformHandle(); + *aOutFileDesc = PR_ImportFile(PROsfd(rawFD.release())); + return NS_OK; + } + + nsresult rv; + nsCOMPtr tmpFile; + rv = GetTempDir(getter_AddRefs(tmpFile)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // Give the temp file a name with a random element. CreateUnique will also + // append a counter to the name if it encounters a name collision. Adding + // a random element to the name reduces the likelihood of a name collision, + // so that CreateUnique() doesn't end up trying a lot of name variants in + // its "try appending an incrementing counter" loop, as file IO can be + // expensive on some mobile flash drives. + nsAutoCString name("mozilla-temp-"); + name.AppendInt(rand()); + + rv = tmpFile->AppendNative(name); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = tmpFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0700); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = tmpFile->OpenNSPRFileDesc(PR_RDWR | nsIFile::DELETE_ON_CLOSE, + PR_IRWXU, aOutFileDesc); + + return rv; +} + +#ifdef XP_WIN + +// On Windows we have an idle service observer that runs some time after +// startup and deletes any stray anonymous temporary files... + +// Duration of idle time before we'll get a callback whereupon we attempt to +// remove any stray and unused anonymous temp files. +#define TEMP_FILE_IDLE_TIME_S 30 + +// The nsAnonTempFileRemover is created in a timer, which sets an idle observer. +// This is expiration time (in ms) which initial timer is set for (3 minutes). +#define SCHEDULE_TIMEOUT_MS 3 * 60 * 1000 + +#define XPCOM_SHUTDOWN_TOPIC "xpcom-shutdown" + +// This class adds itself as an idle observer. When the application has +// been idle for about 30 seconds we'll get a notification, whereupon we'll +// attempt to delete ${TempDir}/mozilla-temp-files/. This is to ensure all +// temp files that were supposed to be deleted on application exit were actually +// deleted, as they may not be if we previously crashed. See bugs 572579 and +// 785662. This is only needed on some versions of Windows, +// nsIFile::DELETE_ON_CLOSE works on other platforms. +// This class adds itself as a shutdown observer so that it can cancel the +// idle observer and its timer on shutdown. Note: the observer and idle +// services hold references to instances of this object, and those references +// are what keep this object alive. +class nsAnonTempFileRemover final : public nsIObserver +{ +public: + NS_DECL_ISUPPORTS + + nsAnonTempFileRemover() {} + + nsresult Init() + { + // We add the idle observer in a timer, so that the app has enough + // time to start up before we add the idle observer. If we register the + // idle observer too early, it will be registered before the fake idle + // service is installed when running in xpcshell, and this interferes with + // the fake idle service, causing xpcshell-test failures. + mTimer = do_CreateInstance(NS_TIMER_CONTRACTID); + if (NS_WARN_IF(!mTimer)) { + return NS_ERROR_FAILURE; + } + nsresult rv = mTimer->Init(this, + SCHEDULE_TIMEOUT_MS, + nsITimer::TYPE_ONE_SHOT); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // Register shutdown observer so we can cancel the timer if we shutdown before + // the timer runs. + nsCOMPtr obsSrv = services::GetObserverService(); + if (NS_WARN_IF(!obsSrv)) { + return NS_ERROR_FAILURE; + } + return obsSrv->AddObserver(this, XPCOM_SHUTDOWN_TOPIC, false); + } + + void Cleanup() + { + // Cancel timer. + if (mTimer) { + mTimer->Cancel(); + mTimer = nullptr; + } + // Remove idle service observer. + nsCOMPtr idleSvc = + do_GetService("@mozilla.org/widget/idleservice;1"); + if (idleSvc) { + idleSvc->RemoveIdleObserver(this, TEMP_FILE_IDLE_TIME_S); + } + // Remove shutdown observer. + nsCOMPtr obsSrv = services::GetObserverService(); + if (obsSrv) { + obsSrv->RemoveObserver(this, XPCOM_SHUTDOWN_TOPIC); + } + } + + NS_IMETHODIMP Observe(nsISupports* aSubject, + const char* aTopic, + const char16_t* aData) + { + if (nsCRT::strcmp(aTopic, NS_TIMER_CALLBACK_TOPIC) == 0 && + NS_FAILED(RegisterIdleObserver())) { + Cleanup(); + } else if (nsCRT::strcmp(aTopic, OBSERVER_TOPIC_IDLE) == 0) { + // The user has been idle for a while, clean up the temp files. + // The idle service will drop its reference to this object after + // we exit, destroying this object. + RemoveAnonTempFileFiles(); + Cleanup(); + } else if (nsCRT::strcmp(aTopic, XPCOM_SHUTDOWN_TOPIC) == 0) { + Cleanup(); + } + return NS_OK; + } + + nsresult RegisterIdleObserver() + { + // Add this as an idle observer. When we've been idle for + // TEMP_FILE_IDLE_TIME_S seconds, we'll get a notification, and we'll then + // try to delete any stray temp files. + nsCOMPtr idleSvc = + do_GetService("@mozilla.org/widget/idleservice;1"); + if (!idleSvc) { + return NS_ERROR_FAILURE; + } + return idleSvc->AddIdleObserver(this, TEMP_FILE_IDLE_TIME_S); + } + + void RemoveAnonTempFileFiles() + { + nsCOMPtr tmpDir; + nsresult rv = GetTempDir(getter_AddRefs(tmpDir)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return; + } + + // Remove the directory recursively. + tmpDir->Remove(true); + } + +private: + ~nsAnonTempFileRemover() {} + + nsCOMPtr mTimer; +}; + +NS_IMPL_ISUPPORTS(nsAnonTempFileRemover, nsIObserver) + +nsresult +CreateAnonTempFileRemover() +{ + // Create a temp file remover. If Init() succeeds, the temp file remover is kept + // alive by a reference held by the observer service, since the temp file remover + // is a shutdown observer. We only create the temp file remover if we're running + // in the main process; there's no point in doing the temp file removal multiple + // times per startup. + if (!XRE_IsParentProcess()) { + return NS_OK; + } + RefPtr tempRemover = new nsAnonTempFileRemover(); + return tempRemover->Init(); +} + +#endif + diff --git a/xpcom/io/nsAnonymousTemporaryFile.h b/xpcom/io/nsAnonymousTemporaryFile.h new file mode 100644 index 000000000..d2f39528f --- /dev/null +++ b/xpcom/io/nsAnonymousTemporaryFile.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/. */ + +#pragma once + +#include "prio.h" +#include "nscore.h" + +/** + * OpenAnonymousTemporaryFile + * + * Creates and opens a temporary file which has a random name. Callers have no + * control over the file name, and the file is opened in a temporary location + * which is appropriate for the platform. + * + * Upon success, aOutFileDesc contains an opened handle to the temporary file. + * The caller is responsible for closing the file when they're finished with it. + * + * The file will be deleted when the file handle is closed. On non-Windows + * platforms the file will be unlinked before this function returns. On Windows + * the OS supplied delete-on-close mechanism is unreliable if the application + * crashes or the computer power cycles unexpectedly, so unopened temporary + * files are purged at some time after application startup. + * + */ +nsresult +NS_OpenAnonymousTemporaryFile(PRFileDesc** aOutFileDesc); + diff --git a/xpcom/io/nsAppDirectoryServiceDefs.h b/xpcom/io/nsAppDirectoryServiceDefs.h new file mode 100644 index 000000000..aa0a68816 --- /dev/null +++ b/xpcom/io/nsAppDirectoryServiceDefs.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/. */ + +#ifndef nsAppDirectoryServiceDefs_h___ +#define nsAppDirectoryServiceDefs_h___ + +//======================================================================================== +// +// Defines property names for directories available from standard nsIDirectoryServiceProviders. +// These keys are not guaranteed to exist because the nsIDirectoryServiceProviders which +// provide them are optional. +// +// Keys whose definition ends in "DIR" or "FILE" return a single nsIFile (or subclass). +// Keys whose definition ends in "LIST" return an nsISimpleEnumerator which enumerates a +// list of file objects. +// +// System and XPCOM level properties are defined in nsDirectoryServiceDefs.h. +// +//======================================================================================== + + +// -------------------------------------------------------------------------------------- +// Files and directories which exist on a per-product basis +// -------------------------------------------------------------------------------------- + +#define NS_APP_APPLICATION_REGISTRY_FILE "AppRegF" +#define NS_APP_APPLICATION_REGISTRY_DIR "AppRegD" + +#define NS_APP_DEFAULTS_50_DIR "DefRt" // The root dir of all defaults dirs +#define NS_APP_PREF_DEFAULTS_50_DIR "PrfDef" + +#define NS_APP_USER_PROFILES_ROOT_DIR "DefProfRt" // The dir where user profile dirs live. +#define NS_APP_USER_PROFILES_LOCAL_ROOT_DIR "DefProfLRt" // The dir where user profile temp dirs live. + +#define NS_APP_RES_DIR "ARes" +#define NS_APP_CHROME_DIR "AChrom" +#define NS_APP_PLUGINS_DIR "APlugns" // Deprecated - use NS_APP_PLUGINS_DIR_LIST +#define NS_APP_SEARCH_DIR "SrchPlugns" + +#define NS_APP_CHROME_DIR_LIST "AChromDL" +#define NS_APP_PLUGINS_DIR_LIST "APluginsDL" +#define NS_APP_SEARCH_DIR_LIST "SrchPluginsDL" +#define NS_APP_DISTRIBUTION_SEARCH_DIR_LIST "SrchPluginsDistDL" + +// -------------------------------------------------------------------------------------- +// Files and directories which exist on a per-profile basis +// These locations are typically provided by the profile mgr +// -------------------------------------------------------------------------------------- + +// In a shared profile environment, prefixing a profile-relative +// key with NS_SHARED returns a location that is shared by +// other users of the profile. Without this prefix, the consumer +// has exclusive access to this location. + +#define NS_SHARED "SHARED" + +#define NS_APP_PREFS_50_DIR "PrefD" // Directory which contains user prefs +#define NS_APP_PREFS_50_FILE "PrefF" +#define NS_APP_PREFS_DEFAULTS_DIR_LIST "PrefDL" +#define NS_EXT_PREFS_DEFAULTS_DIR_LIST "ExtPrefDL" +#define NS_APP_PREFS_OVERRIDE_DIR "PrefDOverride" // Directory for per-profile defaults + +#define NS_APP_USER_PROFILE_50_DIR "ProfD" +#define NS_APP_USER_PROFILE_LOCAL_50_DIR "ProfLD" + +#define NS_APP_USER_CHROME_DIR "UChrm" +#define NS_APP_USER_SEARCH_DIR "UsrSrchPlugns" + +#define NS_APP_LOCALSTORE_50_FILE "LclSt" +#define NS_APP_USER_PANELS_50_FILE "UPnls" +#define NS_APP_USER_MIMETYPES_50_FILE "UMimTyp" +#define NS_APP_CACHE_PARENT_DIR "cachePDir" + +#define NS_APP_DOWNLOADS_50_FILE "DLoads" + +#define NS_APP_SEARCH_50_FILE "SrchF" + +#define NS_APP_INSTALL_CLEANUP_DIR "XPIClnupD" //location of xpicleanup.dat xpicleanup.exe + +#define NS_APP_INDEXEDDB_PARENT_DIR "indexedDBPDir" + +#define NS_APP_PERMISSION_PARENT_DIR "permissionDBPDir" + +#if (defined(XP_WIN) || defined(XP_MACOSX)) && defined(MOZ_CONTENT_SANDBOX) +// +// NS_APP_CONTENT_PROCESS_TEMP_DIR refers to a directory that is read and +// write accessible from a sandboxed content process. The key may be used in +// either process, but the directory is intended to be used for short-lived +// files that need to be saved to the filesystem by the content process and +// don't need to survive browser restarts. The directory is reset on startup. +// The key is only valid when MOZ_CONTENT_SANDBOX is defined. When +// MOZ_CONTENT_SANDBOX is defined, the directory the key refers to differs +// depending on whether or not content sandboxing is enabled. +// +// When MOZ_CONTENT_SANDBOX is defined and sandboxing is enabled (versus +// manually disabled via prefs), the content process replaces NS_OS_TEMP_DIR +// with NS_APP_CONTENT_PROCESS_TEMP_DIR so that legacy code in content +// attempting to write to NS_OS_TEMP_DIR will write to +// NS_APP_CONTENT_PROCESS_TEMP_DIR instead. When MOZ_CONTENT_SANDBOX is +// defined but sandboxing is disabled, NS_APP_CONTENT_PROCESS_TEMP_DIR +// falls back to NS_OS_TEMP_DIR in both content and chrome processes. +// +// New code should avoid writing to the filesystem from the content process +// and should instead proxy through the parent process whenever possible. +// +// At present, all sandboxed content processes use the same directory for +// NS_APP_CONTENT_PROCESS_TEMP_DIR, but that should not be relied upon. +// +#define NS_APP_CONTENT_PROCESS_TEMP_DIR "ContentTmpD" +#else +// Otherwise NS_APP_CONTENT_PROCESS_TEMP_DIR must match NS_OS_TEMP_DIR. +#define NS_APP_CONTENT_PROCESS_TEMP_DIR "TmpD" +#endif // (defined(XP_WIN) || defined(XP_MACOSX)) && defined(MOZ_CONTENT_SANDBOX) + +#endif // nsAppDirectoryServiceDefs_h___ diff --git a/xpcom/io/nsAppFileLocationProvider.cpp b/xpcom/io/nsAppFileLocationProvider.cpp new file mode 100644 index 000000000..27ccd56dc --- /dev/null +++ b/xpcom/io/nsAppFileLocationProvider.cpp @@ -0,0 +1,609 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsAppFileLocationProvider.h" +#include "nsAppDirectoryServiceDefs.h" +#include "nsDirectoryServiceDefs.h" +#include "nsEnumeratorUtils.h" +#include "nsIAtom.h" +#include "nsIFile.h" +#include "nsString.h" +#include "nsXPIDLString.h" +#include "nsISimpleEnumerator.h" +#include "prenv.h" +#include "nsCRT.h" + +#if defined(MOZ_WIDGET_COCOA) +#include +#include "nsILocalFileMac.h" +#elif defined(XP_WIN) +#include +#include +#elif defined(XP_UNIX) +#include +#include +#include +#endif + + +// WARNING: These hard coded names need to go away. They need to +// come from localizable resources + +#if defined(MOZ_WIDGET_COCOA) +#define APP_REGISTRY_NAME NS_LITERAL_CSTRING("Application Registry") +#define ESSENTIAL_FILES NS_LITERAL_CSTRING("Essential Files") +#elif defined(XP_WIN) +#define APP_REGISTRY_NAME NS_LITERAL_CSTRING("registry.dat") +#else +#define APP_REGISTRY_NAME NS_LITERAL_CSTRING("appreg") +#endif + +// define default product directory +#define DEFAULT_PRODUCT_DIR NS_LITERAL_CSTRING(MOZ_USER_DIR) + +// Locally defined keys used by nsAppDirectoryEnumerator +#define NS_ENV_PLUGINS_DIR "EnvPlugins" // env var MOZ_PLUGIN_PATH +#define NS_USER_PLUGINS_DIR "UserPlugins" + +#ifdef MOZ_WIDGET_COCOA +#define NS_MACOSX_USER_PLUGIN_DIR "OSXUserPlugins" +#define NS_MACOSX_LOCAL_PLUGIN_DIR "OSXLocalPlugins" +#define NS_MACOSX_JAVA2_PLUGIN_DIR "OSXJavaPlugins" +#elif XP_UNIX +#define NS_SYSTEM_PLUGINS_DIR "SysPlugins" +#endif + +#define DEFAULTS_DIR_NAME NS_LITERAL_CSTRING("defaults") +#define DEFAULTS_PREF_DIR_NAME NS_LITERAL_CSTRING("pref") +#define RES_DIR_NAME NS_LITERAL_CSTRING("res") +#define CHROME_DIR_NAME NS_LITERAL_CSTRING("chrome") +#define PLUGINS_DIR_NAME NS_LITERAL_CSTRING("plugins") +#define SEARCH_DIR_NAME NS_LITERAL_CSTRING("searchplugins") + +//***************************************************************************** +// nsAppFileLocationProvider::Constructor/Destructor +//***************************************************************************** + +nsAppFileLocationProvider::nsAppFileLocationProvider() +{ +} + +//***************************************************************************** +// nsAppFileLocationProvider::nsISupports +//***************************************************************************** + +NS_IMPL_ISUPPORTS(nsAppFileLocationProvider, + nsIDirectoryServiceProvider, + nsIDirectoryServiceProvider2) + +//***************************************************************************** +// nsAppFileLocationProvider::nsIDirectoryServiceProvider +//***************************************************************************** + +NS_IMETHODIMP +nsAppFileLocationProvider::GetFile(const char* aProp, bool* aPersistent, + nsIFile** aResult) +{ + if (NS_WARN_IF(!aProp)) { + return NS_ERROR_INVALID_ARG; + } + + nsCOMPtr localFile; + nsresult rv = NS_ERROR_FAILURE; + + *aResult = nullptr; + *aPersistent = true; + +#ifdef MOZ_WIDGET_COCOA + FSRef fileRef; + nsCOMPtr macFile; +#endif + + if (nsCRT::strcmp(aProp, NS_APP_APPLICATION_REGISTRY_DIR) == 0) { + rv = GetProductDirectory(getter_AddRefs(localFile)); + } else if (nsCRT::strcmp(aProp, NS_APP_APPLICATION_REGISTRY_FILE) == 0) { + rv = GetProductDirectory(getter_AddRefs(localFile)); + if (NS_SUCCEEDED(rv)) { + rv = localFile->AppendNative(APP_REGISTRY_NAME); + } + } else if (nsCRT::strcmp(aProp, NS_APP_DEFAULTS_50_DIR) == 0) { + rv = CloneMozBinDirectory(getter_AddRefs(localFile)); + if (NS_SUCCEEDED(rv)) { + rv = localFile->AppendRelativeNativePath(DEFAULTS_DIR_NAME); + } + } else if (nsCRT::strcmp(aProp, NS_APP_PREF_DEFAULTS_50_DIR) == 0) { + rv = CloneMozBinDirectory(getter_AddRefs(localFile)); + if (NS_SUCCEEDED(rv)) { + rv = localFile->AppendRelativeNativePath(DEFAULTS_DIR_NAME); + if (NS_SUCCEEDED(rv)) { + rv = localFile->AppendRelativeNativePath(DEFAULTS_PREF_DIR_NAME); + } + } + } else if (nsCRT::strcmp(aProp, NS_APP_USER_PROFILES_ROOT_DIR) == 0) { + rv = GetDefaultUserProfileRoot(getter_AddRefs(localFile)); + } else if (nsCRT::strcmp(aProp, NS_APP_USER_PROFILES_LOCAL_ROOT_DIR) == 0) { + rv = GetDefaultUserProfileRoot(getter_AddRefs(localFile), true); + } else if (nsCRT::strcmp(aProp, NS_APP_RES_DIR) == 0) { + rv = CloneMozBinDirectory(getter_AddRefs(localFile)); + if (NS_SUCCEEDED(rv)) { + rv = localFile->AppendRelativeNativePath(RES_DIR_NAME); + } + } else if (nsCRT::strcmp(aProp, NS_APP_CHROME_DIR) == 0) { + rv = CloneMozBinDirectory(getter_AddRefs(localFile)); + if (NS_SUCCEEDED(rv)) { + rv = localFile->AppendRelativeNativePath(CHROME_DIR_NAME); + } + } else if (nsCRT::strcmp(aProp, NS_APP_PLUGINS_DIR) == 0) { + rv = CloneMozBinDirectory(getter_AddRefs(localFile)); + if (NS_SUCCEEDED(rv)) { + rv = localFile->AppendRelativeNativePath(PLUGINS_DIR_NAME); + } + } +#ifdef MOZ_WIDGET_COCOA + else if (nsCRT::strcmp(aProp, NS_MACOSX_USER_PLUGIN_DIR) == 0) { + if (::FSFindFolder(kUserDomain, kInternetPlugInFolderType, false, + &fileRef) == noErr) { + rv = NS_NewLocalFileWithFSRef(&fileRef, true, getter_AddRefs(macFile)); + if (NS_SUCCEEDED(rv)) { + localFile = macFile; + } + } + } else if (nsCRT::strcmp(aProp, NS_MACOSX_LOCAL_PLUGIN_DIR) == 0) { + if (::FSFindFolder(kLocalDomain, kInternetPlugInFolderType, false, + &fileRef) == noErr) { + rv = NS_NewLocalFileWithFSRef(&fileRef, true, getter_AddRefs(macFile)); + if (NS_SUCCEEDED(rv)) { + localFile = macFile; + } + } + } else if (nsCRT::strcmp(aProp, NS_MACOSX_JAVA2_PLUGIN_DIR) == 0) { + static const char* const java2PluginDirPath = + "/System/Library/Java/Support/Deploy.bundle/Contents/Resources/"; + rv = NS_NewNativeLocalFile(nsDependentCString(java2PluginDirPath), true, + getter_AddRefs(localFile)); + } +#else + else if (nsCRT::strcmp(aProp, NS_ENV_PLUGINS_DIR) == 0) { + NS_ERROR("Don't use nsAppFileLocationProvider::GetFile(NS_ENV_PLUGINS_DIR, ...). " + "Use nsAppFileLocationProvider::GetFiles(...)."); + const char* pathVar = PR_GetEnv("MOZ_PLUGIN_PATH"); + if (pathVar && *pathVar) + rv = NS_NewNativeLocalFile(nsDependentCString(pathVar), true, + getter_AddRefs(localFile)); + } else if (nsCRT::strcmp(aProp, NS_USER_PLUGINS_DIR) == 0) { +#ifdef ENABLE_SYSTEM_EXTENSION_DIRS + rv = GetProductDirectory(getter_AddRefs(localFile)); + if (NS_SUCCEEDED(rv)) { + rv = localFile->AppendRelativeNativePath(PLUGINS_DIR_NAME); + } +#else + rv = NS_ERROR_FAILURE; +#endif + } +#ifdef XP_UNIX + else if (nsCRT::strcmp(aProp, NS_SYSTEM_PLUGINS_DIR) == 0) { +#ifdef ENABLE_SYSTEM_EXTENSION_DIRS + static const char* const sysLPlgDir = +#if defined(HAVE_USR_LIB64_DIR) && defined(__LP64__) + "/usr/lib64/mozilla/plugins"; +#elif defined(__OpenBSD__) || defined (__FreeBSD__) + "/usr/local/lib/mozilla/plugins"; +#else + "/usr/lib/mozilla/plugins"; +#endif + rv = NS_NewNativeLocalFile(nsDependentCString(sysLPlgDir), + false, getter_AddRefs(localFile)); +#else + rv = NS_ERROR_FAILURE; +#endif + } +#endif +#endif + else if (nsCRT::strcmp(aProp, NS_APP_SEARCH_DIR) == 0) { + rv = CloneMozBinDirectory(getter_AddRefs(localFile)); + if (NS_SUCCEEDED(rv)) { + rv = localFile->AppendRelativeNativePath(SEARCH_DIR_NAME); + } + } else if (nsCRT::strcmp(aProp, NS_APP_USER_SEARCH_DIR) == 0) { + rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, aResult); + if (NS_SUCCEEDED(rv)) { + rv = (*aResult)->AppendNative(SEARCH_DIR_NAME); + } + } else if (nsCRT::strcmp(aProp, NS_APP_INSTALL_CLEANUP_DIR) == 0) { + // This is cloned so that embeddors will have a hook to override + // with their own cleanup dir. See bugzilla bug #105087 + rv = CloneMozBinDirectory(getter_AddRefs(localFile)); + } + + if (localFile && NS_SUCCEEDED(rv)) { + localFile.forget(aResult); + return NS_OK; + } + + return rv; +} + + +nsresult +nsAppFileLocationProvider::CloneMozBinDirectory(nsIFile** aLocalFile) +{ + if (NS_WARN_IF(!aLocalFile)) { + return NS_ERROR_INVALID_ARG; + } + nsresult rv; + + if (!mMozBinDirectory) { + // Get the mozilla bin directory + // 1. Check the directory service first for NS_XPCOM_CURRENT_PROCESS_DIR + // This will be set if a directory was passed to NS_InitXPCOM + // 2. If that doesn't work, set it to be the current process directory + nsCOMPtr + directoryService(do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID, &rv)); + if (NS_FAILED(rv)) { + return rv; + } + + rv = directoryService->Get(NS_XPCOM_CURRENT_PROCESS_DIR, NS_GET_IID(nsIFile), + getter_AddRefs(mMozBinDirectory)); + if (NS_FAILED(rv)) { + rv = directoryService->Get(NS_OS_CURRENT_PROCESS_DIR, NS_GET_IID(nsIFile), + getter_AddRefs(mMozBinDirectory)); + if (NS_FAILED(rv)) { + return rv; + } + } + } + + nsCOMPtr aFile; + rv = mMozBinDirectory->Clone(getter_AddRefs(aFile)); + if (NS_FAILED(rv)) { + return rv; + } + + NS_IF_ADDREF(*aLocalFile = aFile); + return NS_OK; +} + + +//---------------------------------------------------------------------------------------- +// GetProductDirectory - Gets the directory which contains the application data folder +// +// UNIX : ~/.mozilla/ +// WIN : \Mozilla +// Mac : :Documents:Mozilla: +//---------------------------------------------------------------------------------------- +nsresult +nsAppFileLocationProvider::GetProductDirectory(nsIFile** aLocalFile, + bool aLocal) +{ + if (NS_WARN_IF(!aLocalFile)) { + return NS_ERROR_INVALID_ARG; + } + + nsresult rv; + bool exists; + nsCOMPtr localDir; + +#if defined(MOZ_WIDGET_COCOA) + FSRef fsRef; + OSType folderType = aLocal ? (OSType)kCachedDataFolderType : + (OSType)kDomainLibraryFolderType; + OSErr err = ::FSFindFolder(kUserDomain, folderType, kCreateFolder, &fsRef); + if (err) { + return NS_ERROR_FAILURE; + } + NS_NewLocalFile(EmptyString(), true, getter_AddRefs(localDir)); + if (!localDir) { + return NS_ERROR_FAILURE; + } + nsCOMPtr localDirMac(do_QueryInterface(localDir)); + rv = localDirMac->InitWithFSRef(&fsRef); + if (NS_FAILED(rv)) { + return rv; + } +#elif defined(XP_WIN) + nsCOMPtr directoryService = + do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID, &rv); + if (NS_FAILED(rv)) { + return rv; + } + const char* prop = aLocal ? NS_WIN_LOCAL_APPDATA_DIR : NS_WIN_APPDATA_DIR; + rv = directoryService->Get(prop, NS_GET_IID(nsIFile), getter_AddRefs(localDir)); + if (NS_FAILED(rv)) { + return rv; + } +#elif defined(XP_UNIX) + rv = NS_NewNativeLocalFile(nsDependentCString(PR_GetEnv("HOME")), true, + getter_AddRefs(localDir)); + if (NS_FAILED(rv)) { + return rv; + } +#else +#error dont_know_how_to_get_product_dir_on_your_platform +#endif + + rv = localDir->AppendRelativeNativePath(DEFAULT_PRODUCT_DIR); + if (NS_FAILED(rv)) { + return rv; + } + rv = localDir->Exists(&exists); + + if (NS_SUCCEEDED(rv) && !exists) { + rv = localDir->Create(nsIFile::DIRECTORY_TYPE, 0700); + } + + if (NS_FAILED(rv)) { + return rv; + } + + localDir.forget(aLocalFile); + + return rv; +} + + +//---------------------------------------------------------------------------------------- +// GetDefaultUserProfileRoot - Gets the directory which contains each user profile dir +// +// UNIX : ~/.mozilla/ +// WIN : \Mozilla\Profiles +// Mac : :Documents:Mozilla:Profiles: +//---------------------------------------------------------------------------------------- +nsresult +nsAppFileLocationProvider::GetDefaultUserProfileRoot(nsIFile** aLocalFile, + bool aLocal) +{ + if (NS_WARN_IF(!aLocalFile)) { + return NS_ERROR_INVALID_ARG; + } + + nsresult rv; + nsCOMPtr localDir; + + rv = GetProductDirectory(getter_AddRefs(localDir), aLocal); + if (NS_FAILED(rv)) { + return rv; + } + +#if defined(MOZ_WIDGET_COCOA) || defined(XP_WIN) + // These 3 platforms share this part of the path - do them as one + rv = localDir->AppendRelativeNativePath(NS_LITERAL_CSTRING("Profiles")); + if (NS_FAILED(rv)) { + return rv; + } + + bool exists; + rv = localDir->Exists(&exists); + if (NS_SUCCEEDED(rv) && !exists) { + rv = localDir->Create(nsIFile::DIRECTORY_TYPE, 0775); + } + if (NS_FAILED(rv)) { + return rv; + } +#endif + + localDir.forget(aLocalFile); + + return rv; +} + +//***************************************************************************** +// nsAppFileLocationProvider::nsIDirectoryServiceProvider2 +//***************************************************************************** + +class nsAppDirectoryEnumerator : public nsISimpleEnumerator +{ +public: + NS_DECL_ISUPPORTS + + /** + * aKeyList is a null-terminated list of properties which are provided by aProvider + * They do not need to be publicly defined keys. + */ + nsAppDirectoryEnumerator(nsIDirectoryServiceProvider* aProvider, + const char* aKeyList[]) : + mProvider(aProvider), + mCurrentKey(aKeyList) + { + } + + NS_IMETHOD HasMoreElements(bool* aResult) override + { + while (!mNext && *mCurrentKey) { + bool dontCare; + nsCOMPtr testFile; + (void)mProvider->GetFile(*mCurrentKey++, &dontCare, getter_AddRefs(testFile)); + // Don't return a file which does not exist. + bool exists; + if (testFile && NS_SUCCEEDED(testFile->Exists(&exists)) && exists) { + mNext = testFile; + } + } + *aResult = mNext != nullptr; + return NS_OK; + } + + NS_IMETHOD GetNext(nsISupports** aResult) override + { + if (NS_WARN_IF(!aResult)) { + return NS_ERROR_INVALID_ARG; + } + *aResult = nullptr; + + bool hasMore; + HasMoreElements(&hasMore); + if (!hasMore) { + return NS_ERROR_FAILURE; + } + + *aResult = mNext; + NS_IF_ADDREF(*aResult); + mNext = nullptr; + + return *aResult ? NS_OK : NS_ERROR_FAILURE; + } + +protected: + nsCOMPtr mProvider; + const char** mCurrentKey; + nsCOMPtr mNext; + + // Virtual destructor since subclass nsPathsDirectoryEnumerator + // does not re-implement Release() + virtual ~nsAppDirectoryEnumerator() + { + } +}; + +NS_IMPL_ISUPPORTS(nsAppDirectoryEnumerator, nsISimpleEnumerator) + +/* nsPathsDirectoryEnumerator and PATH_SEPARATOR + * are not used on MacOS/X. */ + +#if defined(XP_WIN) /* Win32 */ +#define PATH_SEPARATOR ';' +#else +#define PATH_SEPARATOR ':' +#endif + +class nsPathsDirectoryEnumerator final + : public nsAppDirectoryEnumerator +{ + ~nsPathsDirectoryEnumerator() {} + +public: + /** + * aKeyList is a null-terminated list. + * The first element is a path list. + * The remainder are properties provided by aProvider. + * They do not need to be publicly defined keys. + */ + nsPathsDirectoryEnumerator(nsIDirectoryServiceProvider* aProvider, + const char* aKeyList[]) : + nsAppDirectoryEnumerator(aProvider, aKeyList + 1), + mEndPath(aKeyList[0]) + { + } + + NS_IMETHOD HasMoreElements(bool* aResult) + { + if (mEndPath) + while (!mNext && *mEndPath) { + const char* pathVar = mEndPath; + + // skip PATH_SEPARATORs at the begining of the mEndPath + while (*pathVar == PATH_SEPARATOR) { + ++pathVar; + } + + do { + ++mEndPath; + } while (*mEndPath && *mEndPath != PATH_SEPARATOR); + + nsCOMPtr localFile; + NS_NewNativeLocalFile(Substring(pathVar, mEndPath), + true, + getter_AddRefs(localFile)); + if (*mEndPath == PATH_SEPARATOR) { + ++mEndPath; + } + // Don't return a "file" (directory) which does not exist. + bool exists; + if (localFile && + NS_SUCCEEDED(localFile->Exists(&exists)) && + exists) { + mNext = localFile; + } + } + if (mNext) { + *aResult = true; + } else { + nsAppDirectoryEnumerator::HasMoreElements(aResult); + } + + return NS_OK; + } + +protected: + const char* mEndPath; +}; + +NS_IMETHODIMP +nsAppFileLocationProvider::GetFiles(const char* aProp, + nsISimpleEnumerator** aResult) +{ + if (NS_WARN_IF(!aResult)) { + return NS_ERROR_INVALID_ARG; + } + *aResult = nullptr; + nsresult rv = NS_ERROR_FAILURE; + + if (!nsCRT::strcmp(aProp, NS_APP_PLUGINS_DIR_LIST)) { +#ifdef MOZ_WIDGET_COCOA + // As of Java for Mac OS X 10.5 Update 10, Apple has (in effect) deprecated Java Plugin2 on + // on OS X 10.5, and removed the soft link to it from /Library/Internet Plug-Ins/. Java + // Plugin2 is still present and usable, but there are no longer any links to it in the + // "normal" locations. So we won't be able to find it unless we look in the "non-normal" + // location where it actually is. Safari can use the WebKit-specific JavaPluginCocoa.bundle, + // which (of course) is still fully supported on OS X 10.5. But we have no alternative to + // using Java Plugin2. For more information see bug 668639. + static const char* keys[] = { + NS_APP_PLUGINS_DIR, + NS_MACOSX_USER_PLUGIN_DIR, + NS_MACOSX_LOCAL_PLUGIN_DIR, + IsOSXLeopard() ? NS_MACOSX_JAVA2_PLUGIN_DIR : nullptr, + nullptr + }; + *aResult = new nsAppDirectoryEnumerator(this, keys); +#else +#ifdef XP_UNIX + static const char* keys[] = { nullptr, NS_USER_PLUGINS_DIR, NS_APP_PLUGINS_DIR, NS_SYSTEM_PLUGINS_DIR, nullptr }; +#else + static const char* keys[] = { nullptr, NS_USER_PLUGINS_DIR, NS_APP_PLUGINS_DIR, nullptr }; +#endif + if (!keys[0] && !(keys[0] = PR_GetEnv("MOZ_PLUGIN_PATH"))) { + static const char nullstr = 0; + keys[0] = &nullstr; + } + *aResult = new nsPathsDirectoryEnumerator(this, keys); +#endif + NS_ADDREF(*aResult); + rv = NS_OK; + } + if (!nsCRT::strcmp(aProp, NS_APP_SEARCH_DIR_LIST)) { + static const char* keys[] = { nullptr, NS_APP_USER_SEARCH_DIR, nullptr }; + if (!keys[0] && !(keys[0] = PR_GetEnv("MOZ_SEARCH_ENGINE_PATH"))) { + static const char nullstr = 0; + keys[0] = &nullstr; + } + *aResult = new nsPathsDirectoryEnumerator(this, keys); + NS_ADDREF(*aResult); + rv = NS_OK; + } + if (!strcmp(aProp, NS_APP_DISTRIBUTION_SEARCH_DIR_LIST)) { + return NS_NewEmptyEnumerator(aResult); + } + return rv; +} + +#if defined(MOZ_WIDGET_COCOA) +bool +nsAppFileLocationProvider::IsOSXLeopard() +{ + static SInt32 version = 0; + + if (!version) { + OSErr err = ::Gestalt(gestaltSystemVersion, &version); + if (err != noErr) { + version = 0; + } else { + version &= 0xFFFF; // The system version is in the low order word + } + } + + return ((version >= 0x1050) && (version < 0x1060)); +} +#endif diff --git a/xpcom/io/nsAppFileLocationProvider.h b/xpcom/io/nsAppFileLocationProvider.h new file mode 100644 index 000000000..248c5b828 --- /dev/null +++ b/xpcom/io/nsAppFileLocationProvider.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 nsAppFileLocationProvider_h +#define nsAppFileLocationProvider_h + +#include "nsIDirectoryService.h" +#include "nsIFile.h" +#include "mozilla/Attributes.h" + +class nsIFile; + +//***************************************************************************** +// class nsAppFileLocationProvider +//***************************************************************************** + +class nsAppFileLocationProvider final : public nsIDirectoryServiceProvider2 +{ +public: + nsAppFileLocationProvider(); + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIDIRECTORYSERVICEPROVIDER + NS_DECL_NSIDIRECTORYSERVICEPROVIDER2 + +private: + ~nsAppFileLocationProvider() + { + } + +protected: + nsresult CloneMozBinDirectory(nsIFile** aLocalFile); + /** + * Get the product directory. This is a user-specific directory for storing + * application settings (e.g. the Application Data directory on windows + * systems). + * @param aLocal If true, should try to get a directory that is only stored + * locally (ie not transferred with roaming profiles) + */ + nsresult GetProductDirectory(nsIFile** aLocalFile, + bool aLocal = false); + nsresult GetDefaultUserProfileRoot(nsIFile** aLocalFile, + bool aLocal = false); + +#if defined(MOZ_WIDGET_COCOA) + static bool IsOSXLeopard(); +#endif + + nsCOMPtr mMozBinDirectory; +}; + +#endif diff --git a/xpcom/io/nsBinaryStream.cpp b/xpcom/io/nsBinaryStream.cpp new file mode 100644 index 000000000..82159952e --- /dev/null +++ b/xpcom/io/nsBinaryStream.cpp @@ -0,0 +1,1016 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 contains implementations of the nsIBinaryInputStream and + * nsIBinaryOutputStream interfaces. Together, these interfaces allows reading + * and writing of primitive data types (integers, floating-point values, + * booleans, etc.) to a stream in a binary, untagged, fixed-endianness format. + * This might be used, for example, to implement network protocols or to + * produce architecture-neutral binary disk files, i.e. ones that can be read + * and written by both big-endian and little-endian platforms. Output is + * written in big-endian order (high-order byte first), as this is traditional + * network order. + * + * @See nsIBinaryInputStream + * @See nsIBinaryOutputStream + */ +#include +#include + +#include "nsBinaryStream.h" + +#include "mozilla/EndianUtils.h" +#include "mozilla/PodOperations.h" +#include "mozilla/UniquePtr.h" + +#include "nsCRT.h" +#include "nsString.h" +#include "nsISerializable.h" +#include "nsIClassInfo.h" +#include "nsComponentManagerUtils.h" +#include "nsIURI.h" // for NS_IURI_IID +#include "nsIX509Cert.h" // for NS_IX509CERT_IID + +#include "jsfriendapi.h" + +using mozilla::MakeUnique; +using mozilla::PodCopy; +using mozilla::UniquePtr; + +NS_IMPL_ISUPPORTS(nsBinaryOutputStream, + nsIObjectOutputStream, + nsIBinaryOutputStream, + nsIOutputStream) + +NS_IMETHODIMP +nsBinaryOutputStream::Flush() +{ + if (NS_WARN_IF(!mOutputStream)) { + return NS_ERROR_UNEXPECTED; + } + return mOutputStream->Flush(); +} + +NS_IMETHODIMP +nsBinaryOutputStream::Close() +{ + if (NS_WARN_IF(!mOutputStream)) { + return NS_ERROR_UNEXPECTED; + } + return mOutputStream->Close(); +} + +NS_IMETHODIMP +nsBinaryOutputStream::Write(const char* aBuf, uint32_t aCount, + uint32_t* aActualBytes) +{ + if (NS_WARN_IF(!mOutputStream)) { + return NS_ERROR_UNEXPECTED; + } + return mOutputStream->Write(aBuf, aCount, aActualBytes); +} + +NS_IMETHODIMP +nsBinaryOutputStream::WriteFrom(nsIInputStream* aInStr, uint32_t aCount, + uint32_t* aResult) +{ + NS_NOTREACHED("WriteFrom"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsBinaryOutputStream::WriteSegments(nsReadSegmentFun aReader, void* aClosure, + uint32_t aCount, uint32_t* aResult) +{ + NS_NOTREACHED("WriteSegments"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsBinaryOutputStream::IsNonBlocking(bool* aNonBlocking) +{ + if (NS_WARN_IF(!mOutputStream)) { + return NS_ERROR_UNEXPECTED; + } + return mOutputStream->IsNonBlocking(aNonBlocking); +} + +nsresult +nsBinaryOutputStream::WriteFully(const char* aBuf, uint32_t aCount) +{ + if (NS_WARN_IF(!mOutputStream)) { + return NS_ERROR_UNEXPECTED; + } + + nsresult rv; + uint32_t bytesWritten; + + rv = mOutputStream->Write(aBuf, aCount, &bytesWritten); + if (NS_FAILED(rv)) { + return rv; + } + if (bytesWritten != aCount) { + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +NS_IMETHODIMP +nsBinaryOutputStream::SetOutputStream(nsIOutputStream* aOutputStream) +{ + if (NS_WARN_IF(!aOutputStream)) { + return NS_ERROR_INVALID_ARG; + } + mOutputStream = aOutputStream; + mBufferAccess = do_QueryInterface(aOutputStream); + return NS_OK; +} + +NS_IMETHODIMP +nsBinaryOutputStream::WriteBoolean(bool aBoolean) +{ + return Write8(aBoolean); +} + +NS_IMETHODIMP +nsBinaryOutputStream::Write8(uint8_t aByte) +{ + return WriteFully((const char*)&aByte, sizeof(aByte)); +} + +NS_IMETHODIMP +nsBinaryOutputStream::Write16(uint16_t aNum) +{ + aNum = mozilla::NativeEndian::swapToBigEndian(aNum); + return WriteFully((const char*)&aNum, sizeof(aNum)); +} + +NS_IMETHODIMP +nsBinaryOutputStream::Write32(uint32_t aNum) +{ + aNum = mozilla::NativeEndian::swapToBigEndian(aNum); + return WriteFully((const char*)&aNum, sizeof(aNum)); +} + +NS_IMETHODIMP +nsBinaryOutputStream::Write64(uint64_t aNum) +{ + nsresult rv; + uint32_t bytesWritten; + + aNum = mozilla::NativeEndian::swapToBigEndian(aNum); + rv = Write(reinterpret_cast(&aNum), sizeof(aNum), &bytesWritten); + if (NS_FAILED(rv)) { + return rv; + } + if (bytesWritten != sizeof(aNum)) { + return NS_ERROR_FAILURE; + } + return rv; +} + +NS_IMETHODIMP +nsBinaryOutputStream::WriteFloat(float aFloat) +{ + NS_ASSERTION(sizeof(float) == sizeof(uint32_t), + "False assumption about sizeof(float)"); + return Write32(*reinterpret_cast(&aFloat)); +} + +NS_IMETHODIMP +nsBinaryOutputStream::WriteDouble(double aDouble) +{ + NS_ASSERTION(sizeof(double) == sizeof(uint64_t), + "False assumption about sizeof(double)"); + return Write64(*reinterpret_cast(&aDouble)); +} + +NS_IMETHODIMP +nsBinaryOutputStream::WriteStringZ(const char* aString) +{ + uint32_t length; + nsresult rv; + + length = strlen(aString); + rv = Write32(length); + if (NS_FAILED(rv)) { + return rv; + } + return WriteFully(aString, length); +} + +NS_IMETHODIMP +nsBinaryOutputStream::WriteWStringZ(const char16_t* aString) +{ + uint32_t length, byteCount; + nsresult rv; + + length = NS_strlen(aString); + rv = Write32(length); + if (NS_FAILED(rv)) { + return rv; + } + + if (length == 0) { + return NS_OK; + } + byteCount = length * sizeof(char16_t); + +#ifdef IS_BIG_ENDIAN + rv = WriteBytes(reinterpret_cast(aString), byteCount); +#else + // XXX use WriteSegments here to avoid copy! + char16_t* copy; + char16_t temp[64]; + if (length <= 64) { + copy = temp; + } else { + copy = reinterpret_cast(malloc(byteCount)); + if (!copy) { + return NS_ERROR_OUT_OF_MEMORY; + } + } + NS_ASSERTION((uintptr_t(aString) & 0x1) == 0, "aString not properly aligned"); + mozilla::NativeEndian::copyAndSwapToBigEndian(copy, aString, length); + rv = WriteBytes(reinterpret_cast(copy), byteCount); + if (copy != temp) { + free(copy); + } +#endif + + return rv; +} + +NS_IMETHODIMP +nsBinaryOutputStream::WriteUtf8Z(const char16_t* aString) +{ + return WriteStringZ(NS_ConvertUTF16toUTF8(aString).get()); +} + +NS_IMETHODIMP +nsBinaryOutputStream::WriteBytes(const char* aString, uint32_t aLength) +{ + nsresult rv; + uint32_t bytesWritten; + + rv = Write(aString, aLength, &bytesWritten); + if (NS_FAILED(rv)) { + return rv; + } + if (bytesWritten != aLength) { + return NS_ERROR_FAILURE; + } + return rv; +} + +NS_IMETHODIMP +nsBinaryOutputStream::WriteByteArray(uint8_t* aBytes, uint32_t aLength) +{ + return WriteBytes(reinterpret_cast(aBytes), aLength); +} + +NS_IMETHODIMP +nsBinaryOutputStream::WriteObject(nsISupports* aObject, bool aIsStrongRef) +{ + return WriteCompoundObject(aObject, NS_GET_IID(nsISupports), + aIsStrongRef); +} + +NS_IMETHODIMP +nsBinaryOutputStream::WriteSingleRefObject(nsISupports* aObject) +{ + return WriteCompoundObject(aObject, NS_GET_IID(nsISupports), + true); +} + +NS_IMETHODIMP +nsBinaryOutputStream::WriteCompoundObject(nsISupports* aObject, + const nsIID& aIID, + bool aIsStrongRef) +{ + nsCOMPtr classInfo = do_QueryInterface(aObject); + nsCOMPtr serializable = do_QueryInterface(aObject); + + // Can't deal with weak refs + if (NS_WARN_IF(!aIsStrongRef)) { + return NS_ERROR_UNEXPECTED; + } + if (NS_WARN_IF(!classInfo) || NS_WARN_IF(!serializable)) { + return NS_ERROR_NOT_AVAILABLE; + } + + nsCID cid; + nsresult rv = classInfo->GetClassIDNoAlloc(&cid); + if (NS_SUCCEEDED(rv)) { + rv = WriteID(cid); + } else { + nsCID* cidptr = nullptr; + rv = classInfo->GetClassID(&cidptr); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = WriteID(*cidptr); + + free(cidptr); + } + + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = WriteID(aIID); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return serializable->Write(this); +} + +NS_IMETHODIMP +nsBinaryOutputStream::WriteID(const nsIID& aIID) +{ + nsresult rv = Write32(aIID.m0); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = Write16(aIID.m1); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = Write16(aIID.m2); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + for (int i = 0; i < 8; ++i) { + rv = Write8(aIID.m3[i]); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + + return NS_OK; +} + +NS_IMETHODIMP_(char*) +nsBinaryOutputStream::GetBuffer(uint32_t aLength, uint32_t aAlignMask) +{ + if (mBufferAccess) { + return mBufferAccess->GetBuffer(aLength, aAlignMask); + } + return nullptr; +} + +NS_IMETHODIMP_(void) +nsBinaryOutputStream::PutBuffer(char* aBuffer, uint32_t aLength) +{ + if (mBufferAccess) { + mBufferAccess->PutBuffer(aBuffer, aLength); + } +} + +NS_IMPL_ISUPPORTS(nsBinaryInputStream, + nsIObjectInputStream, + nsIBinaryInputStream, + nsIInputStream) + +NS_IMETHODIMP +nsBinaryInputStream::Available(uint64_t* aResult) +{ + if (NS_WARN_IF(!mInputStream)) { + return NS_ERROR_UNEXPECTED; + } + return mInputStream->Available(aResult); +} + +NS_IMETHODIMP +nsBinaryInputStream::Read(char* aBuffer, uint32_t aCount, uint32_t* aNumRead) +{ + if (NS_WARN_IF(!mInputStream)) { + return NS_ERROR_UNEXPECTED; + } + + // mInputStream might give us short reads, so deal with that. + uint32_t totalRead = 0; + + uint32_t bytesRead; + do { + nsresult rv = mInputStream->Read(aBuffer, aCount, &bytesRead); + if (rv == NS_BASE_STREAM_WOULD_BLOCK && totalRead != 0) { + // We already read some data. Return it. + break; + } + + if (NS_FAILED(rv)) { + return rv; + } + + totalRead += bytesRead; + aBuffer += bytesRead; + aCount -= bytesRead; + } while (aCount != 0 && bytesRead != 0); + + *aNumRead = totalRead; + + return NS_OK; +} + + +// when forwarding ReadSegments to mInputStream, we need to make sure +// 'this' is being passed to the writer each time. To do this, we need +// a thunking function which keeps the real input stream around. + +// the closure wrapper +struct MOZ_STACK_CLASS ReadSegmentsClosure +{ + nsCOMPtr mRealInputStream; + void* mRealClosure; + nsWriteSegmentFun mRealWriter; + nsresult mRealResult; + uint32_t mBytesRead; // to properly implement aToOffset +}; + +// the thunking function +static nsresult +ReadSegmentForwardingThunk(nsIInputStream* aStream, + void* aClosure, + const char* aFromSegment, + uint32_t aToOffset, + uint32_t aCount, + uint32_t* aWriteCount) +{ + ReadSegmentsClosure* thunkClosure = + reinterpret_cast(aClosure); + + NS_ASSERTION(NS_SUCCEEDED(thunkClosure->mRealResult), + "How did this get to be a failure status?"); + + thunkClosure->mRealResult = + thunkClosure->mRealWriter(thunkClosure->mRealInputStream, + thunkClosure->mRealClosure, + aFromSegment, + thunkClosure->mBytesRead + aToOffset, + aCount, aWriteCount); + + return thunkClosure->mRealResult; +} + + +NS_IMETHODIMP +nsBinaryInputStream::ReadSegments(nsWriteSegmentFun aWriter, void* aClosure, + uint32_t aCount, uint32_t* aResult) +{ + if (NS_WARN_IF(!mInputStream)) { + return NS_ERROR_UNEXPECTED; + } + + ReadSegmentsClosure thunkClosure = { this, aClosure, aWriter, NS_OK, 0 }; + + // mInputStream might give us short reads, so deal with that. + uint32_t bytesRead; + do { + nsresult rv = mInputStream->ReadSegments(ReadSegmentForwardingThunk, + &thunkClosure, + aCount, &bytesRead); + + if (rv == NS_BASE_STREAM_WOULD_BLOCK && thunkClosure.mBytesRead != 0) { + // We already read some data. Return it. + break; + } + + if (NS_FAILED(rv)) { + return rv; + } + + thunkClosure.mBytesRead += bytesRead; + aCount -= bytesRead; + } while (aCount != 0 && bytesRead != 0 && + NS_SUCCEEDED(thunkClosure.mRealResult)); + + *aResult = thunkClosure.mBytesRead; + + return NS_OK; +} + +NS_IMETHODIMP +nsBinaryInputStream::IsNonBlocking(bool* aNonBlocking) +{ + if (NS_WARN_IF(!mInputStream)) { + return NS_ERROR_UNEXPECTED; + } + return mInputStream->IsNonBlocking(aNonBlocking); +} + +NS_IMETHODIMP +nsBinaryInputStream::Close() +{ + if (NS_WARN_IF(!mInputStream)) { + return NS_ERROR_UNEXPECTED; + } + return mInputStream->Close(); +} + +NS_IMETHODIMP +nsBinaryInputStream::SetInputStream(nsIInputStream* aInputStream) +{ + if (NS_WARN_IF(!aInputStream)) { + return NS_ERROR_INVALID_ARG; + } + mInputStream = aInputStream; + mBufferAccess = do_QueryInterface(aInputStream); + return NS_OK; +} + +NS_IMETHODIMP +nsBinaryInputStream::ReadBoolean(bool* aBoolean) +{ + uint8_t byteResult; + nsresult rv = Read8(&byteResult); + if (NS_FAILED(rv)) { + return rv; + } + *aBoolean = !!byteResult; + return rv; +} + +NS_IMETHODIMP +nsBinaryInputStream::Read8(uint8_t* aByte) +{ + nsresult rv; + uint32_t bytesRead; + + rv = Read(reinterpret_cast(aByte), sizeof(*aByte), &bytesRead); + if (NS_FAILED(rv)) { + return rv; + } + if (bytesRead != 1) { + return NS_ERROR_FAILURE; + } + return rv; +} + +NS_IMETHODIMP +nsBinaryInputStream::Read16(uint16_t* aNum) +{ + uint32_t bytesRead; + nsresult rv = Read(reinterpret_cast(aNum), sizeof(*aNum), &bytesRead); + if (NS_FAILED(rv)) { + return rv; + } + if (bytesRead != sizeof(*aNum)) { + return NS_ERROR_FAILURE; + } + *aNum = mozilla::NativeEndian::swapFromBigEndian(*aNum); + return rv; +} + +NS_IMETHODIMP +nsBinaryInputStream::Read32(uint32_t* aNum) +{ + uint32_t bytesRead; + nsresult rv = Read(reinterpret_cast(aNum), sizeof(*aNum), &bytesRead); + if (NS_FAILED(rv)) { + return rv; + } + if (bytesRead != sizeof(*aNum)) { + return NS_ERROR_FAILURE; + } + *aNum = mozilla::NativeEndian::swapFromBigEndian(*aNum); + return rv; +} + +NS_IMETHODIMP +nsBinaryInputStream::Read64(uint64_t* aNum) +{ + uint32_t bytesRead; + nsresult rv = Read(reinterpret_cast(aNum), sizeof(*aNum), &bytesRead); + if (NS_FAILED(rv)) { + return rv; + } + if (bytesRead != sizeof(*aNum)) { + return NS_ERROR_FAILURE; + } + *aNum = mozilla::NativeEndian::swapFromBigEndian(*aNum); + return rv; +} + +NS_IMETHODIMP +nsBinaryInputStream::ReadFloat(float* aFloat) +{ + NS_ASSERTION(sizeof(float) == sizeof(uint32_t), + "False assumption about sizeof(float)"); + return Read32(reinterpret_cast(aFloat)); +} + +NS_IMETHODIMP +nsBinaryInputStream::ReadDouble(double* aDouble) +{ + NS_ASSERTION(sizeof(double) == sizeof(uint64_t), + "False assumption about sizeof(double)"); + return Read64(reinterpret_cast(aDouble)); +} + +static nsresult +WriteSegmentToCString(nsIInputStream* aStream, + void* aClosure, + const char* aFromSegment, + uint32_t aToOffset, + uint32_t aCount, + uint32_t* aWriteCount) +{ + nsACString* outString = static_cast(aClosure); + + outString->Append(aFromSegment, aCount); + + *aWriteCount = aCount; + + return NS_OK; +} + +NS_IMETHODIMP +nsBinaryInputStream::ReadCString(nsACString& aString) +{ + nsresult rv; + uint32_t length, bytesRead; + + rv = Read32(&length); + if (NS_FAILED(rv)) { + return rv; + } + + aString.Truncate(); + rv = ReadSegments(WriteSegmentToCString, &aString, length, &bytesRead); + if (NS_FAILED(rv)) { + return rv; + } + + if (bytesRead != length) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + + +// sometimes, WriteSegmentToString will be handed an odd-number of +// bytes, which means we only have half of the last char16_t +struct WriteStringClosure +{ + char16_t* mWriteCursor; + bool mHasCarryoverByte; + char mCarryoverByte; +}; + +// there are a few cases we have to account for here: +// * even length buffer, no carryover - easy, just append +// * odd length buffer, no carryover - the last byte needs to be saved +// for carryover +// * odd length buffer, with carryover - first byte needs to be used +// with the carryover byte, and +// the rest of the even length +// buffer is appended as normal +// * even length buffer, with carryover - the first byte needs to be +// used with the previous carryover byte. +// this gives you an odd length buffer, +// so you have to save the last byte for +// the next carryover + + +// same version of the above, but with correct casting and endian swapping +static nsresult +WriteSegmentToString(nsIInputStream* aStream, + void* aClosure, + const char* aFromSegment, + uint32_t aToOffset, + uint32_t aCount, + uint32_t* aWriteCount) +{ + NS_PRECONDITION(aCount > 0, "Why are we being told to write 0 bytes?"); + NS_PRECONDITION(sizeof(char16_t) == 2, "We can't handle other sizes!"); + + WriteStringClosure* closure = static_cast(aClosure); + char16_t* cursor = closure->mWriteCursor; + + // we're always going to consume the whole buffer no matter what + // happens, so take care of that right now.. that allows us to + // tweak aCount later. Do NOT move this! + *aWriteCount = aCount; + + // if the last Write had an odd-number of bytes read, then + if (closure->mHasCarryoverByte) { + // re-create the two-byte sequence we want to work with + char bytes[2] = { closure->mCarryoverByte, *aFromSegment }; + *cursor = *(char16_t*)bytes; + // Now the little endianness dance + mozilla::NativeEndian::swapToBigEndianInPlace(cursor, 1); + ++cursor; + + // now skip past the first byte of the buffer.. code from here + // can assume normal operations, but should not assume aCount + // is relative to the ORIGINAL buffer + ++aFromSegment; + --aCount; + + closure->mHasCarryoverByte = false; + } + + // this array is possibly unaligned... be careful how we access it! + const char16_t* unicodeSegment = + reinterpret_cast(aFromSegment); + + // calculate number of full characters in segment (aCount could be odd!) + uint32_t segmentLength = aCount / sizeof(char16_t); + + // copy all data into our aligned buffer. byte swap if necessary. + // cursor may be unaligned, so we cannot use copyAndSwapToBigEndian directly + memcpy(cursor, unicodeSegment, segmentLength * sizeof(char16_t)); + char16_t* end = cursor + segmentLength; + mozilla::NativeEndian::swapToBigEndianInPlace(cursor, segmentLength); + closure->mWriteCursor = end; + + // remember this is the modifed aCount and aFromSegment, + // so that will take into account the fact that we might have + // skipped the first byte in the buffer + if (aCount % sizeof(char16_t) != 0) { + // we must have had a carryover byte, that we'll need the next + // time around + closure->mCarryoverByte = aFromSegment[aCount - 1]; + closure->mHasCarryoverByte = true; + } + + return NS_OK; +} + + +NS_IMETHODIMP +nsBinaryInputStream::ReadString(nsAString& aString) +{ + nsresult rv; + uint32_t length, bytesRead; + + rv = Read32(&length); + if (NS_FAILED(rv)) { + return rv; + } + + if (length == 0) { + aString.Truncate(); + return NS_OK; + } + + // pre-allocate output buffer, and get direct access to buffer... + if (!aString.SetLength(length, mozilla::fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + nsAString::iterator start; + aString.BeginWriting(start); + + WriteStringClosure closure; + closure.mWriteCursor = start.get(); + closure.mHasCarryoverByte = false; + + rv = ReadSegments(WriteSegmentToString, &closure, + length * sizeof(char16_t), &bytesRead); + if (NS_FAILED(rv)) { + return rv; + } + + NS_ASSERTION(!closure.mHasCarryoverByte, "some strange stream corruption!"); + + if (bytesRead != length * sizeof(char16_t)) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsBinaryInputStream::ReadBytes(uint32_t aLength, char** aResult) +{ + nsresult rv; + uint32_t bytesRead; + char* s; + + s = reinterpret_cast(malloc(aLength)); + if (!s) { + return NS_ERROR_OUT_OF_MEMORY; + } + + rv = Read(s, aLength, &bytesRead); + if (NS_FAILED(rv)) { + free(s); + return rv; + } + if (bytesRead != aLength) { + free(s); + return NS_ERROR_FAILURE; + } + + *aResult = s; + return NS_OK; +} + +NS_IMETHODIMP +nsBinaryInputStream::ReadByteArray(uint32_t aLength, uint8_t** aResult) +{ + return ReadBytes(aLength, reinterpret_cast(aResult)); +} + +NS_IMETHODIMP +nsBinaryInputStream::ReadArrayBuffer(uint32_t aLength, + JS::Handle aBuffer, + JSContext* aCx, uint32_t* aReadLength) +{ + if (!aBuffer.isObject()) { + return NS_ERROR_FAILURE; + } + JS::RootedObject buffer(aCx, &aBuffer.toObject()); + if (!JS_IsArrayBufferObject(buffer)) { + return NS_ERROR_FAILURE; + } + + uint32_t bufferLength = JS_GetArrayBufferByteLength(buffer); + if (bufferLength < aLength) { + return NS_ERROR_FAILURE; + } + + uint32_t bufSize = std::min(aLength, 4096); + UniquePtr buf = MakeUnique(bufSize); + + uint32_t pos = 0; + *aReadLength = 0; + do { + // Read data into temporary buffer. + uint32_t bytesRead; + uint32_t amount = std::min(aLength - pos, bufSize); + nsresult rv = Read(buf.get(), amount, &bytesRead); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + MOZ_ASSERT(bytesRead <= amount); + + if (bytesRead == 0) { + break; + } + + // Copy data into actual buffer. + + JS::AutoCheckCannotGC nogc; + bool isShared; + if (bufferLength != JS_GetArrayBufferByteLength(buffer)) { + return NS_ERROR_FAILURE; + } + + char* data = reinterpret_cast(JS_GetArrayBufferData(buffer, &isShared, nogc)); + MOZ_ASSERT(!isShared); // Implied by JS_GetArrayBufferData() + if (!data) { + return NS_ERROR_FAILURE; + } + + *aReadLength += bytesRead; + PodCopy(data + pos, buf.get(), bytesRead); + + pos += bytesRead; + } while (pos < aLength); + + return NS_OK; +} + +NS_IMETHODIMP +nsBinaryInputStream::ReadObject(bool aIsStrongRef, nsISupports** aObject) +{ + nsCID cid; + nsIID iid; + nsresult rv = ReadID(&cid); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = ReadID(&iid); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // HACK: Intercept old (pre-gecko6) nsIURI IID, and replace with + // the updated IID, so that we're QI'ing to an actual interface. + // (As soon as we drop support for upgrading from pre-gecko6, we can + // remove this chunk.) + static const nsIID oldURIiid = { + 0x7a22cc0, 0xce5, 0x11d3, + { 0x93, 0x31, 0x0, 0x10, 0x4b, 0xa0, 0xfd, 0x40 } + }; + + // hackaround for bug 670542 + static const nsIID oldURIiid2 = { + 0xd6d04c36, 0x0fa4, 0x4db3, + { 0xbe, 0x05, 0x4a, 0x18, 0x39, 0x71, 0x03, 0xe2 } + }; + + // hackaround for bug 682031 + static const nsIID oldURIiid3 = { + 0x12120b20, 0x0929, 0x40e9, + { 0x88, 0xcf, 0x6e, 0x08, 0x76, 0x6e, 0x8b, 0x23 } + }; + + // hackaround for bug 1195415 + static const nsIID oldURIiid4 = { + 0x395fe045, 0x7d18, 0x4adb, + { 0xa3, 0xfd, 0xaf, 0x98, 0xc8, 0xa1, 0xaf, 0x11 } + }; + + if (iid.Equals(oldURIiid) || + iid.Equals(oldURIiid2) || + iid.Equals(oldURIiid3) || + iid.Equals(oldURIiid4)) { + const nsIID newURIiid = NS_IURI_IID; + iid = newURIiid; + } + // END HACK + + // HACK: Service workers store resource security info on disk in the dom + // Cache API. When the uuid of the nsIX509Cert interface changes + // these serialized objects cannot be loaded any more. This hack + // works around this issue. + + // hackaround for bug 1247580 (FF45 to FF46 transition) + static const nsIID oldCertIID = { + 0xf8ed8364, 0xced9, 0x4c6e, + { 0x86, 0xba, 0x48, 0xaf, 0x53, 0xc3, 0x93, 0xe6 } + }; + + if (iid.Equals(oldCertIID)) { + const nsIID newCertIID = NS_IX509CERT_IID; + iid = newCertIID; + } + // END HACK + + nsCOMPtr object = do_CreateInstance(cid, &rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsCOMPtr serializable = do_QueryInterface(object); + if (NS_WARN_IF(!serializable)) { + return NS_ERROR_UNEXPECTED; + } + + rv = serializable->Read(this); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return object->QueryInterface(iid, reinterpret_cast(aObject)); +} + +NS_IMETHODIMP +nsBinaryInputStream::ReadID(nsID* aResult) +{ + nsresult rv = Read32(&aResult->m0); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = Read16(&aResult->m1); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = Read16(&aResult->m2); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + for (int i = 0; i < 8; ++i) { + rv = Read8(&aResult->m3[i]); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + + return NS_OK; +} + +NS_IMETHODIMP_(char*) +nsBinaryInputStream::GetBuffer(uint32_t aLength, uint32_t aAlignMask) +{ + if (mBufferAccess) { + return mBufferAccess->GetBuffer(aLength, aAlignMask); + } + return nullptr; +} + +NS_IMETHODIMP_(void) +nsBinaryInputStream::PutBuffer(char* aBuffer, uint32_t aLength) +{ + if (mBufferAccess) { + mBufferAccess->PutBuffer(aBuffer, aLength); + } +} diff --git a/xpcom/io/nsBinaryStream.h b/xpcom/io/nsBinaryStream.h new file mode 100644 index 000000000..2520d9201 --- /dev/null +++ b/xpcom/io/nsBinaryStream.h @@ -0,0 +1,101 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 nsBinaryStream_h___ +#define nsBinaryStream_h___ + +#include "nsCOMPtr.h" +#include "nsAString.h" +#include "nsIObjectInputStream.h" +#include "nsIObjectOutputStream.h" +#include "nsIStreamBufferAccess.h" + +#define NS_BINARYOUTPUTSTREAM_CID \ +{ /* 86c37b9a-74e7-4672-844e-6e7dd83ba484 */ \ + 0x86c37b9a, \ + 0x74e7, \ + 0x4672, \ + {0x84, 0x4e, 0x6e, 0x7d, 0xd8, 0x3b, 0xa4, 0x84} \ +} + +#define NS_BINARYOUTPUTSTREAM_CONTRACTID "@mozilla.org/binaryoutputstream;1" + +// Derive from nsIObjectOutputStream so this class can be used as a superclass +// by nsObjectOutputStream. +class nsBinaryOutputStream final : public nsIObjectOutputStream +{ +public: + nsBinaryOutputStream() + { + } + +protected: + // nsISupports methods + NS_DECL_ISUPPORTS + + // nsIOutputStream methods + NS_DECL_NSIOUTPUTSTREAM + + // nsIBinaryOutputStream methods + NS_DECL_NSIBINARYOUTPUTSTREAM + + // nsIObjectOutputStream methods + NS_DECL_NSIOBJECTOUTPUTSTREAM + + // Call Write(), ensuring that all proffered data is written + nsresult WriteFully(const char* aBuf, uint32_t aCount); + + nsCOMPtr mOutputStream; + nsCOMPtr mBufferAccess; + +private: + // virtual dtor since subclasses call our Release() + virtual ~nsBinaryOutputStream() + { + } +}; + +#define NS_BINARYINPUTSTREAM_CID \ +{ /* c521a612-2aad-46db-b6ab-3b821fb150b1 */ \ + 0xc521a612, \ + 0x2aad, \ + 0x46db, \ + {0xb6, 0xab, 0x3b, 0x82, 0x1f, 0xb1, 0x50, 0xb1} \ +} + +#define NS_BINARYINPUTSTREAM_CONTRACTID "@mozilla.org/binaryinputstream;1" + +class nsBinaryInputStream final : public nsIObjectInputStream +{ +public: + nsBinaryInputStream() + { + } + +protected: + // nsISupports methods + NS_DECL_ISUPPORTS + + // nsIInputStream methods + NS_DECL_NSIINPUTSTREAM + + // nsIBinaryInputStream methods + NS_DECL_NSIBINARYINPUTSTREAM + + // nsIObjectInputStream methods + NS_DECL_NSIOBJECTINPUTSTREAM + + nsCOMPtr mInputStream; + nsCOMPtr mBufferAccess; + +private: + // virtual dtor since subclasses call our Release() + virtual ~nsBinaryInputStream() + { + } +}; + +#endif // nsBinaryStream_h___ diff --git a/xpcom/io/nsDirectoryService.cpp b/xpcom/io/nsDirectoryService.cpp new file mode 100644 index 000000000..a4d962395 --- /dev/null +++ b/xpcom/io/nsDirectoryService.cpp @@ -0,0 +1,766 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. 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 "nsCOMPtr.h" +#include "nsAutoPtr.h" +#include "nsDirectoryService.h" +#include "nsDirectoryServiceDefs.h" +#include "nsLocalFile.h" +#include "nsDebug.h" +#include "nsStaticAtom.h" +#include "nsEnumeratorUtils.h" + +#include "nsICategoryManager.h" +#include "nsISimpleEnumerator.h" +#include "nsIStringEnumerator.h" + +#if defined(XP_WIN) +#include +#include +#include +#include +#elif defined(XP_UNIX) +#include +#include +#include +#include "prenv.h" +#ifdef MOZ_WIDGET_COCOA +#include +#include +#endif +#endif + +#include "SpecialSystemDirectory.h" +#include "nsAppFileLocationProvider.h" + +using namespace mozilla; + +// define home directory +// For Windows platform, We are choosing Appdata folder as HOME +#if defined (XP_WIN) +#define HOME_DIR NS_WIN_APPDATA_DIR +#elif defined (MOZ_WIDGET_COCOA) +#define HOME_DIR NS_OSX_HOME_DIR +#elif defined (XP_UNIX) +#define HOME_DIR NS_UNIX_HOME_DIR +#endif + +//---------------------------------------------------------------------------------------- +nsresult +nsDirectoryService::GetCurrentProcessDirectory(nsIFile** aFile) +//---------------------------------------------------------------------------------------- +{ + if (NS_WARN_IF(!aFile)) { + return NS_ERROR_INVALID_ARG; + } + *aFile = nullptr; + + // Set the component registry location: + if (!gService) { + return NS_ERROR_FAILURE; + } + + nsresult rv; + + nsCOMPtr dirService; + rv = nsDirectoryService::Create(nullptr, + NS_GET_IID(nsIProperties), + getter_AddRefs(dirService)); // needs to be around for life of product + if (NS_FAILED(rv)) { + return rv; + } + + if (dirService) { + nsCOMPtr localFile; + dirService->Get(NS_XPCOM_INIT_CURRENT_PROCESS_DIR, NS_GET_IID(nsIFile), + getter_AddRefs(localFile)); + if (localFile) { + localFile.forget(aFile); + return NS_OK; + } + } + + RefPtr localFile = new nsLocalFile; + +#ifdef XP_WIN + wchar_t buf[MAX_PATH + 1]; + SetLastError(ERROR_SUCCESS); + if (GetModuleFileNameW(0, buf, mozilla::ArrayLength(buf)) && + GetLastError() != ERROR_INSUFFICIENT_BUFFER) { + // chop off the executable name by finding the rightmost backslash + wchar_t* lastSlash = wcsrchr(buf, L'\\'); + if (lastSlash) { + *(lastSlash + 1) = L'\0'; + } + + localFile->InitWithPath(nsDependentString(buf)); + localFile.forget(aFile); + return NS_OK; + } + +#elif defined(MOZ_WIDGET_COCOA) + // Works even if we're not bundled. + CFBundleRef appBundle = CFBundleGetMainBundle(); + if (appBundle) { + CFURLRef bundleURL = CFBundleCopyExecutableURL(appBundle); + if (bundleURL) { + CFURLRef parentURL = CFURLCreateCopyDeletingLastPathComponent( + kCFAllocatorDefault, bundleURL); + if (parentURL) { + // Pass true for the "resolveAgainstBase" arg to CFURLGetFileSystemRepresentation. + // This will resolve the relative portion of the CFURL against it base, giving a full + // path, which CFURLCopyFileSystemPath doesn't do. + char buffer[PATH_MAX]; + if (CFURLGetFileSystemRepresentation(parentURL, true, + (UInt8*)buffer, sizeof(buffer))) { +#ifdef DEBUG_conrad + printf("nsDirectoryService - CurrentProcessDir is: %s\n", buffer); +#endif + rv = localFile->InitWithNativePath(nsDependentCString(buffer)); + if (NS_SUCCEEDED(rv)) { + localFile.forget(aFile); + } + } + CFRelease(parentURL); + } + CFRelease(bundleURL); + } + } + + NS_ASSERTION(*aFile, "nsDirectoryService - Could not determine CurrentProcessDir.\n"); + if (*aFile) { + return NS_OK; + } + +#elif defined(XP_UNIX) + + // In the absence of a good way to get the executable directory let + // us try this for unix: + // - if MOZILLA_FIVE_HOME is defined, that is it + // - else give the current directory + char buf[MAXPATHLEN]; + + // The MOZ_DEFAULT_MOZILLA_FIVE_HOME variable can be set at configure time with + // a --with-default-mozilla-five-home=foo autoconf flag. + // + // The idea here is to allow for builds that have a default MOZILLA_FIVE_HOME + // regardless of the environment. This makes it easier to write apps that + // embed mozilla without having to worry about setting up the environment + // + // We do this by putenv()ing the default value into the environment. Note that + // we only do this if it is not already set. +#ifdef MOZ_DEFAULT_MOZILLA_FIVE_HOME + const char* home = PR_GetEnv("MOZILLA_FIVE_HOME"); + if (!home || !*home) { + putenv("MOZILLA_FIVE_HOME=" MOZ_DEFAULT_MOZILLA_FIVE_HOME); + } +#endif + + char* moz5 = PR_GetEnv("MOZILLA_FIVE_HOME"); + if (moz5 && *moz5) { + if (realpath(moz5, buf)) { + localFile->InitWithNativePath(nsDependentCString(buf)); + localFile.forget(aFile); + return NS_OK; + } + } +#if defined(DEBUG) + static bool firstWarning = true; + + if ((!moz5 || !*moz5) && firstWarning) { + // Warn that MOZILLA_FIVE_HOME not set, once. + printf("Warning: MOZILLA_FIVE_HOME not set.\n"); + firstWarning = false; + } +#endif /* DEBUG */ + + // Fall back to current directory. + if (getcwd(buf, sizeof(buf))) { + localFile->InitWithNativePath(nsDependentCString(buf)); + localFile.forget(aFile); + return NS_OK; + } + +#endif + + NS_ERROR("unable to get current process directory"); + return NS_ERROR_FAILURE; +} // GetCurrentProcessDirectory() + +StaticRefPtr nsDirectoryService::gService; + +nsDirectoryService::nsDirectoryService() + : mHashtable(128) +{ +} + +nsresult +nsDirectoryService::Create(nsISupports* aOuter, REFNSIID aIID, void** aResult) +{ + if (NS_WARN_IF(!aResult)) { + return NS_ERROR_INVALID_ARG; + } + if (NS_WARN_IF(aOuter)) { + return NS_ERROR_NO_AGGREGATION; + } + + if (!gService) { + return NS_ERROR_NOT_INITIALIZED; + } + + return gService->QueryInterface(aIID, aResult); +} + +#define DIR_ATOM(name_, value_) nsIAtom* nsDirectoryService::name_ = nullptr; +#include "nsDirectoryServiceAtomList.h" +#undef DIR_ATOM + +#define DIR_ATOM(name_, value_) NS_STATIC_ATOM_BUFFER(name_##_buffer, value_) +#include "nsDirectoryServiceAtomList.h" +#undef DIR_ATOM + +static const nsStaticAtom directory_atoms[] = { +#define DIR_ATOM(name_, value_) NS_STATIC_ATOM(name_##_buffer, &nsDirectoryService::name_), +#include "nsDirectoryServiceAtomList.h" +#undef DIR_ATOM +}; + +NS_IMETHODIMP +nsDirectoryService::Init() +{ + NS_NOTREACHED("nsDirectoryService::Init() for internal use only!"); + return NS_OK; +} + +void +nsDirectoryService::RealInit() +{ + NS_ASSERTION(!gService, + "nsDirectoryService::RealInit Mustn't initialize twice!"); + + gService = new nsDirectoryService(); + + NS_RegisterStaticAtoms(directory_atoms); + + // Let the list hold the only reference to the provider. + nsAppFileLocationProvider* defaultProvider = new nsAppFileLocationProvider; + gService->mProviders.AppendElement(defaultProvider); +} + +nsDirectoryService::~nsDirectoryService() +{ +} + +NS_IMPL_ISUPPORTS(nsDirectoryService, + nsIProperties, + nsIDirectoryService, + nsIDirectoryServiceProvider, + nsIDirectoryServiceProvider2) + + +NS_IMETHODIMP +nsDirectoryService::Undefine(const char* aProp) +{ + if (NS_WARN_IF(!aProp)) { + return NS_ERROR_INVALID_ARG; + } + + nsDependentCString key(aProp); + if (!mHashtable.Get(key, nullptr)) { + return NS_ERROR_FAILURE; + } + + mHashtable.Remove(key); + return NS_OK; +} + +NS_IMETHODIMP +nsDirectoryService::GetKeys(uint32_t* aCount, char*** aKeys) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +struct MOZ_STACK_CLASS FileData +{ + FileData(const char* aProperty, const nsIID& aUUID) + : property(aProperty) + , data(nullptr) + , persistent(true) + , uuid(aUUID) + { + } + + const char* property; + nsCOMPtr data; + bool persistent; + const nsIID& uuid; +}; + +static bool +FindProviderFile(nsIDirectoryServiceProvider* aElement, FileData* aData) +{ + nsresult rv; + if (aData->uuid.Equals(NS_GET_IID(nsISimpleEnumerator))) { + // Not all providers implement this iface + nsCOMPtr prov2 = do_QueryInterface(aElement); + if (prov2) { + nsCOMPtr newFiles; + rv = prov2->GetFiles(aData->property, getter_AddRefs(newFiles)); + if (NS_SUCCEEDED(rv) && newFiles) { + if (aData->data) { + nsCOMPtr unionFiles; + + NS_NewUnionEnumerator(getter_AddRefs(unionFiles), + (nsISimpleEnumerator*)aData->data.get(), newFiles); + + if (unionFiles) { + unionFiles.swap(*(nsISimpleEnumerator**)&aData->data); + } + } else { + aData->data = newFiles; + } + + aData->persistent = false; // Enumerators can never be persistent + return rv == NS_SUCCESS_AGGREGATE_RESULT; + } + } + } else { + rv = aElement->GetFile(aData->property, &aData->persistent, + (nsIFile**)&aData->data); + if (NS_SUCCEEDED(rv) && aData->data) { + return false; + } + } + + return true; +} + +NS_IMETHODIMP +nsDirectoryService::Get(const char* aProp, const nsIID& aUuid, void** aResult) +{ + if (NS_WARN_IF(!aProp)) { + return NS_ERROR_INVALID_ARG; + } + + nsDependentCString key(aProp); + + nsCOMPtr cachedFile = mHashtable.Get(key); + + if (cachedFile) { + nsCOMPtr cloneFile; + cachedFile->Clone(getter_AddRefs(cloneFile)); + return cloneFile->QueryInterface(aUuid, aResult); + } + + // it is not one of our defaults, lets check any providers + FileData fileData(aProp, aUuid); + + for (int32_t i = mProviders.Length() - 1; i >= 0; i--) { + if (!FindProviderFile(mProviders[i], &fileData)) { + break; + } + } + if (fileData.data) { + if (fileData.persistent) { + Set(aProp, static_cast(fileData.data.get())); + } + nsresult rv = (fileData.data)->QueryInterface(aUuid, aResult); + fileData.data = nullptr; // AddRef occurs in FindProviderFile() + return rv; + } + + FindProviderFile(static_cast(this), &fileData); + if (fileData.data) { + if (fileData.persistent) { + Set(aProp, static_cast(fileData.data.get())); + } + nsresult rv = (fileData.data)->QueryInterface(aUuid, aResult); + fileData.data = nullptr; // AddRef occurs in FindProviderFile() + return rv; + } + + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsDirectoryService::Set(const char* aProp, nsISupports* aValue) +{ + if (NS_WARN_IF(!aProp)) { + return NS_ERROR_INVALID_ARG; + } + + nsDependentCString key(aProp); + if (mHashtable.Get(key, nullptr) || !aValue) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr ourFile = do_QueryInterface(aValue); + if (ourFile) { + nsCOMPtr cloneFile; + ourFile->Clone(getter_AddRefs(cloneFile)); + mHashtable.Put(key, cloneFile); + + return NS_OK; + } + + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsDirectoryService::Has(const char* aProp, bool* aResult) +{ + if (NS_WARN_IF(!aProp)) { + return NS_ERROR_INVALID_ARG; + } + + *aResult = false; + nsCOMPtr value; + nsresult rv = Get(aProp, NS_GET_IID(nsIFile), getter_AddRefs(value)); + if (NS_FAILED(rv)) { + return NS_OK; + } + + if (value) { + *aResult = true; + } + + return rv; +} + +NS_IMETHODIMP +nsDirectoryService::RegisterProvider(nsIDirectoryServiceProvider* aProv) +{ + if (!aProv) { + return NS_ERROR_FAILURE; + } + + mProviders.AppendElement(aProv); + return NS_OK; +} + +void +nsDirectoryService::RegisterCategoryProviders() +{ + nsCOMPtr catman + (do_GetService(NS_CATEGORYMANAGER_CONTRACTID)); + if (!catman) { + return; + } + + nsCOMPtr entries; + catman->EnumerateCategory(XPCOM_DIRECTORY_PROVIDER_CATEGORY, + getter_AddRefs(entries)); + + nsCOMPtr strings(do_QueryInterface(entries)); + if (!strings) { + return; + } + + bool more; + while (NS_SUCCEEDED(strings->HasMore(&more)) && more) { + nsAutoCString entry; + strings->GetNext(entry); + + nsXPIDLCString contractID; + catman->GetCategoryEntry(XPCOM_DIRECTORY_PROVIDER_CATEGORY, entry.get(), + getter_Copies(contractID)); + + if (contractID) { + nsCOMPtr provider = do_GetService(contractID.get()); + if (provider) { + RegisterProvider(provider); + } + } + } +} + +NS_IMETHODIMP +nsDirectoryService::UnregisterProvider(nsIDirectoryServiceProvider* aProv) +{ + if (!aProv) { + return NS_ERROR_FAILURE; + } + + mProviders.RemoveElement(aProv); + return NS_OK; +} + +#if defined(MOZ_CONTENT_SANDBOX) && defined(XP_WIN) +static nsresult +GetLowIntegrityTempBase(nsIFile** aLowIntegrityTempBase) +{ + nsCOMPtr localFile; + nsresult rv = GetSpecialSystemDirectory(Win_LocalAppdataLow, + getter_AddRefs(localFile)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = localFile->Append(NS_LITERAL_STRING(MOZ_USER_DIR)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + localFile.forget(aLowIntegrityTempBase); + return rv; +} +#endif + +// DO NOT ADD ANY LOCATIONS TO THIS FUNCTION UNTIL YOU TALK TO: dougt@netscape.com. +// This is meant to be a place of xpcom or system specific file locations, not +// application specific locations. If you need the later, register a callback for +// your application. + +NS_IMETHODIMP +nsDirectoryService::GetFile(const char* aProp, bool* aPersistent, + nsIFile** aResult) +{ + nsCOMPtr localFile; + nsresult rv = NS_ERROR_FAILURE; + + *aResult = nullptr; + *aPersistent = true; + + nsCOMPtr inAtom = NS_Atomize(aProp); + + // check to see if it is one of our defaults + + if (inAtom == nsDirectoryService::sCurrentProcess || + inAtom == nsDirectoryService::sOS_CurrentProcessDirectory) { + rv = GetCurrentProcessDirectory(getter_AddRefs(localFile)); + } + + // Unless otherwise set, the core pieces of the GRE exist + // in the current process directory. + else if (inAtom == nsDirectoryService::sGRE_Directory || + inAtom == nsDirectoryService::sGRE_BinDirectory) { + rv = GetCurrentProcessDirectory(getter_AddRefs(localFile)); + } else if (inAtom == nsDirectoryService::sOS_DriveDirectory) { + rv = GetSpecialSystemDirectory(OS_DriveDirectory, getter_AddRefs(localFile)); + } else if (inAtom == nsDirectoryService::sOS_TemporaryDirectory) { + rv = GetSpecialSystemDirectory(OS_TemporaryDirectory, getter_AddRefs(localFile)); + } else if (inAtom == nsDirectoryService::sOS_CurrentProcessDirectory) { + rv = GetSpecialSystemDirectory(OS_CurrentProcessDirectory, getter_AddRefs(localFile)); + } else if (inAtom == nsDirectoryService::sOS_CurrentWorkingDirectory) { + rv = GetSpecialSystemDirectory(OS_CurrentWorkingDirectory, getter_AddRefs(localFile)); + } + +#if defined(MOZ_WIDGET_COCOA) + else if (inAtom == nsDirectoryService::sDirectory) { + rv = GetOSXFolderType(kClassicDomain, kSystemFolderType, getter_AddRefs(localFile)); + } else if (inAtom == nsDirectoryService::sTrashDirectory) { + rv = GetOSXFolderType(kClassicDomain, kTrashFolderType, getter_AddRefs(localFile)); + } else if (inAtom == nsDirectoryService::sStartupDirectory) { + rv = GetOSXFolderType(kClassicDomain, kStartupFolderType, getter_AddRefs(localFile)); + } else if (inAtom == nsDirectoryService::sShutdownDirectory) { + rv = GetOSXFolderType(kClassicDomain, kShutdownFolderType, getter_AddRefs(localFile)); + } else if (inAtom == nsDirectoryService::sAppleMenuDirectory) { + rv = GetOSXFolderType(kClassicDomain, kAppleMenuFolderType, getter_AddRefs(localFile)); + } else if (inAtom == nsDirectoryService::sControlPanelDirectory) { + rv = GetOSXFolderType(kClassicDomain, kControlPanelFolderType, getter_AddRefs(localFile)); + } else if (inAtom == nsDirectoryService::sExtensionDirectory) { + rv = GetOSXFolderType(kClassicDomain, kExtensionFolderType, getter_AddRefs(localFile)); + } else if (inAtom == nsDirectoryService::sFontsDirectory) { + rv = GetOSXFolderType(kClassicDomain, kFontsFolderType, getter_AddRefs(localFile)); + } else if (inAtom == nsDirectoryService::sPreferencesDirectory) { + rv = GetOSXFolderType(kClassicDomain, kPreferencesFolderType, getter_AddRefs(localFile)); + } else if (inAtom == nsDirectoryService::sDocumentsDirectory) { + rv = GetOSXFolderType(kClassicDomain, kDocumentsFolderType, getter_AddRefs(localFile)); + } else if (inAtom == nsDirectoryService::sInternetSearchDirectory) { + rv = GetOSXFolderType(kClassicDomain, kInternetSearchSitesFolderType, getter_AddRefs(localFile)); + } else if (inAtom == nsDirectoryService::sUserLibDirectory) { + rv = GetOSXFolderType(kUserDomain, kDomainLibraryFolderType, getter_AddRefs(localFile)); + } else if (inAtom == nsDirectoryService::sOS_HomeDirectory) { + rv = GetOSXFolderType(kUserDomain, kDomainTopLevelFolderType, getter_AddRefs(localFile)); + } else if (inAtom == nsDirectoryService::sDefaultDownloadDirectory) { + // 10.5 and later, we can use kDownloadsFolderType which is defined in + // Folders.h as "down". However, in order to support 10.4 still, we + // cannot use the named constant. We'll use it's value, and if it + // fails, fall back to the desktop. +#ifndef kDownloadsFolderType +#define kDownloadsFolderType 'down' +#endif + + rv = GetOSXFolderType(kUserDomain, kDownloadsFolderType, + getter_AddRefs(localFile)); + if (NS_FAILED(rv)) { + rv = GetOSXFolderType(kUserDomain, kDesktopFolderType, + getter_AddRefs(localFile)); + } + } else if (inAtom == nsDirectoryService::sUserDesktopDirectory || + inAtom == nsDirectoryService::sOS_DesktopDirectory) { + rv = GetOSXFolderType(kUserDomain, kDesktopFolderType, getter_AddRefs(localFile)); + } else if (inAtom == nsDirectoryService::sLocalDesktopDirectory) { + rv = GetOSXFolderType(kLocalDomain, kDesktopFolderType, getter_AddRefs(localFile)); + } else if (inAtom == nsDirectoryService::sUserApplicationsDirectory) { + rv = GetOSXFolderType(kUserDomain, kApplicationsFolderType, getter_AddRefs(localFile)); + } else if (inAtom == nsDirectoryService::sLocalApplicationsDirectory) { + rv = GetOSXFolderType(kLocalDomain, kApplicationsFolderType, getter_AddRefs(localFile)); + } else if (inAtom == nsDirectoryService::sUserDocumentsDirectory) { + rv = GetOSXFolderType(kUserDomain, kDocumentsFolderType, getter_AddRefs(localFile)); + } else if (inAtom == nsDirectoryService::sLocalDocumentsDirectory) { + rv = GetOSXFolderType(kLocalDomain, kDocumentsFolderType, getter_AddRefs(localFile)); + } else if (inAtom == nsDirectoryService::sUserInternetPlugInDirectory) { + rv = GetOSXFolderType(kUserDomain, kInternetPlugInFolderType, getter_AddRefs(localFile)); + } else if (inAtom == nsDirectoryService::sLocalInternetPlugInDirectory) { + rv = GetOSXFolderType(kLocalDomain, kInternetPlugInFolderType, getter_AddRefs(localFile)); + } else if (inAtom == nsDirectoryService::sUserFrameworksDirectory) { + rv = GetOSXFolderType(kUserDomain, kFrameworksFolderType, getter_AddRefs(localFile)); + } else if (inAtom == nsDirectoryService::sLocalFrameworksDirectory) { + rv = GetOSXFolderType(kLocalDomain, kFrameworksFolderType, getter_AddRefs(localFile)); + } else if (inAtom == nsDirectoryService::sUserPreferencesDirectory) { + rv = GetOSXFolderType(kUserDomain, kPreferencesFolderType, getter_AddRefs(localFile)); + } else if (inAtom == nsDirectoryService::sLocalPreferencesDirectory) { + rv = GetOSXFolderType(kLocalDomain, kPreferencesFolderType, getter_AddRefs(localFile)); + } else if (inAtom == nsDirectoryService::sPictureDocumentsDirectory) { + rv = GetOSXFolderType(kUserDomain, kPictureDocumentsFolderType, getter_AddRefs(localFile)); + } else if (inAtom == nsDirectoryService::sMovieDocumentsDirectory) { + rv = GetOSXFolderType(kUserDomain, kMovieDocumentsFolderType, getter_AddRefs(localFile)); + } else if (inAtom == nsDirectoryService::sMusicDocumentsDirectory) { + rv = GetOSXFolderType(kUserDomain, kMusicDocumentsFolderType, getter_AddRefs(localFile)); + } else if (inAtom == nsDirectoryService::sInternetSitesDirectory) { + rv = GetOSXFolderType(kUserDomain, kInternetSitesFolderType, getter_AddRefs(localFile)); + } +#elif defined (XP_WIN) + else if (inAtom == nsDirectoryService::sSystemDirectory) { + rv = GetSpecialSystemDirectory(Win_SystemDirectory, getter_AddRefs(localFile)); + } else if (inAtom == nsDirectoryService::sWindowsDirectory) { + rv = GetSpecialSystemDirectory(Win_WindowsDirectory, getter_AddRefs(localFile)); + } else if (inAtom == nsDirectoryService::sWindowsProgramFiles) { + rv = GetSpecialSystemDirectory(Win_ProgramFiles, getter_AddRefs(localFile)); + } else if (inAtom == nsDirectoryService::sOS_HomeDirectory) { + rv = GetSpecialSystemDirectory(Win_HomeDirectory, getter_AddRefs(localFile)); + } else if (inAtom == nsDirectoryService::sDesktop) { + rv = GetSpecialSystemDirectory(Win_Desktop, getter_AddRefs(localFile)); + } else if (inAtom == nsDirectoryService::sPrograms) { + rv = GetSpecialSystemDirectory(Win_Programs, getter_AddRefs(localFile)); + } else if (inAtom == nsDirectoryService::sControls) { + rv = GetSpecialSystemDirectory(Win_Controls, getter_AddRefs(localFile)); + } else if (inAtom == nsDirectoryService::sPrinters) { + rv = GetSpecialSystemDirectory(Win_Printers, getter_AddRefs(localFile)); + } else if (inAtom == nsDirectoryService::sPersonal) { + rv = GetSpecialSystemDirectory(Win_Personal, getter_AddRefs(localFile)); + } else if (inAtom == nsDirectoryService::sFavorites) { + rv = GetSpecialSystemDirectory(Win_Favorites, getter_AddRefs(localFile)); + } else if (inAtom == nsDirectoryService::sStartup) { + rv = GetSpecialSystemDirectory(Win_Startup, getter_AddRefs(localFile)); + } else if (inAtom == nsDirectoryService::sRecent) { + rv = GetSpecialSystemDirectory(Win_Recent, getter_AddRefs(localFile)); + } else if (inAtom == nsDirectoryService::sSendto) { + rv = GetSpecialSystemDirectory(Win_Sendto, getter_AddRefs(localFile)); + } else if (inAtom == nsDirectoryService::sBitbucket) { + rv = GetSpecialSystemDirectory(Win_Bitbucket, getter_AddRefs(localFile)); + } else if (inAtom == nsDirectoryService::sStartmenu) { + rv = GetSpecialSystemDirectory(Win_Startmenu, getter_AddRefs(localFile)); + } else if (inAtom == nsDirectoryService::sDesktopdirectory || + inAtom == nsDirectoryService::sOS_DesktopDirectory) { + rv = GetSpecialSystemDirectory(Win_Desktopdirectory, getter_AddRefs(localFile)); + } else if (inAtom == nsDirectoryService::sDrives) { + rv = GetSpecialSystemDirectory(Win_Drives, getter_AddRefs(localFile)); + } else if (inAtom == nsDirectoryService::sNetwork) { + rv = GetSpecialSystemDirectory(Win_Network, getter_AddRefs(localFile)); + } else if (inAtom == nsDirectoryService::sNethood) { + rv = GetSpecialSystemDirectory(Win_Nethood, getter_AddRefs(localFile)); + } else if (inAtom == nsDirectoryService::sFonts) { + rv = GetSpecialSystemDirectory(Win_Fonts, getter_AddRefs(localFile)); + } else if (inAtom == nsDirectoryService::sTemplates) { + rv = GetSpecialSystemDirectory(Win_Templates, getter_AddRefs(localFile)); + } else if (inAtom == nsDirectoryService::sCommon_Startmenu) { + rv = GetSpecialSystemDirectory(Win_Common_Startmenu, getter_AddRefs(localFile)); + } else if (inAtom == nsDirectoryService::sCommon_Programs) { + rv = GetSpecialSystemDirectory(Win_Common_Programs, getter_AddRefs(localFile)); + } else if (inAtom == nsDirectoryService::sCommon_Startup) { + rv = GetSpecialSystemDirectory(Win_Common_Startup, getter_AddRefs(localFile)); + } else if (inAtom == nsDirectoryService::sCommon_Desktopdirectory) { + rv = GetSpecialSystemDirectory(Win_Common_Desktopdirectory, getter_AddRefs(localFile)); + } else if (inAtom == nsDirectoryService::sCommon_AppData) { + rv = GetSpecialSystemDirectory(Win_Common_AppData, getter_AddRefs(localFile)); + } else if (inAtom == nsDirectoryService::sAppdata) { + rv = GetSpecialSystemDirectory(Win_Appdata, getter_AddRefs(localFile)); + } else if (inAtom == nsDirectoryService::sLocalAppdata) { + rv = GetSpecialSystemDirectory(Win_LocalAppdata, getter_AddRefs(localFile)); +#if defined(MOZ_CONTENT_SANDBOX) + } else if (inAtom == nsDirectoryService::sLocalAppdataLow) { + rv = GetSpecialSystemDirectory(Win_LocalAppdataLow, getter_AddRefs(localFile)); + } else if (inAtom == nsDirectoryService::sLowIntegrityTempBase) { + rv = GetLowIntegrityTempBase(getter_AddRefs(localFile)); +#endif + } else if (inAtom == nsDirectoryService::sPrinthood) { + rv = GetSpecialSystemDirectory(Win_Printhood, getter_AddRefs(localFile)); + } else if (inAtom == nsDirectoryService::sWinCookiesDirectory) { + rv = GetSpecialSystemDirectory(Win_Cookies, getter_AddRefs(localFile)); + } else if (inAtom == nsDirectoryService::sDefaultDownloadDirectory) { + rv = GetSpecialSystemDirectory(Win_Downloads, getter_AddRefs(localFile)); + } else if (inAtom == nsDirectoryService::sDocs) { + rv = GetSpecialSystemDirectory(Win_Documents, getter_AddRefs(localFile)); + } else if (inAtom == nsDirectoryService::sPictures) { + rv = GetSpecialSystemDirectory(Win_Pictures, getter_AddRefs(localFile)); + } else if (inAtom == nsDirectoryService::sMusic) { + rv = GetSpecialSystemDirectory(Win_Music, getter_AddRefs(localFile)); + } else if (inAtom == nsDirectoryService::sVideos) { + rv = GetSpecialSystemDirectory(Win_Videos, getter_AddRefs(localFile)); + } +#elif defined (XP_UNIX) + + else if (inAtom == nsDirectoryService::sLocalDirectory) { + rv = GetSpecialSystemDirectory(Unix_LocalDirectory, getter_AddRefs(localFile)); + } else if (inAtom == nsDirectoryService::sLibDirectory) { + rv = GetSpecialSystemDirectory(Unix_LibDirectory, getter_AddRefs(localFile)); + } else if (inAtom == nsDirectoryService::sOS_HomeDirectory) { + rv = GetSpecialSystemDirectory(Unix_HomeDirectory, getter_AddRefs(localFile)); + } else if (inAtom == nsDirectoryService::sXDGDesktop || + inAtom == nsDirectoryService::sOS_DesktopDirectory) { + rv = GetSpecialSystemDirectory(Unix_XDG_Desktop, getter_AddRefs(localFile)); + *aPersistent = false; + } else if (inAtom == nsDirectoryService::sXDGDocuments) { + rv = GetSpecialSystemDirectory(Unix_XDG_Documents, getter_AddRefs(localFile)); + *aPersistent = false; + } else if (inAtom == nsDirectoryService::sXDGDownload || + inAtom == nsDirectoryService::sDefaultDownloadDirectory) { + rv = GetSpecialSystemDirectory(Unix_XDG_Download, getter_AddRefs(localFile)); + *aPersistent = false; + } else if (inAtom == nsDirectoryService::sXDGMusic) { + rv = GetSpecialSystemDirectory(Unix_XDG_Music, getter_AddRefs(localFile)); + *aPersistent = false; + } else if (inAtom == nsDirectoryService::sXDGPictures) { + rv = GetSpecialSystemDirectory(Unix_XDG_Pictures, getter_AddRefs(localFile)); + *aPersistent = false; + } else if (inAtom == nsDirectoryService::sXDGPublicShare) { + rv = GetSpecialSystemDirectory(Unix_XDG_PublicShare, getter_AddRefs(localFile)); + *aPersistent = false; + } else if (inAtom == nsDirectoryService::sXDGTemplates) { + rv = GetSpecialSystemDirectory(Unix_XDG_Templates, getter_AddRefs(localFile)); + *aPersistent = false; + } else if (inAtom == nsDirectoryService::sXDGVideos) { + rv = GetSpecialSystemDirectory(Unix_XDG_Videos, getter_AddRefs(localFile)); + *aPersistent = false; + } +#endif + + if (NS_FAILED(rv)) { + return rv; + } + + if (!localFile) { + return NS_ERROR_FAILURE; + } + + localFile.forget(aResult); + return NS_OK; +} + +NS_IMETHODIMP +nsDirectoryService::GetFiles(const char* aProp, nsISimpleEnumerator** aResult) +{ + if (NS_WARN_IF(!aResult)) { + return NS_ERROR_INVALID_ARG; + } + *aResult = nullptr; + + return NS_ERROR_FAILURE; +} diff --git a/xpcom/io/nsDirectoryService.h b/xpcom/io/nsDirectoryService.h new file mode 100644 index 000000000..d0f92b75a --- /dev/null +++ b/xpcom/io/nsDirectoryService.h @@ -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/. */ + +#ifndef nsDirectoryService_h___ +#define nsDirectoryService_h___ + +#include "nsIDirectoryService.h" +#include "nsInterfaceHashtable.h" +#include "nsIFile.h" +#include "nsIAtom.h" +#include "nsTArray.h" +#include "mozilla/Attributes.h" +#include "mozilla/StaticPtr.h" + +#define NS_XPCOM_INIT_CURRENT_PROCESS_DIR "MozBinD" // Can be used to set NS_XPCOM_CURRENT_PROCESS_DIR + // CANNOT be used to GET a location +#define NS_DIRECTORY_SERVICE_CID {0xf00152d0,0xb40b,0x11d3,{0x8c, 0x9c, 0x00, 0x00, 0x64, 0x65, 0x73, 0x74}} + +class nsDirectoryService final + : public nsIDirectoryService + , public nsIProperties + , public nsIDirectoryServiceProvider2 +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + + NS_DECL_NSIPROPERTIES + + NS_DECL_NSIDIRECTORYSERVICE + + NS_DECL_NSIDIRECTORYSERVICEPROVIDER + + NS_DECL_NSIDIRECTORYSERVICEPROVIDER2 + + nsDirectoryService(); + + static void RealInit(); + void RegisterCategoryProviders(); + + static nsresult + Create(nsISupports* aOuter, REFNSIID aIID, void** aResult); + + static mozilla::StaticRefPtr gService; + +private: + ~nsDirectoryService(); + + nsresult GetCurrentProcessDirectory(nsIFile** aFile); + + nsInterfaceHashtable mHashtable; + nsTArray> mProviders; + +public: + +#define DIR_ATOM(name_, value_) static nsIAtom* name_; +#include "nsDirectoryServiceAtomList.h" +#undef DIR_ATOM + +}; + + +#endif + diff --git a/xpcom/io/nsDirectoryServiceAtomList.h b/xpcom/io/nsDirectoryServiceAtomList.h new file mode 100644 index 000000000..38a2f0e9d --- /dev/null +++ b/xpcom/io/nsDirectoryServiceAtomList.h @@ -0,0 +1,98 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +DIR_ATOM(sCurrentProcess, NS_XPCOM_CURRENT_PROCESS_DIR) +DIR_ATOM(sGRE_Directory, NS_GRE_DIR) +DIR_ATOM(sGRE_BinDirectory, NS_GRE_BIN_DIR) +DIR_ATOM(sOS_DriveDirectory, NS_OS_DRIVE_DIR) +DIR_ATOM(sOS_TemporaryDirectory, NS_OS_TEMP_DIR) +DIR_ATOM(sOS_CurrentProcessDirectory, NS_OS_CURRENT_PROCESS_DIR) +DIR_ATOM(sOS_CurrentWorkingDirectory, NS_OS_CURRENT_WORKING_DIR) +DIR_ATOM(sOS_HomeDirectory, NS_OS_HOME_DIR) +DIR_ATOM(sOS_DesktopDirectory, NS_OS_DESKTOP_DIR) +DIR_ATOM(sInitCurrentProcess_dummy, NS_XPCOM_INIT_CURRENT_PROCESS_DIR) +#if defined (MOZ_WIDGET_COCOA) +DIR_ATOM(sDirectory, NS_OS_SYSTEM_DIR) +DIR_ATOM(sTrashDirectory, NS_MAC_TRASH_DIR) +DIR_ATOM(sStartupDirectory, NS_MAC_STARTUP_DIR) +DIR_ATOM(sShutdownDirectory, NS_MAC_SHUTDOWN_DIR) +DIR_ATOM(sAppleMenuDirectory, NS_MAC_APPLE_MENU_DIR) +DIR_ATOM(sControlPanelDirectory, NS_MAC_CONTROL_PANELS_DIR) +DIR_ATOM(sExtensionDirectory, NS_MAC_EXTENSIONS_DIR) +DIR_ATOM(sFontsDirectory, NS_MAC_FONTS_DIR) +DIR_ATOM(sPreferencesDirectory, NS_MAC_PREFS_DIR) +DIR_ATOM(sDocumentsDirectory, NS_MAC_DOCUMENTS_DIR) +DIR_ATOM(sInternetSearchDirectory, NS_MAC_INTERNET_SEARCH_DIR) +DIR_ATOM(sUserLibDirectory, NS_MAC_USER_LIB_DIR) +DIR_ATOM(sDefaultDownloadDirectory, NS_OSX_DEFAULT_DOWNLOAD_DIR) +DIR_ATOM(sUserDesktopDirectory, NS_OSX_USER_DESKTOP_DIR) +DIR_ATOM(sLocalDesktopDirectory, NS_OSX_LOCAL_DESKTOP_DIR) +DIR_ATOM(sUserApplicationsDirectory, NS_OSX_USER_APPLICATIONS_DIR) +DIR_ATOM(sLocalApplicationsDirectory, NS_OSX_LOCAL_APPLICATIONS_DIR) +DIR_ATOM(sUserDocumentsDirectory, NS_OSX_USER_DOCUMENTS_DIR) +DIR_ATOM(sLocalDocumentsDirectory, NS_OSX_LOCAL_DOCUMENTS_DIR) +DIR_ATOM(sUserInternetPlugInDirectory, NS_OSX_USER_INTERNET_PLUGIN_DIR) +DIR_ATOM(sLocalInternetPlugInDirectory, NS_OSX_LOCAL_INTERNET_PLUGIN_DIR) +DIR_ATOM(sUserFrameworksDirectory, NS_OSX_USER_FRAMEWORKS_DIR) +DIR_ATOM(sLocalFrameworksDirectory, NS_OSX_LOCAL_FRAMEWORKS_DIR) +DIR_ATOM(sUserPreferencesDirectory, NS_OSX_USER_PREFERENCES_DIR) +DIR_ATOM(sLocalPreferencesDirectory, NS_OSX_LOCAL_PREFERENCES_DIR) +DIR_ATOM(sPictureDocumentsDirectory, NS_OSX_PICTURE_DOCUMENTS_DIR) +DIR_ATOM(sMovieDocumentsDirectory, NS_OSX_MOVIE_DOCUMENTS_DIR) +DIR_ATOM(sMusicDocumentsDirectory, NS_OSX_MUSIC_DOCUMENTS_DIR) +DIR_ATOM(sInternetSitesDirectory, NS_OSX_INTERNET_SITES_DIR) +#elif defined (XP_WIN) +DIR_ATOM(sSystemDirectory, NS_OS_SYSTEM_DIR) +DIR_ATOM(sWindowsDirectory, NS_WIN_WINDOWS_DIR) +DIR_ATOM(sWindowsProgramFiles, NS_WIN_PROGRAM_FILES_DIR) +DIR_ATOM(sDesktop, NS_WIN_DESKTOP_DIR) +DIR_ATOM(sPrograms, NS_WIN_PROGRAMS_DIR) +DIR_ATOM(sControls, NS_WIN_CONTROLS_DIR) +DIR_ATOM(sPrinters, NS_WIN_PRINTERS_DIR) +DIR_ATOM(sPersonal, NS_WIN_PERSONAL_DIR) +DIR_ATOM(sFavorites, NS_WIN_FAVORITES_DIR) +DIR_ATOM(sStartup, NS_WIN_STARTUP_DIR) +DIR_ATOM(sRecent, NS_WIN_RECENT_DIR) +DIR_ATOM(sSendto, NS_WIN_SEND_TO_DIR) +DIR_ATOM(sBitbucket, NS_WIN_BITBUCKET_DIR) +DIR_ATOM(sStartmenu, NS_WIN_STARTMENU_DIR) +DIR_ATOM(sDesktopdirectory, NS_WIN_DESKTOP_DIRECTORY) +DIR_ATOM(sDrives, NS_WIN_DRIVES_DIR) +DIR_ATOM(sNetwork, NS_WIN_NETWORK_DIR) +DIR_ATOM(sNethood, NS_WIN_NETHOOD_DIR) +DIR_ATOM(sFonts, NS_WIN_FONTS_DIR) +DIR_ATOM(sTemplates, NS_WIN_TEMPLATES_DIR) +DIR_ATOM(sCommon_Startmenu, NS_WIN_COMMON_STARTMENU_DIR) +DIR_ATOM(sCommon_Programs, NS_WIN_COMMON_PROGRAMS_DIR) +DIR_ATOM(sCommon_Startup, NS_WIN_COMMON_STARTUP_DIR) +DIR_ATOM(sCommon_Desktopdirectory, NS_WIN_COMMON_DESKTOP_DIRECTORY) +DIR_ATOM(sCommon_AppData, NS_WIN_COMMON_APPDATA_DIR) +DIR_ATOM(sAppdata, NS_WIN_APPDATA_DIR) +DIR_ATOM(sLocalAppdata, NS_WIN_LOCAL_APPDATA_DIR) +#if defined(MOZ_CONTENT_SANDBOX) +DIR_ATOM(sLocalAppdataLow, NS_WIN_LOCAL_APPDATA_LOW_DIR) +DIR_ATOM(sLowIntegrityTempBase, NS_WIN_LOW_INTEGRITY_TEMP_BASE) +#endif +DIR_ATOM(sPrinthood, NS_WIN_PRINTHOOD) +DIR_ATOM(sWinCookiesDirectory, NS_WIN_COOKIES_DIR) +DIR_ATOM(sDefaultDownloadDirectory, NS_WIN_DEFAULT_DOWNLOAD_DIR) +DIR_ATOM(sDocs, NS_WIN_DOCUMENTS_DIR) +DIR_ATOM(sPictures, NS_WIN_PICTURES_DIR) +DIR_ATOM(sMusic, NS_WIN_MUSIC_DIR) +DIR_ATOM(sVideos, NS_WIN_VIDEOS_DIR) +#elif defined (XP_UNIX) +DIR_ATOM(sLocalDirectory, NS_UNIX_LOCAL_DIR) +DIR_ATOM(sLibDirectory, NS_UNIX_LIB_DIR) +DIR_ATOM(sDefaultDownloadDirectory, NS_UNIX_DEFAULT_DOWNLOAD_DIR) +DIR_ATOM(sXDGDesktop, NS_UNIX_XDG_DESKTOP_DIR) +DIR_ATOM(sXDGDocuments, NS_UNIX_XDG_DOCUMENTS_DIR) +DIR_ATOM(sXDGDownload, NS_UNIX_XDG_DOWNLOAD_DIR) +DIR_ATOM(sXDGMusic, NS_UNIX_XDG_MUSIC_DIR) +DIR_ATOM(sXDGPictures, NS_UNIX_XDG_PICTURES_DIR) +DIR_ATOM(sXDGPublicShare, NS_UNIX_XDG_PUBLIC_SHARE_DIR) +DIR_ATOM(sXDGTemplates, NS_UNIX_XDG_TEMPLATES_DIR) +DIR_ATOM(sXDGVideos, NS_UNIX_XDG_VIDEOS_DIR) +#endif diff --git a/xpcom/io/nsDirectoryServiceDefs.h b/xpcom/io/nsDirectoryServiceDefs.h new file mode 100644 index 000000000..0bdc5e390 --- /dev/null +++ b/xpcom/io/nsDirectoryServiceDefs.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/. */ + +/** + * Defines the property names for directories available from + * nsIDirectoryService. These dirs are always available even if no + * nsIDirectoryServiceProviders have been registered with the service. + * Application level keys are defined in nsAppDirectoryServiceDefs.h. + * + * Keys whose definition ends in "DIR" or "FILE" return a single nsIFile (or + * subclass). Keys whose definition ends in "LIST" return an nsISimpleEnumerator + * which enumerates a list of file objects. + * + * Defines listed in this file are FROZEN. This list may grow. + */ + +#ifndef nsDirectoryServiceDefs_h___ +#define nsDirectoryServiceDefs_h___ + +/* General OS specific locations */ + +#define NS_OS_HOME_DIR "Home" +#define NS_OS_TEMP_DIR "TmpD" +#define NS_OS_CURRENT_WORKING_DIR "CurWorkD" +/* Files stored in this directory will appear on the user's desktop, + * if there is one, otherwise it's just the same as "Home" + */ +#define NS_OS_DESKTOP_DIR "Desk" + +/* Property returns the directory in which the procces was started from. + * On Unix this will be the path in the MOZILLA_FIVE_HOME env var and if + * unset will be the current working directory. + */ +#define NS_OS_CURRENT_PROCESS_DIR "CurProcD" + +/* This location is similar to NS_OS_CURRENT_PROCESS_DIR, however, + * NS_XPCOM_CURRENT_PROCESS_DIR can be overriden by passing a "bin + * directory" to NS_InitXPCOM2(). + */ +#define NS_XPCOM_CURRENT_PROCESS_DIR "XCurProcD" + +/* Property will return the location of the the XPCOM Shared Library. + */ +#define NS_XPCOM_LIBRARY_FILE "XpcomLib" + +/* Property will return the current location of the GRE directory. + * On OSX, this typically points to Contents/Resources in the app bundle. + * If no GRE is used, this propery will behave like + * NS_XPCOM_CURRENT_PROCESS_DIR. + */ +#define NS_GRE_DIR "GreD" + +/* Property will return the current location of the GRE-binaries directory. + * On OSX, this typically points to Contents/MacOS in the app bundle. On + * all other platforms, this will be identical to NS_GRE_DIR. + * Since this property is based on the NS_GRE_DIR, if no GRE is used, this + * propery will behave like NS_XPCOM_CURRENT_PROCESS_DIR. + */ +#define NS_GRE_BIN_DIR "GreBinD" + +/* Platform Specific Locations */ + +#if !defined (XP_UNIX) || defined(MOZ_WIDGET_COCOA) + #define NS_OS_SYSTEM_DIR "SysD" +#endif + +#if defined (MOZ_WIDGET_COCOA) + #define NS_MAC_DESKTOP_DIR NS_OS_DESKTOP_DIR + #define NS_MAC_TRASH_DIR "Trsh" + #define NS_MAC_STARTUP_DIR "Strt" + #define NS_MAC_SHUTDOWN_DIR "Shdwn" + #define NS_MAC_APPLE_MENU_DIR "ApplMenu" + #define NS_MAC_CONTROL_PANELS_DIR "CntlPnl" + #define NS_MAC_EXTENSIONS_DIR "Exts" + #define NS_MAC_FONTS_DIR "Fnts" + #define NS_MAC_PREFS_DIR "Prfs" + #define NS_MAC_DOCUMENTS_DIR "Docs" + #define NS_MAC_INTERNET_SEARCH_DIR "ISrch" + #define NS_OSX_HOME_DIR NS_OS_HOME_DIR + #define NS_MAC_HOME_DIR NS_OS_HOME_DIR + #define NS_MAC_DEFAULT_DOWNLOAD_DIR "DfltDwnld" + #define NS_MAC_USER_LIB_DIR "ULibDir" // Only available under OS X + #define NS_OSX_DEFAULT_DOWNLOAD_DIR NS_MAC_DEFAULT_DOWNLOAD_DIR + #define NS_OSX_USER_DESKTOP_DIR "UsrDsk" + #define NS_OSX_LOCAL_DESKTOP_DIR "LocDsk" + #define NS_OSX_USER_APPLICATIONS_DIR "UsrApp" + #define NS_OSX_LOCAL_APPLICATIONS_DIR "LocApp" + #define NS_OSX_USER_DOCUMENTS_DIR "UsrDocs" + #define NS_OSX_LOCAL_DOCUMENTS_DIR "LocDocs" + #define NS_OSX_USER_INTERNET_PLUGIN_DIR "UsrIntrntPlgn" + #define NS_OSX_LOCAL_INTERNET_PLUGIN_DIR "LoclIntrntPlgn" + #define NS_OSX_USER_FRAMEWORKS_DIR "UsrFrmwrks" + #define NS_OSX_LOCAL_FRAMEWORKS_DIR "LocFrmwrks" + #define NS_OSX_USER_PREFERENCES_DIR "UsrPrfs" + #define NS_OSX_LOCAL_PREFERENCES_DIR "LocPrfs" + #define NS_OSX_PICTURE_DOCUMENTS_DIR "Pct" + #define NS_OSX_MOVIE_DOCUMENTS_DIR "Mov" + #define NS_OSX_MUSIC_DOCUMENTS_DIR "Music" + #define NS_OSX_INTERNET_SITES_DIR "IntrntSts" +#elif defined (XP_WIN) + #define NS_WIN_WINDOWS_DIR "WinD" + #define NS_WIN_PROGRAM_FILES_DIR "ProgF" + #define NS_WIN_HOME_DIR NS_OS_HOME_DIR + #define NS_WIN_DESKTOP_DIR "DeskV" // virtual folder at the root of the namespace + #define NS_WIN_PROGRAMS_DIR "Progs" // User start menu programs directory! + #define NS_WIN_CONTROLS_DIR "Cntls" + #define NS_WIN_PRINTERS_DIR "Prnts" + #define NS_WIN_PERSONAL_DIR "Pers" + #define NS_WIN_FAVORITES_DIR "Favs" + #define NS_WIN_STARTUP_DIR "Strt" + #define NS_WIN_RECENT_DIR "Rcnt" + #define NS_WIN_SEND_TO_DIR "SndTo" + #define NS_WIN_BITBUCKET_DIR "Buckt" + #define NS_WIN_STARTMENU_DIR "Strt" +// This gives the same thing as NS_OS_DESKTOP_DIR + #define NS_WIN_DESKTOP_DIRECTORY "DeskP" // file sys dir which physically stores objects on desktop + #define NS_WIN_DRIVES_DIR "Drivs" + #define NS_WIN_NETWORK_DIR "NetW" + #define NS_WIN_NETHOOD_DIR "netH" + #define NS_WIN_FONTS_DIR "Fnts" + #define NS_WIN_TEMPLATES_DIR "Tmpls" + #define NS_WIN_COMMON_STARTMENU_DIR "CmStrt" + #define NS_WIN_COMMON_PROGRAMS_DIR "CmPrgs" + #define NS_WIN_COMMON_STARTUP_DIR "CmStrt" + #define NS_WIN_COMMON_DESKTOP_DIRECTORY "CmDeskP" + #define NS_WIN_COMMON_APPDATA_DIR "CmAppData" + #define NS_WIN_APPDATA_DIR "AppData" + #define NS_WIN_LOCAL_APPDATA_DIR "LocalAppData" +#if defined(MOZ_CONTENT_SANDBOX) + #define NS_WIN_LOCAL_APPDATA_LOW_DIR "LocalAppDataLow" + #define NS_WIN_LOW_INTEGRITY_TEMP_BASE "LowTmpDBase" +#endif + #define NS_WIN_PRINTHOOD "PrntHd" + #define NS_WIN_COOKIES_DIR "CookD" + #define NS_WIN_DEFAULT_DOWNLOAD_DIR "DfltDwnld" + // On Win7 and up these ids will return the default save-to location for + // Windows Libraries associated with the specific content type. For other + // os they return the local user folder. Note these can return network file + // paths which can jank the ui thread so be careful how you access them. + #define NS_WIN_DOCUMENTS_DIR "Docs" + #define NS_WIN_PICTURES_DIR "Pict" + #define NS_WIN_MUSIC_DIR "Music" + #define NS_WIN_VIDEOS_DIR "Vids" +#elif defined (XP_UNIX) + #define NS_UNIX_LOCAL_DIR "Locl" + #define NS_UNIX_LIB_DIR "LibD" + #define NS_UNIX_HOME_DIR NS_OS_HOME_DIR + #define NS_UNIX_XDG_DESKTOP_DIR "XDGDesk" + #define NS_UNIX_XDG_DOCUMENTS_DIR "XDGDocs" + #define NS_UNIX_XDG_DOWNLOAD_DIR "XDGDwnld" + #define NS_UNIX_XDG_MUSIC_DIR "XDGMusic" + #define NS_UNIX_XDG_PICTURES_DIR "XDGPict" + #define NS_UNIX_XDG_PUBLIC_SHARE_DIR "XDGPubSh" + #define NS_UNIX_XDG_TEMPLATES_DIR "XDGTempl" + #define NS_UNIX_XDG_VIDEOS_DIR "XDGVids" + #define NS_UNIX_DEFAULT_DOWNLOAD_DIR "DfltDwnld" +#endif + +/* Deprecated */ + +#define NS_OS_DRIVE_DIR "DrvD" + + + +#endif diff --git a/xpcom/io/nsDirectoryServiceUtils.h b/xpcom/io/nsDirectoryServiceUtils.h new file mode 100644 index 000000000..6100e7565 --- /dev/null +++ b/xpcom/io/nsDirectoryServiceUtils.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 nsDirectoryServiceUtils_h___ +#define nsDirectoryServiceUtils_h___ + +#include "nsIServiceManager.h" +#include "nsIProperties.h" +#include "nsServiceManagerUtils.h" +#include "nsCOMPtr.h" +#include "nsXPCOMCID.h" +#include "nsIFile.h" + +inline nsresult +NS_GetSpecialDirectory(const char* aSpecialDirName, nsIFile** aResult) +{ + nsresult rv; + nsCOMPtr serv(do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID, + &rv)); + if (NS_FAILED(rv)) { + return rv; + } + + return serv->Get(aSpecialDirName, NS_GET_IID(nsIFile), + reinterpret_cast(aResult)); +} + +#endif diff --git a/xpcom/io/nsEscape.cpp b/xpcom/io/nsEscape.cpp new file mode 100644 index 000000000..f16edc4ce --- /dev/null +++ b/xpcom/io/nsEscape.cpp @@ -0,0 +1,633 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. 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 "mozilla/ArrayUtils.h" +#include "mozilla/BinarySearch.h" +#include "nsTArray.h" +#include "nsCRT.h" +#include "plstr.h" + +static const char hexCharsUpper[] = "0123456789ABCDEF"; +static const char hexCharsUpperLower[] = "0123456789ABCDEFabcdef"; + +static const int netCharType[256] = +/* Bit 0 xalpha -- the alphas +** Bit 1 xpalpha -- as xalpha but +** converts spaces to plus and plus to %2B +** Bit 3 ... path -- as xalphas but doesn't escape '/' +*/ + /* 0 1 2 3 4 5 6 7 8 9 A B C D E F */ + { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 0x */ + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 1x */ + 0,0,0,0,0,0,0,0,0,0,7,4,0,7,7,4, /* 2x !"#$%&'()*+,-./ */ + 7,7,7,7,7,7,7,7,7,7,0,0,0,0,0,0, /* 3x 0123456789:;<=>? */ + 0,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, /* 4x @ABCDEFGHIJKLMNO */ + /* bits for '@' changed from 7 to 0 so '@' can be escaped */ + /* in usernames and passwords in publishing. */ + 7,7,7,7,7,7,7,7,7,7,7,0,0,0,0,7, /* 5X PQRSTUVWXYZ[\]^_ */ + 0,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, /* 6x `abcdefghijklmno */ + 7,7,7,7,7,7,7,7,7,7,7,0,0,0,0,0, /* 7X pqrstuvwxyz{\}~ DEL */ + 0, }; + +/* decode % escaped hex codes into character values + */ +#define UNHEX(C) \ + ((C >= '0' && C <= '9') ? C - '0' : \ + ((C >= 'A' && C <= 'F') ? C - 'A' + 10 : \ + ((C >= 'a' && C <= 'f') ? C - 'a' + 10 : 0))) + + +#define IS_OK(C) (netCharType[((unsigned int)(C))] & (aFlags)) +#define HEX_ESCAPE '%' + +static const uint32_t ENCODE_MAX_LEN = 6; // %uABCD + +static uint32_t +AppendPercentHex(char* aBuffer, unsigned char aChar) +{ + uint32_t i = 0; + aBuffer[i++] = '%'; + aBuffer[i++] = hexCharsUpper[aChar >> 4]; // high nibble + aBuffer[i++] = hexCharsUpper[aChar & 0xF]; // low nibble + return i; +} + +static uint32_t +AppendPercentHex(char16_t* aBuffer, char16_t aChar) +{ + uint32_t i = 0; + aBuffer[i++] = '%'; + if (aChar & 0xff00) { + aBuffer[i++] = 'u'; + aBuffer[i++] = hexCharsUpper[aChar >> 12]; // high-byte high nibble + aBuffer[i++] = hexCharsUpper[(aChar >> 8) & 0xF]; // high-byte low nibble + } + aBuffer[i++] = hexCharsUpper[(aChar >> 4) & 0xF]; // low-byte high nibble + aBuffer[i++] = hexCharsUpper[aChar & 0xF]; // low-byte low nibble + return i; +} + +//---------------------------------------------------------------------------------------- +char* +nsEscape(const char* aStr, size_t aLength, size_t* aOutputLength, + nsEscapeMask aFlags) +//---------------------------------------------------------------------------------------- +{ + if (!aStr) { + return nullptr; + } + + size_t charsToEscape = 0; + + const unsigned char* src = (const unsigned char*)aStr; + for (size_t i = 0; i < aLength; ++i) { + if (!IS_OK(src[i])) { + charsToEscape++; + } + } + + // calculate how much memory should be allocated + // original length + 2 bytes for each escaped character + terminating '\0' + // do the sum in steps to check for overflow + size_t dstSize = aLength + 1 + charsToEscape; + if (dstSize <= aLength) { + return nullptr; + } + dstSize += charsToEscape; + if (dstSize < aLength) { + return nullptr; + } + + // fail if we need more than 4GB + if (dstSize > UINT32_MAX) { + return nullptr; + } + + char* result = (char*)moz_xmalloc(dstSize); + if (!result) { + return nullptr; + } + + unsigned char* dst = (unsigned char*)result; + src = (const unsigned char*)aStr; + if (aFlags == url_XPAlphas) { + for (size_t i = 0; i < aLength; ++i) { + unsigned char c = *src++; + if (IS_OK(c)) { + *dst++ = c; + } else if (c == ' ') { + *dst++ = '+'; /* convert spaces to pluses */ + } else { + *dst++ = HEX_ESCAPE; + *dst++ = hexCharsUpper[c >> 4]; /* high nibble */ + *dst++ = hexCharsUpper[c & 0x0f]; /* low nibble */ + } + } + } else { + for (size_t i = 0; i < aLength; ++i) { + unsigned char c = *src++; + if (IS_OK(c)) { + *dst++ = c; + } else { + *dst++ = HEX_ESCAPE; + *dst++ = hexCharsUpper[c >> 4]; /* high nibble */ + *dst++ = hexCharsUpper[c & 0x0f]; /* low nibble */ + } + } + } + + *dst = '\0'; /* tack on eos */ + if (aOutputLength) { + *aOutputLength = dst - (unsigned char*)result; + } + + return result; +} + +//---------------------------------------------------------------------------------------- +char* +nsUnescape(char* aStr) +//---------------------------------------------------------------------------------------- +{ + nsUnescapeCount(aStr); + return aStr; +} + +//---------------------------------------------------------------------------------------- +int32_t +nsUnescapeCount(char* aStr) +//---------------------------------------------------------------------------------------- +{ + char* src = aStr; + char* dst = aStr; + + char c1[] = " "; + char c2[] = " "; + char* const pc1 = c1; + char* const pc2 = c2; + + if (!*src) { + // A null string was passed in. Nothing to escape. + // Returns early as the string might not actually be mutable with + // length 0. + return 0; + } + + while (*src) { + c1[0] = *(src + 1); + if (*(src + 1) == '\0') { + c2[0] = '\0'; + } else { + c2[0] = *(src + 2); + } + + if (*src != HEX_ESCAPE || PL_strpbrk(pc1, hexCharsUpperLower) == 0 || + PL_strpbrk(pc2, hexCharsUpperLower) == 0) { + *dst++ = *src++; + } else { + src++; /* walk over escape */ + if (*src) { + *dst = UNHEX(*src) << 4; + src++; + } + if (*src) { + *dst = (*dst + UNHEX(*src)); + src++; + } + dst++; + } + } + + *dst = 0; + return (int)(dst - aStr); + +} /* NET_UnEscapeCnt */ + + +char* +nsEscapeHTML(const char* aString) +{ + char* rv = nullptr; + /* XXX Hardcoded max entity len. The +1 is for the trailing null. */ + uint32_t len = strlen(aString); + if (len >= (UINT32_MAX / 6)) { + return nullptr; + } + + rv = (char*)moz_xmalloc((6 * len) + 1); + char* ptr = rv; + + if (rv) { + for (; *aString != '\0'; ++aString) { + if (*aString == '<') { + *ptr++ = '&'; + *ptr++ = 'l'; + *ptr++ = 't'; + *ptr++ = ';'; + } else if (*aString == '>') { + *ptr++ = '&'; + *ptr++ = 'g'; + *ptr++ = 't'; + *ptr++ = ';'; + } else if (*aString == '&') { + *ptr++ = '&'; + *ptr++ = 'a'; + *ptr++ = 'm'; + *ptr++ = 'p'; + *ptr++ = ';'; + } else if (*aString == '"') { + *ptr++ = '&'; + *ptr++ = 'q'; + *ptr++ = 'u'; + *ptr++ = 'o'; + *ptr++ = 't'; + *ptr++ = ';'; + } else if (*aString == '\'') { + *ptr++ = '&'; + *ptr++ = '#'; + *ptr++ = '3'; + *ptr++ = '9'; + *ptr++ = ';'; + } else { + *ptr++ = *aString; + } + } + *ptr = '\0'; + } + + return rv; +} + +char16_t* +nsEscapeHTML2(const char16_t* aSourceBuffer, int32_t aSourceBufferLen) +{ + // Calculate the length, if the caller didn't. + if (aSourceBufferLen < 0) { + aSourceBufferLen = NS_strlen(aSourceBuffer); + } + + /* XXX Hardcoded max entity len. */ + if (uint32_t(aSourceBufferLen) >= + ((UINT32_MAX - sizeof(char16_t)) / (6 * sizeof(char16_t)))) { + return nullptr; + } + + char16_t* resultBuffer = (char16_t*)moz_xmalloc( + aSourceBufferLen * 6 * sizeof(char16_t) + sizeof(char16_t('\0'))); + char16_t* ptr = resultBuffer; + + if (resultBuffer) { + int32_t i; + + for (i = 0; i < aSourceBufferLen; ++i) { + if (aSourceBuffer[i] == '<') { + *ptr++ = '&'; + *ptr++ = 'l'; + *ptr++ = 't'; + *ptr++ = ';'; + } else if (aSourceBuffer[i] == '>') { + *ptr++ = '&'; + *ptr++ = 'g'; + *ptr++ = 't'; + *ptr++ = ';'; + } else if (aSourceBuffer[i] == '&') { + *ptr++ = '&'; + *ptr++ = 'a'; + *ptr++ = 'm'; + *ptr++ = 'p'; + *ptr++ = ';'; + } else if (aSourceBuffer[i] == '"') { + *ptr++ = '&'; + *ptr++ = 'q'; + *ptr++ = 'u'; + *ptr++ = 'o'; + *ptr++ = 't'; + *ptr++ = ';'; + } else if (aSourceBuffer[i] == '\'') { + *ptr++ = '&'; + *ptr++ = '#'; + *ptr++ = '3'; + *ptr++ = '9'; + *ptr++ = ';'; + } else { + *ptr++ = aSourceBuffer[i]; + } + } + *ptr = 0; + } + + return resultBuffer; +} + +//---------------------------------------------------------------------------------------- +// +// The following table encodes which characters needs to be escaped for which +// parts of an URL. The bits are the "url components" in the enum EscapeMask, +// see nsEscape.h. +// +// esc_Scheme = 1 +// esc_Username = 2 +// esc_Password = 4 +// esc_Host = 8 +// esc_Directory = 16 +// esc_FileBaseName = 32 +// esc_FileExtension = 64 +// esc_Param = 128 +// esc_Query = 256 +// esc_Ref = 512 + +static const uint32_t EscapeChars[256] = +// 0 1 2 3 4 5 6 7 8 9 A B C D E F +{ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 1x + 0,1023, 0, 512,1023, 0,1023, 112,1023,1023,1023,1023,1023,1023, 953, 784, // 2x !"#$%&'()*+,-./ + 1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1008,1008, 0,1008, 0, 768, // 3x 0123456789:;<=>? + 1008,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023, // 4x @ABCDEFGHIJKLMNO + 1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1008, 896,1008, 896,1023, // 5x PQRSTUVWXYZ[\]^_ + 384,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023, // 6x `abcdefghijklmno + 1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023, 896,1012, 896,1023, 0, // 7x pqrstuvwxyz{|}~ DEL + 0 // 80 to FF are zero +}; + +static uint16_t dontNeedEscape(unsigned char aChar, uint32_t aFlags) +{ + return EscapeChars[(uint32_t)aChar] & aFlags; +} +static uint16_t dontNeedEscape(uint16_t aChar, uint32_t aFlags) +{ + return aChar < mozilla::ArrayLength(EscapeChars) ? + (EscapeChars[(uint32_t)aChar] & aFlags) : 0; +} + +//---------------------------------------------------------------------------------------- + +/** + * Templated helper for URL escaping a portion of a string. + * + * @param aPart The pointer to the beginning of the portion of the string to + * escape. + * @param aPartLen The length of the string to escape. + * @param aFlags Flags used to configure escaping. @see EscapeMask + * @param aResult String that has the URL escaped portion appended to. Only + * altered if the string is URL escaped or |esc_AlwaysCopy| is specified. + * @param aDidAppend Indicates whether or not data was appended to |aResult|. + * @return NS_ERROR_INVALID_ARG, NS_ERROR_OUT_OF_MEMORY on failure. + */ +template +static nsresult +T_EscapeURL(const typename T::char_type* aPart, size_t aPartLen, + uint32_t aFlags, T& aResult, bool& aDidAppend) +{ + typedef nsCharTraits traits; + typedef typename traits::unsigned_char_type unsigned_char_type; + static_assert(sizeof(*aPart) == 1 || sizeof(*aPart) == 2, + "unexpected char type"); + + if (!aPart) { + NS_NOTREACHED("null pointer"); + return NS_ERROR_INVALID_ARG; + } + + bool forced = !!(aFlags & esc_Forced); + bool ignoreNonAscii = !!(aFlags & esc_OnlyASCII); + bool ignoreAscii = !!(aFlags & esc_OnlyNonASCII); + bool writing = !!(aFlags & esc_AlwaysCopy); + bool colon = !!(aFlags & esc_Colon); + + auto src = reinterpret_cast(aPart); + + typename T::char_type tempBuffer[100]; + unsigned int tempBufferPos = 0; + + bool previousIsNonASCII = false; + for (size_t i = 0; i < aPartLen; ++i) { + unsigned_char_type c = *src++; + + // if the char has not to be escaped or whatever follows % is + // a valid escaped string, just copy the char. + // + // Also the % will not be escaped until forced + // See bugzilla bug 61269 for details why we changed this + // + // And, we will not escape non-ascii characters if requested. + // On special request we will also escape the colon even when + // not covered by the matrix. + // ignoreAscii is not honored for control characters (C0 and DEL) + // + // And, we should escape the '|' character when it occurs after any + // non-ASCII character as it may be aPart of a multi-byte character. + // + // 0x20..0x7e are the valid ASCII characters. We also escape spaces + // (0x20) since they are not legal in URLs. + if ((dontNeedEscape(c, aFlags) || (c == HEX_ESCAPE && !forced) + || (c > 0x7f && ignoreNonAscii) + || (c > 0x20 && c < 0x7f && ignoreAscii)) + && !(c == ':' && colon) + && !(previousIsNonASCII && c == '|' && !ignoreNonAscii)) { + if (writing) { + tempBuffer[tempBufferPos++] = c; + } + } else { /* do the escape magic */ + if (!writing) { + if (!aResult.Append(aPart, i, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + writing = true; + } + uint32_t len = ::AppendPercentHex(tempBuffer + tempBufferPos, c); + tempBufferPos += len; + MOZ_ASSERT(len <= ENCODE_MAX_LEN, "potential buffer overflow"); + } + + // Flush the temp buffer if it doesnt't have room for another encoded char. + if (tempBufferPos >= mozilla::ArrayLength(tempBuffer) - ENCODE_MAX_LEN) { + NS_ASSERTION(writing, "should be writing"); + if (!aResult.Append(tempBuffer, tempBufferPos, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + tempBufferPos = 0; + } + + previousIsNonASCII = (c > 0x7f); + } + if (writing) { + if (!aResult.Append(tempBuffer, tempBufferPos, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + } + aDidAppend = writing; + return NS_OK; +} + +bool +NS_EscapeURL(const char* aPart, int32_t aPartLen, uint32_t aFlags, + nsACString& aResult) +{ + if (aPartLen < 0) { + aPartLen = strlen(aPart); + } + + bool result = false; + nsresult rv = T_EscapeURL(aPart, aPartLen, aFlags, aResult, result); + if (NS_FAILED(rv)) { + ::NS_ABORT_OOM(aResult.Length() * sizeof(nsACString::char_type)); + } + + return result; +} + +nsresult +NS_EscapeURL(const nsCSubstring& aStr, uint32_t aFlags, nsCSubstring& aResult, + const mozilla::fallible_t&) +{ + bool appended = false; + nsresult rv = T_EscapeURL(aStr.Data(), aStr.Length(), aFlags, aResult, appended); + if (NS_FAILED(rv)) { + aResult.Truncate(); + return rv; + } + + if (!appended) { + aResult = aStr; + } + + return rv; +} + +const nsSubstring& +NS_EscapeURL(const nsSubstring& aStr, uint32_t aFlags, nsSubstring& aResult) +{ + bool result = false; + nsresult rv = T_EscapeURL(aStr.Data(), aStr.Length(), aFlags, aResult, result); + + if (NS_FAILED(rv)) { + ::NS_ABORT_OOM(aResult.Length() * sizeof(nsSubstring::char_type)); + } + + if (result) { + return aResult; + } + return aStr; +} + +// Starting at aStr[aStart] find the first index in aStr that matches any +// character in aForbidden. Return false if not found. +static bool +FindFirstMatchFrom(const nsAFlatString& aStr, size_t aStart, + const nsTArray& aForbidden, size_t* aIndex) +{ + const size_t len = aForbidden.Length(); + for (size_t j = aStart, l = aStr.Length(); j < l; ++j) { + size_t unused; + if (mozilla::BinarySearch(aForbidden, 0, len, aStr[j], &unused)) { + *aIndex = j; + return true; + } + } + return false; +} + +const nsSubstring& +NS_EscapeURL(const nsAFlatString& aStr, const nsTArray& aForbidden, + nsSubstring& aResult) +{ + bool didEscape = false; + for (size_t i = 0, strLen = aStr.Length(); i < strLen; ) { + size_t j; + if (MOZ_UNLIKELY(FindFirstMatchFrom(aStr, i, aForbidden, &j))) { + if (i == 0) { + didEscape = true; + aResult.Truncate(); + aResult.SetCapacity(aStr.Length()); + } + if (j != i) { + // The substring from 'i' up to 'j' that needs no escaping. + aResult.Append(nsDependentSubstring(aStr, i, j - i)); + } + char16_t buffer[ENCODE_MAX_LEN]; + uint32_t bufferLen = ::AppendPercentHex(buffer, aStr[j]); + MOZ_ASSERT(bufferLen <= ENCODE_MAX_LEN, "buffer overflow"); + aResult.Append(buffer, bufferLen); + i = j + 1; + } else { + if (MOZ_UNLIKELY(didEscape)) { + // The tail of the string that needs no escaping. + aResult.Append(nsDependentSubstring(aStr, i, strLen - i)); + } + break; + } + } + if (MOZ_UNLIKELY(didEscape)) { + return aResult; + } + return aStr; +} + +#define ISHEX(c) memchr(hexCharsUpperLower, c, sizeof(hexCharsUpperLower)-1) + +bool +NS_UnescapeURL(const char* aStr, int32_t aLen, uint32_t aFlags, + nsACString& aResult) +{ + if (!aStr) { + NS_NOTREACHED("null pointer"); + return false; + } + + MOZ_ASSERT(aResult.IsEmpty(), + "Passing a non-empty string as an out parameter!"); + + if (aLen < 0) { + aLen = strlen(aStr); + } + + bool ignoreNonAscii = !!(aFlags & esc_OnlyASCII); + bool ignoreAscii = !!(aFlags & esc_OnlyNonASCII); + bool writing = !!(aFlags & esc_AlwaysCopy); + bool skipControl = !!(aFlags & esc_SkipControl); + bool skipInvalidHostChar = !!(aFlags & esc_Host); + + if (writing) { + aResult.SetCapacity(aLen); + } + + const char* last = aStr; + const char* p = aStr; + + for (int i = 0; i < aLen; ++i, ++p) { + if (*p == HEX_ESCAPE && i < aLen - 2) { + unsigned char c1 = *((unsigned char*)p + 1); + unsigned char c2 = *((unsigned char*)p + 2); + unsigned char u = (UNHEX(c1) << 4) + UNHEX(c2); + if (ISHEX(c1) && ISHEX(c2) && + (!skipInvalidHostChar || dontNeedEscape(u, aFlags) || c1 >= '8') && + ((c1 < '8' && !ignoreAscii) || (c1 >= '8' && !ignoreNonAscii)) && + !(skipControl && + (c1 < '2' || (c1 == '7' && (c2 == 'f' || c2 == 'F'))))) { + if (!writing) { + writing = true; + aResult.SetCapacity(aLen); + } + if (p > last) { + aResult.Append(last, p - last); + last = p; + } + aResult.Append(u); + i += 2; + p += 2; + last += 3; + } + } + } + if (writing && last < aStr + aLen) { + aResult.Append(last, aStr + aLen - last); + } + + return writing; +} diff --git a/xpcom/io/nsEscape.h b/xpcom/io/nsEscape.h new file mode 100644 index 000000000..bf89b737a --- /dev/null +++ b/xpcom/io/nsEscape.h @@ -0,0 +1,224 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* First checked in on 98/12/03 by John R. McMullen, derived from net.h/mkparse.c. */ + +#ifndef _ESCAPE_H_ +#define _ESCAPE_H_ + +#include "nscore.h" +#include "nsError.h" +#include "nsString.h" + +/** + * Valid mask values for nsEscape + * Note: these values are copied in nsINetUtil.idl. Any changes should be kept + * in sync. + */ +typedef enum { + url_All = 0, // %-escape every byte unconditionally + url_XAlphas = 1u << 0, // Normal escape - leave alphas intact, escape the rest + url_XPAlphas = 1u << 1, // As url_XAlphas, but convert spaces (0x20) to '+' and plus to %2B + url_Path = 1u << 2 // As url_XAlphas, but don't escape slash ('/') +} nsEscapeMask; + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Escape the given string according to mask + * @param aSstr The string to escape + * @param aLength The length of the string to escape + * @param aOutputLen A pointer that will be used to store the length of the + * output string, if not null + * @param aMask How to escape the string + * @return A newly allocated escaped string that must be free'd with + * nsCRT::free, or null on failure + * @note: Please, don't use this function. Use NS_Escape instead! + */ +char* nsEscape(const char* aStr, size_t aLength, size_t* aOutputLen, + nsEscapeMask aMask); + +char* nsUnescape(char* aStr); +/* decode % escaped hex codes into character values, + * modifies the parameter, returns the same buffer + */ + +int32_t nsUnescapeCount(char* aStr); +/* decode % escaped hex codes into character values, + * modifies the parameter buffer, returns the length of the result + * (result may contain \0's). + */ + +char* +nsEscapeHTML(const char* aString); + +char16_t* +nsEscapeHTML2(const char16_t* aSourceBuffer, + int32_t aSourceBufferLen = -1); +/* + * Escape problem char's for HTML display + */ + + +#ifdef __cplusplus +} +#endif + + +/** + * NS_EscapeURL/NS_UnescapeURL constants for |flags| parameter: + * + * Note: These values are copied to nsINetUtil.idl + * Any changes should be kept in sync + */ +enum EscapeMask { + /** url components **/ + esc_Scheme = 1u << 0, + esc_Username = 1u << 1, + esc_Password = 1u << 2, + esc_Host = 1u << 3, + esc_Directory = 1u << 4, + esc_FileBaseName = 1u << 5, + esc_FileExtension = 1u << 6, + esc_FilePath = esc_Directory | esc_FileBaseName | esc_FileExtension, + esc_Param = 1u << 7, + esc_Query = 1u << 8, + esc_Ref = 1u << 9, + /** special flags **/ + esc_Minimal = esc_Scheme | esc_Username | esc_Password | esc_Host | esc_FilePath | esc_Param | esc_Query | esc_Ref, + esc_Forced = 1u << 10, /* forces escaping of existing escape sequences */ + esc_OnlyASCII = 1u << 11, /* causes non-ascii octets to be skipped */ + esc_OnlyNonASCII = 1u << 12, /* causes _graphic_ ascii octets (0x20-0x7E) + * to be skipped when escaping. causes all + * ascii octets (<= 0x7F) to be skipped when unescaping */ + esc_AlwaysCopy = 1u << 13, /* copy input to result buf even if escaping is unnecessary */ + esc_Colon = 1u << 14, /* forces escape of colon */ + esc_SkipControl = 1u << 15 /* skips C0 and DEL from unescaping */ +}; + +/** + * NS_EscapeURL + * + * Escapes invalid char's in an URL segment. Has no side-effect if the URL + * segment is already escaped, unless aFlags has the esc_Forced bit in which + * case % will also be escaped. Iff some part of aStr is escaped is the + * final result appended to aResult. You can also request that aStr is + * always appended to aResult with esc_AlwaysCopy. + * + * @param aStr url segment string + * @param aLen url segment string length (-1 if unknown) + * @param aFlags url segment type flag (see EscapeMask above) + * @param aResult result buffer, untouched if aStr is already escaped unless + * aFlags has esc_AlwaysCopy + * + * @return true if aResult was written to (i.e. at least one character was + * escaped or esc_AlwaysCopy was requested), false otherwise. + */ +bool NS_EscapeURL(const char* aStr, + int32_t aLen, + uint32_t aFlags, + nsACString& aResult); + +/** + * Expands URL escape sequences... beware embedded null bytes! + * + * @param aStr url string to unescape + * @param aLen length of aStr + * @param aFlags only esc_OnlyNonASCII, esc_SkipControl and esc_AlwaysCopy + * are recognized + * @param aResult result buffer, untouched if aStr is already unescaped unless + * aFlags has esc_AlwaysCopy + * + * @return true if aResult was written to (i.e. at least one character was + * unescaped or esc_AlwaysCopy was requested), false otherwise. + */ +bool NS_UnescapeURL(const char* aStr, + int32_t aLen, + uint32_t aFlags, + nsACString& aResult); + +/** returns resultant string length **/ +inline int32_t +NS_UnescapeURL(char* aStr) +{ + return nsUnescapeCount(aStr); +} + +/** + * String friendly versions... + */ +inline const nsCSubstring& +NS_EscapeURL(const nsCSubstring& aStr, uint32_t aFlags, nsCSubstring& aResult) +{ + if (NS_EscapeURL(aStr.Data(), aStr.Length(), aFlags, aResult)) { + return aResult; + } + return aStr; +} + +/** + * Fallible version of NS_EscapeURL. On success aResult will point to either + * the original string or an escaped copy. + */ +nsresult +NS_EscapeURL(const nsCSubstring& aStr, uint32_t aFlags, nsCSubstring& aResult, + const mozilla::fallible_t&); + +inline const nsCSubstring& +NS_UnescapeURL(const nsCSubstring& aStr, uint32_t aFlags, nsCSubstring& aResult) +{ + if (NS_UnescapeURL(aStr.Data(), aStr.Length(), aFlags, aResult)) { + return aResult; + } + return aStr; +} +const nsSubstring& +NS_EscapeURL(const nsSubstring& aStr, uint32_t aFlags, nsSubstring& aResult); + +/** + * Percent-escapes all characters in aStr that occurs in aForbidden. + * @param aStr the input URL string + * @param aForbidden the characters that should be escaped if found in aStr + * @note that aForbidden MUST be sorted (low to high) + * @param aResult the result if some characters were escaped + * @return aResult if some characters were escaped, or aStr otherwise (aResult + * is unmodified in that case) + */ +const nsSubstring& +NS_EscapeURL(const nsAFlatString& aStr, const nsTArray& aForbidden, + nsSubstring& aResult); + +/** + * CString version of nsEscape. Returns true on success, false + * on out of memory. To reverse this function, use NS_UnescapeURL. + */ +inline bool +NS_Escape(const nsACString& aOriginal, nsACString& aEscaped, + nsEscapeMask aMask) +{ + size_t escLen = 0; + char* esc = nsEscape(aOriginal.BeginReading(), aOriginal.Length(), &escLen, + aMask); + if (! esc) { + return false; + } + aEscaped.Adopt(esc, escLen); + return true; +} + +/** + * Inline unescape of mutable string object. + */ +inline nsACString& +NS_UnescapeURL(nsACString& aStr) +{ + aStr.SetLength(nsUnescapeCount(aStr.BeginWriting())); + return aStr; +} + +#endif // _ESCAPE_H_ diff --git a/xpcom/io/nsIAsyncInputStream.idl b/xpcom/io/nsIAsyncInputStream.idl new file mode 100644 index 000000000..5570817dd --- /dev/null +++ b/xpcom/io/nsIAsyncInputStream.idl @@ -0,0 +1,104 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIInputStream.idl" + +interface nsIInputStreamCallback; +interface nsIEventTarget; + +/** + * If an input stream is non-blocking, it may return NS_BASE_STREAM_WOULD_BLOCK + * when read. The caller must then wait for the stream to have some data to + * read. If the stream implements nsIAsyncInputStream, then the caller can use + * this interface to request an asynchronous notification when the stream + * becomes readable or closed (via the AsyncWait method). + * + * While this interface is almost exclusively used with non-blocking streams, it + * is not necessary that nsIInputStream::isNonBlocking return true. Nor is it + * necessary that a non-blocking nsIInputStream implementation also implement + * nsIAsyncInputStream. + */ +[scriptable, uuid(a5f255ab-4801-4161-8816-277ac92f6ad1)] +interface nsIAsyncInputStream : nsIInputStream +{ + /** + * This method closes the stream and sets its internal status. If the + * stream is already closed, then this method is ignored. Once the stream + * is closed, the stream's status cannot be changed. Any successful status + * code passed to this method is treated as NS_BASE_STREAM_CLOSED, which + * has an effect equivalent to nsIInputStream::close. + * + * NOTE: this method exists in part to support pipes, which have both an + * input end and an output end. If the input end of a pipe is closed, then + * writes to the output end of the pipe will fail. The error code returned + * when an attempt is made to write to a "broken" pipe corresponds to the + * status code passed in when the input end of the pipe was closed, which + * greatly simplifies working with pipes in some cases. + * + * @param aStatus + * The error that will be reported if this stream is accessed after + * it has been closed. + */ + void closeWithStatus(in nsresult aStatus); + + /** + * Asynchronously wait for the stream to be readable or closed. The + * notification is one-shot, meaning that each asyncWait call will result + * in exactly one notification callback. After the OnInputStreamReady event + * is dispatched, the stream releases its reference to the + * nsIInputStreamCallback object. It is safe to call asyncWait again from the + * notification handler. + * + * This method may be called at any time (even if read has not been called). + * In other words, this method may be called when the stream already has + * data to read. It may also be called when the stream is closed. If the + * stream is already readable or closed when AsyncWait is called, then the + * OnInputStreamReady event will be dispatched immediately. Otherwise, the + * event will be dispatched when the stream becomes readable or closed. + * + * @param aCallback + * This object is notified when the stream becomes ready. This + * parameter may be null to clear an existing callback. + * @param aFlags + * This parameter specifies optional flags passed in to configure + * the behavior of this method. Pass zero to specify no flags. + * @param aRequestedCount + * Wait until at least this many bytes can be read. This is only + * a suggestion to the underlying stream; it may be ignored. The + * caller may pass zero to indicate no preference. + * @param aEventTarget + * Specify NULL to receive notification on ANY thread (possibly even + * recursively on the calling thread -- i.e., synchronously), or + * specify that the notification be delivered to a specific event + * target. + */ + void asyncWait(in nsIInputStreamCallback aCallback, + in unsigned long aFlags, + in unsigned long aRequestedCount, + in nsIEventTarget aEventTarget); + + /** + * If passed to asyncWait, this flag overrides the default behavior, + * causing the OnInputStreamReady notification to be suppressed until the + * stream becomes closed (either as a result of closeWithStatus/close being + * called on the stream or possibly due to some error in the underlying + * stream). + */ + const unsigned long WAIT_CLOSURE_ONLY = (1<<0); +}; + +/** + * This is a companion interface for nsIAsyncInputStream::asyncWait. + */ +[function, scriptable, uuid(d1f28e94-3a6e-4050-a5f5-2e81b1fc2a43)] +interface nsIInputStreamCallback : nsISupports +{ + /** + * Called to indicate that the stream is either readable or closed. + * + * @param aStream + * The stream whose asyncWait method was called. + */ + void onInputStreamReady(in nsIAsyncInputStream aStream); +}; diff --git a/xpcom/io/nsIAsyncOutputStream.idl b/xpcom/io/nsIAsyncOutputStream.idl new file mode 100644 index 000000000..7e74579c6 --- /dev/null +++ b/xpcom/io/nsIAsyncOutputStream.idl @@ -0,0 +1,104 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIOutputStream.idl" + +interface nsIOutputStreamCallback; +interface nsIEventTarget; + +/** + * If an output stream is non-blocking, it may return NS_BASE_STREAM_WOULD_BLOCK + * when written to. The caller must then wait for the stream to become + * writable. If the stream implements nsIAsyncOutputStream, then the caller can + * use this interface to request an asynchronous notification when the stream + * becomes writable or closed (via the AsyncWait method). + * + * While this interface is almost exclusively used with non-blocking streams, it + * is not necessary that nsIOutputStream::isNonBlocking return true. Nor is it + * necessary that a non-blocking nsIOutputStream implementation also implement + * nsIAsyncOutputStream. + */ +[scriptable, uuid(beb632d3-d77a-4e90-9134-f9ece69e8200)] +interface nsIAsyncOutputStream : nsIOutputStream +{ + /** + * This method closes the stream and sets its internal status. If the + * stream is already closed, then this method is ignored. Once the stream + * is closed, the stream's status cannot be changed. Any successful status + * code passed to this method is treated as NS_BASE_STREAM_CLOSED, which + * is equivalent to nsIInputStream::close. + * + * NOTE: this method exists in part to support pipes, which have both an + * input end and an output end. If the output end of a pipe is closed, then + * reads from the input end of the pipe will fail. The error code returned + * when an attempt is made to read from a "closed" pipe corresponds to the + * status code passed in when the output end of the pipe is closed, which + * greatly simplifies working with pipes in some cases. + * + * @param aStatus + * The error that will be reported if this stream is accessed after + * it has been closed. + */ + void closeWithStatus(in nsresult reason); + + /** + * Asynchronously wait for the stream to be writable or closed. The + * notification is one-shot, meaning that each asyncWait call will result + * in exactly one notification callback. After the OnOutputStreamReady event + * is dispatched, the stream releases its reference to the + * nsIOutputStreamCallback object. It is safe to call asyncWait again from the + * notification handler. + * + * This method may be called at any time (even if write has not been called). + * In other words, this method may be called when the stream already has + * room for more data. It may also be called when the stream is closed. If + * the stream is already writable or closed when AsyncWait is called, then the + * OnOutputStreamReady event will be dispatched immediately. Otherwise, the + * event will be dispatched when the stream becomes writable or closed. + * + * @param aCallback + * This object is notified when the stream becomes ready. This + * parameter may be null to clear an existing callback. + * @param aFlags + * This parameter specifies optional flags passed in to configure + * the behavior of this method. Pass zero to specify no flags. + * @param aRequestedCount + * Wait until at least this many bytes can be written. This is only + * a suggestion to the underlying stream; it may be ignored. The + * caller may pass zero to indicate no preference. + * @param aEventTarget + * Specify NULL to receive notification on ANY thread (possibly even + * recursively on the calling thread -- i.e., synchronously), or + * specify that the notification be delivered to a specific event + * target. + */ + void asyncWait(in nsIOutputStreamCallback aCallback, + in unsigned long aFlags, + in unsigned long aRequestedCount, + in nsIEventTarget aEventTarget); + + /** + * If passed to asyncWait, this flag overrides the default behavior, + * causing the OnOutputStreamReady notification to be suppressed until the + * stream becomes closed (either as a result of closeWithStatus/close being + * called on the stream or possibly due to some error in the underlying + * stream). + */ + const unsigned long WAIT_CLOSURE_ONLY = (1<<0); +}; + +/** + * This is a companion interface for nsIAsyncOutputStream::asyncWait. + */ +[function, scriptable, uuid(40dbcdff-9053-42c5-a57c-3ec910d0f148)] +interface nsIOutputStreamCallback : nsISupports +{ + /** + * Called to indicate that the stream is either writable or closed. + * + * @param aStream + * The stream whose asyncWait method was called. + */ + void onOutputStreamReady(in nsIAsyncOutputStream aStream); +}; diff --git a/xpcom/io/nsIBinaryInputStream.idl b/xpcom/io/nsIBinaryInputStream.idl new file mode 100644 index 000000000..c3a27e203 --- /dev/null +++ b/xpcom/io/nsIBinaryInputStream.idl @@ -0,0 +1,119 @@ +/* -*- 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 "nsIInputStream.idl" + +/** + * This interface allows consumption of primitive data types from a "binary + * stream" containing untagged, big-endian binary data, i.e. as produced by an + * implementation of nsIBinaryOutputStream. This might be used, for example, + * to implement network protocols or to read from architecture-neutral disk + * files, i.e. ones that can be read and written by both big-endian and + * little-endian platforms. + * + * @See nsIBinaryOutputStream + */ + +[scriptable, uuid(899b826b-2eb3-469c-8b31-4c29f5d341a6)] +interface nsIBinaryInputStream : nsIInputStream { + void setInputStream(in nsIInputStream aInputStream); + + /** + * Read 8-bits from the stream. + * + * @return that byte to be treated as a boolean. + */ + boolean readBoolean(); + + uint8_t read8(); + uint16_t read16(); + uint32_t read32(); + uint64_t read64(); + + float readFloat(); + double readDouble(); + + /** + * Read an 8-bit pascal style string from the stream. + * 32-bit length field, followed by length 8-bit chars. + */ + ACString readCString(); + + /** + * Read an 16-bit pascal style string from the stream. + * 32-bit length field, followed by length PRUnichars. + */ + AString readString(); + + /** + * Read an opaque byte array from the stream. + * + * @param aLength the number of bytes that must be read. + * + * @throws NS_ERROR_FAILURE if it can't read aLength bytes + */ + void readBytes(in uint32_t aLength, + [size_is(aLength), retval] out string aString); + + /** + * Read an opaque byte array from the stream, storing the results + * as an array of PRUint8s. + * + * @param aLength the number of bytes that must be read. + * + * @throws NS_ERROR_FAILURE if it can't read aLength bytes + */ + void readByteArray(in uint32_t aLength, + [array, size_is(aLength), retval] out uint8_t aBytes); + + /** + * Read opaque bytes from the stream, storing the results in an ArrayBuffer. + * + * @param aLength the number of bytes that must be read + * @param aArrayBuffer the arraybuffer in which to store the results + * Note: passing view.buffer, where view is an ArrayBufferView of an + * ArrayBuffer, is not valid unless view.byteOffset == 0. + * + * @return The number of bytes actually read into aArrayBuffer. + */ + [implicit_jscontext] + unsigned long readArrayBuffer(in uint32_t aLength, in jsval aArrayBuffer); +}; + +%{C++ + +#ifdef MOZILLA_INTERNAL_API +#include "nsString.h" + +inline nsresult +NS_ReadOptionalCString(nsIBinaryInputStream* aStream, nsACString& aResult) +{ + bool nonnull; + nsresult rv = aStream->ReadBoolean(&nonnull); + if (NS_SUCCEEDED(rv)) { + if (nonnull) + rv = aStream->ReadCString(aResult); + else + aResult.Truncate(); + } + return rv; +} + +inline nsresult +NS_ReadOptionalString(nsIBinaryInputStream* aStream, nsAString& aResult) +{ + bool nonnull; + nsresult rv = aStream->ReadBoolean(&nonnull); + if (NS_SUCCEEDED(rv)) { + if (nonnull) + rv = aStream->ReadString(aResult); + else + aResult.Truncate(); + } + return rv; +} +#endif + +%} diff --git a/xpcom/io/nsIBinaryOutputStream.idl b/xpcom/io/nsIBinaryOutputStream.idl new file mode 100644 index 000000000..4d426d580 --- /dev/null +++ b/xpcom/io/nsIBinaryOutputStream.idl @@ -0,0 +1,90 @@ +/* -*- 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 "nsIOutputStream.idl" + +/** + * This interface allows writing of primitive data types (integers, + * floating-point values, booleans, etc.) to a stream in a binary, untagged, + * fixed-endianness format. This might be used, for example, to implement + * network protocols or to produce architecture-neutral binary disk files, + * i.e. ones that can be read and written by both big-endian and little-endian + * platforms. Output is written in big-endian order (high-order byte first), + * as this is traditional network order. + * + * @See nsIBinaryInputStream + */ + +[scriptable, uuid(204ee610-8765-11d3-90cf-0040056a906e)] +interface nsIBinaryOutputStream : nsIOutputStream { + void setOutputStream(in nsIOutputStream aOutputStream); + + /** + * Write a boolean as an 8-bit char to the stream. + */ + void writeBoolean(in boolean aBoolean); + + void write8(in uint8_t aByte); + void write16(in uint16_t a16); + void write32(in uint32_t a32); + void write64(in uint64_t a64); + + void writeFloat(in float aFloat); + void writeDouble(in double aDouble); + + /** + * Write an 8-bit pascal style string to the stream. + * 32-bit length field, followed by length 8-bit chars. + */ + void writeStringZ(in string aString); + + /** + * Write a 16-bit pascal style string to the stream. + * 32-bit length field, followed by length PRUnichars. + */ + void writeWStringZ(in wstring aString); + + /** + * Write an 8-bit pascal style string (UTF8-encoded) to the stream. + * 32-bit length field, followed by length 8-bit chars. + */ + void writeUtf8Z(in wstring aString); + + /** + * Write an opaque byte array to the stream. + */ + void writeBytes([size_is(aLength)] in string aString, in uint32_t aLength); + + /** + * Write an opaque byte array to the stream. + */ + void writeByteArray([array, size_is(aLength)] in uint8_t aBytes, + in uint32_t aLength); + +}; + +%{C++ + +inline nsresult +NS_WriteOptionalStringZ(nsIBinaryOutputStream* aStream, const char* aString) +{ + bool nonnull = (aString != nullptr); + nsresult rv = aStream->WriteBoolean(nonnull); + if (NS_SUCCEEDED(rv) && nonnull) + rv = aStream->WriteStringZ(aString); + return rv; +} + +inline nsresult +NS_WriteOptionalWStringZ(nsIBinaryOutputStream* aStream, const char16_t* aString) +{ + bool nonnull = (aString != nullptr); + nsresult rv = aStream->WriteBoolean(nonnull); + if (NS_SUCCEEDED(rv) && nonnull) + rv = aStream->WriteWStringZ(aString); + return rv; +} + +%} diff --git a/xpcom/io/nsICloneableInputStream.idl b/xpcom/io/nsICloneableInputStream.idl new file mode 100644 index 000000000..4fd9a74f7 --- /dev/null +++ b/xpcom/io/nsICloneableInputStream.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/. */ + +#include "nsIInputStream.idl" + +[scriptable, builtinclass, uuid(8149be1f-44d3-4f14-8b65-a57a5fbbeb97)] +interface nsICloneableInputStream : nsISupports +{ + // Allow streams that implement the interface to determine if cloning + // possible at runtime. For example, this allows wrappers to check if + // their base stream supports cloning. + [infallible] readonly attribute boolean cloneable; + + // Produce a copy of the current stream in the most efficient way possible. + // In this case "copy" means that both the original and cloned streams + // should produce the same bytes for all future reads. Bytes that have + // already been consumed from the original stream are not copied to the + // clone. Operations on the two streams should be completely independent + // after the clone() occurs. + nsIInputStream clone(); +}; diff --git a/xpcom/io/nsIConverterInputStream.idl b/xpcom/io/nsIConverterInputStream.idl new file mode 100644 index 000000000..7f35d8c5c --- /dev/null +++ b/xpcom/io/nsIConverterInputStream.idl @@ -0,0 +1,40 @@ +/* -*- 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 "nsIUnicharInputStream.idl" + +interface nsIInputStream; + +/** + * A unichar input stream that wraps an input stream. + * This allows reading unicode strings from a stream, automatically converting + * the bytes from a selected character encoding. + */ +[scriptable, uuid(FC66FFB6-5404-4908-A4A3-27F92FA0579D)] +interface nsIConverterInputStream : nsIUnicharInputStream { + /** + * Default replacement char value, U+FFFD REPLACEMENT CHARACTER. + */ + const char16_t DEFAULT_REPLACEMENT_CHARACTER = 0xFFFD; + + /** + * Initialize this stream. + * @param aStream + * The underlying stream to read from. + * @param aCharset + * The character encoding to use for converting the bytes of the + * stream. A null charset will be interpreted as UTF-8. + * @param aBufferSize + * How many bytes to buffer. + * @param aReplacementChar + * The character to replace unknown byte sequences in the stream + * with. The standard replacement character is U+FFFD. + * A value of 0x0000 will cause an exception to be thrown if unknown + * byte sequences are encountered in the stream. + */ + void init (in nsIInputStream aStream, in string aCharset, + in long aBufferSize, in char16_t aReplacementChar); +}; + diff --git a/xpcom/io/nsIConverterOutputStream.idl b/xpcom/io/nsIConverterOutputStream.idl new file mode 100644 index 000000000..a93d3cfa6 --- /dev/null +++ b/xpcom/io/nsIConverterOutputStream.idl @@ -0,0 +1,44 @@ +/* vim:set expandtab ts=4 sw=4 sts=4 cin: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIUnicharOutputStream.idl" + +interface nsIOutputStream; + +/** + * This interface allows writing strings to a stream, doing automatic + * character encoding conversion. + */ +[scriptable, uuid(4b71113a-cb0d-479f-8ed5-01daeba2e8d4)] +interface nsIConverterOutputStream : nsIUnicharOutputStream +{ + /** + * Initialize this stream. Must be called before any other method on this + * interface, or you will crash. The output stream passed to this method + * must not be null, or you will crash. + * + * @param aOutStream + * The underlying output stream to which the converted strings will + * be written. + * @param aCharset + * The character set to use for encoding the characters. A null + * charset will be interpreted as UTF-8. + * @param aBufferSize + * How many bytes to buffer. A value of 0 means that no bytes will be + * buffered. Implementations not supporting buffering may ignore + * this parameter. + * @param aReplacementCharacter + * The replacement character to use when an unsupported character is found. + * The character must be encodable in the selected character + * encoding; otherwise, attempts to write an unsupported character + * will throw NS_ERROR_LOSS_OF_SIGNIFICANT_DATA. + * + * A value of 0x0000 will cause an exception to be thrown upon + * attempts to write unsupported characters. + */ + void init(in nsIOutputStream aOutStream, in string aCharset, + in unsigned long aBufferSize, + in char16_t aReplacementCharacter); +}; diff --git a/xpcom/io/nsIDirectoryEnumerator.idl b/xpcom/io/nsIDirectoryEnumerator.idl new file mode 100644 index 000000000..7a1135fda --- /dev/null +++ b/xpcom/io/nsIDirectoryEnumerator.idl @@ -0,0 +1,34 @@ +/* -*- 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 nsIFile; + +/** + * This interface provides a means for enumerating the contents of a directory. + * It is similar to nsISimpleEnumerator except the retrieved entries are QI'ed + * to nsIFile, and there is a mechanism for closing the directory when the + * enumeration is complete. + */ +[scriptable, uuid(31f7f4ae-6916-4f2d-a81e-926a4e3022ee)] +interface nsIDirectoryEnumerator : nsISupports +{ + /** + * Retrieves the next file in the sequence. The "nextFile" element is the + * first element upon the first call. This attribute is null if there is no + * next element. + */ + readonly attribute nsIFile nextFile; + + /** + * Closes the directory being enumerated, releasing the system resource. + * @throws NS_OK if the call succeeded and the directory was closed. + * NS_ERROR_FAILURE if the directory close failed. + * It is safe to call this function many times. + */ + void close(); +}; + diff --git a/xpcom/io/nsIDirectoryService.idl b/xpcom/io/nsIDirectoryService.idl new file mode 100644 index 000000000..6f58e37b9 --- /dev/null +++ b/xpcom/io/nsIDirectoryService.idl @@ -0,0 +1,103 @@ +/* -*- 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" + +interface nsIFile; +interface nsISimpleEnumerator; + +/** + * nsIDirectoryServiceProvider + * + * Used by Directory Service to get file locations. + */ + +[scriptable, uuid(bbf8cab0-d43a-11d3-8cc2-00609792278c)] +interface nsIDirectoryServiceProvider: nsISupports +{ + /** + * getFile + * + * Directory Service calls this when it gets the first request for + * a prop or on every request if the prop is not persistent. + * + * @param prop The symbolic name of the file. + * @param persistent TRUE - The returned file will be cached by Directory + * Service. Subsequent requests for this prop will + * bypass the provider and use the cache. + * FALSE - The provider will be asked for this prop + * each time it is requested. + * + * @return The file represented by the property. + * + */ + nsIFile getFile(in string prop, out boolean persistent); +}; + +/** + * nsIDirectoryServiceProvider2 + * + * An extension of nsIDirectoryServiceProvider which allows + * multiple files to be returned for the given key. + */ + +[scriptable, uuid(2f977d4b-5485-11d4-87e2-0010a4e75ef2)] +interface nsIDirectoryServiceProvider2: nsIDirectoryServiceProvider +{ + /** + * getFiles + * + * Directory Service calls this when it gets a request for + * a prop and the requested type is nsISimpleEnumerator. + * + * @param prop The symbolic name of the file list. + * + * @return An enumerator for a list of file locations. + * The elements in the enumeration are nsIFile + * @returnCode NS_SUCCESS_AGGREGATE_RESULT if this result should be + * aggregated with other "lower" providers. + */ + nsISimpleEnumerator getFiles(in string prop); +}; + +/** + * nsIDirectoryService + */ + +[scriptable, uuid(57a66a60-d43a-11d3-8cc2-00609792278c)] +interface nsIDirectoryService: nsISupports +{ + /** + * init + * + * Must be called. Used internally by XPCOM initialization. + * + */ + void init(); + + /** + * registerProvider + * + * Register a provider with the service. + * + * @param prov The service will keep a strong reference + * to this object. It will be released when + * the service is released. + * + */ + void registerProvider(in nsIDirectoryServiceProvider prov); + + /** + * unregisterProvider + * + * Unregister a provider with the service. + * + * @param prov + * + */ + void unregisterProvider(in nsIDirectoryServiceProvider prov); +}; + + diff --git a/xpcom/io/nsIFile.idl b/xpcom/io/nsIFile.idl new file mode 100644 index 000000000..fc07106b9 --- /dev/null +++ b/xpcom/io/nsIFile.idl @@ -0,0 +1,521 @@ +/* -*- 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" + +%{C++ +struct PRFileDesc; +struct PRLibrary; +#include +%} + +[ptr] native PRFileDescStar(PRFileDesc); +[ptr] native PRLibraryStar(PRLibrary); +[ptr] native FILE(FILE); + +interface nsISimpleEnumerator; + +/** + * An nsIFile is an abstract representation of a filename. It manages + * filename encoding issues, pathname component separators ('/' vs. '\\' + * vs. ':') and weird stuff like differing volumes with identical names, as + * on pre-Darwin Macintoshes. + * + * This file has long introduced itself to new hackers with this opening + * paragraph: + * + * This is the only correct cross-platform way to specify a file. + * Strings are not such a way. If you grew up on windows or unix, you + * may think they are. Welcome to reality. + * + * While taking the pose struck here to heart would be uncalled for, one + * may safely conclude that writing cross-platform code is an embittering + * experience. + * + * All methods with string parameters have two forms. The preferred + * form operates on UCS-2 encoded characters strings. An alternate + * form operates on characters strings encoded in the "native" charset. + * + * A string containing characters encoded in the native charset cannot + * be safely passed to javascript via xpconnect. Therefore, the "native + * methods" are not scriptable. + */ +[scriptable, main_process_scriptable_only, uuid(2fa6884a-ae65-412a-9d4c-ce6e34544ba1), builtinclass] +interface nsIFile : nsISupports +{ + /** + * Create Types + * + * NORMAL_FILE_TYPE - A normal file. + * DIRECTORY_TYPE - A directory/folder. + */ + const unsigned long NORMAL_FILE_TYPE = 0; + const unsigned long DIRECTORY_TYPE = 1; + + /** + * append[Native] + * + * This function is used for constructing a descendent of the + * current nsIFile. + * + * @param node + * A string which is intended to be a child node of the nsIFile. + * For the |appendNative| method, the node must be in the native + * filesystem charset. + */ + void append(in AString node); + [noscript] void appendNative(in ACString node); + + /** + * Normalize the pathName (e.g. removing .. and . components on Unix). + */ + void normalize(); + + /** + * create + * + * This function will create a new file or directory in the + * file system. Any nodes that have not been created or + * resolved, will be. If the file or directory already + * exists create() will return NS_ERROR_FILE_ALREADY_EXISTS. + * + * @param type + * This specifies the type of file system object + * to be made. The only two types at this time + * are file and directory which are defined above. + * If the type is unrecongnized, we will return an + * error (NS_ERROR_FILE_UNKNOWN_TYPE). + * + * @param permissions + * The unix style octal permissions. This may + * be ignored on systems that do not need to do + * permissions. + */ + [must_use] void create(in unsigned long type, in unsigned long permissions); + + /** + * Accessor to the leaf name of the file itself. + * For the |nativeLeafName| method, the nativeLeafName must + * be in the native filesystem charset. + */ + attribute AString leafName; + [noscript] attribute ACString nativeLeafName; + + /** + * copyTo[Native] + * + * This will copy this file to the specified newParentDir. + * If a newName is specified, the file will be renamed. + * If 'this' is not created we will return an error + * (NS_ERROR_FILE_TARGET_DOES_NOT_EXIST). + * + * copyTo may fail if the file already exists in the destination + * directory. + * + * copyTo will NOT resolve aliases/shortcuts during the copy. + * + * @param newParentDir + * This param is the destination directory. If the + * newParentDir is null, copyTo() will use the parent + * directory of this file. If the newParentDir is not + * empty and is not a directory, an error will be + * returned (NS_ERROR_FILE_DESTINATION_NOT_DIR). For the + * |CopyToNative| method, the newName must be in the + * native filesystem charset. + * + * @param newName + * This param allows you to specify a new name for + * the file to be copied. This param may be empty, in + * which case the current leaf name will be used. + */ + void copyTo(in nsIFile newParentDir, in AString newName); + [noscript] void CopyToNative(in nsIFile newParentDir, in ACString newName); + + /** + * copyToFollowingLinks[Native] + * + * This function is identical to copyTo with the exception that, + * as the name implies, it follows symbolic links. The XP_UNIX + * implementation always follow symbolic links when copying. For + * the |CopyToFollowingLinks| method, the newName must be in the + * native filesystem charset. + */ + void copyToFollowingLinks(in nsIFile newParentDir, in AString newName); + [noscript] void copyToFollowingLinksNative(in nsIFile newParentDir, in ACString newName); + + /** + * moveTo[Native] + * + * A method to move this file or directory to newParentDir. + * If a newName is specified, the file or directory will be renamed. + * If 'this' is not created we will return an error + * (NS_ERROR_FILE_TARGET_DOES_NOT_EXIST). + * If 'this' is a file, and the destination file already exists, moveTo + * will replace the old file. + * This object is updated to refer to the new file. + * + * moveTo will NOT resolve aliases/shortcuts during the copy. + * moveTo will do the right thing and allow copies across volumes. + * moveTo will return an error (NS_ERROR_FILE_DIR_NOT_EMPTY) if 'this' is + * a directory and the destination directory is not empty. + * moveTo will return an error (NS_ERROR_FILE_ACCESS_DENIED) if 'this' is + * a directory and the destination directory is not writable. + * + * @param newParentDir + * This param is the destination directory. If the + * newParentDir is empty, moveTo() will rename the file + * within its current directory. If the newParentDir is + * not empty and does not name a directory, an error will + * be returned (NS_ERROR_FILE_DESTINATION_NOT_DIR). For + * the |moveToNative| method, the newName must be in the + * native filesystem charset. + * + * @param newName + * This param allows you to specify a new name for + * the file to be moved. This param may be empty, in + * which case the current leaf name will be used. + */ + void moveTo(in nsIFile newParentDir, in AString newName); + [noscript] void moveToNative(in nsIFile newParentDir, in ACString newName); + + /** + * renameTo + * + * This method is identical to moveTo except that if this file or directory + * is moved to a a different volume, it fails and returns an error + * (NS_ERROR_FILE_ACCESS_DENIED). + * This object will still point to the old location after renaming. + */ + void renameTo(in nsIFile newParentDir, in AString newName); + [noscript] void renameToNative(in nsIFile newParentDir, in ACString newName); + + /** + * This will try to delete this file. The 'recursive' flag + * must be PR_TRUE to delete directories which are not empty. + * + * This will not resolve any symlinks. + */ + void remove(in boolean recursive); + + /** + * Attributes of nsIFile. + */ + + attribute unsigned long permissions; + attribute unsigned long permissionsOfLink; + + /** + * File Times are to be in milliseconds from + * midnight (00:00:00), January 1, 1970 Greenwich Mean + * Time (GMT). + */ + attribute PRTime lastModifiedTime; + attribute PRTime lastModifiedTimeOfLink; + + /** + * WARNING! On the Mac, getting/setting the file size with nsIFile + * only deals with the size of the data fork. If you need to + * know the size of the combined data and resource forks use the + * GetFileSizeWithResFork() method defined on nsILocalFileMac. + */ + attribute int64_t fileSize; + readonly attribute int64_t fileSizeOfLink; + + /** + * target & path + * + * Accessor to the string path. The native version of these + * strings are not guaranteed to be a usable path to pass to + * NSPR or the C stdlib. There are problems that affect + * platforms on which a path does not fully specify a file + * because two volumes can have the same name (e.g., mac). + * This is solved by holding "private", native data in the + * nsIFile implementation. This native data is lost when + * you convert to a string. + * + * DO NOT PASS TO USE WITH NSPR OR STDLIB! + * + * target + * Find out what the symlink points at. Will give error + * (NS_ERROR_FILE_INVALID_PATH) if not a symlink. + * + * path + * Find out what the nsIFile points at. + * + * Note that the ACString attributes are returned in the + * native filesystem charset. + * + */ + readonly attribute AString target; + [noscript] readonly attribute ACString nativeTarget; + readonly attribute AString path; + [noscript] readonly attribute ACString nativePath; + + boolean exists(); + boolean isWritable(); + boolean isReadable(); + boolean isExecutable(); + boolean isHidden(); + boolean isDirectory(); + boolean isFile(); + boolean isSymlink(); + /** + * Not a regular file, not a directory, not a symlink. + */ + boolean isSpecial(); + + /** + * createUnique + * + * This function will create a new file or directory in the + * file system. Any nodes that have not been created or + * resolved, will be. If this file already exists, we try + * variations on the leaf name "suggestedName" until we find + * one that did not already exist. + * + * If the search for nonexistent files takes too long + * (thousands of the variants already exist), we give up and + * return NS_ERROR_FILE_TOO_BIG. + * + * @param type + * This specifies the type of file system object + * to be made. The only two types at this time + * are file and directory which are defined above. + * If the type is unrecongnized, we will return an + * error (NS_ERROR_FILE_UNKNOWN_TYPE). + * + * @param permissions + * The unix style octal permissions. This may + * be ignored on systems that do not need to do + * permissions. + */ + [must_use] + void createUnique(in unsigned long type, in unsigned long permissions); + + /** + * clone() + * + * This function will allocate and initialize a nsIFile object to the + * exact location of the |this| nsIFile. + * + * @param file + * A nsIFile which this object will be initialize + * with. + * + */ + nsIFile clone(); + + /** + * Will determine if the inFile equals this. + */ + boolean equals(in nsIFile inFile); + + /** + * Will determine if inFile is a descendant of this file. + * This routine looks in subdirectories too. + */ + boolean contains(in nsIFile inFile); + + /** + * Parent will be null when this is at the top of the volume. + */ + readonly attribute nsIFile parent; + + /** + * Returns an enumeration of the elements in a directory. Each + * element in the enumeration is an nsIFile. + * + * @throws NS_ERROR_FILE_NOT_DIRECTORY if the current nsIFile does + * not specify a directory. + */ + readonly attribute nsISimpleEnumerator directoryEntries; + + /** + * initWith[Native]Path + * + * This function will initialize the nsIFile object. Any + * internal state information will be reset. + * + * @param filePath + * A string which specifies a full file path to a + * location. Relative paths will be treated as an + * error (NS_ERROR_FILE_UNRECOGNIZED_PATH). For + * initWithNativePath, the filePath must be in the native + * filesystem charset. + */ + void initWithPath(in AString filePath); + [noscript] void initWithNativePath(in ACString filePath); + + /** + * initWithFile + * + * Initialize this object with another file + * + * @param aFile + * the file this becomes equivalent to + */ + void initWithFile(in nsIFile aFile); + + /** + * followLinks + * + * 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. + */ + attribute boolean followLinks; + + /** + * Flag for openNSPRFileDesc(), to hint to the OS that the file will be + * read sequentially with agressive readahead. + */ + const unsigned long OS_READAHEAD = 0x40000000; + + /** + * Flag for openNSPRFileDesc(). Deprecated and unreliable! + * Instead use NS_OpenAnonymousTemporaryFile() to create a temporary + * file which will be deleted upon close! + */ + const unsigned long DELETE_ON_CLOSE = 0x80000000; + + /** + * Return the result of PR_Open on the file. The caller is + * responsible for calling PR_Close on the result. On success, the + * returned PRFileDescr must be non-null. + * + * @param flags the PR_Open flags from prio.h, plus optionally + * OS_READAHEAD or DELETE_ON_CLOSE. OS_READAHEAD is a hint to the + * OS that the file will be read sequentially with agressive + * readahead. DELETE_ON_CLOSE is unreliable on Windows and is deprecated. + * Instead use NS_OpenAnonymousTemporaryFile() to create a temporary + * file which will be deleted upon close. + */ + [noscript, must_use] PRFileDescStar openNSPRFileDesc(in long flags, + in long mode); + + /** + * Return the result of fopen on the file. The caller is + * responsible for calling fclose on the result. On success, the + * returned FILE pointer must be non-null. + */ + [noscript, must_use] FILE openANSIFileDesc(in string mode); + + /** + * Return the result of PR_LoadLibrary on the file. The caller is + * responsible for calling PR_UnloadLibrary on the result. + */ + [noscript, must_use] PRLibraryStar load(); + + // number of bytes available on disk to non-superuser + [must_use] readonly attribute int64_t diskSpaceAvailable; + + /** + * appendRelative[Native]Path + * + * Append a relative path to the current path of the nsIFile object. + * + * @param relativeFilePath + * relativeFilePath is a native relative path. For security reasons, + * this cannot contain .. or cannot start with a directory separator. + * For the |appendRelativeNativePath| method, the relativeFilePath + * must be in the native filesystem charset. + */ + void appendRelativePath(in AString relativeFilePath); + [noscript] void appendRelativeNativePath(in ACString relativeFilePath); + + /** + * Accessor to a null terminated string which will specify + * the file in a persistent manner for disk storage. + * + * The character set of this attribute is undefined. DO NOT TRY TO + * INTERPRET IT AS HUMAN READABLE TEXT! + */ + [must_use] attribute ACString persistentDescriptor; + + /** + * reveal + * + * Ask the operating system to open the folder which contains + * this file or folder. This routine only works on platforms which + * support the ability to open a folder and is run async on Windows. + * This routine must be called on the main. + */ + [must_use] void reveal(); + + /** + * launch + * + * Ask the operating system to attempt to open the file. + * this really just simulates "double clicking" the file on your platform. + * This routine only works on platforms which support this functionality + * and is run async on Windows. This routine must be called on the + * main thread. + */ + [must_use] void launch(); + + /** + * getRelativeDescriptor + * + * Returns a relative file path in an opaque, XP format. It is therefore + * not a native path. + * + * The character set of the string returned from this function is + * undefined. DO NOT TRY TO INTERPRET IT AS HUMAN READABLE TEXT! + * + * @param fromFile + * the file from which the descriptor is relative. + * Throws if fromFile is null. + */ + [must_use] ACString getRelativeDescriptor(in nsIFile fromFile); + + /** + * setRelativeDescriptor + * + * Initializes the file to the location relative to fromFile using + * a string returned by getRelativeDescriptor. + * + * @param fromFile + * the file to which the descriptor is relative + * @param relative + * the relative descriptor obtained from getRelativeDescriptor + */ + [must_use] + void setRelativeDescriptor(in nsIFile fromFile, in ACString relativeDesc); + + /** + * getRelativePath + * + * Returns a relative file from 'fromFile' to this file as a UTF-8 string. + * Going up the directory tree is represented via "../". '/' is used as + * the path segment separator. This is not a native path, since it's UTF-8 + * encoded. + * + * @param fromFile + * the file from which the path is relative. + * Throws if fromFile is null. + */ + [must_use] AUTF8String getRelativePath(in nsIFile fromFile); + + /** + * setRelativePath + * + * Initializes the file to the location relative to fromFile using + * a string returned by getRelativePath. + * + * @param fromFile + * the file from which the path is relative + * @param relative + * the relative path obtained from getRelativePath + */ + [must_use] + void setRelativePath(in nsIFile fromFile, in AUTF8String relativeDesc); +}; + +%{C++ +#ifdef MOZILLA_INTERNAL_API +#include "nsDirectoryServiceUtils.h" +#endif +%} diff --git a/xpcom/io/nsIIOUtil.idl b/xpcom/io/nsIIOUtil.idl new file mode 100644 index 000000000..577ba4046 --- /dev/null +++ b/xpcom/io/nsIIOUtil.idl @@ -0,0 +1,34 @@ +/* -*- 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" + +interface nsIInputStream; +interface nsIOutputStream; + +/** + * nsIIOUtil provdes various xpcom/io-related utility methods. + */ +[scriptable, uuid(e8152f7f-4209-4c63-ad23-c3d2aa0c5a49)] +interface nsIIOUtil : nsISupports +{ + /** + * Test whether an input stream is buffered. See nsStreamUtils.h + * documentation for NS_InputStreamIsBuffered for the definition of + * "buffered" used here and for edge-case behavior. + * + * @throws NS_ERROR_INVALID_POINTER if null is passed in. + */ + boolean inputStreamIsBuffered(in nsIInputStream aStream); + + /** + * Test whether an output stream is buffered. See nsStreamUtils.h + * documentation for NS_OutputStreamIsBuffered for the definition of + * "buffered" used here and for edge-case behavior. + * + * @throws NS_ERROR_INVALID_POINTER if null is passed in. + */ + boolean outputStreamIsBuffered(in nsIOutputStream aStream); +}; diff --git a/xpcom/io/nsIInputStream.idl b/xpcom/io/nsIInputStream.idl new file mode 100644 index 000000000..b2f165274 --- /dev/null +++ b/xpcom/io/nsIInputStream.idl @@ -0,0 +1,147 @@ +/* -*- 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" + +interface nsIInputStream; + +%{C++ +/** + * The signature of the writer function passed to ReadSegments. This + * is the "consumer" of data that gets read from the stream's buffer. + * + * @param aInStream stream being read + * @param aClosure opaque parameter passed to ReadSegments + * @param aFromSegment pointer to memory owned by the input stream. This is + * where the writer function should start consuming data. + * @param aToOffset amount of data already consumed by this writer during this + * ReadSegments call. This is also the sum of the aWriteCount + * returns from this writer over the previous invocations of + * the writer by this ReadSegments call. + * @param aCount Number of bytes available to be read starting at aFromSegment + * @param [out] aWriteCount number of bytes read by this writer function call + * + * Implementers should return the following: + * + * @return NS_OK and (*aWriteCount > 0) if consumed some data + * @return if not interested in consuming any data + * + * Errors are never passed to the caller of ReadSegments. + * + * NOTE: returning NS_OK and (*aWriteCount = 0) has undefined behavior. + */ +typedef nsresult (*nsWriteSegmentFun)(nsIInputStream *aInStream, + void *aClosure, + const char *aFromSegment, + uint32_t aToOffset, + uint32_t aCount, + uint32_t *aWriteCount); +%} + +native nsWriteSegmentFun(nsWriteSegmentFun); + +/** + * nsIInputStream + * + * An interface describing a readable stream of data. An input stream may be + * "blocking" or "non-blocking" (see the IsNonBlocking method). A blocking + * input stream may suspend the calling thread in order to satisfy a call to + * Close, Available, Read, or ReadSegments. A non-blocking input stream, on + * the other hand, must not block the calling thread of execution. + * + * NOTE: blocking input streams are often read on a background thread to avoid + * locking up the main application thread. For this reason, it is generally + * the case that a blocking input stream should be implemented using thread- + * safe AddRef and Release. + */ +[scriptable, uuid(53cdbc97-c2d7-4e30-b2c3-45b2ee79db18)] +interface nsIInputStream : nsISupports +{ + /** + * Close the stream. This method causes subsequent calls to Read and + * ReadSegments to return 0 bytes read to indicate end-of-file. Any + * subsequent calls to Available should throw NS_BASE_STREAM_CLOSED. + */ + void close(); + + /** + * Determine number of bytes available in the stream. A non-blocking + * stream that does not yet have any data to read should return 0 bytes + * from this method (i.e., it must not throw the NS_BASE_STREAM_WOULD_BLOCK + * exception). + * + * In addition to the number of bytes available in the stream, this method + * also informs the caller of the current status of the stream. A stream + * that is closed will throw an exception when this method is called. That + * enables the caller to know the condition of the stream before attempting + * to read from it. If a stream is at end-of-file, but not closed, then + * this method returns 0 bytes available. (Note: some nsIInputStream + * implementations automatically close when eof is reached; some do not). + * + * @return number of bytes currently available in the stream. + * + * @throws NS_BASE_STREAM_CLOSED if the stream is closed normally. + * @throws if the stream is closed due to some error + * condition + */ + unsigned long long available(); + + /** + * Read data from the stream. + * + * @param aBuf the buffer into which the data is to be read + * @param aCount the maximum number of bytes to be read + * + * @return number of bytes read (may be less than aCount). + * @return 0 if reached end-of-file + * + * @throws NS_BASE_STREAM_WOULD_BLOCK if reading from the input stream would + * block the calling thread (non-blocking mode only) + * @throws on failure + * + * NOTE: this method should not throw NS_BASE_STREAM_CLOSED. + */ + [noscript] unsigned long read(in charPtr aBuf, in unsigned long aCount); + + /** + * Low-level read method that provides access to the stream's underlying + * buffer. The writer function may be called multiple times for segmented + * buffers. ReadSegments is expected to keep calling the writer until + * either there is nothing left to read or the writer returns an error. + * ReadSegments should not call the writer with zero bytes to consume. + * + * @param aWriter the "consumer" of the data to be read + * @param aClosure opaque parameter passed to writer + * @param aCount the maximum number of bytes to be read + * + * @return number of bytes read (may be less than aCount) + * @return 0 if reached end-of-file (or if aWriter refused to consume data) + * + * @throws NS_BASE_STREAM_WOULD_BLOCK if reading from the input stream would + * block the calling thread (non-blocking mode only) + * @throws NS_ERROR_NOT_IMPLEMENTED if the stream has no underlying buffer + * @throws on failure + * + * NOTE: this function may be unimplemented if a stream has no underlying + * buffer (e.g., socket input stream). + * + * NOTE: this method should not throw NS_BASE_STREAM_CLOSED. + */ + [noscript] unsigned long readSegments(in nsWriteSegmentFun aWriter, + in voidPtr aClosure, + in unsigned long aCount); + + /** + * @return true if stream is non-blocking + * + * NOTE: reading from a blocking input stream will block the calling thread + * until at least one byte of data can be extracted from the stream. + * + * NOTE: a non-blocking input stream may implement nsIAsyncInputStream to + * provide consumers with a way to wait for the stream to have more data + * once its read method is unable to return any data without blocking. + */ + boolean isNonBlocking(); +}; diff --git a/xpcom/io/nsIInputStreamTee.idl b/xpcom/io/nsIInputStreamTee.idl new file mode 100644 index 000000000..953be7a7c --- /dev/null +++ b/xpcom/io/nsIInputStreamTee.idl @@ -0,0 +1,42 @@ +/* -*- 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 "nsIInputStream.idl" + +interface nsIOutputStream; +interface nsIEventTarget; + +/** + * A nsIInputStreamTee is a wrapper for an input stream, that when read + * reads the specified amount of data from its |source| and copies that + * data to its |sink|. |sink| must be a blocking output stream. + */ +[scriptable, uuid(90a9d790-3bca-421e-a73b-98f68e13c917)] +interface nsIInputStreamTee : nsIInputStream +{ + attribute nsIInputStream source; + attribute nsIOutputStream sink; + + /** + * If |eventTarget| is set, copying to sink is done asynchronously using + * the event-target (e.g. a thread). If |eventTarget| is not set, copying + * to sink happens synchronously while reading from the source. + */ + attribute nsIEventTarget eventTarget; +}; + +%{C++ +// factory methods +extern nsresult +NS_NewInputStreamTee(nsIInputStream **tee, // read from this input stream + nsIInputStream *source, + nsIOutputStream *sink); + +extern nsresult +NS_NewInputStreamTeeAsync(nsIInputStream **tee, // read from this input stream + nsIInputStream *source, + nsIOutputStream *sink, + nsIEventTarget *eventTarget); +%} diff --git a/xpcom/io/nsILineInputStream.idl b/xpcom/io/nsILineInputStream.idl new file mode 100644 index 000000000..4a8eff42b --- /dev/null +++ b/xpcom/io/nsILineInputStream.idl @@ -0,0 +1,26 @@ +/* -*- 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" + +[scriptable, uuid(c97b466c-1e6e-4773-a4ab-2b2b3190a7a6)] +interface nsILineInputStream : nsISupports +{ + /** + * Read a single line from the stream, where a line is a + * possibly zero length sequence of 8bit chars terminated by a + * CR, LF, CRLF, LFCR, or eof. + * The line terminator is not returned. + * @retval false + * End of file. This line is the last line of the file + * (aLine is valid). + * @retval true + * The file contains further lines. + * @note Do not mix readLine with other read functions. + * Doing so can cause various problems and is not supported. + */ + boolean readLine(out ACString aLine); +}; diff --git a/xpcom/io/nsILocalFile.idl b/xpcom/io/nsILocalFile.idl new file mode 100644 index 000000000..cead3e3f4 --- /dev/null +++ b/xpcom/io/nsILocalFile.idl @@ -0,0 +1,17 @@ +/* -*- 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 "nsIFile.idl" + +/** + * An empty interface to provide backwards compatibility for existing code. + * + * @see nsIFile + */ +[scriptable, builtinclass, uuid(7ba8c6ba-2ce2-48b1-bd60-4c32aac35f9c)] +interface nsILocalFile : nsIFile +{ +}; + diff --git a/xpcom/io/nsILocalFileMac.idl b/xpcom/io/nsILocalFileMac.idl new file mode 100644 index 000000000..d8655449b --- /dev/null +++ b/xpcom/io/nsILocalFileMac.idl @@ -0,0 +1,179 @@ +/* -*- 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 "nsILocalFile.idl" + +%{C++ +#include +#include +%} + + native OSType(OSType); + native FSSpec(FSSpec); + native FSRef(FSRef); +[ptr] native FSRefPtr(FSRef); + native CFURLRef(CFURLRef); + +[scriptable, builtinclass, uuid(623eca5b-c25d-4e27-be5a-789a66c4b2f7)] +interface nsILocalFileMac : nsILocalFile +{ + /** + * initWithCFURL + * + * Init this object with a CFURLRef + * + * NOTE: Supported only for XP_MACOSX + * NOTE: If the path of the CFURL is /a/b/c, at least a/b must exist beforehand. + * + * @param aCFURL the CoreFoundation URL + * + */ + [noscript] void initWithCFURL(in CFURLRef aCFURL); + + /** + * initWithFSRef + * + * Init this object with an FSRef + * + * NOTE: Supported only for XP_MACOSX + * + * @param aFSRef the native FSRef + * + */ + [noscript] void initWithFSRef([const] in FSRefPtr aFSRef); + + /** + * getCFURL + * + * Returns the CFURLRef of the file object. The caller is + * responsible for calling CFRelease() on it. + * + * NOTE: Observes the state of the followLinks attribute. + * If the file object is an alias and followLinks is TRUE, returns + * the target of the alias. If followLinks is FALSE, returns + * the unresolved alias file. + * + * NOTE: Supported only for XP_MACOSX + * + * @return + * + */ + [noscript] CFURLRef getCFURL(); + + /** + * getFSRef + * + * Returns the FSRef of the file object. + * + * NOTE: Observes the state of the followLinks attribute. + * If the file object is an alias and followLinks is TRUE, returns + * the target of the alias. If followLinks is FALSE, returns + * the unresolved alias file. + * + * NOTE: Supported only for XP_MACOSX + * + * @return + * + */ + [noscript] FSRef getFSRef(); + + /** + * getFSSpec + * + * Returns the FSSpec of the file object. + * + * NOTE: Observes the state of the followLinks attribute. + * If the file object is an alias and followLinks is TRUE, returns + * the target of the alias. If followLinks is FALSE, returns + * the unresolved alias file. + * + * @return + * + */ + [noscript] FSSpec getFSSpec(); + + /** + * fileSizeWithResFork + * + * Returns the combined size of both the data fork and the resource + * fork (if present) rather than just the size of the data fork + * as returned by GetFileSize() + * + */ + readonly attribute int64_t fileSizeWithResFork; + + /** + * fileType, creator + * + * File type and creator attributes + * + */ + [noscript] attribute OSType fileType; + [noscript] attribute OSType fileCreator; + + /** + * launchWithDoc + * + * Launch the application that this file points to with a document. + * + * @param aDocToLoad Must not be NULL. If no document, use nsIFile::launch + * @param aLaunchInBackground TRUE if the application should not come to the front. + * + */ + void launchWithDoc(in nsIFile aDocToLoad, in boolean aLaunchInBackground); + + /** + * openDocWithApp + * + * Open the document that this file points to with the given application. + * + * @param aAppToOpenWith The application with which to open the document. + * If NULL, the creator code of the document is used + * to determine the application. + * @param aLaunchInBackground TRUE if the application should not come to the front. + * + */ + void openDocWithApp(in nsIFile aAppToOpenWith, in boolean aLaunchInBackground); + + /** + * isPackage + * + * returns true if a directory is determined to be a package under Mac OS 9/X + * + */ + boolean isPackage(); + + /** + * bundleDisplayName + * + * returns the display name of the application bundle (usually the human + * readable name of the application) + */ + readonly attribute AString bundleDisplayName; + + /** + * bundleIdentifier + * + * returns the identifier of the bundle + */ + readonly attribute AUTF8String bundleIdentifier; + + /** + * Last modified time of a bundle's contents (as opposed to its package + * directory). Our convention is to make the bundle's Info.plist file + * stand in for the rest of its contents -- since this file contains the + * bundle's version information and other identifiers. For non-bundles + * this is the same as lastModifiedTime. + */ + readonly attribute int64_t bundleContentsLastModifiedTime; +}; + +%{C++ +extern "C" +{ +NS_EXPORT nsresult NS_NewLocalFileWithFSRef(const FSRef* aFSRef, bool aFollowSymlinks, nsILocalFileMac** result); +NS_EXPORT nsresult NS_NewLocalFileWithCFURL(const CFURLRef aURL, bool aFollowSymlinks, nsILocalFileMac** result); +} +%} diff --git a/xpcom/io/nsILocalFileWin.idl b/xpcom/io/nsILocalFileWin.idl new file mode 100644 index 000000000..c036cb96b --- /dev/null +++ b/xpcom/io/nsILocalFileWin.idl @@ -0,0 +1,121 @@ +/* -*- Mode: Java; 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 "nsILocalFile.idl" + +%{C++ +struct PRFileDesc; +%} + +[ptr] native PRFileDescStar(PRFileDesc); + +[scriptable, builtinclass, uuid(e7a3a954-384b-4aeb-a5f7-55626b0de9be)] +interface nsILocalFileWin : nsILocalFile +{ + /** + * initWithCommandLine + * + * Initialize this object based on the main app path of a commandline + * handler. + * + * @param aCommandLine + * the commandline to parse an app path out of. + */ + void initWithCommandLine(in AString aCommandLine); + /** + * getVersionInfoValue + * + * Retrieve a metadata field from the file's VERSIONINFO block. + * Throws NS_ERROR_FAILURE if no value is found, or the value is empty. + * + * @param aField The field to look up. + * + */ + AString getVersionInfoField(in string aField); + + /** + * The canonical path of the file, which avoids short/long + * pathname inconsistencies. The nsIFile persistent + * descriptor is not guaranteed to be canonicalized (it may + * persist either the long or the short path name). The format of + * the canonical path will vary with the underlying file system: + * it will typically be the short pathname on filesystems that + * support both short and long path forms. + */ + readonly attribute AString canonicalPath; + [noscript] readonly attribute ACString nativeCanonicalPath; + + /** + * Windows specific file attributes. + */ + + /* + * WFA_SEARCH_INDEXED: Generally the default on files in Windows except + * those created in temp locations. Valid on XP and up. When set the + * file or directory is marked to be indexed by desktop search services. + */ + const unsigned long WFA_SEARCH_INDEXED = 1; + + /* + * WFA_READONLY: Whether the file is readonly or not. + */ + const unsigned long WFA_READONLY = 2; + + /* + * WFA_READWRITE: Used to clear the readonly attribute. + */ + const unsigned long WFA_READWRITE = 4; + + /** + * fileAttributesWin + * + * Set or get windows specific file attributes. + * + * Throws NS_ERROR_FILE_INVALID_PATH for an invalid file. + * Throws NS_ERROR_FAILURE if the set or get fails. + */ + attribute unsigned long fileAttributesWin; + + /** + * setShortcut + * + * Creates the specified shortcut, or updates it if it already exists. + * + * If the shortcut is being updated (i.e. the shortcut already exists), + * any excluded parameters will remain unchanged in the shortcut file. + * For example, if you want to change the description of a specific + * shortcut but keep the target, working dir, args, and icon the same, + * pass null for those parameters and only pass in a value for the + * description. + * + * If the shortcut does not already exist and targetFile is not specified, + * setShortcut will throw NS_ERROR_FILE_TARGET_DOES_NOT_EXIST. + * + * @param targetFile the path that the shortcut should target + * @param workingDir the working dir that should be set for the shortcut + * @param args the args string that should be set for the shortcut + * @param description the description that should be set for the shortcut + * @param iconFile the file containing an icon to be used for this + shortcut + * @param iconIndex this value selects a specific icon from within + iconFile. If iconFile contains only one icon, this + value should be 0. + */ + void setShortcut([optional] in nsIFile targetFile, + [optional] in nsIFile workingDir, + [optional] in wstring args, + [optional] in wstring description, + [optional] in nsIFile iconFile, + [optional] in long iconIndex); + + /** + * Identical to nsIFile::openNSPRFileDesc except it also uses the + * FILE_SHARE_DELETE flag. + */ + [noscript] PRFileDescStar openNSPRFileDescShareDelete(in long flags, + in long mode); +}; + diff --git a/xpcom/io/nsIMultiplexInputStream.idl b/xpcom/io/nsIMultiplexInputStream.idl new file mode 100644 index 000000000..d42adcbd4 --- /dev/null +++ b/xpcom/io/nsIMultiplexInputStream.idl @@ -0,0 +1,55 @@ +/* -*- 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 "nsIInputStream.idl" + +/** + * The multiplex stream concatenates a list of input streams into a single + * stream. + */ + +[scriptable, uuid(a076fd12-1dd1-11b2-b19a-d53b5dffaade)] +interface nsIMultiplexInputStream : nsIInputStream +{ + /** + * Number of streams in this multiplex-stream + */ + readonly attribute unsigned long count; + + /** + * Appends a stream to the end of the streams. The cursor of the stream + * should be located at the beginning of the stream if the implementation + * of this nsIMultiplexInputStream also is used as an nsISeekableStream. + * @param stream stream to append + */ + void appendStream(in nsIInputStream stream); + + /** + * Insert a stream at specified index. If the cursor of this stream is at + * the beginning of the stream at index, the cursor will be placed at the + * beginning of the inserted stream instead. + * The cursor of the new stream should be located at the beginning of the + * stream if the implementation of this nsIMultiplexInputStream also is + * used as an nsISeekableStream. + * @param stream stream to insert + * @param index index to insert stream at, must be <= count + */ + void insertStream(in nsIInputStream stream, in unsigned long index); + + /** + * Remove stream at specified index. If this stream is the one currently + * being read the readcursor is moved to the beginning of the next + * stream + * @param index remove stream at this index, must be < count + */ + void removeStream(in unsigned long index); + + /** + * Get stream at specified index. + * @param index return stream at this index, must be < count + * @return stream at specified index + */ + nsIInputStream getStream(in unsigned long index); +}; diff --git a/xpcom/io/nsIOUtil.cpp b/xpcom/io/nsIOUtil.cpp new file mode 100644 index 000000000..d583dd75b --- /dev/null +++ b/xpcom/io/nsIOUtil.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 "nsIOUtil.h" +#include "nsIInputStream.h" +#include "nsIOutputStream.h" +#include "nsStreamUtils.h" + +NS_IMPL_ISUPPORTS(nsIOUtil, nsIIOUtil) + +NS_IMETHODIMP +nsIOUtil::InputStreamIsBuffered(nsIInputStream* aStream, bool* aResult) +{ + if (NS_WARN_IF(!aStream)) { + return NS_ERROR_INVALID_ARG; + } + *aResult = NS_InputStreamIsBuffered(aStream); + return NS_OK; +} + +NS_IMETHODIMP +nsIOUtil::OutputStreamIsBuffered(nsIOutputStream* aStream, bool* aResult) +{ + if (NS_WARN_IF(!aStream)) { + return NS_ERROR_INVALID_ARG; + } + *aResult = NS_OutputStreamIsBuffered(aStream); + return NS_OK; +} diff --git a/xpcom/io/nsIOUtil.h b/xpcom/io/nsIOUtil.h new file mode 100644 index 000000000..47b02a02e --- /dev/null +++ b/xpcom/io/nsIOUtil.h @@ -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/. */ + +#ifndef nsIOUtil_h__ +#define nsIOUtil_h__ + +#define NS_IOUTIL_CID \ +{ 0xeb833911, 0x4f49, 0x4623, \ + { 0x84, 0x5f, 0xe5, 0x8a, 0x8e, 0x6d, 0xe4, 0xc2 } } + + +#include "nsIIOUtil.h" +#include "mozilla/Attributes.h" + +class nsIOUtil final : public nsIIOUtil +{ + ~nsIOUtil() {} + +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIIOUTIL +}; + +#endif /* nsIOUtil_h__ */ diff --git a/xpcom/io/nsIObjectInputStream.idl b/xpcom/io/nsIObjectInputStream.idl new file mode 100644 index 000000000..c482d3b89 --- /dev/null +++ b/xpcom/io/nsIObjectInputStream.idl @@ -0,0 +1,53 @@ +/* -*- 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 "nsIBinaryInputStream.idl" + +/** + * @see nsIObjectOutputStream + * @see nsIBinaryInputStream + */ + +[scriptable, uuid(6c248606-4eae-46fa-9df0-ba58502368eb)] +interface nsIObjectInputStream : nsIBinaryInputStream +{ + /** + * Read an object from this stream to satisfy a strong or weak reference + * to one of its interfaces. If the interface was not along the primary + * inheritance chain ending in the "root" or XPCOM-identity nsISupports, + * readObject will QueryInterface from the deserialized object root to the + * correct interface, which was specified when the object was serialized. + * + * @see nsIObjectOutputStream + */ + nsISupports readObject(in boolean aIsStrongRef); + + [notxpcom] nsresult readID(out nsID aID); + + /** + * Optimized deserialization support -- see nsIStreamBufferAccess.idl. + */ + [notxpcom] charPtr getBuffer(in uint32_t aLength, in uint32_t aAlignMask); + [notxpcom] void putBuffer(in charPtr aBuffer, in uint32_t aLength); +}; + +%{C++ + +inline nsresult +NS_ReadOptionalObject(nsIObjectInputStream* aStream, bool aIsStrongRef, + nsISupports* *aResult) +{ + bool nonnull; + nsresult rv = aStream->ReadBoolean(&nonnull); + if (NS_SUCCEEDED(rv)) { + if (nonnull) + rv = aStream->ReadObject(aIsStrongRef, aResult); + else + *aResult = nullptr; + } + return rv; +} + +%} diff --git a/xpcom/io/nsIObjectOutputStream.idl b/xpcom/io/nsIObjectOutputStream.idl new file mode 100644 index 000000000..3ef6711d4 --- /dev/null +++ b/xpcom/io/nsIObjectOutputStream.idl @@ -0,0 +1,97 @@ +/* -*- 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 "nsIBinaryOutputStream.idl" + +/** + * @See nsIObjectInputStream + * @See nsIBinaryOutputStream + */ + +[scriptable, uuid(92c898ac-5fde-4b99-87b3-5d486422094b)] +interface nsIObjectOutputStream : nsIBinaryOutputStream +{ + /** + * Write the object whose "root" or XPCOM-identity nsISupports is aObject. + * The cause for writing this object is a strong or weak reference, so the + * aIsStrongRef argument must tell which kind of pointer is being followed + * here during serialization. + * + * If the object has only one strong reference in the serialization and no + * weak refs, use writeSingleRefObject. This is a valuable optimization: + * it saves space in the stream, and cycles on both ends of the process. + * + * If the reference being serialized is a pointer to an interface not on + * the primary inheritance chain ending in the root nsISupports, you must + * call writeCompoundObject instead of this method. + */ + void writeObject(in nsISupports aObject, in boolean aIsStrongRef); + + /** + * Write an object referenced singly and strongly via its root nsISupports + * or a subclass of its root nsISupports. There must not be other refs to + * aObject in memory, or in the serialization. + */ + void writeSingleRefObject(in nsISupports aObject); + + /** + * Write the object referenced by an interface pointer at aObject that + * inherits from a non-primary nsISupports, i.e., a reference to one of + * the multiply inherited interfaces derived from an nsISupports other + * than the root or XPCOM-identity nsISupports; or a reference to an + * inner object in the case of true XPCOM aggregation. aIID identifies + * this interface. + */ + void writeCompoundObject(in nsISupports aObject, + in nsIIDRef aIID, + in boolean aIsStrongRef); + + void writeID(in nsIDRef aID); + + /** + * Optimized serialization support -- see nsIStreamBufferAccess.idl. + */ + [notxpcom] charPtr getBuffer(in uint32_t aLength, in uint32_t aAlignMask); + [notxpcom] void putBuffer(in charPtr aBuffer, in uint32_t aLength); +}; + +%{C++ + +inline nsresult +NS_WriteOptionalObject(nsIObjectOutputStream* aStream, nsISupports* aObject, + bool aIsStrongRef) +{ + bool nonnull = (aObject != nullptr); + nsresult rv = aStream->WriteBoolean(nonnull); + if (NS_SUCCEEDED(rv) && nonnull) + rv = aStream->WriteObject(aObject, aIsStrongRef); + return rv; +} + +inline nsresult +NS_WriteOptionalSingleRefObject(nsIObjectOutputStream* aStream, + nsISupports* aObject) +{ + bool nonnull = (aObject != nullptr); + nsresult rv = aStream->WriteBoolean(nonnull); + if (NS_SUCCEEDED(rv) && nonnull) + rv = aStream->WriteSingleRefObject(aObject); + return rv; +} + +inline nsresult +NS_WriteOptionalCompoundObject(nsIObjectOutputStream* aStream, + nsISupports* aObject, + const nsIID& aIID, + bool aIsStrongRef) +{ + bool nonnull = (aObject != nullptr); + nsresult rv = aStream->WriteBoolean(nonnull); + if (NS_SUCCEEDED(rv) && nonnull) + rv = aStream->WriteCompoundObject(aObject, aIID, aIsStrongRef); + return rv; +} + +%} diff --git a/xpcom/io/nsIOutputStream.idl b/xpcom/io/nsIOutputStream.idl new file mode 100644 index 000000000..0e04a3910 --- /dev/null +++ b/xpcom/io/nsIOutputStream.idl @@ -0,0 +1,145 @@ +/* -*- 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" + +interface nsIOutputStream; +interface nsIInputStream; + +%{C++ +/** + * The signature for the reader function passed to WriteSegments. This + * is the "provider" of data that gets written into the stream's buffer. + * + * @param aOutStream stream being written to + * @param aClosure opaque parameter passed to WriteSegments + * @param aToSegment pointer to memory owned by the output stream + * @param aFromOffset amount already written (since WriteSegments was called) + * @param aCount length of toSegment + * @param aReadCount number of bytes written + * + * Implementers should return the following: + * + * @throws if not interested in providing any data + * + * Errors are never passed to the caller of WriteSegments. + */ +typedef nsresult (*nsReadSegmentFun)(nsIOutputStream *aOutStream, + void *aClosure, + char *aToSegment, + uint32_t aFromOffset, + uint32_t aCount, + uint32_t *aReadCount); +%} + +native nsReadSegmentFun(nsReadSegmentFun); + +/** + * nsIOutputStream + * + * An interface describing a writable stream of data. An output stream may be + * "blocking" or "non-blocking" (see the IsNonBlocking method). A blocking + * output stream may suspend the calling thread in order to satisfy a call to + * Close, Flush, Write, WriteFrom, or WriteSegments. A non-blocking output + * stream, on the other hand, must not block the calling thread of execution. + * + * NOTE: blocking output streams are often written to on a background thread to + * avoid locking up the main application thread. For this reason, it is + * generally the case that a blocking output stream should be implemented using + * thread- safe AddRef and Release. + */ +[scriptable, uuid(0d0acd2a-61b4-11d4-9877-00c04fa0cf4a)] +interface nsIOutputStream : nsISupports +{ + /** + * Close the stream. Forces the output stream to flush any buffered data. + * + * @throws NS_BASE_STREAM_WOULD_BLOCK if unable to flush without blocking + * the calling thread (non-blocking mode only) + */ + void close(); + + /** + * Flush the stream. + * + * @throws NS_BASE_STREAM_WOULD_BLOCK if unable to flush without blocking + * the calling thread (non-blocking mode only) + */ + void flush(); + + /** + * Write data into the stream. + * + * @param aBuf the buffer containing the data to be written + * @param aCount the maximum number of bytes to be written + * + * @return number of bytes written (may be less than aCount) + * + * @throws NS_BASE_STREAM_WOULD_BLOCK if writing to the output stream would + * block the calling thread (non-blocking mode only) + * @throws on failure + */ + unsigned long write(in string aBuf, in unsigned long aCount); + + /** + * Writes data into the stream from an input stream. + * + * @param aFromStream the stream containing the data to be written + * @param aCount the maximum number of bytes to be written + * + * @return number of bytes written (may be less than aCount) + * + * @throws NS_BASE_STREAM_WOULD_BLOCK if writing to the output stream would + * block the calling thread (non-blocking mode only). This failure + * means no bytes were transferred. + * @throws on failure + * + * NOTE: This method is defined by this interface in order to allow the + * output stream to efficiently copy the data from the input stream into + * its internal buffer (if any). If this method was provided as an external + * facility, a separate char* buffer would need to be used in order to call + * the output stream's other Write method. + */ + unsigned long writeFrom(in nsIInputStream aFromStream, + in unsigned long aCount); + + /** + * Low-level write method that has access to the stream's underlying buffer. + * The reader function may be called multiple times for segmented buffers. + * WriteSegments is expected to keep calling the reader until either there + * is nothing left to write or the reader returns an error. WriteSegments + * should not call the reader with zero bytes to provide. + * + * @param aReader the "provider" of the data to be written + * @param aClosure opaque parameter passed to reader + * @param aCount the maximum number of bytes to be written + * + * @return number of bytes written (may be less than aCount) + * + * @throws NS_BASE_STREAM_WOULD_BLOCK if writing to the output stream would + * block the calling thread (non-blocking mode only). This failure + * means no bytes were transferred. + * @throws NS_ERROR_NOT_IMPLEMENTED if the stream has no underlying buffer + * @throws on failure + * + * NOTE: this function may be unimplemented if a stream has no underlying + * buffer (e.g., socket output stream). + */ + [noscript] unsigned long writeSegments(in nsReadSegmentFun aReader, + in voidPtr aClosure, + in unsigned long aCount); + + /** + * @return true if stream is non-blocking + * + * NOTE: writing to a blocking output stream will block the calling thread + * until all given data can be consumed by the stream. + * + * NOTE: a non-blocking output stream may implement nsIAsyncOutputStream to + * provide consumers with a way to wait for the stream to accept more data + * once its write method is unable to accept any data without blocking. + */ + boolean isNonBlocking(); +}; diff --git a/xpcom/io/nsIPipe.idl b/xpcom/io/nsIPipe.idl new file mode 100644 index 000000000..596be92e9 --- /dev/null +++ b/xpcom/io/nsIPipe.idl @@ -0,0 +1,171 @@ +/* -*- 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 nsIAsyncInputStream; +interface nsIAsyncOutputStream; + +/** + * nsIPipe represents an in-process buffer that can be read using nsIInputStream + * and written using nsIOutputStream. The reader and writer of a pipe do not + * have to be on the same thread. As a result, the pipe is an ideal mechanism + * to bridge data exchange between two threads. For example, a worker thread + * might write data to a pipe from which the main thread will read. + * + * Each end of the pipe can be either blocking or non-blocking. Recall that a + * non-blocking stream will return NS_BASE_STREAM_WOULD_BLOCK if it cannot be + * read or written to without blocking the calling thread. For example, if you + * try to read from an empty pipe that has not yet been closed, then if that + * pipe's input end is non-blocking, then the read call will fail immediately + * with NS_BASE_STREAM_WOULD_BLOCK as the error condition. However, if that + * pipe's input end is blocking, then the read call will not return until the + * pipe has data or until the pipe is closed. This example presumes that the + * pipe is being filled asynchronously on some background thread. + * + * The pipe supports nsIAsyncInputStream and nsIAsyncOutputStream, which give + * the user of a non-blocking pipe the ability to wait for the pipe to become + * ready again. For example, in the case of an empty non-blocking pipe, the + * user can call AsyncWait on the input end of the pipe to be notified when + * the pipe has data to read (or when the pipe becomes closed). + * + * NS_NewPipe2 and NS_NewPipe provide convenient pipe constructors. In most + * cases nsIPipe is not actually used. It is usually enough to just get + * references to the pipe's input and output end. In which case, the pipe is + * automatically closed when the respective pipe ends are released. + */ +[scriptable, uuid(25d0de93-685e-4ea4-95d3-d884e31df63c)] +interface nsIPipe : nsISupports +{ + /** + * initialize this pipe + * + * @param nonBlockingInput + * true specifies non-blocking input stream behavior + * @param nonBlockingOutput + * true specifies non-blocking output stream behavior + * @param segmentSize + * specifies the segment size in bytes (pass 0 to use default value) + * @param segmentCount + * specifies the max number of segments (pass 0 to use default + * value). Passing UINT32_MAX here causes the pipe to have + * "infinite" space. This mode can be useful in some cases, but + * should always be used with caution. The default value for this + * parameter is a finite value. + */ + [must_use] void init(in boolean nonBlockingInput, + in boolean nonBlockingOutput, + in unsigned long segmentSize, + in unsigned long segmentCount); + + /** + * The pipe's input end, which also implements nsISearchableInputStream. + * Getting fails if the pipe hasn't been initialized. + */ + [must_use] readonly attribute nsIAsyncInputStream inputStream; + + /** + * The pipe's output end. Getting fails if the pipe hasn't been + * initialized. + */ + [must_use] readonly attribute nsIAsyncOutputStream outputStream; +}; + +/** + * XXX this interface doesn't really belong in here. It is here because + * currently nsPipeInputStream is the only implementation of this interface. + */ +[scriptable, uuid(8C39EF62-F7C9-11d4-98F5-001083010E9B)] +interface nsISearchableInputStream : nsISupports +{ + /** + * Searches for a string in the input stream. Since the stream has a notion + * of EOF, it is possible that the string may at some time be in the + * buffer, but is is not currently found up to some offset. Consequently, + * both the found and not found cases return an offset: + * if found, return offset where it was found + * if not found, return offset of the first byte not searched + * In the case the stream is at EOF and the string is not found, the first + * byte not searched will correspond to the length of the buffer. + */ + void search(in string forString, + in boolean ignoreCase, + out boolean found, + out unsigned long offsetSearchedTo); +}; + +%{C++ + +class nsIInputStream; +class nsIOutputStream; + +/** + * NS_NewPipe2 + * + * This function supersedes NS_NewPipe. It differs from NS_NewPipe in two + * major ways: + * (1) returns nsIAsyncInputStream and nsIAsyncOutputStream, so it is + * not necessary to QI in order to access these interfaces. + * (2) the size of the pipe is determined by the number of segments + * times the size of each segment. + * + * @param pipeIn + * resulting input end of the pipe + * @param pipeOut + * resulting output end of the pipe + * @param nonBlockingInput + * true specifies non-blocking input stream behavior + * @param nonBlockingOutput + * true specifies non-blocking output stream behavior + * @param segmentSize + * specifies the segment size in bytes (pass 0 to use default value) + * @param segmentCount + * specifies the max number of segments (pass 0 to use default value) + * passing UINT32_MAX here causes the pipe to have "infinite" space. + * this mode can be useful in some cases, but should always be used with + * caution. the default value for this parameter is a finite value. + */ +extern MOZ_MUST_USE nsresult +NS_NewPipe2(nsIAsyncInputStream **pipeIn, + nsIAsyncOutputStream **pipeOut, + bool nonBlockingInput = false, + bool nonBlockingOutput = false, + uint32_t segmentSize = 0, + uint32_t segmentCount = 0); + +/** + * NS_NewPipe + * + * Preserved for backwards compatibility. Plus, this interface is more + * amiable in certain contexts (e.g., when you don't need the pipe's async + * capabilities). + * + * @param pipeIn + * resulting input end of the pipe + * @param pipeOut + * resulting output end of the pipe + * @param segmentSize + * specifies the segment size in bytes (pass 0 to use default value) + * @param maxSize + * specifies the max size of the pipe (pass 0 to use default value) + * number of segments is maxSize / segmentSize, and maxSize must be a + * multiple of segmentSize. passing UINT32_MAX here causes the + * pipe to have "infinite" space. this mode can be useful in some + * cases, but should always be used with caution. the default value + * for this parameter is a finite value. + * @param nonBlockingInput + * true specifies non-blocking input stream behavior + * @param nonBlockingOutput + * true specifies non-blocking output stream behavior + */ +extern MOZ_MUST_USE nsresult +NS_NewPipe(nsIInputStream **pipeIn, + nsIOutputStream **pipeOut, + uint32_t segmentSize = 0, + uint32_t maxSize = 0, + bool nonBlockingInput = false, + bool nonBlockingOutput = false); + +%} diff --git a/xpcom/io/nsISafeOutputStream.idl b/xpcom/io/nsISafeOutputStream.idl new file mode 100644 index 000000000..c62d85f07 --- /dev/null +++ b/xpcom/io/nsISafeOutputStream.idl @@ -0,0 +1,39 @@ +/* -*- 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" + +/** + * This interface provides a mechanism to control an output stream + * that takes care not to overwrite an existing target until it is known + * that all writes to the destination succeeded. + * + * An object that supports this interface is intended to also support + * nsIOutputStream. + * + * For example, a file output stream that supports this interface writes to + * a temporary file, and moves it over the original file when |finish| is + * called only if the stream can be successfully closed and all writes + * succeeded. If |finish| is called but something went wrong during + * writing, it will delete the temporary file and not touch the original. + * If the stream is closed by calling |close| directly, or the stream + * goes away, the original file will not be overwritten, and the temporary + * file will be deleted. + * + * Currently, this interface is implemented only for file output streams. + */ +[scriptable, uuid(5f914307-5c34-4e1f-8e32-ec749d25b27a)] +interface nsISafeOutputStream : nsISupports +{ + /** + * Call this method to close the stream and cause the original target + * to be overwritten. Note: if any call to |write| failed to write out + * all of the data given to it, then calling this method will |close| the + * stream and return failure. Further, if closing the stream fails, this + * method will return failure. The original target will be overwritten only + * if all calls to |write| succeeded and the stream was successfully closed. + */ + void finish(); +}; diff --git a/xpcom/io/nsIScriptableBase64Encoder.idl b/xpcom/io/nsIScriptableBase64Encoder.idl new file mode 100644 index 000000000..641451805 --- /dev/null +++ b/xpcom/io/nsIScriptableBase64Encoder.idl @@ -0,0 +1,32 @@ +/* -*- 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 nsIInputStream; + +/** + * nsIScriptableBase64Encoder efficiently encodes the contents + * of a nsIInputStream to a Base64 string. This avoids the need + * to read the entire stream into a buffer, and only then do the + * Base64 encoding. + * + * If you already have a buffer full of data, you should use + * btoa instead! + */ +[scriptable, uuid(9479c864-d1f9-45ab-b7b9-28b907bd2ba9)] +interface nsIScriptableBase64Encoder : nsISupports +{ + /** + * These methods take an nsIInputStream and return a narrow or wide + * string with the contents of the nsIInputStream base64 encoded. + * + * The stream passed in must support ReadSegments and must not be + * a non-blocking stream that will return NS_BASE_STREAM_WOULD_BLOCK. + * If either of these restrictions are violated we will abort. + */ + ACString encodeToCString(in nsIInputStream stream, in unsigned long length); + AString encodeToString(in nsIInputStream stream, in unsigned long length); +}; diff --git a/xpcom/io/nsIScriptableInputStream.idl b/xpcom/io/nsIScriptableInputStream.idl new file mode 100644 index 000000000..d9248f425 --- /dev/null +++ b/xpcom/io/nsIScriptableInputStream.idl @@ -0,0 +1,67 @@ +/* -*- 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 nsIInputStream; + +/** + * nsIScriptableInputStream provides scriptable access to an nsIInputStream + * instance. + */ +[scriptable, uuid(3fce9015-472a-4080-ac3e-cd875dbe361e)] +interface nsIScriptableInputStream : nsISupports +{ + /** + * Closes the stream. + */ + void close(); + + /** + * Wrap the given nsIInputStream with this nsIScriptableInputStream. + * + * @param aInputStream parameter providing the stream to wrap + */ + void init(in nsIInputStream aInputStream); + + /** + * Return the number of bytes currently available in the stream + * + * @return the number of bytes + * + * @throws NS_BASE_STREAM_CLOSED if called after the stream has been closed + */ + unsigned long long available(); + + /** + * Read data from the stream. + * + * WARNING: If the data contains a null byte, then this method will return + * a truncated string. + * + * @param aCount the maximum number of bytes to read + * + * @return the data, which will be an empty string if the stream is at EOF. + * + * @throws NS_BASE_STREAM_CLOSED if called after the stream has been closed + * @throws NS_ERROR_NOT_INITIALIZED if init was not called + */ + string read(in unsigned long aCount); + + /** + * Read data from the stream, including NULL bytes. + * + * @param aCount the maximum number of bytes to read. + * + * @return the data from the stream, which will be an empty string if EOF + * has been reached. + * + * @throws NS_BASE_STREAM_WOULD_BLOCK if reading from the input stream + * would block the calling thread (non-blocking mode only). + * @throws NS_ERROR_FAILURE if there are not enough bytes available to read + * aCount amount of data. + */ + ACString readBytes(in unsigned long aCount); +}; diff --git a/xpcom/io/nsISeekableStream.idl b/xpcom/io/nsISeekableStream.idl new file mode 100644 index 000000000..1be7ea60c --- /dev/null +++ b/xpcom/io/nsISeekableStream.idl @@ -0,0 +1,74 @@ +/* -*- 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/. */ + + +/* + * nsISeekableStream + * + * Note that a stream might not implement all methods (e.g., a readonly stream + * won't implement setEOF) + */ + +#include "nsISupports.idl" + +[scriptable, uuid(8429d350-1040-4661-8b71-f2a6ba455980)] +interface nsISeekableStream : nsISupports +{ + /* + * Sets the stream pointer to the value of the 'offset' parameter + */ + const int32_t NS_SEEK_SET = 0; + + /* + * Sets the stream pointer to its current location plus the value + * of the offset parameter. + */ + const int32_t NS_SEEK_CUR = 1; + + /* + * Sets the stream pointer to the size of the stream plus the value + * of the offset parameter. + */ + const int32_t NS_SEEK_END = 2; + + /** + * seek + * + * This method moves the stream offset of the steam implementing this + * interface. + * + * @param whence specifies how to interpret the 'offset' parameter in + * setting the stream offset associated with the implementing + * stream. + * + * @param offset specifies a value, in bytes, that is used in conjunction + * with the 'whence' parameter to set the stream offset of the + * implementing stream. A negative value causes seeking in + * the reverse direction. + * + * @throws NS_BASE_STREAM_CLOSED if called on a closed stream. + */ + void seek(in long whence, in long long offset); + + /** + * tell + * + * This method reports the current offset, in bytes, from the start of the + * stream. + * + * @throws NS_BASE_STREAM_CLOSED if called on a closed stream. + */ + long long tell(); + + + /** + * setEOF + * + * This method truncates the stream at the current offset. + * + * @throws NS_BASE_STREAM_CLOSED if called on a closed stream. + */ + void setEOF(); +}; diff --git a/xpcom/io/nsIStorageStream.idl b/xpcom/io/nsIStorageStream.idl new file mode 100644 index 000000000..7d5061e23 --- /dev/null +++ b/xpcom/io/nsIStorageStream.idl @@ -0,0 +1,69 @@ +/* -*- 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 nsIInputStream; +interface nsIOutputStream; + +/** + * The nsIStorageStream interface maintains an internal data buffer that can be + * filled using a single output stream. One or more independent input streams + * can be created to read the data from the buffer non-destructively. + */ + +[scriptable, uuid(44a200fe-6c2b-4b41-b4e3-63e8c14e7c0d)] +interface nsIStorageStream : nsISupports +{ + /** + * + * Initialize the stream, setting up the amount of space that will be + * allocated for the stream's backing-store. + * + * @param segmentSize + * Size of each segment. Must be a power of two. + * @param maxSize + * Maximum total size of this stream. length will always be less + * than or equal to this value. Passing UINT32_MAX is safe. + */ + void init(in uint32_t segmentSize, in uint32_t maxSize); + + /** + * Get a reference to the one and only output stream for this instance. + * The zero-based startPosition argument is used is used to set the initial + * write cursor position. The startPosition cannot be set larger than the + * current buffer length. Calling this method has the side-effect of + * truncating the internal buffer to startPosition bytes. + */ + nsIOutputStream getOutputStream(in int32_t startPosition); + + /** + * Create a new input stream to read data (written by the singleton output + * stream) from the internal buffer. Multiple, independent input streams + * can be created. + */ + nsIInputStream newInputStream(in int32_t startPosition); + + /** + * The length attribute indicates the total number of bytes stored in the + * nsIStorageStream internal buffer, regardless of any consumption by input + * streams. Assigning to the length field can be used to truncate the + * buffer data, but can not be used when either the instance's output + * stream is in use. + * + * @See #writeInProgress */ + attribute uint32_t length; + + /** + * True, when output stream has not yet been Close'ed + */ + readonly attribute boolean writeInProgress; +}; + +%{C++ +// Factory method +nsresult +NS_NewStorageStream(uint32_t segmentSize, uint32_t maxSize, nsIStorageStream **result); +%} diff --git a/xpcom/io/nsIStreamBufferAccess.idl b/xpcom/io/nsIStreamBufferAccess.idl new file mode 100644 index 000000000..c37afd8dc --- /dev/null +++ b/xpcom/io/nsIStreamBufferAccess.idl @@ -0,0 +1,88 @@ +/* -*- 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" + +/** + * An interface for access to a buffering stream implementation's underlying + * memory buffer. + * + * Stream implementations that QueryInterface to nsIStreamBufferAccess must + * ensure that all buffers are aligned on the most restrictive type size for + * the current architecture (e.g., sizeof(double) for RISCy CPUs). malloc(3) + * satisfies this requirement. + */ +[scriptable, uuid(ac923b72-ac87-4892-ac7a-ca385d429435)] +interface nsIStreamBufferAccess : nsISupports +{ + /** + * Get access to a contiguous, aligned run of bytes in the stream's buffer. + * Exactly one successful getBuffer call must occur before a putBuffer call + * taking the non-null pointer returned by the successful getBuffer. + * + * The run of bytes are the next bytes (modulo alignment padding) to read + * for an input stream, and the next bytes (modulo alignment padding) to + * store before (eventually) writing buffered data to an output stream. + * There can be space beyond this run of bytes in the buffer for further + * accesses before the fill or flush point is reached. + * + * @param aLength + * Count of contiguous bytes requested at the address A that satisfies + * (A & aAlignMask) == 0 in the buffer, starting from the current stream + * position, mapped to a buffer address B. The stream implementation + * must pad from B to A by skipping bytes (if input stream) or storing + * zero bytes (if output stream). + * + * @param aAlignMask + * Bit-mask computed by subtracting 1 from the power-of-two alignment + * modulus (e.g., 3 or sizeof(uint32_t)-1 for uint32_t alignment). + * + * @return + * The aligned pointer to aLength bytes in the buffer, or null if the + * buffer has no room for aLength bytes starting at the next address A + * after the current position that satisfies (A & aAlignMask) == 0. + */ + [notxpcom,noscript] charPtr getBuffer(in uint32_t aLength, in uint32_t aAlignMask); + + /** + * Relinquish access to the stream's buffer, filling if at end of an input + * buffer, flushing if completing an output buffer. After a getBuffer call + * that returns non-null, putBuffer must be called. + * + * @param aBuffer + * A non-null pointer returned by getBuffer on the same stream buffer + * access object. + * + * @param aLength + * The same count of contiguous bytes passed to the getBuffer call that + * returned aBuffer. + */ + [notxpcom,noscript] void putBuffer(in charPtr aBuffer, in uint32_t aLength); + + /** + * Disable and enable buffering on the stream implementing this interface. + * DisableBuffering flushes an output stream's buffer, and invalidates an + * input stream's buffer. + */ + void disableBuffering(); + void enableBuffering(); + + /** + * The underlying, unbuffered input or output stream. + */ + readonly attribute nsISupports unbufferedStream; +}; + +%{C++ + +/** + * These macros get and put a buffer given either an sba parameter that may + * point to an object implementing nsIStreamBufferAccess, nsIObjectInputStream, + * or nsIObjectOutputStream. + */ +#define NS_GET_BUFFER(sba,n,a) ((sba)->GetBuffer(n, a)) +#define NS_PUT_BUFFER(sba,p,n) ((sba)->PutBuffer(p, n)) + +%} diff --git a/xpcom/io/nsIStringStream.idl b/xpcom/io/nsIStringStream.idl new file mode 100644 index 000000000..d43200382 --- /dev/null +++ b/xpcom/io/nsIStringStream.idl @@ -0,0 +1,66 @@ +/* -*- 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 "nsIInputStream.idl" + +%{C++ +#include "mozilla/MemoryReporting.h" +%} + +native MallocSizeOf(mozilla::MallocSizeOf); + +/** + * nsIStringInputStream + * + * Provides scriptable and specialized C++-only methods for initializing a + * nsIInputStream implementation with a simple character array. + */ +[scriptable, builtinclass, uuid(450cd2d4-f0fd-424d-b365-b1251f80fd53)] +interface nsIStringInputStream : nsIInputStream +{ + /** + * SetData - assign data to the input stream (copied on assignment). + * + * @param data - stream data + * @param dataLen - stream data length (-1 if length should be computed) + * + * NOTE: C++ code should consider using AdoptData or ShareData to avoid + * making an extra copy of the stream data. + * + * NOTE: For JS callers, the given data must not contain null characters + * (other than a null terminator) because a null character in the middle of + * the data string will be seen as a terminator when the data is converted + * from a JS string to a C++ character array. + */ + void setData(in string data, in long dataLen); + + /** + * NOTE: the following methods are designed to give C++ code added control + * over the ownership and lifetime of the stream data. Use with care :-) + */ + + /** + * AdoptData - assign data to the input stream. the input stream takes + * ownership of the given data buffer and will free it when + * the input stream is destroyed. + * + * @param data - stream data + * @param dataLen - stream data length (-1 if length should be computed) + */ + [noscript] void adoptData(in charPtr data, in long dataLen); + + /** + * ShareData - assign data to the input stream. the input stream references + * the given data buffer until the input stream is destroyed. the given + * data buffer must outlive the input stream. + * + * @param data - stream data + * @param dataLen - stream data length (-1 if length should be computed) + */ + [noscript] void shareData(in string data, in long dataLen); + + [noscript, notxpcom] + size_t SizeOfIncludingThis(in MallocSizeOf aMallocSizeOf); +}; diff --git a/xpcom/io/nsIUnicharInputStream.idl b/xpcom/io/nsIUnicharInputStream.idl new file mode 100644 index 000000000..8d0459fed --- /dev/null +++ b/xpcom/io/nsIUnicharInputStream.idl @@ -0,0 +1,95 @@ +/* -*- 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 nsIUnicharInputStream; +interface nsIInputStream; + +%{C++ +/** + * The signature of the writer function passed to ReadSegments. This + * is the "consumer" of data that gets read from the stream's buffer. + * + * @param aInStream stream being read + * @param aClosure opaque parameter passed to ReadSegments + * @param aFromSegment pointer to memory owned by the input stream + * @param aToOffset amount already read (since ReadSegments was called) + * @param aCount length of fromSegment + * @param aWriteCount number of bytes read + * + * Implementers should return the following: + * + * @throws if not interested in consuming any data + * + * Errors are never passed to the caller of ReadSegments. + * + * NOTE: returning NS_OK and (*aWriteCount = 0) has undefined behavior. + */ +typedef nsresult (*nsWriteUnicharSegmentFun)(nsIUnicharInputStream *aInStream, + void *aClosure, + const char16_t *aFromSegment, + uint32_t aToOffset, + uint32_t aCount, + uint32_t *aWriteCount); +%} +native nsWriteUnicharSegmentFun(nsWriteUnicharSegmentFun); + +/** + * Abstract unicode character input stream + * @see nsIInputStream + */ +[scriptable, uuid(d5e3bd80-6723-4b92-b0c9-22f6162fd94f)] +interface nsIUnicharInputStream : nsISupports { + /** + * Reads into a caller-provided character array. + * + * @return The number of characters that were successfully read. May be less + * than aCount, even if there is more data in the input stream. + * A return value of 0 means EOF. + * + * @note To read more than 2^32 characters, call this method multiple times. + */ + [noscript] unsigned long read([array, size_is(aCount)] in char16_t aBuf, + in unsigned long aCount); + + /** + * Low-level read method that has access to the stream's underlying buffer. + * The writer function may be called multiple times for segmented buffers. + * ReadSegments is expected to keep calling the writer until either there is + * nothing left to read or the writer returns an error. ReadSegments should + * not call the writer with zero characters to consume. + * + * @param aWriter the "consumer" of the data to be read + * @param aClosure opaque parameter passed to writer + * @param aCount the maximum number of characters to be read + * + * @return number of characters read (may be less than aCount) + * @return 0 if reached end of file (or if aWriter refused to consume data) + * + * @throws NS_BASE_STREAM_WOULD_BLOCK if reading from the input stream would + * block the calling thread (non-blocking mode only) + * @throws on failure + * + * NOTE: this function may be unimplemented if a stream has no underlying + * buffer + */ + [noscript] unsigned long readSegments(in nsWriteUnicharSegmentFun aWriter, + in voidPtr aClosure, + in unsigned long aCount); + + /** + * Read into a string object. + * @param aCount The number of characters that should be read + * @return The number of characters that were read. + */ + unsigned long readString(in unsigned long aCount, out AString aString); + + /** + * Close the stream and free associated resources. This also closes the + * underlying stream, if any. + */ + void close(); +}; diff --git a/xpcom/io/nsIUnicharLineInputStream.idl b/xpcom/io/nsIUnicharLineInputStream.idl new file mode 100644 index 000000000..34a67f099 --- /dev/null +++ b/xpcom/io/nsIUnicharLineInputStream.idl @@ -0,0 +1,26 @@ +/* -*- 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" + +[scriptable, uuid(67f42475-ba80-40f8-ac0b-649c89230184)] +interface nsIUnicharLineInputStream : nsISupports +{ + /** + * Read a single line from the stream, where a line is a + * possibly zero length sequence of characters terminated by a + * CR, LF, CRLF, LFCR, or eof. + * The line terminator is not returned. + * @retval false + * End of file. This line is the last line of the file + * (aLine is valid). + * @retval true + * The file contains further lines. + * @note Do not mix readLine with other read functions. + * Doing so can cause various problems and is not supported. + */ + boolean readLine(out AString aLine); +}; diff --git a/xpcom/io/nsIUnicharOutputStream.idl b/xpcom/io/nsIUnicharOutputStream.idl new file mode 100644 index 000000000..599224dd0 --- /dev/null +++ b/xpcom/io/nsIUnicharOutputStream.idl @@ -0,0 +1,47 @@ +/* vim:set expandtab ts=4 sw=4 sts=4 cin: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. 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" + +/** + * An interface that allows writing unicode data. + */ +[scriptable, uuid(2d00b1bb-8b21-4a63-bcc6-7213f513ac2e)] +interface nsIUnicharOutputStream : nsISupports +{ + /** + * Write a single character to the stream. When writing many characters, + * prefer the string-taking write method. + * + * @retval true The character was written successfully + * @retval false Not all bytes of the character could be written. + */ + boolean write(in unsigned long aCount, + [const, array, size_is(aCount)] in char16_t c); + + /** + * Write a string to the stream. + * + * @retval true The string was written successfully + * @retval false Not all bytes of the string could be written. + */ + boolean writeString(in AString str); + + /** + * Flush the stream. This finishes the conversion and writes any bytes that + * finish the current byte sequence. + * + * It does NOT flush the underlying stream. + * + * @see nsIUnicodeEncoder::Finish + */ + void flush(); + + /** + * Close the stream and free associated resources. This also closes the + * underlying stream. + */ + void close(); +}; diff --git a/xpcom/io/nsInputStreamTee.cpp b/xpcom/io/nsInputStreamTee.cpp new file mode 100644 index 000000000..8f01cb87e --- /dev/null +++ b/xpcom/io/nsInputStreamTee.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 +#include "mozilla/Logging.h" + +#include "mozilla/Mutex.h" +#include "mozilla/Attributes.h" +#include "nsIInputStreamTee.h" +#include "nsIInputStream.h" +#include "nsIOutputStream.h" +#include "nsCOMPtr.h" +#include "nsAutoPtr.h" +#include "nsIEventTarget.h" +#include "nsThreadUtils.h" + +using namespace mozilla; + +#ifdef LOG +#undef LOG +#endif + +static LazyLogModule sTeeLog("nsInputStreamTee"); +#define LOG(args) MOZ_LOG(sTeeLog, mozilla::LogLevel::Debug, args) + +class nsInputStreamTee final : public nsIInputStreamTee +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIINPUTSTREAM + NS_DECL_NSIINPUTSTREAMTEE + + nsInputStreamTee(); + bool SinkIsValid(); + void InvalidateSink(); + +private: + ~nsInputStreamTee() + { + } + + nsresult TeeSegment(const char* aBuf, uint32_t aCount); + + static nsresult WriteSegmentFun(nsIInputStream*, void*, const char*, + uint32_t, uint32_t, uint32_t*); + +private: + nsCOMPtr mSource; + nsCOMPtr mSink; + nsCOMPtr mEventTarget; + nsWriteSegmentFun mWriter; // for implementing ReadSegments + void* mClosure; // for implementing ReadSegments + nsAutoPtr mLock; // synchronize access to mSinkIsValid + bool mSinkIsValid; // False if TeeWriteEvent fails +}; + +class nsInputStreamTeeWriteEvent : public Runnable +{ +public: + // aTee's lock is held across construction of this object + nsInputStreamTeeWriteEvent(const char* aBuf, uint32_t aCount, + nsIOutputStream* aSink, nsInputStreamTee* aTee) + { + // copy the buffer - will be free'd by dtor + mBuf = (char*)malloc(aCount); + if (mBuf) { + memcpy(mBuf, (char*)aBuf, aCount); + } + mCount = aCount; + mSink = aSink; + bool isNonBlocking; + mSink->IsNonBlocking(&isNonBlocking); + NS_ASSERTION(isNonBlocking == false, "mSink is nonblocking"); + mTee = aTee; + } + + NS_IMETHOD Run() override + { + if (!mBuf) { + NS_WARNING("nsInputStreamTeeWriteEvent::Run() " + "memory not allocated\n"); + return NS_OK; + } + MOZ_ASSERT(mSink, "mSink is null!"); + + // The output stream could have been invalidated between when + // this event was dispatched and now, so check before writing. + if (!mTee->SinkIsValid()) { + return NS_OK; + } + + LOG(("nsInputStreamTeeWriteEvent::Run() [%p]" + "will write %u bytes to %p\n", + this, mCount, mSink.get())); + + uint32_t totalBytesWritten = 0; + while (mCount) { + nsresult rv; + uint32_t bytesWritten = 0; + rv = mSink->Write(mBuf + totalBytesWritten, mCount, &bytesWritten); + if (NS_FAILED(rv)) { + LOG(("nsInputStreamTeeWriteEvent::Run[%p] error %x in writing", + this, rv)); + mTee->InvalidateSink(); + break; + } + totalBytesWritten += bytesWritten; + NS_ASSERTION(bytesWritten <= mCount, "wrote too much"); + mCount -= bytesWritten; + } + return NS_OK; + } + +protected: + virtual ~nsInputStreamTeeWriteEvent() + { + if (mBuf) { + free(mBuf); + } + mBuf = nullptr; + } + +private: + char* mBuf; + uint32_t mCount; + nsCOMPtr mSink; + // back pointer to the tee that created this runnable + RefPtr mTee; +}; + +nsInputStreamTee::nsInputStreamTee(): mLock(nullptr) + , mSinkIsValid(true) +{ +} + +bool +nsInputStreamTee::SinkIsValid() +{ + MutexAutoLock lock(*mLock); + return mSinkIsValid; +} + +void +nsInputStreamTee::InvalidateSink() +{ + MutexAutoLock lock(*mLock); + mSinkIsValid = false; +} + +nsresult +nsInputStreamTee::TeeSegment(const char* aBuf, uint32_t aCount) +{ + if (!mSink) { + return NS_OK; // nothing to do + } + if (mLock) { // asynchronous case + NS_ASSERTION(mEventTarget, "mEventTarget is null, mLock is not null."); + if (!SinkIsValid()) { + return NS_OK; // nothing to do + } + nsCOMPtr event = + new nsInputStreamTeeWriteEvent(aBuf, aCount, mSink, this); + LOG(("nsInputStreamTee::TeeSegment [%p] dispatching write %u bytes\n", + this, aCount)); + return mEventTarget->Dispatch(event, NS_DISPATCH_NORMAL); + } else { // synchronous case + NS_ASSERTION(!mEventTarget, "mEventTarget is not null, mLock is null."); + nsresult rv; + uint32_t totalBytesWritten = 0; + while (aCount) { + uint32_t bytesWritten = 0; + rv = mSink->Write(aBuf + totalBytesWritten, aCount, &bytesWritten); + if (NS_FAILED(rv)) { + // ok, this is not a fatal error... just drop our reference to mSink + // and continue on as if nothing happened. + NS_WARNING("Write failed (non-fatal)"); + // catch possible misuse of the input stream tee + NS_ASSERTION(rv != NS_BASE_STREAM_WOULD_BLOCK, "sink must be a blocking stream"); + mSink = nullptr; + break; + } + totalBytesWritten += bytesWritten; + NS_ASSERTION(bytesWritten <= aCount, "wrote too much"); + aCount -= bytesWritten; + } + return NS_OK; + } +} + +nsresult +nsInputStreamTee::WriteSegmentFun(nsIInputStream* aIn, void* aClosure, + const char* aFromSegment, uint32_t aOffset, + uint32_t aCount, uint32_t* aWriteCount) +{ + nsInputStreamTee* tee = reinterpret_cast(aClosure); + nsresult rv = tee->mWriter(aIn, tee->mClosure, aFromSegment, aOffset, + aCount, aWriteCount); + if (NS_FAILED(rv) || (*aWriteCount == 0)) { + NS_ASSERTION((NS_FAILED(rv) ? (*aWriteCount == 0) : true), + "writer returned an error with non-zero writeCount"); + return rv; + } + + return tee->TeeSegment(aFromSegment, *aWriteCount); +} + +NS_IMPL_ISUPPORTS(nsInputStreamTee, + nsIInputStreamTee, + nsIInputStream) +NS_IMETHODIMP +nsInputStreamTee::Close() +{ + if (NS_WARN_IF(!mSource)) { + return NS_ERROR_NOT_INITIALIZED; + } + nsresult rv = mSource->Close(); + mSource = nullptr; + mSink = nullptr; + return rv; +} + +NS_IMETHODIMP +nsInputStreamTee::Available(uint64_t* aAvail) +{ + if (NS_WARN_IF(!mSource)) { + return NS_ERROR_NOT_INITIALIZED; + } + return mSource->Available(aAvail); +} + +NS_IMETHODIMP +nsInputStreamTee::Read(char* aBuf, uint32_t aCount, uint32_t* aBytesRead) +{ + if (NS_WARN_IF(!mSource)) { + return NS_ERROR_NOT_INITIALIZED; + } + + nsresult rv = mSource->Read(aBuf, aCount, aBytesRead); + if (NS_FAILED(rv) || (*aBytesRead == 0)) { + return rv; + } + + return TeeSegment(aBuf, *aBytesRead); +} + +NS_IMETHODIMP +nsInputStreamTee::ReadSegments(nsWriteSegmentFun aWriter, + void* aClosure, + uint32_t aCount, + uint32_t* aBytesRead) +{ + if (NS_WARN_IF(!mSource)) { + return NS_ERROR_NOT_INITIALIZED; + } + + mWriter = aWriter; + mClosure = aClosure; + + return mSource->ReadSegments(WriteSegmentFun, this, aCount, aBytesRead); +} + +NS_IMETHODIMP +nsInputStreamTee::IsNonBlocking(bool* aResult) +{ + if (NS_WARN_IF(!mSource)) { + return NS_ERROR_NOT_INITIALIZED; + } + return mSource->IsNonBlocking(aResult); +} + +NS_IMETHODIMP +nsInputStreamTee::SetSource(nsIInputStream* aSource) +{ + mSource = aSource; + return NS_OK; +} + +NS_IMETHODIMP +nsInputStreamTee::GetSource(nsIInputStream** aSource) +{ + NS_IF_ADDREF(*aSource = mSource); + return NS_OK; +} + +NS_IMETHODIMP +nsInputStreamTee::SetSink(nsIOutputStream* aSink) +{ +#ifdef DEBUG + if (aSink) { + bool nonBlocking; + nsresult rv = aSink->IsNonBlocking(&nonBlocking); + if (NS_FAILED(rv) || nonBlocking) { + NS_ERROR("aSink should be a blocking stream"); + } + } +#endif + mSink = aSink; + return NS_OK; +} + +NS_IMETHODIMP +nsInputStreamTee::GetSink(nsIOutputStream** aSink) +{ + NS_IF_ADDREF(*aSink = mSink); + return NS_OK; +} + +NS_IMETHODIMP +nsInputStreamTee::SetEventTarget(nsIEventTarget* aEventTarget) +{ + mEventTarget = aEventTarget; + if (mEventTarget) { + // Only need synchronization if this is an async tee + mLock = new Mutex("nsInputStreamTee.mLock"); + } + return NS_OK; +} + +NS_IMETHODIMP +nsInputStreamTee::GetEventTarget(nsIEventTarget** aEventTarget) +{ + NS_IF_ADDREF(*aEventTarget = mEventTarget); + return NS_OK; +} + + +nsresult +NS_NewInputStreamTeeAsync(nsIInputStream** aResult, + nsIInputStream* aSource, + nsIOutputStream* aSink, + nsIEventTarget* aEventTarget) +{ + nsresult rv; + + nsCOMPtr tee = new nsInputStreamTee(); + rv = tee->SetSource(aSource); + if (NS_FAILED(rv)) { + return rv; + } + + rv = tee->SetSink(aSink); + if (NS_FAILED(rv)) { + return rv; + } + + rv = tee->SetEventTarget(aEventTarget); + if (NS_FAILED(rv)) { + return rv; + } + + tee.forget(aResult); + return rv; +} + +nsresult +NS_NewInputStreamTee(nsIInputStream** aResult, + nsIInputStream* aSource, + nsIOutputStream* aSink) +{ + return NS_NewInputStreamTeeAsync(aResult, aSource, aSink, nullptr); +} + +#undef LOG diff --git a/xpcom/io/nsLinebreakConverter.cpp b/xpcom/io/nsLinebreakConverter.cpp new file mode 100644 index 000000000..007685a6a --- /dev/null +++ b/xpcom/io/nsLinebreakConverter.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 "nsLinebreakConverter.h" + +#include "nsMemory.h" +#include "nsCRT.h" + + +/*---------------------------------------------------------------------------- + GetLinebreakString + + Could make this inline +----------------------------------------------------------------------------*/ +static const char* +GetLinebreakString(nsLinebreakConverter::ELinebreakType aBreakType) +{ + static const char* const sLinebreaks[] = { + "", // any + NS_LINEBREAK, // platform + LFSTR, // content + CRLF, // net + CRSTR, // Mac + LFSTR, // Unix + CRLF, // Windows + " ", // space + nullptr + }; + + return sLinebreaks[aBreakType]; +} + + +/*---------------------------------------------------------------------------- + AppendLinebreak + + Wee inline method to append a line break. Modifies ioDest. +----------------------------------------------------------------------------*/ +template +void +AppendLinebreak(T*& aIoDest, const char* aLineBreakStr) +{ + *aIoDest++ = *aLineBreakStr; + + if (aLineBreakStr[1]) { + *aIoDest++ = aLineBreakStr[1]; + } +} + +/*---------------------------------------------------------------------------- + CountChars + + Counts occurrences of breakStr in aSrc +----------------------------------------------------------------------------*/ +template +int32_t +CountLinebreaks(const T* aSrc, int32_t aInLen, const char* aBreakStr) +{ + const T* src = aSrc; + const T* srcEnd = aSrc + aInLen; + int32_t theCount = 0; + + while (src < srcEnd) { + if (*src == *aBreakStr) { + src++; + + if (aBreakStr[1]) { + if (src < srcEnd && *src == aBreakStr[1]) { + src++; + theCount++; + } + } else { + theCount++; + } + } else { + src++; + } + } + + return theCount; +} + + +/*---------------------------------------------------------------------------- + ConvertBreaks + + ioLen *includes* a terminating null, if any +----------------------------------------------------------------------------*/ +template +static T* +ConvertBreaks(const T* aInSrc, int32_t& aIoLen, const char* aSrcBreak, + const char* aDestBreak) +{ + NS_ASSERTION(aInSrc && aSrcBreak && aDestBreak, "Got a null string"); + + T* resultString = nullptr; + + // handle the no conversion case + if (nsCRT::strcmp(aSrcBreak, aDestBreak) == 0) { + resultString = (T*)malloc(sizeof(T) * aIoLen); + if (!resultString) { + return nullptr; + } + memcpy(resultString, aInSrc, sizeof(T) * aIoLen); // includes the null, if any + return resultString; + } + + int32_t srcBreakLen = strlen(aSrcBreak); + int32_t destBreakLen = strlen(aDestBreak); + + // handle the easy case, where the string length does not change, and the + // breaks are only 1 char long, i.e. CR <-> LF + if (srcBreakLen == destBreakLen && srcBreakLen == 1) { + resultString = (T*)malloc(sizeof(T) * aIoLen); + if (!resultString) { + return nullptr; + } + + const T* src = aInSrc; + const T* srcEnd = aInSrc + aIoLen; // includes null, if any + T* dst = resultString; + + char srcBreakChar = *aSrcBreak; // we know it's one char long already + char dstBreakChar = *aDestBreak; + + while (src < srcEnd) { + if (*src == srcBreakChar) { + *dst++ = dstBreakChar; + src++; + } else { + *dst++ = *src++; + } + } + + // aIoLen does not change + } else { + // src and dest termination is different length. Do it a slower way. + + // count linebreaks in src. Assumes that chars in 2-char linebreaks are unique. + int32_t numLinebreaks = CountLinebreaks(aInSrc, aIoLen, aSrcBreak); + + int32_t newBufLen = + aIoLen - (numLinebreaks * srcBreakLen) + (numLinebreaks * destBreakLen); + resultString = (T*)malloc(sizeof(T) * newBufLen); + if (!resultString) { + return nullptr; + } + + const T* src = aInSrc; + const T* srcEnd = aInSrc + aIoLen; // includes null, if any + T* dst = resultString; + + while (src < srcEnd) { + if (*src == *aSrcBreak) { + *dst++ = *aDestBreak; + if (aDestBreak[1]) { + *dst++ = aDestBreak[1]; + } + + src++; + if (src < srcEnd && aSrcBreak[1] && *src == aSrcBreak[1]) { + src++; + } + } else { + *dst++ = *src++; + } + } + + aIoLen = newBufLen; + } + + return resultString; +} + + +/*---------------------------------------------------------------------------- + ConvertBreaksInSitu + + Convert breaks in situ. Can only do this if the linebreak length + does not change. +----------------------------------------------------------------------------*/ +template +static void +ConvertBreaksInSitu(T* aInSrc, int32_t aInLen, char aSrcBreak, char aDestBreak) +{ + T* src = aInSrc; + T* srcEnd = aInSrc + aInLen; + + while (src < srcEnd) { + if (*src == aSrcBreak) { + *src = aDestBreak; + } + + src++; + } +} + + +/*---------------------------------------------------------------------------- + ConvertUnknownBreaks + + Convert unknown line breaks to the specified break. + + This will convert CRLF pairs to one break, and single CR or LF to a break. +----------------------------------------------------------------------------*/ +template +static T* +ConvertUnknownBreaks(const T* aInSrc, int32_t& aIoLen, const char* aDestBreak) +{ + const T* src = aInSrc; + const T* srcEnd = aInSrc + aIoLen; // includes null, if any + + int32_t destBreakLen = strlen(aDestBreak); + int32_t finalLen = 0; + + while (src < srcEnd) { + if (*src == nsCRT::CR) { + if (src < srcEnd && src[1] == nsCRT::LF) { + // CRLF + finalLen += destBreakLen; + src++; + } else { + // Lone CR + finalLen += destBreakLen; + } + } else if (*src == nsCRT::LF) { + // Lone LF + finalLen += destBreakLen; + } else { + finalLen++; + } + src++; + } + + T* resultString = (T*)malloc(sizeof(T) * finalLen); + if (!resultString) { + return nullptr; + } + + src = aInSrc; + srcEnd = aInSrc + aIoLen; // includes null, if any + + T* dst = resultString; + + while (src < srcEnd) { + if (*src == nsCRT::CR) { + if (src < srcEnd && src[1] == nsCRT::LF) { + // CRLF + AppendLinebreak(dst, aDestBreak); + src++; + } else { + // Lone CR + AppendLinebreak(dst, aDestBreak); + } + } else if (*src == nsCRT::LF) { + // Lone LF + AppendLinebreak(dst, aDestBreak); + } else { + *dst++ = *src; + } + src++; + } + + aIoLen = finalLen; + return resultString; +} + + +/*---------------------------------------------------------------------------- + ConvertLineBreaks + +----------------------------------------------------------------------------*/ +char* +nsLinebreakConverter::ConvertLineBreaks(const char* aSrc, + ELinebreakType aSrcBreaks, + ELinebreakType aDestBreaks, + int32_t aSrcLen, int32_t* aOutLen) +{ + NS_ASSERTION(aDestBreaks != eLinebreakAny && + aSrcBreaks != eLinebreakSpace, "Invalid parameter"); + if (!aSrc) { + return nullptr; + } + + int32_t sourceLen = (aSrcLen == kIgnoreLen) ? strlen(aSrc) + 1 : aSrcLen; + + char* resultString; + if (aSrcBreaks == eLinebreakAny) { + resultString = ConvertUnknownBreaks(aSrc, sourceLen, + GetLinebreakString(aDestBreaks)); + } else + resultString = ConvertBreaks(aSrc, sourceLen, + GetLinebreakString(aSrcBreaks), + GetLinebreakString(aDestBreaks)); + + if (aOutLen) { + *aOutLen = sourceLen; + } + return resultString; +} + + +/*---------------------------------------------------------------------------- + ConvertLineBreaksInSitu + +----------------------------------------------------------------------------*/ +nsresult +nsLinebreakConverter::ConvertLineBreaksInSitu(char** aIoBuffer, + ELinebreakType aSrcBreaks, + ELinebreakType aDestBreaks, + int32_t aSrcLen, int32_t* aOutLen) +{ + NS_ASSERTION(aIoBuffer && *aIoBuffer, "Null pointer passed"); + if (!aIoBuffer || !*aIoBuffer) { + return NS_ERROR_NULL_POINTER; + } + + NS_ASSERTION(aDestBreaks != eLinebreakAny && + aSrcBreaks != eLinebreakSpace, "Invalid parameter"); + + int32_t sourceLen = (aSrcLen == kIgnoreLen) ? strlen(*aIoBuffer) + 1 : aSrcLen; + + // can we convert in-place? + const char* srcBreaks = GetLinebreakString(aSrcBreaks); + const char* dstBreaks = GetLinebreakString(aDestBreaks); + + if (aSrcBreaks != eLinebreakAny && + strlen(srcBreaks) == 1 && + strlen(dstBreaks) == 1) { + ConvertBreaksInSitu(*aIoBuffer, sourceLen, *srcBreaks, *dstBreaks); + if (aOutLen) { + *aOutLen = sourceLen; + } + } else { + char* destBuffer; + + if (aSrcBreaks == eLinebreakAny) { + destBuffer = ConvertUnknownBreaks(*aIoBuffer, sourceLen, dstBreaks); + } else { + destBuffer = ConvertBreaks(*aIoBuffer, sourceLen, srcBreaks, dstBreaks); + } + + if (!destBuffer) { + return NS_ERROR_OUT_OF_MEMORY; + } + *aIoBuffer = destBuffer; + if (aOutLen) { + *aOutLen = sourceLen; + } + } + + return NS_OK; +} + + +/*---------------------------------------------------------------------------- + ConvertUnicharLineBreaks + +----------------------------------------------------------------------------*/ +char16_t* +nsLinebreakConverter::ConvertUnicharLineBreaks(const char16_t* aSrc, + ELinebreakType aSrcBreaks, + ELinebreakType aDestBreaks, + int32_t aSrcLen, + int32_t* aOutLen) +{ + NS_ASSERTION(aDestBreaks != eLinebreakAny && + aSrcBreaks != eLinebreakSpace, "Invalid parameter"); + if (!aSrc) { + return nullptr; + } + + int32_t bufLen = (aSrcLen == kIgnoreLen) ? NS_strlen(aSrc) + 1 : aSrcLen; + + char16_t* resultString; + if (aSrcBreaks == eLinebreakAny) { + resultString = ConvertUnknownBreaks(aSrc, bufLen, + GetLinebreakString(aDestBreaks)); + } else + resultString = ConvertBreaks(aSrc, bufLen, GetLinebreakString(aSrcBreaks), + GetLinebreakString(aDestBreaks)); + + if (aOutLen) { + *aOutLen = bufLen; + } + return resultString; +} + + +/*---------------------------------------------------------------------------- + ConvertStringLineBreaks + +----------------------------------------------------------------------------*/ +nsresult +nsLinebreakConverter::ConvertUnicharLineBreaksInSitu( + char16_t** aIoBuffer, ELinebreakType aSrcBreaks, ELinebreakType aDestBreaks, + int32_t aSrcLen, int32_t* aOutLen) +{ + NS_ASSERTION(aIoBuffer && *aIoBuffer, "Null pointer passed"); + if (!aIoBuffer || !*aIoBuffer) { + return NS_ERROR_NULL_POINTER; + } + NS_ASSERTION(aDestBreaks != eLinebreakAny && + aSrcBreaks != eLinebreakSpace, "Invalid parameter"); + + int32_t sourceLen = + (aSrcLen == kIgnoreLen) ? NS_strlen(*aIoBuffer) + 1 : aSrcLen; + + // can we convert in-place? + const char* srcBreaks = GetLinebreakString(aSrcBreaks); + const char* dstBreaks = GetLinebreakString(aDestBreaks); + + if ((aSrcBreaks != eLinebreakAny) && + (strlen(srcBreaks) == 1) && + (strlen(dstBreaks) == 1)) { + ConvertBreaksInSitu(*aIoBuffer, sourceLen, *srcBreaks, *dstBreaks); + if (aOutLen) { + *aOutLen = sourceLen; + } + } else { + char16_t* destBuffer; + + if (aSrcBreaks == eLinebreakAny) { + destBuffer = ConvertUnknownBreaks(*aIoBuffer, sourceLen, dstBreaks); + } else { + destBuffer = ConvertBreaks(*aIoBuffer, sourceLen, srcBreaks, dstBreaks); + } + + if (!destBuffer) { + return NS_ERROR_OUT_OF_MEMORY; + } + *aIoBuffer = destBuffer; + if (aOutLen) { + *aOutLen = sourceLen; + } + } + + return NS_OK; +} + +/*---------------------------------------------------------------------------- + ConvertStringLineBreaks + +----------------------------------------------------------------------------*/ +nsresult +nsLinebreakConverter::ConvertStringLineBreaks(nsString& aIoString, + ELinebreakType aSrcBreaks, + ELinebreakType aDestBreaks) +{ + + NS_ASSERTION(aDestBreaks != eLinebreakAny && + aSrcBreaks != eLinebreakSpace, "Invalid parameter"); + + // nothing to do + if (aIoString.IsEmpty()) { + return NS_OK; + } + + nsresult rv; + + // remember the old buffer in case + // we blow it away later + nsString::char_iterator stringBuf; + if (!aIoString.BeginWriting(stringBuf, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + int32_t newLen; + + rv = ConvertUnicharLineBreaksInSitu(&stringBuf, + aSrcBreaks, aDestBreaks, + aIoString.Length() + 1, &newLen); + if (NS_FAILED(rv)) { + return rv; + } + + if (stringBuf != aIoString.get()) { + aIoString.Adopt(stringBuf, newLen - 1); + } + + return NS_OK; +} + + + diff --git a/xpcom/io/nsLinebreakConverter.h b/xpcom/io/nsLinebreakConverter.h new file mode 100644 index 000000000..a1678ef2d --- /dev/null +++ b/xpcom/io/nsLinebreakConverter.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 nsLinebreakConverter_h_ +#define nsLinebreakConverter_h_ + +#include "nscore.h" +#include "nsString.h" + +// utility class for converting between different line breaks. + +class nsLinebreakConverter +{ +public: + + // Note: enum must match char* array in GetLinebreakString + typedef enum { + eLinebreakAny, // any kind of linebreak (i.e. "don't care" source) + + eLinebreakPlatform, // platform linebreak + eLinebreakContent, // Content model linebreak (LF) + eLinebreakNet, // Form submission linebreak (CRLF) + + eLinebreakMac, // CR + eLinebreakUnix, // LF + eLinebreakWindows, // CRLF + + eLinebreakSpace // space characters. Only valid as destination type + + } ELinebreakType; + + enum { + kIgnoreLen = -1 + }; + + /* ConvertLineBreaks + * Convert line breaks in the supplied string, allocating and returning + * a new buffer. Returns nullptr on failure. + * @param aSrc: the source string. if aSrcLen == kIgnoreLen this string is assumed + * to be null terminated, otherwise it must be at least aSrcLen long. + * @param aSrcBreaks: the line breaks in the source. If unknown, pass eLinebreakAny. + * If known, pass the known value, as this may be more efficient. + * @param aDestBreaks: the line breaks you want in the output. + * @param aSrcLen: length of the source. If -1, the source is assumed to be a null- + * terminated string. + * @param aOutLen: used to return character length of returned buffer, if not null. + */ + static char* ConvertLineBreaks(const char* aSrc, + ELinebreakType aSrcBreaks, ELinebreakType aDestBreaks, + int32_t aSrcLen = kIgnoreLen, int32_t* aOutLen = nullptr); + + + /* ConvertUnicharLineBreaks + * Convert line breaks in the supplied string, allocating and returning + * a new buffer. Returns nullptr on failure. + * @param aSrc: the source string. if aSrcLen == kIgnoreLen this string is assumed + * to be null terminated, otherwise it must be at least aSrcLen long. + * @param aSrcBreaks: the line breaks in the source. If unknown, pass eLinebreakAny. + * If known, pass the known value, as this may be more efficient. + * @param aDestBreaks: the line breaks you want in the output. + * @param aSrcLen: length of the source, in characters. If -1, the source is assumed to be a null- + * terminated string. + * @param aOutLen: used to return character length of returned buffer, if not null. + */ + static char16_t* ConvertUnicharLineBreaks(const char16_t* aSrc, + ELinebreakType aSrcBreaks, ELinebreakType aDestBreaks, + int32_t aSrcLen = kIgnoreLen, int32_t* aOutLen = nullptr); + + + /* ConvertStringLineBreaks + * Convert line breaks in the supplied string, changing the string buffer (i.e. in-place conversion) + * @param ioString: the string to be converted. + * @param aSrcBreaks: the line breaks in the source. If unknown, pass eLinebreakAny. + * If known, pass the known value, as this may be more efficient. + * @param aDestBreaks: the line breaks you want in the output. + * @param aSrcLen: length of the source, in characters. If -1, the source is assumed to be a null- + * terminated string. + */ + static nsresult ConvertStringLineBreaks(nsString& aIoString, + ELinebreakType aSrcBreaks, + ELinebreakType aDestBreaks); + + + /* ConvertLineBreaksInSitu + * Convert line breaks in place if possible. NOTE: THIS MAY REALLOCATE THE BUFFER, + * BUT IT WON'T FREE THE OLD BUFFER (because it doesn't know how). So be prepared + * to keep a copy of the old pointer, and free it if this passes back a new pointer. + * ALSO NOTE: DON'T PASS A STATIC STRING POINTER TO THIS FUNCTION. + * + * @param ioBuffer: the source buffer. if aSrcLen == kIgnoreLen this string is assumed + * to be null terminated, otherwise it must be at least aSrcLen long. + * @param aSrcBreaks: the line breaks in the source. If unknown, pass eLinebreakAny. + * If known, pass the known value, as this may be more efficient. + * @param aDestBreaks: the line breaks you want in the output. + * @param aSrcLen: length of the source. If -1, the source is assumed to be a null- + * terminated string. + * @param aOutLen: used to return character length of returned buffer, if not null. + */ + static nsresult ConvertLineBreaksInSitu(char** aIoBuffer, + ELinebreakType aSrcBreaks, + ELinebreakType aDestBreaks, + int32_t aSrcLen = kIgnoreLen, + int32_t* aOutLen = nullptr); + + + /* ConvertUnicharLineBreaksInSitu + * Convert line breaks in place if possible. NOTE: THIS MAY REALLOCATE THE BUFFER, + * BUT IT WON'T FREE THE OLD BUFFER (because it doesn't know how). So be prepared + * to keep a copy of the old pointer, and free it if this passes back a new pointer. + * + * @param ioBuffer: the source buffer. if aSrcLen == kIgnoreLen this string is assumed + * to be null terminated, otherwise it must be at least aSrcLen long. + * @param aSrcBreaks: the line breaks in the source. If unknown, pass eLinebreakAny. + * If known, pass the known value, as this may be more efficient. + * @param aDestBreaks: the line breaks you want in the output. + * @param aSrcLen: length of the source in characters. If -1, the source is assumed to be a null- + * terminated string. + * @param aOutLen: used to return character length of returned buffer, if not null. + */ + static nsresult ConvertUnicharLineBreaksInSitu(char16_t** aIoBuffer, + ELinebreakType aSrcBreaks, + ELinebreakType aDestBreaks, + int32_t aSrcLen = kIgnoreLen, + int32_t* aOutLen = nullptr); + +}; + +#endif // nsLinebreakConverter_h_ diff --git a/xpcom/io/nsLocalFile.h b/xpcom/io/nsLocalFile.h new file mode 100644 index 000000000..f7bdb86f7 --- /dev/null +++ b/xpcom/io/nsLocalFile.h @@ -0,0 +1,124 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 Original Code has been modified by IBM Corporation. Modifications made by IBM + * described herein are Copyright (c) International Business Machines Corporation, 2000. + * Modifications to Mozilla code or documentation identified per MPL Section 3.3 + * + * Date Modified by Description of modification + * 04/20/2000 IBM Corp. OS/2 build. + */ + +#ifndef _NS_LOCAL_FILE_H_ +#define _NS_LOCAL_FILE_H_ + +#include "nscore.h" + +#define NS_LOCAL_FILE_CID {0x2e23e220, 0x60be, 0x11d3, {0x8c, 0x4a, 0x00, 0x00, 0x64, 0x65, 0x73, 0x74}} + +#define NS_DECL_NSLOCALFILE_UNICODE_METHODS \ + nsresult AppendUnicode(const char16_t *aNode); \ + nsresult GetUnicodeLeafName(char16_t **aLeafName); \ + nsresult SetUnicodeLeafName(const char16_t *aLeafName); \ + nsresult CopyToUnicode(nsIFile *aNewParentDir, const char16_t *aNewLeafName); \ + nsresult CopyToFollowingLinksUnicode(nsIFile *aNewParentDir, const char16_t *aNewLeafName); \ + nsresult MoveToUnicode(nsIFile *aNewParentDir, const char16_t *aNewLeafName); \ + nsresult GetUnicodeTarget(char16_t **aTarget); \ + nsresult GetUnicodePath(char16_t **aPath); \ + nsresult InitWithUnicodePath(const char16_t *aPath); \ + nsresult AppendRelativeUnicodePath(const char16_t *aRelativePath); + +// XPCOMInit needs to know about how we are implemented, +// so here we will export it. Other users should not depend +// on this. + +#include +#include "nsILocalFile.h" + +#ifdef XP_WIN +#include "nsLocalFileWin.h" +#elif defined(XP_UNIX) +#include "nsLocalFileUnix.h" +#else +#error NOT_IMPLEMENTED +#endif + +#define NSRESULT_FOR_RETURN(ret) (((ret) < 0) ? NSRESULT_FOR_ERRNO() : NS_OK) + +inline nsresult +nsresultForErrno(int aErr) +{ + switch (aErr) { + case 0: + return NS_OK; +#ifdef EDQUOT + case EDQUOT: /* Quota exceeded */ + // FALLTHROUGH to return NS_ERROR_FILE_DISK_FULL +#endif + case ENOSPC: + return NS_ERROR_FILE_DISK_FULL; +#ifdef EISDIR + case EISDIR: /* Is a directory. */ + return NS_ERROR_FILE_IS_DIRECTORY; +#endif + case ENAMETOOLONG: + return NS_ERROR_FILE_NAME_TOO_LONG; + case ENOEXEC: /* Executable file format error. */ + return NS_ERROR_FILE_EXECUTION_FAILED; + case ENOENT: + return NS_ERROR_FILE_TARGET_DOES_NOT_EXIST; + case ENOTDIR: + return NS_ERROR_FILE_DESTINATION_NOT_DIR; +#ifdef ELOOP + case ELOOP: + return NS_ERROR_FILE_UNRESOLVABLE_SYMLINK; +#endif /* ELOOP */ +#ifdef ENOLINK + case ENOLINK: + return NS_ERROR_FILE_UNRESOLVABLE_SYMLINK; +#endif /* ENOLINK */ + case EEXIST: + return NS_ERROR_FILE_ALREADY_EXISTS; +#ifdef EPERM + case EPERM: +#endif /* EPERM */ + case EACCES: + return NS_ERROR_FILE_ACCESS_DENIED; +#ifdef EROFS + case EROFS: /* Read-only file system. */ + return NS_ERROR_FILE_READ_ONLY; +#endif + /* + * On AIX 4.3, ENOTEMPTY is defined as EEXIST, + * so there can't be cases for both without + * preprocessing. + */ +#if ENOTEMPTY != EEXIST + case ENOTEMPTY: + return NS_ERROR_FILE_DIR_NOT_EMPTY; +#endif /* ENOTEMPTY != EEXIST */ + /* Note that nsIFile.createUnique() returns + NS_ERROR_FILE_TOO_BIG when it cannot create a temporary + file with a unique filename. + See https://developer.mozilla.org/en-US/docs/Table_Of_Errors + Other usages of NS_ERROR_FILE_TOO_BIG in the source tree + are in line with the POSIX semantics of EFBIG. + So this is a reasonably good approximation. + */ + case EFBIG: /* File too large. */ + return NS_ERROR_FILE_TOO_BIG; + + default: + return NS_ERROR_FAILURE; + } +} + +#define NSRESULT_FOR_ERRNO() nsresultForErrno(errno) + +void NS_StartupLocalFile(); +void NS_ShutdownLocalFile(); + +#endif diff --git a/xpcom/io/nsLocalFileCommon.cpp b/xpcom/io/nsLocalFileCommon.cpp new file mode 100644 index 000000000..8fbb7227d --- /dev/null +++ b/xpcom/io/nsLocalFileCommon.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 "nsIServiceManager.h" + +#include "nsLocalFile.h" // includes platform-specific headers + +#include "nsString.h" +#include "nsCOMPtr.h" +#include "nsReadableUtils.h" +#include "nsPrintfCString.h" +#include "nsCRT.h" +#include "nsNativeCharsetUtils.h" +#include "nsUTF8Utils.h" + +#ifdef XP_WIN +#include +#endif + + +void +NS_StartupLocalFile() +{ + nsLocalFile::GlobalInit(); +} + +void +NS_ShutdownLocalFile() +{ + nsLocalFile::GlobalShutdown(); +} + +#if !defined(MOZ_WIDGET_COCOA) && !defined(XP_WIN) +NS_IMETHODIMP +nsLocalFile::InitWithFile(nsIFile* aFile) +{ + if (NS_WARN_IF(!aFile)) { + return NS_ERROR_INVALID_ARG; + } + + nsAutoCString path; + aFile->GetNativePath(path); + if (path.IsEmpty()) { + return NS_ERROR_INVALID_ARG; + } + return InitWithNativePath(path); +} +#endif + +#define kMaxFilenameLength 255 +#define kMaxExtensionLength 100 +#define kMaxSequenceNumberLength 5 // "-9999" +// requirement: kMaxExtensionLength < kMaxFilenameLength - kMaxSequenceNumberLength + +NS_IMETHODIMP +nsLocalFile::CreateUnique(uint32_t aType, uint32_t aAttributes) +{ + nsresult rv; + bool longName; + +#ifdef XP_WIN + nsAutoString pathName, leafName, rootName, suffix; + rv = GetPath(pathName); +#else + nsAutoCString pathName, leafName, rootName, suffix; + rv = GetNativePath(pathName); +#endif + if (NS_FAILED(rv)) { + return rv; + } + + longName = (pathName.Length() + kMaxSequenceNumberLength > + kMaxFilenameLength); + if (!longName) { + rv = Create(aType, aAttributes); + if (rv != NS_ERROR_FILE_ALREADY_EXISTS) { + return rv; + } + } + +#ifdef XP_WIN + rv = GetLeafName(leafName); + if (NS_FAILED(rv)) { + return rv; + } + + const int32_t lastDot = leafName.RFindChar(char16_t('.')); +#else + rv = GetNativeLeafName(leafName); + if (NS_FAILED(rv)) { + return rv; + } + + const int32_t lastDot = leafName.RFindChar('.'); +#endif + + if (lastDot == kNotFound) { + rootName = leafName; + } else { + suffix = Substring(leafName, lastDot); // include '.' + rootName = Substring(leafName, 0, lastDot); // strip suffix and dot + } + + if (longName) { + int32_t maxRootLength = (kMaxFilenameLength - + (pathName.Length() - leafName.Length()) - + suffix.Length() - kMaxSequenceNumberLength); + + // We cannot create an item inside a directory whose name is too long. + // Also, ensure that at least one character remains after we truncate + // the root name, as we don't want to end up with an empty leaf name. + if (maxRootLength < 2) { + return NS_ERROR_FILE_UNRECOGNIZED_PATH; + } + +#ifdef XP_WIN + // ensure that we don't cut the name in mid-UTF16-character + rootName.SetLength(NS_IS_LOW_SURROGATE(rootName[maxRootLength]) ? + maxRootLength - 1 : maxRootLength); + SetLeafName(rootName + suffix); +#else + if (NS_IsNativeUTF8()) { + // ensure that we don't cut the name in mid-UTF8-character + // (assume the name is valid UTF8 to begin with) + while (UTF8traits::isInSeq(rootName[maxRootLength])) { + --maxRootLength; + } + + // Another check to avoid ending up with an empty leaf name. + if (maxRootLength == 0 && suffix.IsEmpty()) { + return NS_ERROR_FILE_UNRECOGNIZED_PATH; + } + } + + rootName.SetLength(maxRootLength); + SetNativeLeafName(rootName + suffix); +#endif + nsresult rvCreate = Create(aType, aAttributes); + if (rvCreate != NS_ERROR_FILE_ALREADY_EXISTS) { + return rvCreate; + } + } + + for (int indx = 1; indx < 10000; ++indx) { + // start with "Picture-1.jpg" after "Picture.jpg" exists +#ifdef XP_WIN + SetLeafName(rootName + + NS_ConvertASCIItoUTF16(nsPrintfCString("-%d", indx)) + + suffix); +#else + SetNativeLeafName(rootName + nsPrintfCString("-%d", indx) + suffix); +#endif + rv = Create(aType, aAttributes); + if (NS_SUCCEEDED(rv) || rv != NS_ERROR_FILE_ALREADY_EXISTS) { + return rv; + } + } + + // The disk is full, sort of + return NS_ERROR_FILE_TOO_BIG; +} + +#if defined(XP_WIN) +static const char16_t kPathSeparatorChar = '\\'; +#elif defined(XP_UNIX) +static const char16_t kPathSeparatorChar = '/'; +#else +#error Need to define file path separator for your platform +#endif + +static void +SplitPath(char16_t* aPath, nsTArray& aNodeArray) +{ + if (*aPath == 0) { + return; + } + + if (*aPath == kPathSeparatorChar) { + aPath++; + } + aNodeArray.AppendElement(aPath); + + for (char16_t* cp = aPath; *cp != 0; ++cp) { + if (*cp == kPathSeparatorChar) { + *cp++ = 0; + if (*cp == 0) { + break; + } + aNodeArray.AppendElement(cp); + } + } +} + + +NS_IMETHODIMP +nsLocalFile::GetRelativeDescriptor(nsIFile* aFromFile, nsACString& aResult) +{ + if (NS_WARN_IF(!aFromFile)) { + return NS_ERROR_INVALID_ARG; + } + + // + // aResult will be UTF-8 encoded + // + + nsresult rv; + aResult.Truncate(0); + + nsAutoString thisPath, fromPath; + AutoTArray thisNodes; + AutoTArray fromNodes; + + rv = GetPath(thisPath); + if (NS_FAILED(rv)) { + return rv; + } + rv = aFromFile->GetPath(fromPath); + if (NS_FAILED(rv)) { + return rv; + } + + // get raw pointer to mutable string buffer + char16_t* thisPathPtr; + thisPath.BeginWriting(thisPathPtr); + char16_t* fromPathPtr; + fromPath.BeginWriting(fromPathPtr); + + SplitPath(thisPathPtr, thisNodes); + SplitPath(fromPathPtr, fromNodes); + + size_t nodeIndex; + for (nodeIndex = 0; + nodeIndex < thisNodes.Length() && nodeIndex < fromNodes.Length(); + ++nodeIndex) { +#ifdef XP_WIN + if (_wcsicmp(char16ptr_t(thisNodes[nodeIndex]), + char16ptr_t(fromNodes[nodeIndex]))) { + break; + } +#else + if (nsCRT::strcmp(thisNodes[nodeIndex], fromNodes[nodeIndex])) { + break; + } +#endif + } + + size_t branchIndex = nodeIndex; + for (nodeIndex = branchIndex; nodeIndex < fromNodes.Length(); ++nodeIndex) { + aResult.AppendLiteral("../"); + } + for (nodeIndex = branchIndex; nodeIndex < thisNodes.Length(); ++nodeIndex) { + NS_ConvertUTF16toUTF8 nodeStr(thisNodes[nodeIndex]); + aResult.Append(nodeStr); + if (nodeIndex + 1 < thisNodes.Length()) { + aResult.Append('/'); + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::SetRelativeDescriptor(nsIFile* aFromFile, + const nsACString& aRelativeDesc) +{ + NS_NAMED_LITERAL_CSTRING(kParentDirStr, "../"); + + nsCOMPtr targetFile; + nsresult rv = aFromFile->Clone(getter_AddRefs(targetFile)); + if (NS_FAILED(rv)) { + return rv; + } + + // + // aRelativeDesc is UTF-8 encoded + // + + nsCString::const_iterator strBegin, strEnd; + aRelativeDesc.BeginReading(strBegin); + aRelativeDesc.EndReading(strEnd); + + nsCString::const_iterator nodeBegin(strBegin), nodeEnd(strEnd); + nsCString::const_iterator pos(strBegin); + + nsCOMPtr parentDir; + while (FindInReadable(kParentDirStr, nodeBegin, nodeEnd)) { + rv = targetFile->GetParent(getter_AddRefs(parentDir)); + if (NS_FAILED(rv)) { + return rv; + } + if (!parentDir) { + return NS_ERROR_FILE_UNRECOGNIZED_PATH; + } + targetFile = parentDir; + + nodeBegin = nodeEnd; + pos = nodeEnd; + nodeEnd = strEnd; + } + + nodeBegin = nodeEnd = pos; + while (nodeEnd != strEnd) { + FindCharInReadable('/', nodeEnd, strEnd); + targetFile->Append(NS_ConvertUTF8toUTF16(Substring(nodeBegin, nodeEnd))); + if (nodeEnd != strEnd) { // If there's more left in the string, inc over the '/' nodeEnd is on. + ++nodeEnd; + } + nodeBegin = nodeEnd; + } + + return InitWithFile(targetFile); +} + +NS_IMETHODIMP +nsLocalFile::GetRelativePath(nsIFile* aFromFile, nsACString& aResult) +{ + return GetRelativeDescriptor(aFromFile, aResult); +} + +NS_IMETHODIMP +nsLocalFile::SetRelativePath(nsIFile* aFromFile, + const nsACString& aRelativePath) +{ + return SetRelativeDescriptor(aFromFile, aRelativePath); +} diff --git a/xpcom/io/nsLocalFileUnix.cpp b/xpcom/io/nsLocalFileUnix.cpp new file mode 100644 index 000000000..194e5835e --- /dev/null +++ b/xpcom/io/nsLocalFileUnix.cpp @@ -0,0 +1,2715 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * Implementation of nsIFile for "unixy" systems. + */ + +#include "mozilla/ArrayUtils.h" +#include "mozilla/Attributes.h" +#include "mozilla/Sprintf.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(HAVE_SYS_QUOTA_H) && defined(HAVE_LINUX_QUOTA_H) +#define USE_LINUX_QUOTACTL +#include +#include +#include +#ifndef BLOCK_SIZE +#define BLOCK_SIZE 1024 /* kernel block size */ +#endif +#endif + +#include "xpcom-private.h" +#include "nsDirectoryServiceDefs.h" +#include "nsCRT.h" +#include "nsCOMPtr.h" +#include "nsMemory.h" +#include "nsIFile.h" +#include "nsString.h" +#include "nsReadableUtils.h" +#include "nsLocalFile.h" +#include "nsIComponentManager.h" +#include "nsXPIDLString.h" +#include "prproces.h" +#include "nsIDirectoryEnumerator.h" +#include "nsISimpleEnumerator.h" +#include "private/pprio.h" +#include "prlink.h" + +#ifdef MOZ_WIDGET_GTK +#include "nsIGIOService.h" +#endif + +#ifdef MOZ_WIDGET_COCOA +#include +#include "CocoaFileUtils.h" +#include "prmem.h" +#include "plbase64.h" + +static nsresult MacErrorMapper(OSErr inErr); +#endif + +#ifdef MOZ_WIDGET_ANDROID +#include "GeneratedJNIWrappers.h" +#include "nsIMIMEService.h" +#include +#endif + +#ifdef MOZ_ENABLE_CONTENTACTION +#include +#endif + +#include "nsNativeCharsetUtils.h" +#include "nsTraceRefcnt.h" +#include "nsHashKeys.h" + +using namespace mozilla; + +#define ENSURE_STAT_CACHE() \ + PR_BEGIN_MACRO \ + if (!FillStatCache()) \ + return NSRESULT_FOR_ERRNO(); \ + PR_END_MACRO + +#define CHECK_mPath() \ + PR_BEGIN_MACRO \ + if (mPath.IsEmpty()) \ + return NS_ERROR_NOT_INITIALIZED; \ + PR_END_MACRO + +/* directory enumerator */ +class nsDirEnumeratorUnix final + : public nsISimpleEnumerator + , public nsIDirectoryEnumerator +{ +public: + nsDirEnumeratorUnix(); + + // nsISupports interface + NS_DECL_ISUPPORTS + + // nsISimpleEnumerator interface + NS_DECL_NSISIMPLEENUMERATOR + + // nsIDirectoryEnumerator interface + NS_DECL_NSIDIRECTORYENUMERATOR + + NS_IMETHOD Init(nsLocalFile* aParent, bool aIgnored); + +private: + ~nsDirEnumeratorUnix(); + +protected: + NS_IMETHOD GetNextEntry(); + + DIR* mDir; + struct dirent* mEntry; + nsCString mParentPath; +}; + +nsDirEnumeratorUnix::nsDirEnumeratorUnix() : + mDir(nullptr), + mEntry(nullptr) +{ +} + +nsDirEnumeratorUnix::~nsDirEnumeratorUnix() +{ + Close(); +} + +NS_IMPL_ISUPPORTS(nsDirEnumeratorUnix, nsISimpleEnumerator, + nsIDirectoryEnumerator) + +NS_IMETHODIMP +nsDirEnumeratorUnix::Init(nsLocalFile* aParent, + bool aResolveSymlinks /*ignored*/) +{ + nsAutoCString dirPath; + if (NS_FAILED(aParent->GetNativePath(dirPath)) || + dirPath.IsEmpty()) { + return NS_ERROR_FILE_INVALID_PATH; + } + + if (NS_FAILED(aParent->GetNativePath(mParentPath))) { + return NS_ERROR_FAILURE; + } + + mDir = opendir(dirPath.get()); + if (!mDir) { + return NSRESULT_FOR_ERRNO(); + } + return GetNextEntry(); +} + +NS_IMETHODIMP +nsDirEnumeratorUnix::HasMoreElements(bool* aResult) +{ + *aResult = mDir && mEntry; + if (!*aResult) { + Close(); + } + return NS_OK; +} + +NS_IMETHODIMP +nsDirEnumeratorUnix::GetNext(nsISupports** aResult) +{ + nsCOMPtr file; + nsresult rv = GetNextFile(getter_AddRefs(file)); + if (NS_FAILED(rv)) { + return rv; + } + NS_IF_ADDREF(*aResult = file); + return NS_OK; +} + +NS_IMETHODIMP +nsDirEnumeratorUnix::GetNextEntry() +{ + do { + errno = 0; + mEntry = readdir(mDir); + + // end of dir or error + if (!mEntry) { + return NSRESULT_FOR_ERRNO(); + } + + // keep going past "." and ".." + } while (mEntry->d_name[0] == '.' && + (mEntry->d_name[1] == '\0' || // .\0 + (mEntry->d_name[1] == '.' && + mEntry->d_name[2] == '\0'))); // ..\0 + return NS_OK; +} + +NS_IMETHODIMP +nsDirEnumeratorUnix::GetNextFile(nsIFile** aResult) +{ + nsresult rv; + if (!mDir || !mEntry) { + *aResult = nullptr; + return NS_OK; + } + + nsCOMPtr file = new nsLocalFile(); + + if (NS_FAILED(rv = file->InitWithNativePath(mParentPath)) || + NS_FAILED(rv = file->AppendNative(nsDependentCString(mEntry->d_name)))) { + return rv; + } + + file.forget(aResult); + return GetNextEntry(); +} + +NS_IMETHODIMP +nsDirEnumeratorUnix::Close() +{ + if (mDir) { + closedir(mDir); + mDir = nullptr; + } + return NS_OK; +} + +nsLocalFile::nsLocalFile() +{ +} + +nsLocalFile::nsLocalFile(const nsLocalFile& aOther) + : mPath(aOther.mPath) +{ +} + +#ifdef MOZ_WIDGET_COCOA +NS_IMPL_ISUPPORTS(nsLocalFile, + nsILocalFileMac, + nsILocalFile, + nsIFile, + nsIHashable) +#else +NS_IMPL_ISUPPORTS(nsLocalFile, + nsILocalFile, + nsIFile, + nsIHashable) +#endif + +nsresult +nsLocalFile::nsLocalFileConstructor(nsISupports* aOuter, + const nsIID& aIID, + void** aInstancePtr) +{ + if (NS_WARN_IF(!aInstancePtr)) { + return NS_ERROR_INVALID_ARG; + } + if (NS_WARN_IF(aOuter)) { + return NS_ERROR_NO_AGGREGATION; + } + + *aInstancePtr = nullptr; + + nsCOMPtr inst = new nsLocalFile(); + return inst->QueryInterface(aIID, aInstancePtr); +} + +bool +nsLocalFile::FillStatCache() +{ + if (STAT(mPath.get(), &mCachedStat) == -1) { + // try lstat it may be a symlink + if (LSTAT(mPath.get(), &mCachedStat) == -1) { + return false; + } + } + return true; +} + +NS_IMETHODIMP +nsLocalFile::Clone(nsIFile** aFile) +{ + // Just copy-construct ourselves + RefPtr copy = new nsLocalFile(*this); + copy.forget(aFile); + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::InitWithNativePath(const nsACString& aFilePath) +{ + if (aFilePath.EqualsLiteral("~") || + Substring(aFilePath, 0, 2).EqualsLiteral("~/")) { + nsCOMPtr homeDir; + nsAutoCString homePath; + if (NS_FAILED(NS_GetSpecialDirectory(NS_OS_HOME_DIR, + getter_AddRefs(homeDir))) || + NS_FAILED(homeDir->GetNativePath(homePath))) { + return NS_ERROR_FAILURE; + } + + mPath = homePath; + if (aFilePath.Length() > 2) { + mPath.Append(Substring(aFilePath, 1, aFilePath.Length() - 1)); + } + } else { + if (aFilePath.IsEmpty() || aFilePath.First() != '/') { + return NS_ERROR_FILE_UNRECOGNIZED_PATH; + } + mPath = aFilePath; + } + + // trim off trailing slashes + ssize_t len = mPath.Length(); + while ((len > 1) && (mPath[len - 1] == '/')) { + --len; + } + mPath.SetLength(len); + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::CreateAllAncestors(uint32_t aPermissions) +{ + // I promise to play nice + char* buffer = mPath.BeginWriting(); + char* slashp = buffer; + +#ifdef DEBUG_NSIFILE + fprintf(stderr, "nsIFile: before: %s\n", buffer); +#endif + + while ((slashp = strchr(slashp + 1, '/'))) { + /* + * Sequences of '/' are equivalent to a single '/'. + */ + if (slashp[1] == '/') { + continue; + } + + /* + * If the path has a trailing slash, don't make the last component, + * because we'll get EEXIST in Create when we try to build the final + * component again, and it's easier to condition the logic here than + * there. + */ + if (slashp[1] == '\0') { + break; + } + + /* Temporarily NUL-terminate here */ + *slashp = '\0'; +#ifdef DEBUG_NSIFILE + fprintf(stderr, "nsIFile: mkdir(\"%s\")\n", buffer); +#endif + int mkdir_result = mkdir(buffer, aPermissions); + int mkdir_errno = errno; + if (mkdir_result == -1) { + /* + * Always set |errno| to EEXIST if the dir already exists + * (we have to do this here since the errno value is not consistent + * in all cases - various reasons like different platform, + * automounter-controlled dir, etc. can affect it (see bug 125489 + * for details)). + */ + if (access(buffer, F_OK) == 0) { + mkdir_errno = EEXIST; + } + } + + /* Put the / back before we (maybe) return */ + *slashp = '/'; + + /* + * We could get EEXIST for an existing file -- not directory -- + * with the name of one of our ancestors, but that's OK: we'll get + * ENOTDIR when we try to make the next component in the path, + * either here on back in Create, and error out appropriately. + */ + if (mkdir_result == -1 && mkdir_errno != EEXIST) { + return nsresultForErrno(mkdir_errno); + } + } + +#ifdef DEBUG_NSIFILE + fprintf(stderr, "nsIFile: after: %s\n", buffer); +#endif + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::OpenNSPRFileDesc(int32_t aFlags, int32_t aMode, + PRFileDesc** aResult) +{ + *aResult = PR_Open(mPath.get(), aFlags, aMode); + if (!*aResult) { + return NS_ErrorAccordingToNSPR(); + } + + if (aFlags & DELETE_ON_CLOSE) { + PR_Delete(mPath.get()); + } + +#if defined(HAVE_POSIX_FADVISE) + if (aFlags & OS_READAHEAD) { + posix_fadvise(PR_FileDesc2NativeHandle(*aResult), 0, 0, + POSIX_FADV_SEQUENTIAL); + } +#endif + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::OpenANSIFileDesc(const char* aMode, FILE** aResult) +{ + *aResult = fopen(mPath.get(), aMode); + if (!*aResult) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +static int +do_create(const char* aPath, int aFlags, mode_t aMode, PRFileDesc** aResult) +{ + *aResult = PR_Open(aPath, aFlags, aMode); + return *aResult ? 0 : -1; +} + +static int +do_mkdir(const char* aPath, int aFlags, mode_t aMode, PRFileDesc** aResult) +{ + *aResult = nullptr; + return mkdir(aPath, aMode); +} + +nsresult +nsLocalFile::CreateAndKeepOpen(uint32_t aType, int aFlags, + uint32_t aPermissions, PRFileDesc** aResult) +{ + if (aType != NORMAL_FILE_TYPE && aType != DIRECTORY_TYPE) { + return NS_ERROR_FILE_UNKNOWN_TYPE; + } + + int (*createFunc)(const char*, int, mode_t, PRFileDesc**) = + (aType == NORMAL_FILE_TYPE) ? do_create : do_mkdir; + + int result = createFunc(mPath.get(), aFlags, aPermissions, aResult); + if (result == -1 && errno == ENOENT) { + /* + * If we failed because of missing ancestor components, try to create + * them and then retry the original creation. + * + * Ancestor directories get the same permissions as the file we're + * creating, with the X bit set for each of (user,group,other) with + * an R bit in the original permissions. If you want to do anything + * fancy like setgid or sticky bits, do it by hand. + */ + int dirperm = aPermissions; + if (aPermissions & S_IRUSR) { + dirperm |= S_IXUSR; + } + if (aPermissions & S_IRGRP) { + dirperm |= S_IXGRP; + } + if (aPermissions & S_IROTH) { + dirperm |= S_IXOTH; + } + +#ifdef DEBUG_NSIFILE + fprintf(stderr, "nsIFile: perm = %o, dirperm = %o\n", aPermissions, + dirperm); +#endif + + if (NS_FAILED(CreateAllAncestors(dirperm))) { + return NS_ERROR_FAILURE; + } + +#ifdef DEBUG_NSIFILE + fprintf(stderr, "nsIFile: Create(\"%s\") again\n", mPath.get()); +#endif + result = createFunc(mPath.get(), aFlags, aPermissions, aResult); + } + return NSRESULT_FOR_RETURN(result); +} + +NS_IMETHODIMP +nsLocalFile::Create(uint32_t aType, uint32_t aPermissions) +{ + PRFileDesc* junk = nullptr; + nsresult rv = CreateAndKeepOpen(aType, + PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE | + PR_EXCL, + aPermissions, + &junk); + if (junk) { + PR_Close(junk); + } + return rv; +} + +NS_IMETHODIMP +nsLocalFile::AppendNative(const nsACString& aFragment) +{ + if (aFragment.IsEmpty()) { + return NS_OK; + } + + // only one component of path can be appended + nsACString::const_iterator begin, end; + if (FindCharInReadable('/', aFragment.BeginReading(begin), + aFragment.EndReading(end))) { + return NS_ERROR_FILE_UNRECOGNIZED_PATH; + } + + return AppendRelativeNativePath(aFragment); +} + +NS_IMETHODIMP +nsLocalFile::AppendRelativeNativePath(const nsACString& aFragment) +{ + if (aFragment.IsEmpty()) { + return NS_OK; + } + + // No leading '/' + if (aFragment.First() == '/') { + return NS_ERROR_FILE_UNRECOGNIZED_PATH; + } + + if (!mPath.EqualsLiteral("/")) { + mPath.Append('/'); + } + mPath.Append(aFragment); + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::Normalize() +{ + char resolved_path[PATH_MAX] = ""; + char* resolved_path_ptr = nullptr; + + resolved_path_ptr = realpath(mPath.get(), resolved_path); + + // if there is an error, the return is null. + if (!resolved_path_ptr) { + return NSRESULT_FOR_ERRNO(); + } + + mPath = resolved_path; + return NS_OK; +} + +void +nsLocalFile::LocateNativeLeafName(nsACString::const_iterator& aBegin, + nsACString::const_iterator& aEnd) +{ + // XXX perhaps we should cache this?? + + mPath.BeginReading(aBegin); + mPath.EndReading(aEnd); + + nsACString::const_iterator it = aEnd; + nsACString::const_iterator stop = aBegin; + --stop; + while (--it != stop) { + if (*it == '/') { + aBegin = ++it; + return; + } + } + // else, the entire path is the leaf name (which means this + // isn't an absolute path... unexpected??) +} + +NS_IMETHODIMP +nsLocalFile::GetNativeLeafName(nsACString& aLeafName) +{ + nsACString::const_iterator begin, end; + LocateNativeLeafName(begin, end); + aLeafName = Substring(begin, end); + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::SetNativeLeafName(const nsACString& aLeafName) +{ + nsACString::const_iterator begin, end; + LocateNativeLeafName(begin, end); + mPath.Replace(begin.get() - mPath.get(), Distance(begin, end), aLeafName); + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::GetNativePath(nsACString& aResult) +{ + aResult = mPath; + return NS_OK; +} + +nsresult +nsLocalFile::GetNativeTargetPathName(nsIFile* aNewParent, + const nsACString& aNewName, + nsACString& aResult) +{ + nsresult rv; + nsCOMPtr oldParent; + + if (!aNewParent) { + if (NS_FAILED(rv = GetParent(getter_AddRefs(oldParent)))) { + return rv; + } + aNewParent = oldParent.get(); + } else { + // check to see if our target directory exists + bool targetExists; + if (NS_FAILED(rv = aNewParent->Exists(&targetExists))) { + return rv; + } + + if (!targetExists) { + // XXX create the new directory with some permissions + rv = aNewParent->Create(DIRECTORY_TYPE, 0755); + if (NS_FAILED(rv)) { + return rv; + } + } else { + // make sure that the target is actually a directory + bool targetIsDirectory; + if (NS_FAILED(rv = aNewParent->IsDirectory(&targetIsDirectory))) { + return rv; + } + if (!targetIsDirectory) { + return NS_ERROR_FILE_DESTINATION_NOT_DIR; + } + } + } + + nsACString::const_iterator nameBegin, nameEnd; + if (!aNewName.IsEmpty()) { + aNewName.BeginReading(nameBegin); + aNewName.EndReading(nameEnd); + } else { + LocateNativeLeafName(nameBegin, nameEnd); + } + + nsAutoCString dirName; + if (NS_FAILED(rv = aNewParent->GetNativePath(dirName))) { + return rv; + } + + aResult = dirName + NS_LITERAL_CSTRING("/") + Substring(nameBegin, nameEnd); + return NS_OK; +} + +nsresult +nsLocalFile::CopyDirectoryTo(nsIFile* aNewParent) +{ + nsresult rv; + /* + * dirCheck is used for various boolean test results such as from Equals, + * Exists, isDir, etc. + */ + bool dirCheck, isSymlink; + uint32_t oldPerms; + + if (NS_FAILED(rv = IsDirectory(&dirCheck))) { + return rv; + } + if (!dirCheck) { + return CopyToNative(aNewParent, EmptyCString()); + } + + if (NS_FAILED(rv = Equals(aNewParent, &dirCheck))) { + return rv; + } + if (dirCheck) { + // can't copy dir to itself + return NS_ERROR_INVALID_ARG; + } + + if (NS_FAILED(rv = aNewParent->Exists(&dirCheck))) { + return rv; + } + // get the dirs old permissions + if (NS_FAILED(rv = GetPermissions(&oldPerms))) { + return rv; + } + if (!dirCheck) { + if (NS_FAILED(rv = aNewParent->Create(DIRECTORY_TYPE, oldPerms))) { + return rv; + } + } else { // dir exists lets try to use leaf + nsAutoCString leafName; + if (NS_FAILED(rv = GetNativeLeafName(leafName))) { + return rv; + } + if (NS_FAILED(rv = aNewParent->AppendNative(leafName))) { + return rv; + } + if (NS_FAILED(rv = aNewParent->Exists(&dirCheck))) { + return rv; + } + if (dirCheck) { + return NS_ERROR_FILE_ALREADY_EXISTS; // dest exists + } + if (NS_FAILED(rv = aNewParent->Create(DIRECTORY_TYPE, oldPerms))) { + return rv; + } + } + + nsCOMPtr dirIterator; + if (NS_FAILED(rv = GetDirectoryEntries(getter_AddRefs(dirIterator)))) { + return rv; + } + + bool hasMore = false; + while (dirIterator->HasMoreElements(&hasMore), hasMore) { + nsCOMPtr supports; + nsCOMPtr entry; + rv = dirIterator->GetNext(getter_AddRefs(supports)); + entry = do_QueryInterface(supports); + if (NS_FAILED(rv) || !entry) { + continue; + } + if (NS_FAILED(rv = entry->IsSymlink(&isSymlink))) { + return rv; + } + if (NS_FAILED(rv = entry->IsDirectory(&dirCheck))) { + return rv; + } + if (dirCheck && !isSymlink) { + nsCOMPtr destClone; + rv = aNewParent->Clone(getter_AddRefs(destClone)); + if (NS_SUCCEEDED(rv)) { + if (NS_FAILED(rv = entry->CopyToNative(destClone, EmptyCString()))) { +#ifdef DEBUG + nsresult rv2; + nsAutoCString pathName; + if (NS_FAILED(rv2 = entry->GetNativePath(pathName))) { + return rv2; + } + printf("Operation not supported: %s\n", pathName.get()); +#endif + if (rv == NS_ERROR_OUT_OF_MEMORY) { + return rv; + } + continue; + } + } + } else { + if (NS_FAILED(rv = entry->CopyToNative(aNewParent, EmptyCString()))) { +#ifdef DEBUG + nsresult rv2; + nsAutoCString pathName; + if (NS_FAILED(rv2 = entry->GetNativePath(pathName))) { + return rv2; + } + printf("Operation not supported: %s\n", pathName.get()); +#endif + if (rv == NS_ERROR_OUT_OF_MEMORY) { + return rv; + } + continue; + } + } + } + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::CopyToNative(nsIFile* aNewParent, const nsACString& aNewName) +{ + nsresult rv; + // check to make sure that this has been initialized properly + CHECK_mPath(); + + // we copy the parent here so 'aNewParent' remains immutable + nsCOMPtr workParent; + if (aNewParent) { + if (NS_FAILED(rv = aNewParent->Clone(getter_AddRefs(workParent)))) { + return rv; + } + } else { + if (NS_FAILED(rv = GetParent(getter_AddRefs(workParent)))) { + return rv; + } + } + + // check to see if we are a directory or if we are a file + bool isDirectory; + if (NS_FAILED(rv = IsDirectory(&isDirectory))) { + return rv; + } + + nsAutoCString newPathName; + if (isDirectory) { + if (!aNewName.IsEmpty()) { + if (NS_FAILED(rv = workParent->AppendNative(aNewName))) { + return rv; + } + } else { + if (NS_FAILED(rv = GetNativeLeafName(newPathName))) { + return rv; + } + if (NS_FAILED(rv = workParent->AppendNative(newPathName))) { + return rv; + } + } + if (NS_FAILED(rv = CopyDirectoryTo(workParent))) { + return rv; + } + } else { + rv = GetNativeTargetPathName(workParent, aNewName, newPathName); + if (NS_FAILED(rv)) { + return rv; + } + +#ifdef DEBUG_blizzard + printf("nsLocalFile::CopyTo() %s -> %s\n", mPath.get(), newPathName.get()); +#endif + + // actually create the file. + nsLocalFile* newFile = new nsLocalFile(); + nsCOMPtr fileRef(newFile); // release on exit + + rv = newFile->InitWithNativePath(newPathName); + if (NS_FAILED(rv)) { + return rv; + } + + // get the old permissions + uint32_t myPerms; + GetPermissions(&myPerms); + + // Create the new file with the old file's permissions, even if write + // permission is missing. We can't create with write permission and + // then change back to myPerm on all filesystems (FAT on Linux, e.g.). + // But we can write to a read-only file on all Unix filesystems if we + // open it successfully for writing. + + PRFileDesc* newFD; + rv = newFile->CreateAndKeepOpen(NORMAL_FILE_TYPE, + PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE, + myPerms, + &newFD); + if (NS_FAILED(rv)) { + return rv; + } + + // open the old file, too + bool specialFile; + if (NS_FAILED(rv = IsSpecial(&specialFile))) { + PR_Close(newFD); + return rv; + } + if (specialFile) { +#ifdef DEBUG + printf("Operation not supported: %s\n", mPath.get()); +#endif + // make sure to clean up properly + PR_Close(newFD); + return NS_OK; + } + + PRFileDesc* oldFD; + rv = OpenNSPRFileDesc(PR_RDONLY, myPerms, &oldFD); + if (NS_FAILED(rv)) { + // make sure to clean up properly + PR_Close(newFD); + return rv; + } + +#ifdef DEBUG_blizzard + int32_t totalRead = 0; + int32_t totalWritten = 0; +#endif + char buf[BUFSIZ]; + int32_t bytesRead; + + // record PR_Write() error for better error message later. + nsresult saved_write_error = NS_OK; + nsresult saved_read_error = NS_OK; + nsresult saved_read_close_error = NS_OK; + nsresult saved_write_close_error = NS_OK; + + // DONE: Does PR_Read() return bytesRead < 0 for error? + // Yes., The errors from PR_Read are not so common and + // the value may not have correspondence in NS_ERROR_*, but + // we do catch it still, immediately after while() loop. + // We can differentiate errors pf PR_Read and PR_Write by + // looking at saved_write_error value. If PR_Write error occurs (and not + // PR_Read() error), save_write_error is not NS_OK. + + while ((bytesRead = PR_Read(oldFD, buf, BUFSIZ)) > 0) { +#ifdef DEBUG_blizzard + totalRead += bytesRead; +#endif + + // PR_Write promises never to do a short write + int32_t bytesWritten = PR_Write(newFD, buf, bytesRead); + if (bytesWritten < 0) { + saved_write_error = NSRESULT_FOR_ERRNO(); + bytesRead = -1; + break; + } + NS_ASSERTION(bytesWritten == bytesRead, "short PR_Write?"); + +#ifdef DEBUG_blizzard + totalWritten += bytesWritten; +#endif + } + + // TODO/FIXME: If CIFS (and NFS?) may force read/write to return EINTR, + // we are better off to prepare for retrying. But we need confirmation if + // EINTR is returned. + + // Record error if PR_Read() failed. + // Must be done before any other I/O which may reset errno. + if (bytesRead < 0 && saved_write_error == NS_OK) { + saved_read_error = NSRESULT_FOR_ERRNO(); + } + +#ifdef DEBUG_blizzard + printf("read %d bytes, wrote %d bytes\n", + totalRead, totalWritten); +#endif + + // DONE: Errors of close can occur. Read man page of + // close(2); + // This is likely to happen if the file system is remote file + // system (NFS, CIFS, etc.) and network outage occurs. + // At least, we should tell the user that filesystem/disk is + // hosed (possibly due to network error, hard disk failure, + // etc.) so that users can take remedial action. + + // close the files + if (PR_Close(newFD) < 0) { + saved_write_close_error = NSRESULT_FOR_ERRNO(); +#if DEBUG + // This error merits printing. + fprintf(stderr, "ERROR: PR_Close(newFD) returned error. errno = %d\n", errno); +#endif + } + + if (PR_Close(oldFD) < 0) { + saved_read_close_error = NSRESULT_FOR_ERRNO(); +#if DEBUG + fprintf(stderr, "ERROR: PR_Close(oldFD) returned error. errno = %d\n", errno); +#endif + } + + // Let us report the failure to write and read. + // check for write/read error after cleaning up + if (bytesRead < 0) { + if (saved_write_error != NS_OK) { + return saved_write_error; + } else if (saved_read_error != NS_OK) { + return saved_read_error; + } +#if DEBUG + else { // sanity check. Die and debug. + MOZ_ASSERT(0); + } +#endif + } + + if (saved_write_close_error != NS_OK) { + return saved_write_close_error; + } + if (saved_read_close_error != NS_OK) { + return saved_read_close_error; + } + } + return rv; +} + +NS_IMETHODIMP +nsLocalFile::CopyToFollowingLinksNative(nsIFile* aNewParent, + const nsACString& aNewName) +{ + return CopyToNative(aNewParent, aNewName); +} + +NS_IMETHODIMP +nsLocalFile::MoveToNative(nsIFile* aNewParent, const nsACString& aNewName) +{ + nsresult rv; + + // check to make sure that this has been initialized properly + CHECK_mPath(); + + // check to make sure that we have a new parent + nsAutoCString newPathName; + rv = GetNativeTargetPathName(aNewParent, aNewName, newPathName); + if (NS_FAILED(rv)) { + return rv; + } + + // try for atomic rename, falling back to copy/delete + if (rename(mPath.get(), newPathName.get()) < 0) { + if (errno == EXDEV) { + rv = CopyToNative(aNewParent, aNewName); + if (NS_SUCCEEDED(rv)) { + rv = Remove(true); + } + } else { + rv = NSRESULT_FOR_ERRNO(); + } + } + + if (NS_SUCCEEDED(rv)) { + // Adjust this + mPath = newPathName; + } + return rv; +} + +NS_IMETHODIMP +nsLocalFile::Remove(bool aRecursive) +{ + CHECK_mPath(); + ENSURE_STAT_CACHE(); + + bool isSymLink; + + nsresult rv = IsSymlink(&isSymLink); + if (NS_FAILED(rv)) { + return rv; + } + + if (isSymLink || !S_ISDIR(mCachedStat.st_mode)) { + return NSRESULT_FOR_RETURN(unlink(mPath.get())); + } + + if (aRecursive) { + nsDirEnumeratorUnix* dir = new nsDirEnumeratorUnix(); + + nsCOMPtr dirRef(dir); // release on exit + + rv = dir->Init(this, false); + if (NS_FAILED(rv)) { + return rv; + } + + bool more; + while (dir->HasMoreElements(&more), more) { + nsCOMPtr item; + rv = dir->GetNext(getter_AddRefs(item)); + if (NS_FAILED(rv)) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr file = do_QueryInterface(item, &rv); + if (NS_FAILED(rv)) { + return NS_ERROR_FAILURE; + } + rv = file->Remove(aRecursive); + +#ifdef ANDROID + // See bug 580434 - Bionic gives us just deleted files + if (rv == NS_ERROR_FILE_TARGET_DOES_NOT_EXIST) { + continue; + } +#endif + if (NS_FAILED(rv)) { + return rv; + } + } + } + + return NSRESULT_FOR_RETURN(rmdir(mPath.get())); +} + +NS_IMETHODIMP +nsLocalFile::GetLastModifiedTime(PRTime* aLastModTime) +{ + CHECK_mPath(); + if (NS_WARN_IF(!aLastModTime)) { + return NS_ERROR_INVALID_ARG; + } + + PRFileInfo64 info; + if (PR_GetFileInfo64(mPath.get(), &info) != PR_SUCCESS) { + return NSRESULT_FOR_ERRNO(); + } + PRTime modTime = info.modifyTime; + if (modTime == 0) { + *aLastModTime = 0; + } else { + *aLastModTime = modTime / PR_USEC_PER_MSEC; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::SetLastModifiedTime(PRTime aLastModTime) +{ + CHECK_mPath(); + + int result; + if (aLastModTime != 0) { + ENSURE_STAT_CACHE(); + struct utimbuf ut; + ut.actime = mCachedStat.st_atime; + + // convert milliseconds to seconds since the unix epoch + ut.modtime = (time_t)(aLastModTime / PR_MSEC_PER_SEC); + result = utime(mPath.get(), &ut); + } else { + result = utime(mPath.get(), nullptr); + } + return NSRESULT_FOR_RETURN(result); +} + +NS_IMETHODIMP +nsLocalFile::GetLastModifiedTimeOfLink(PRTime* aLastModTimeOfLink) +{ + CHECK_mPath(); + if (NS_WARN_IF(!aLastModTimeOfLink)) { + return NS_ERROR_INVALID_ARG; + } + + struct STAT sbuf; + if (LSTAT(mPath.get(), &sbuf) == -1) { + return NSRESULT_FOR_ERRNO(); + } + *aLastModTimeOfLink = PRTime(sbuf.st_mtime) * PR_MSEC_PER_SEC; + + return NS_OK; +} + +/* + * utime(2) may or may not dereference symlinks, joy. + */ +NS_IMETHODIMP +nsLocalFile::SetLastModifiedTimeOfLink(PRTime aLastModTimeOfLink) +{ + return SetLastModifiedTime(aLastModTimeOfLink); +} + +/* + * Only send back permissions bits: maybe we want to send back the whole + * mode_t to permit checks against other file types? + */ + +#define NORMALIZE_PERMS(mode) ((mode)& (S_IRWXU | S_IRWXG | S_IRWXO)) + +NS_IMETHODIMP +nsLocalFile::GetPermissions(uint32_t* aPermissions) +{ + if (NS_WARN_IF(!aPermissions)) { + return NS_ERROR_INVALID_ARG; + } + ENSURE_STAT_CACHE(); + *aPermissions = NORMALIZE_PERMS(mCachedStat.st_mode); + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::GetPermissionsOfLink(uint32_t* aPermissionsOfLink) +{ + CHECK_mPath(); + if (NS_WARN_IF(!aPermissionsOfLink)) { + return NS_ERROR_INVALID_ARG; + } + + struct STAT sbuf; + if (LSTAT(mPath.get(), &sbuf) == -1) { + return NSRESULT_FOR_ERRNO(); + } + *aPermissionsOfLink = NORMALIZE_PERMS(sbuf.st_mode); + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::SetPermissions(uint32_t aPermissions) +{ + CHECK_mPath(); + + /* + * Race condition here: we should use fchmod instead, there's no way to + * guarantee the name still refers to the same file. + */ + if (chmod(mPath.get(), aPermissions) >= 0) { + return NS_OK; + } +#if defined(ANDROID) && defined(STATFS) + // For the time being, this is restricted for use by Android, but we + // will figure out what to do for all platforms in bug 638503 + struct STATFS sfs; + if (STATFS(mPath.get(), &sfs) < 0) { + return NSRESULT_FOR_ERRNO(); + } + + // if this is a FAT file system we can't set file permissions + if (sfs.f_type == MSDOS_SUPER_MAGIC) { + return NS_OK; + } +#endif + return NSRESULT_FOR_ERRNO(); +} + +NS_IMETHODIMP +nsLocalFile::SetPermissionsOfLink(uint32_t aPermissions) +{ + // There isn't a consistent mechanism for doing this on UNIX platforms. We + // might want to carefully implement this in the future though. + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsLocalFile::GetFileSize(int64_t* aFileSize) +{ + if (NS_WARN_IF(!aFileSize)) { + return NS_ERROR_INVALID_ARG; + } + *aFileSize = 0; + ENSURE_STAT_CACHE(); + + if (!S_ISDIR(mCachedStat.st_mode)) { + *aFileSize = (int64_t)mCachedStat.st_size; + } + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::SetFileSize(int64_t aFileSize) +{ + CHECK_mPath(); + +#if defined(ANDROID) + /* no truncate on bionic */ + int fd = open(mPath.get(), O_WRONLY); + if (fd == -1) { + return NSRESULT_FOR_ERRNO(); + } + + int ret = ftruncate(fd, (off_t)aFileSize); + close(fd); + + if (ret == -1) { + return NSRESULT_FOR_ERRNO(); + } +#elif defined(HAVE_TRUNCATE64) + if (truncate64(mPath.get(), (off64_t)aFileSize) == -1) { + return NSRESULT_FOR_ERRNO(); + } +#else + off_t size = (off_t)aFileSize; + if (truncate(mPath.get(), size) == -1) { + return NSRESULT_FOR_ERRNO(); + } +#endif + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::GetFileSizeOfLink(int64_t* aFileSize) +{ + CHECK_mPath(); + if (NS_WARN_IF(!aFileSize)) { + return NS_ERROR_INVALID_ARG; + } + + struct STAT sbuf; + if (LSTAT(mPath.get(), &sbuf) == -1) { + return NSRESULT_FOR_ERRNO(); + } + + *aFileSize = (int64_t)sbuf.st_size; + return NS_OK; +} + +#if defined(USE_LINUX_QUOTACTL) +/* + * Searches /proc/self/mountinfo for given device (Major:Minor), + * returns exported name from /dev + * + * Fails when /proc/self/mountinfo or diven device don't exist. + */ +static bool +GetDeviceName(int aDeviceMajor, int aDeviceMinor, nsACString& aDeviceName) +{ + bool ret = false; + + const int kMountInfoLineLength = 200; + const int kMountInfoDevPosition = 6; + + char mountinfoLine[kMountInfoLineLength]; + char deviceNum[kMountInfoLineLength]; + + SprintfLiteral(deviceNum, "%d:%d", aDeviceMajor, aDeviceMinor); + + FILE* f = fopen("/proc/self/mountinfo", "rt"); + if (!f) { + return ret; + } + + // Expects /proc/self/mountinfo in format: + // 'ID ID major:minor root mountpoint flags - type devicename flags' + while (fgets(mountinfoLine, kMountInfoLineLength, f)) { + char* p_dev = strstr(mountinfoLine, deviceNum); + + for (int i = 0; i < kMountInfoDevPosition && p_dev; ++i) { + p_dev = strchr(p_dev, ' '); + if (p_dev) { + p_dev++; + } + } + + if (p_dev) { + char* p_dev_end = strchr(p_dev, ' '); + if (p_dev_end) { + *p_dev_end = '\0'; + aDeviceName.Assign(p_dev); + ret = true; + break; + } + } + } + + fclose(f); + return ret; +} +#endif + +NS_IMETHODIMP +nsLocalFile::GetDiskSpaceAvailable(int64_t* aDiskSpaceAvailable) +{ + if (NS_WARN_IF(!aDiskSpaceAvailable)) { + return NS_ERROR_INVALID_ARG; + } + + // These systems have the operations necessary to check disk space. + +#ifdef STATFS + + // check to make sure that mPath is properly initialized + CHECK_mPath(); + + struct STATFS fs_buf; + + /* + * Members of the STATFS struct that you should know about: + * F_BSIZE = block size on disk. + * f_bavail = number of free blocks available to a non-superuser. + * f_bfree = number of total free blocks in file system. + */ + + if (STATFS(mPath.get(), &fs_buf) < 0) { + // The call to STATFS failed. +#ifdef DEBUG + printf("ERROR: GetDiskSpaceAvailable: STATFS call FAILED. \n"); +#endif + return NS_ERROR_FAILURE; + } + + *aDiskSpaceAvailable = (int64_t)fs_buf.F_BSIZE * fs_buf.f_bavail; + +#ifdef DEBUG_DISK_SPACE + printf("DiskSpaceAvailable: %lu bytes\n", + *aDiskSpaceAvailable); +#endif + +#if defined(USE_LINUX_QUOTACTL) + + if (!FillStatCache()) { + // Return available size from statfs + return NS_OK; + } + + nsCString deviceName; + if (!GetDeviceName(major(mCachedStat.st_dev), + minor(mCachedStat.st_dev), + deviceName)) { + return NS_OK; + } + + struct dqblk dq; + if (!quotactl(QCMD(Q_GETQUOTA, USRQUOTA), deviceName.get(), + getuid(), (caddr_t)&dq) +#ifdef QIF_BLIMITS + && dq.dqb_valid & QIF_BLIMITS +#endif + && dq.dqb_bhardlimit) { + int64_t QuotaSpaceAvailable = 0; + // dqb_bhardlimit is count of BLOCK_SIZE blocks, dqb_curspace is bytes + if ((BLOCK_SIZE * dq.dqb_bhardlimit) > dq.dqb_curspace) + QuotaSpaceAvailable = int64_t(BLOCK_SIZE * dq.dqb_bhardlimit - dq.dqb_curspace); + if (QuotaSpaceAvailable < *aDiskSpaceAvailable) { + *aDiskSpaceAvailable = QuotaSpaceAvailable; + } + } +#endif + + return NS_OK; + +#else + /* + * This platform doesn't have statfs or statvfs. I'm sure that there's + * a way to check for free disk space on platforms that don't have statfs + * (I'm SURE they have df, for example). + * + * Until we figure out how to do that, lets be honest and say that this + * command isn't implemented properly for these platforms yet. + */ +#ifdef DEBUG + printf("ERROR: GetDiskSpaceAvailable: Not implemented for plaforms without statfs.\n"); +#endif + return NS_ERROR_NOT_IMPLEMENTED; + +#endif /* STATFS */ + +} + +NS_IMETHODIMP +nsLocalFile::GetParent(nsIFile** aParent) +{ + CHECK_mPath(); + if (NS_WARN_IF(!aParent)) { + return NS_ERROR_INVALID_ARG; + } + *aParent = nullptr; + + // if '/' we are at the top of the volume, return null + if (mPath.EqualsLiteral("/")) { + return NS_OK; + } + + // I promise to play nice + char* buffer = mPath.BeginWriting(); + // find the last significant slash in buffer + char* slashp = strrchr(buffer, '/'); + NS_ASSERTION(slashp, "non-canonical path?"); + if (!slashp) { + return NS_ERROR_FILE_INVALID_PATH; + } + + // for the case where we are at '/' + if (slashp == buffer) { + slashp++; + } + + // temporarily terminate buffer at the last significant slash + char c = *slashp; + *slashp = '\0'; + + nsCOMPtr localFile; + nsresult rv = NS_NewNativeLocalFile(nsDependentCString(buffer), true, + getter_AddRefs(localFile)); + + // make buffer whole again + *slashp = c; + + if (NS_FAILED(rv)) { + return rv; + } + + localFile.forget(aParent); + return NS_OK; +} + +/* + * The results of Exists, isWritable and isReadable are not cached. + */ + + +NS_IMETHODIMP +nsLocalFile::Exists(bool* aResult) +{ + CHECK_mPath(); + if (NS_WARN_IF(!aResult)) { + return NS_ERROR_INVALID_ARG; + } + + *aResult = (access(mPath.get(), F_OK) == 0); + return NS_OK; +} + + +NS_IMETHODIMP +nsLocalFile::IsWritable(bool* aResult) +{ + CHECK_mPath(); + if (NS_WARN_IF(!aResult)) { + return NS_ERROR_INVALID_ARG; + } + + *aResult = (access(mPath.get(), W_OK) == 0); + if (*aResult || errno == EACCES) { + return NS_OK; + } + return NSRESULT_FOR_ERRNO(); +} + +NS_IMETHODIMP +nsLocalFile::IsReadable(bool* aResult) +{ + CHECK_mPath(); + if (NS_WARN_IF(!aResult)) { + return NS_ERROR_INVALID_ARG; + } + + *aResult = (access(mPath.get(), R_OK) == 0); + if (*aResult || errno == EACCES) { + return NS_OK; + } + return NSRESULT_FOR_ERRNO(); +} + +NS_IMETHODIMP +nsLocalFile::IsExecutable(bool* aResult) +{ + CHECK_mPath(); + if (NS_WARN_IF(!aResult)) { + return NS_ERROR_INVALID_ARG; + } + + // Check extension (bug 663899). On certain platforms, the file + // extension may cause the OS to treat it as executable regardless of + // the execute bit, such as .jar on Mac OS X. We borrow the code from + // nsLocalFileWin, slightly modified. + + // Don't be fooled by symlinks. + bool symLink; + nsresult rv = IsSymlink(&symLink); + if (NS_FAILED(rv)) { + return rv; + } + + nsAutoString path; + if (symLink) { + GetTarget(path); + } else { + GetPath(path); + } + + int32_t dotIdx = path.RFindChar(char16_t('.')); + if (dotIdx != kNotFound) { + // Convert extension to lower case. + char16_t* p = path.BeginWriting(); + for (p += dotIdx + 1; *p; ++p) { + *p += (*p >= L'A' && *p <= L'Z') ? 'a' - 'A' : 0; + } + + // Search for any of the set of executable extensions. + static const char* const executableExts[] = { + "air", // Adobe AIR installer + "jar" // java application bundle + }; + nsDependentSubstring ext = Substring(path, dotIdx + 1); + for (size_t i = 0; i < ArrayLength(executableExts); i++) { + if (ext.EqualsASCII(executableExts[i])) { + // Found a match. Set result and quit. + *aResult = true; + return NS_OK; + } + } + } + + // On OS X, then query Launch Services. +#ifdef MOZ_WIDGET_COCOA + // Certain Mac applications, such as Classic applications, which + // run under Rosetta, might not have the +x mode bit but are still + // considered to be executable by Launch Services (bug 646748). + CFURLRef url; + if (NS_FAILED(GetCFURL(&url))) { + return NS_ERROR_FAILURE; + } + + LSRequestedInfo theInfoRequest = kLSRequestAllInfo; + LSItemInfoRecord theInfo; + OSStatus result = ::LSCopyItemInfoForURL(url, theInfoRequest, &theInfo); + ::CFRelease(url); + if (result == noErr) { + if ((theInfo.flags & kLSItemInfoIsApplication) != 0) { + *aResult = true; + return NS_OK; + } + } +#endif + + // Then check the execute bit. + *aResult = (access(mPath.get(), X_OK) == 0); +#ifdef SOLARIS + // On Solaris, access will always return 0 for root user, however + // the file is only executable if S_IXUSR | S_IXGRP | S_IXOTH is set. + // See bug 351950, https://bugzilla.mozilla.org/show_bug.cgi?id=351950 + if (*aResult) { + struct STAT buf; + + *aResult = (STAT(mPath.get(), &buf) == 0); + if (*aResult || errno == EACCES) { + *aResult = *aResult && (buf.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)); + return NS_OK; + } + + return NSRESULT_FOR_ERRNO(); + } +#endif + if (*aResult || errno == EACCES) { + return NS_OK; + } + return NSRESULT_FOR_ERRNO(); +} + +NS_IMETHODIMP +nsLocalFile::IsDirectory(bool* aResult) +{ + if (NS_WARN_IF(!aResult)) { + return NS_ERROR_INVALID_ARG; + } + *aResult = false; + ENSURE_STAT_CACHE(); + *aResult = S_ISDIR(mCachedStat.st_mode); + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::IsFile(bool* aResult) +{ + if (NS_WARN_IF(!aResult)) { + return NS_ERROR_INVALID_ARG; + } + *aResult = false; + ENSURE_STAT_CACHE(); + *aResult = S_ISREG(mCachedStat.st_mode); + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::IsHidden(bool* aResult) +{ + if (NS_WARN_IF(!aResult)) { + return NS_ERROR_INVALID_ARG; + } + nsACString::const_iterator begin, end; + LocateNativeLeafName(begin, end); + *aResult = (*begin == '.'); + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::IsSymlink(bool* aResult) +{ + if (NS_WARN_IF(!aResult)) { + return NS_ERROR_INVALID_ARG; + } + CHECK_mPath(); + + struct STAT symStat; + if (LSTAT(mPath.get(), &symStat) == -1) { + return NSRESULT_FOR_ERRNO(); + } + *aResult = S_ISLNK(symStat.st_mode); + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::IsSpecial(bool* aResult) +{ + if (NS_WARN_IF(!aResult)) { + return NS_ERROR_INVALID_ARG; + } + ENSURE_STAT_CACHE(); + *aResult = S_ISCHR(mCachedStat.st_mode) || + S_ISBLK(mCachedStat.st_mode) || +#ifdef S_ISSOCK + S_ISSOCK(mCachedStat.st_mode) || +#endif + S_ISFIFO(mCachedStat.st_mode); + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::Equals(nsIFile* aInFile, bool* aResult) +{ + if (NS_WARN_IF(!aInFile)) { + return NS_ERROR_INVALID_ARG; + } + if (NS_WARN_IF(!aResult)) { + return NS_ERROR_INVALID_ARG; + } + *aResult = false; + + nsAutoCString inPath; + nsresult rv = aInFile->GetNativePath(inPath); + if (NS_FAILED(rv)) { + return rv; + } + + // We don't need to worry about "/foo/" vs. "/foo" here + // because trailing slashes are stripped on init. + *aResult = !strcmp(inPath.get(), mPath.get()); + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::Contains(nsIFile* aInFile, bool* aResult) +{ + CHECK_mPath(); + if (NS_WARN_IF(!aInFile)) { + return NS_ERROR_INVALID_ARG; + } + if (NS_WARN_IF(!aResult)) { + return NS_ERROR_INVALID_ARG; + } + + nsAutoCString inPath; + nsresult rv; + + if (NS_FAILED(rv = aInFile->GetNativePath(inPath))) { + return rv; + } + + *aResult = false; + + ssize_t len = mPath.Length(); + if (strncmp(mPath.get(), inPath.get(), len) == 0) { + // Now make sure that the |aInFile|'s path has a separator at len, + // which implies that it has more components after len. + if (inPath[len] == '/') { + *aResult = true; + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::GetNativeTarget(nsACString& aResult) +{ + CHECK_mPath(); + aResult.Truncate(); + + struct STAT symStat; + if (LSTAT(mPath.get(), &symStat) == -1) { + return NSRESULT_FOR_ERRNO(); + } + + if (!S_ISLNK(symStat.st_mode)) { + return NS_ERROR_FILE_INVALID_PATH; + } + + int32_t size = (int32_t)symStat.st_size; + char* target = (char*)moz_xmalloc(size + 1); + if (!target) { + return NS_ERROR_OUT_OF_MEMORY; + } + + if (readlink(mPath.get(), target, (size_t)size) < 0) { + free(target); + return NSRESULT_FOR_ERRNO(); + } + target[size] = '\0'; + + nsresult rv = NS_OK; + nsCOMPtr self(this); + int32_t maxLinks = 40; + while (true) { + if (maxLinks-- == 0) { + rv = NS_ERROR_FILE_UNRESOLVABLE_SYMLINK; + break; + } + + if (target[0] != '/') { + nsCOMPtr parent; + if (NS_FAILED(rv = self->GetParent(getter_AddRefs(parent)))) { + break; + } + if (NS_FAILED(rv = parent->AppendRelativeNativePath(nsDependentCString(target)))) { + break; + } + if (NS_FAILED(rv = parent->GetNativePath(aResult))) { + break; + } + self = parent; + } else { + aResult = target; + } + + const nsPromiseFlatCString& flatRetval = PromiseFlatCString(aResult); + + // Any failure in testing the current target we'll just interpret + // as having reached our destiny. + if (LSTAT(flatRetval.get(), &symStat) == -1) { + break; + } + + // And of course we're done if it isn't a symlink. + if (!S_ISLNK(symStat.st_mode)) { + break; + } + + int32_t newSize = (int32_t)symStat.st_size; + if (newSize > size) { + char* newTarget = (char*)moz_xrealloc(target, newSize + 1); + if (!newTarget) { + rv = NS_ERROR_OUT_OF_MEMORY; + break; + } + target = newTarget; + size = newSize; + } + + int32_t linkLen = readlink(flatRetval.get(), target, size); + if (linkLen == -1) { + rv = NSRESULT_FOR_ERRNO(); + break; + } + target[linkLen] = '\0'; + } + + free(target); + + if (NS_FAILED(rv)) { + aResult.Truncate(); + } + return rv; +} + +NS_IMETHODIMP +nsLocalFile::GetFollowLinks(bool* aFollowLinks) +{ + *aFollowLinks = true; + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::SetFollowLinks(bool aFollowLinks) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::GetDirectoryEntries(nsISimpleEnumerator** aEntries) +{ + RefPtr dir = new nsDirEnumeratorUnix(); + + nsresult rv = dir->Init(this, false); + if (NS_FAILED(rv)) { + *aEntries = nullptr; + } else { + dir.forget(aEntries); + } + + return rv; +} + +NS_IMETHODIMP +nsLocalFile::Load(PRLibrary** aResult) +{ + CHECK_mPath(); + if (NS_WARN_IF(!aResult)) { + return NS_ERROR_INVALID_ARG; + } + +#ifdef NS_BUILD_REFCNT_LOGGING + nsTraceRefcnt::SetActivityIsLegal(false); +#endif + + *aResult = PR_LoadLibrary(mPath.get()); + +#ifdef NS_BUILD_REFCNT_LOGGING + nsTraceRefcnt::SetActivityIsLegal(true); +#endif + + if (!*aResult) { + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::GetPersistentDescriptor(nsACString& aPersistentDescriptor) +{ + return GetNativePath(aPersistentDescriptor); +} + +NS_IMETHODIMP +nsLocalFile::SetPersistentDescriptor(const nsACString& aPersistentDescriptor) +{ +#ifdef MOZ_WIDGET_COCOA + if (aPersistentDescriptor.IsEmpty()) { + return NS_ERROR_INVALID_ARG; + } + + // Support pathnames as user-supplied descriptors if they begin with '/' + // or '~'. These characters do not collide with the base64 set used for + // encoding alias records. + char first = aPersistentDescriptor.First(); + if (first == '/' || first == '~') { + return InitWithNativePath(aPersistentDescriptor); + } + + uint32_t dataSize = aPersistentDescriptor.Length(); + char* decodedData = PL_Base64Decode( + PromiseFlatCString(aPersistentDescriptor).get(), dataSize, nullptr); + if (!decodedData) { + NS_ERROR("SetPersistentDescriptor was given bad data"); + return NS_ERROR_FAILURE; + } + + // Cast to an alias record and resolve. + AliasRecord aliasHeader = *(AliasPtr)decodedData; + int32_t aliasSize = ::GetAliasSizeFromPtr(&aliasHeader); + if (aliasSize > ((int32_t)dataSize * 3) / 4) { // be paranoid about having too few data + PR_Free(decodedData); + return NS_ERROR_FAILURE; + } + + nsresult rv = NS_OK; + + // Move the now-decoded data into the Handle. + // The size of the decoded data is 3/4 the size of the encoded data. See plbase64.h + Handle newHandle = nullptr; + if (::PtrToHand(decodedData, &newHandle, aliasSize) != noErr) { + rv = NS_ERROR_OUT_OF_MEMORY; + } + PR_Free(decodedData); + if (NS_FAILED(rv)) { + return rv; + } + + Boolean changed; + FSRef resolvedFSRef; + OSErr err = ::FSResolveAlias(nullptr, (AliasHandle)newHandle, &resolvedFSRef, + &changed); + + rv = MacErrorMapper(err); + DisposeHandle(newHandle); + if (NS_FAILED(rv)) { + return rv; + } + + return InitWithFSRef(&resolvedFSRef); +#else + return InitWithNativePath(aPersistentDescriptor); +#endif +} + +NS_IMETHODIMP +nsLocalFile::Reveal() +{ +#ifdef MOZ_WIDGET_GTK + nsCOMPtr giovfs = do_GetService(NS_GIOSERVICE_CONTRACTID); + if (!giovfs) { + return NS_ERROR_FAILURE; + } + + bool isDirectory; + if (NS_FAILED(IsDirectory(&isDirectory))) { + return NS_ERROR_FAILURE; + } + + if (isDirectory) { + return giovfs->ShowURIForInput(mPath); + } else if (NS_SUCCEEDED(giovfs->OrgFreedesktopFileManager1ShowItems(mPath))) { + return NS_OK; + } else { + nsCOMPtr parentDir; + nsAutoCString dirPath; + if (NS_FAILED(GetParent(getter_AddRefs(parentDir)))) { + return NS_ERROR_FAILURE; + } + if (NS_FAILED(parentDir->GetNativePath(dirPath))) { + return NS_ERROR_FAILURE; + } + + return giovfs->ShowURIForInput(dirPath); + } +#elif defined(MOZ_WIDGET_COCOA) + CFURLRef url; + if (NS_SUCCEEDED(GetCFURL(&url))) { + nsresult rv = CocoaFileUtils::RevealFileInFinder(url); + ::CFRelease(url); + return rv; + } + return NS_ERROR_FAILURE; +#else + return NS_ERROR_FAILURE; +#endif +} + +NS_IMETHODIMP +nsLocalFile::Launch() +{ +#ifdef MOZ_WIDGET_GTK + nsCOMPtr giovfs = do_GetService(NS_GIOSERVICE_CONTRACTID); + if (!giovfs) { + return NS_ERROR_FAILURE; + } + + return giovfs->ShowURIForInput(mPath); +#elif defined(MOZ_ENABLE_CONTENTACTION) + QUrl uri = QUrl::fromLocalFile(QString::fromUtf8(mPath.get())); + ContentAction::Action action = + ContentAction::Action::defaultActionForFile(uri); + + if (action.isValid()) { + action.trigger(); + return NS_OK; + } + + return NS_ERROR_FAILURE; +#elif defined(MOZ_WIDGET_ANDROID) + // Try to get a mimetype, if this fails just use the file uri alone + nsresult rv; + nsAutoCString type; + nsCOMPtr mimeService(do_GetService("@mozilla.org/mime;1", &rv)); + if (NS_SUCCEEDED(rv)) { + rv = mimeService->GetTypeFromFile(this, type); + } + + nsAutoCString fileUri = NS_LITERAL_CSTRING("file://") + mPath; + return java::GeckoAppShell::OpenUriExternal( + NS_ConvertUTF8toUTF16(fileUri), + NS_ConvertUTF8toUTF16(type), + EmptyString(), + EmptyString(), + EmptyString(), + EmptyString()) ? NS_OK : NS_ERROR_FAILURE; +#elif defined(MOZ_WIDGET_COCOA) + CFURLRef url; + if (NS_SUCCEEDED(GetCFURL(&url))) { + nsresult rv = CocoaFileUtils::OpenURL(url); + ::CFRelease(url); + return rv; + } + return NS_ERROR_FAILURE; +#else + return NS_ERROR_FAILURE; +#endif +} + +nsresult +NS_NewNativeLocalFile(const nsACString& aPath, bool aFollowSymlinks, + nsIFile** aResult) +{ + RefPtr file = new nsLocalFile(); + + file->SetFollowLinks(aFollowSymlinks); + + if (!aPath.IsEmpty()) { + nsresult rv = file->InitWithNativePath(aPath); + if (NS_FAILED(rv)) { + return rv; + } + } + file.forget(aResult); + return NS_OK; +} + +//----------------------------------------------------------------------------- +// unicode support +//----------------------------------------------------------------------------- + +#define SET_UCS(func, ucsArg) \ + { \ + nsAutoCString buf; \ + nsresult rv = NS_CopyUnicodeToNative(ucsArg, buf); \ + if (NS_FAILED(rv)) \ + return rv; \ + return (func)(buf); \ + } + +#define GET_UCS(func, ucsArg) \ + { \ + nsAutoCString buf; \ + nsresult rv = (func)(buf); \ + if (NS_FAILED(rv)) return rv; \ + return NS_CopyNativeToUnicode(buf, ucsArg); \ + } + +#define SET_UCS_2ARGS_2(func, opaqueArg, ucsArg) \ + { \ + nsAutoCString buf; \ + nsresult rv = NS_CopyUnicodeToNative(ucsArg, buf); \ + if (NS_FAILED(rv)) \ + return rv; \ + return (func)(opaqueArg, buf); \ + } + +// Unicode interface Wrapper +nsresult +nsLocalFile::InitWithPath(const nsAString& aFilePath) +{ + SET_UCS(InitWithNativePath, aFilePath); +} +nsresult +nsLocalFile::Append(const nsAString& aNode) +{ + SET_UCS(AppendNative, aNode); +} +nsresult +nsLocalFile::AppendRelativePath(const nsAString& aNode) +{ + SET_UCS(AppendRelativeNativePath, aNode); +} +nsresult +nsLocalFile::GetLeafName(nsAString& aLeafName) +{ + GET_UCS(GetNativeLeafName, aLeafName); +} +nsresult +nsLocalFile::SetLeafName(const nsAString& aLeafName) +{ + SET_UCS(SetNativeLeafName, aLeafName); +} +nsresult +nsLocalFile::GetPath(nsAString& aResult) +{ + return NS_CopyNativeToUnicode(mPath, aResult); +} +nsresult +nsLocalFile::CopyTo(nsIFile* aNewParentDir, const nsAString& aNewName) +{ + SET_UCS_2ARGS_2(CopyToNative , aNewParentDir, aNewName); +} +nsresult +nsLocalFile::CopyToFollowingLinks(nsIFile* aNewParentDir, + const nsAString& aNewName) +{ + SET_UCS_2ARGS_2(CopyToFollowingLinksNative , aNewParentDir, aNewName); +} +nsresult +nsLocalFile::MoveTo(nsIFile* aNewParentDir, const nsAString& aNewName) +{ + SET_UCS_2ARGS_2(MoveToNative, aNewParentDir, aNewName); +} + +NS_IMETHODIMP +nsLocalFile::RenameTo(nsIFile* aNewParentDir, const nsAString& aNewName) +{ + SET_UCS_2ARGS_2(RenameToNative, aNewParentDir, aNewName); +} + +NS_IMETHODIMP +nsLocalFile::RenameToNative(nsIFile* aNewParentDir, const nsACString& aNewName) +{ + nsresult rv; + + // check to make sure that this has been initialized properly + CHECK_mPath(); + + // check to make sure that we have a new parent + nsAutoCString newPathName; + rv = GetNativeTargetPathName(aNewParentDir, aNewName, newPathName); + if (NS_FAILED(rv)) { + return rv; + } + + // try for atomic rename + if (rename(mPath.get(), newPathName.get()) < 0) { + if (errno == EXDEV) { + rv = NS_ERROR_FILE_ACCESS_DENIED; + } else { + rv = NSRESULT_FOR_ERRNO(); + } + } + + return rv; +} + +nsresult +nsLocalFile::GetTarget(nsAString& aResult) +{ + GET_UCS(GetNativeTarget, aResult); +} + +// nsIHashable + +NS_IMETHODIMP +nsLocalFile::Equals(nsIHashable* aOther, bool* aResult) +{ + nsCOMPtr otherFile(do_QueryInterface(aOther)); + if (!otherFile) { + *aResult = false; + return NS_OK; + } + + return Equals(otherFile, aResult); +} + +NS_IMETHODIMP +nsLocalFile::GetHashCode(uint32_t* aResult) +{ + *aResult = HashString(mPath); + return NS_OK; +} + +nsresult +NS_NewLocalFile(const nsAString& aPath, bool aFollowLinks, nsIFile** aResult) +{ + nsAutoCString buf; + nsresult rv = NS_CopyUnicodeToNative(aPath, buf); + if (NS_FAILED(rv)) { + return rv; + } + return NS_NewNativeLocalFile(buf, aFollowLinks, aResult); +} + +//----------------------------------------------------------------------------- +// global init/shutdown +//----------------------------------------------------------------------------- + +void +nsLocalFile::GlobalInit() +{ +} + +void +nsLocalFile::GlobalShutdown() +{ +} + +// nsILocalFileMac + +#ifdef MOZ_WIDGET_COCOA + +static nsresult MacErrorMapper(OSErr inErr) +{ + nsresult outErr; + + switch (inErr) { + case noErr: + outErr = NS_OK; + break; + + case fnfErr: + case afpObjectNotFound: + case afpDirNotFound: + outErr = NS_ERROR_FILE_NOT_FOUND; + break; + + case dupFNErr: + case afpObjectExists: + outErr = NS_ERROR_FILE_ALREADY_EXISTS; + break; + + case dskFulErr: + case afpDiskFull: + outErr = NS_ERROR_FILE_DISK_FULL; + break; + + case fLckdErr: + case afpVolLocked: + outErr = NS_ERROR_FILE_IS_LOCKED; + break; + + case afpAccessDenied: + outErr = NS_ERROR_FILE_ACCESS_DENIED; + break; + + case afpDirNotEmpty: + outErr = NS_ERROR_FILE_DIR_NOT_EMPTY; + break; + + // Can't find good map for some + case bdNamErr: + outErr = NS_ERROR_FAILURE; + break; + + default: + outErr = NS_ERROR_FAILURE; + break; + } + + return outErr; +} + +static nsresult CFStringReftoUTF8(CFStringRef aInStrRef, nsACString& aOutStr) +{ + // first see if the conversion would succeed and find the length of the result + CFIndex usedBufLen, inStrLen = ::CFStringGetLength(aInStrRef); + CFIndex charsConverted = ::CFStringGetBytes(aInStrRef, CFRangeMake(0, inStrLen), + kCFStringEncodingUTF8, 0, false, + nullptr, 0, &usedBufLen); + if (charsConverted == inStrLen) { + // all characters converted, do the actual conversion + aOutStr.SetLength(usedBufLen); + if (aOutStr.Length() != (unsigned int)usedBufLen) { + return NS_ERROR_OUT_OF_MEMORY; + } + UInt8* buffer = (UInt8*)aOutStr.BeginWriting(); + ::CFStringGetBytes(aInStrRef, CFRangeMake(0, inStrLen), kCFStringEncodingUTF8, + 0, false, buffer, usedBufLen, &usedBufLen); + return NS_OK; + } + + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsLocalFile::InitWithCFURL(CFURLRef aCFURL) +{ + UInt8 path[PATH_MAX]; + if (::CFURLGetFileSystemRepresentation(aCFURL, true, path, PATH_MAX)) { + nsDependentCString nativePath((char*)path); + return InitWithNativePath(nativePath); + } + + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsLocalFile::InitWithFSRef(const FSRef* aFSRef) +{ + if (NS_WARN_IF(!aFSRef)) { + return NS_ERROR_INVALID_ARG; + } + + CFURLRef newURLRef = ::CFURLCreateFromFSRef(kCFAllocatorDefault, aFSRef); + if (newURLRef) { + nsresult rv = InitWithCFURL(newURLRef); + ::CFRelease(newURLRef); + return rv; + } + + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsLocalFile::GetCFURL(CFURLRef* aResult) +{ + CHECK_mPath(); + + bool isDir; + IsDirectory(&isDir); + *aResult = ::CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault, + (UInt8*)mPath.get(), + mPath.Length(), + isDir); + + return (*aResult ? NS_OK : NS_ERROR_FAILURE); +} + +NS_IMETHODIMP +nsLocalFile::GetFSRef(FSRef* aResult) +{ + if (NS_WARN_IF(!aResult)) { + return NS_ERROR_INVALID_ARG; + } + + nsresult rv = NS_ERROR_FAILURE; + + CFURLRef url = nullptr; + if (NS_SUCCEEDED(GetCFURL(&url))) { + if (::CFURLGetFSRef(url, aResult)) { + rv = NS_OK; + } + ::CFRelease(url); + } + + return rv; +} + +NS_IMETHODIMP +nsLocalFile::GetFSSpec(FSSpec* aResult) +{ + if (NS_WARN_IF(!aResult)) { + return NS_ERROR_INVALID_ARG; + } + + FSRef fsRef; + nsresult rv = GetFSRef(&fsRef); + if (NS_SUCCEEDED(rv)) { + OSErr err = ::FSGetCatalogInfo(&fsRef, kFSCatInfoNone, nullptr, nullptr, + aResult, nullptr); + return MacErrorMapper(err); + } + + return rv; +} + +NS_IMETHODIMP +nsLocalFile::GetFileSizeWithResFork(int64_t* aFileSizeWithResFork) +{ + if (NS_WARN_IF(!aFileSizeWithResFork)) { + return NS_ERROR_INVALID_ARG; + } + + FSRef fsRef; + nsresult rv = GetFSRef(&fsRef); + if (NS_FAILED(rv)) { + return rv; + } + + FSCatalogInfo catalogInfo; + OSErr err = ::FSGetCatalogInfo(&fsRef, kFSCatInfoDataSizes + kFSCatInfoRsrcSizes, + &catalogInfo, nullptr, nullptr, nullptr); + if (err != noErr) { + return MacErrorMapper(err); + } + + *aFileSizeWithResFork = + catalogInfo.dataLogicalSize + catalogInfo.rsrcLogicalSize; + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::GetFileType(OSType* aFileType) +{ + CFURLRef url; + if (NS_SUCCEEDED(GetCFURL(&url))) { + nsresult rv = CocoaFileUtils::GetFileTypeCode(url, aFileType); + ::CFRelease(url); + return rv; + } + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsLocalFile::SetFileType(OSType aFileType) +{ + CFURLRef url; + if (NS_SUCCEEDED(GetCFURL(&url))) { + nsresult rv = CocoaFileUtils::SetFileTypeCode(url, aFileType); + ::CFRelease(url); + return rv; + } + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsLocalFile::GetFileCreator(OSType* aFileCreator) +{ + CFURLRef url; + if (NS_SUCCEEDED(GetCFURL(&url))) { + nsresult rv = CocoaFileUtils::GetFileCreatorCode(url, aFileCreator); + ::CFRelease(url); + return rv; + } + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsLocalFile::SetFileCreator(OSType aFileCreator) +{ + CFURLRef url; + if (NS_SUCCEEDED(GetCFURL(&url))) { + nsresult rv = CocoaFileUtils::SetFileCreatorCode(url, aFileCreator); + ::CFRelease(url); + return rv; + } + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsLocalFile::LaunchWithDoc(nsIFile* aDocToLoad, bool aLaunchInBackground) +{ + bool isExecutable; + nsresult rv = IsExecutable(&isExecutable); + if (NS_FAILED(rv)) { + return rv; + } + if (!isExecutable) { + return NS_ERROR_FILE_EXECUTION_FAILED; + } + + FSRef appFSRef, docFSRef; + rv = GetFSRef(&appFSRef); + if (NS_FAILED(rv)) { + return rv; + } + + if (aDocToLoad) { + nsCOMPtr macDoc = do_QueryInterface(aDocToLoad); + rv = macDoc->GetFSRef(&docFSRef); + if (NS_FAILED(rv)) { + return rv; + } + } + + LSLaunchFlags theLaunchFlags = kLSLaunchDefaults; + LSLaunchFSRefSpec thelaunchSpec; + + if (aLaunchInBackground) { + theLaunchFlags |= kLSLaunchDontSwitch; + } + memset(&thelaunchSpec, 0, sizeof(LSLaunchFSRefSpec)); + + thelaunchSpec.appRef = &appFSRef; + if (aDocToLoad) { + thelaunchSpec.numDocs = 1; + thelaunchSpec.itemRefs = &docFSRef; + } + thelaunchSpec.launchFlags = theLaunchFlags; + + OSErr err = ::LSOpenFromRefSpec(&thelaunchSpec, nullptr); + if (err != noErr) { + return MacErrorMapper(err); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::OpenDocWithApp(nsIFile* aAppToOpenWith, bool aLaunchInBackground) +{ + FSRef docFSRef; + nsresult rv = GetFSRef(&docFSRef); + if (NS_FAILED(rv)) { + return rv; + } + + if (!aAppToOpenWith) { + OSErr err = ::LSOpenFSRef(&docFSRef, nullptr); + return MacErrorMapper(err); + } + + nsCOMPtr appFileMac = do_QueryInterface(aAppToOpenWith, &rv); + if (!appFileMac) { + return rv; + } + + bool isExecutable; + rv = appFileMac->IsExecutable(&isExecutable); + if (NS_FAILED(rv)) { + return rv; + } + if (!isExecutable) { + return NS_ERROR_FILE_EXECUTION_FAILED; + } + + FSRef appFSRef; + rv = appFileMac->GetFSRef(&appFSRef); + if (NS_FAILED(rv)) { + return rv; + } + + LSLaunchFlags theLaunchFlags = kLSLaunchDefaults; + LSLaunchFSRefSpec thelaunchSpec; + + if (aLaunchInBackground) { + theLaunchFlags |= kLSLaunchDontSwitch; + } + memset(&thelaunchSpec, 0, sizeof(LSLaunchFSRefSpec)); + + thelaunchSpec.appRef = &appFSRef; + thelaunchSpec.numDocs = 1; + thelaunchSpec.itemRefs = &docFSRef; + thelaunchSpec.launchFlags = theLaunchFlags; + + OSErr err = ::LSOpenFromRefSpec(&thelaunchSpec, nullptr); + if (err != noErr) { + return MacErrorMapper(err); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::IsPackage(bool* aResult) +{ + if (NS_WARN_IF(!aResult)) { + return NS_ERROR_INVALID_ARG; + } + *aResult = false; + + CFURLRef url; + nsresult rv = GetCFURL(&url); + if (NS_FAILED(rv)) { + return rv; + } + + LSItemInfoRecord info; + OSStatus status = ::LSCopyItemInfoForURL(url, kLSRequestBasicFlagsOnly, &info); + + ::CFRelease(url); + + if (status != noErr) { + return NS_ERROR_FAILURE; + } + + *aResult = !!(info.flags & kLSItemInfoIsPackage); + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::GetBundleDisplayName(nsAString& aOutBundleName) +{ + bool isPackage = false; + nsresult rv = IsPackage(&isPackage); + if (NS_FAILED(rv) || !isPackage) { + return NS_ERROR_FAILURE; + } + + nsAutoString name; + rv = GetLeafName(name); + if (NS_FAILED(rv)) { + return rv; + } + + int32_t length = name.Length(); + if (Substring(name, length - 4, length).EqualsLiteral(".app")) { + // 4 characters in ".app" + aOutBundleName = Substring(name, 0, length - 4); + } else { + aOutBundleName = name; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::GetBundleIdentifier(nsACString& aOutBundleIdentifier) +{ + nsresult rv = NS_ERROR_FAILURE; + + CFURLRef urlRef; + if (NS_SUCCEEDED(GetCFURL(&urlRef))) { + CFBundleRef bundle = ::CFBundleCreate(nullptr, urlRef); + if (bundle) { + CFStringRef bundleIdentifier = ::CFBundleGetIdentifier(bundle); + if (bundleIdentifier) { + rv = CFStringReftoUTF8(bundleIdentifier, aOutBundleIdentifier); + } + ::CFRelease(bundle); + } + ::CFRelease(urlRef); + } + + return rv; +} + +NS_IMETHODIMP +nsLocalFile::GetBundleContentsLastModifiedTime(int64_t* aLastModTime) +{ + CHECK_mPath(); + if (NS_WARN_IF(!aLastModTime)) { + return NS_ERROR_INVALID_ARG; + } + + bool isPackage = false; + nsresult rv = IsPackage(&isPackage); + if (NS_FAILED(rv) || !isPackage) { + return GetLastModifiedTime(aLastModTime); + } + + nsAutoCString infoPlistPath(mPath); + infoPlistPath.AppendLiteral("/Contents/Info.plist"); + PRFileInfo64 info; + if (PR_GetFileInfo64(infoPlistPath.get(), &info) != PR_SUCCESS) { + return GetLastModifiedTime(aLastModTime); + } + int64_t modTime = int64_t(info.modifyTime); + if (modTime == 0) { + *aLastModTime = 0; + } else { + *aLastModTime = modTime / int64_t(PR_USEC_PER_MSEC); + } + + return NS_OK; +} + +NS_IMETHODIMP nsLocalFile::InitWithFile(nsIFile* aFile) +{ + if (NS_WARN_IF(!aFile)) { + return NS_ERROR_INVALID_ARG; + } + + nsAutoCString nativePath; + nsresult rv = aFile->GetNativePath(nativePath); + if (NS_FAILED(rv)) { + return rv; + } + + return InitWithNativePath(nativePath); +} + +nsresult +NS_NewLocalFileWithFSRef(const FSRef* aFSRef, bool aFollowLinks, + nsILocalFileMac** aResult) +{ + RefPtr file = new nsLocalFile(); + + file->SetFollowLinks(aFollowLinks); + + nsresult rv = file->InitWithFSRef(aFSRef); + if (NS_FAILED(rv)) { + return rv; + } + file.forget(aResult); + return NS_OK; +} + +nsresult +NS_NewLocalFileWithCFURL(const CFURLRef aURL, bool aFollowLinks, + nsILocalFileMac** aResult) +{ + RefPtr file = new nsLocalFile(); + + file->SetFollowLinks(aFollowLinks); + + nsresult rv = file->InitWithCFURL(aURL); + if (NS_FAILED(rv)) { + return rv; + } + file.forget(aResult); + return NS_OK; +} + +#endif diff --git a/xpcom/io/nsLocalFileUnix.h b/xpcom/io/nsLocalFileUnix.h new file mode 100644 index 000000000..9a3e7d6af --- /dev/null +++ b/xpcom/io/nsLocalFileUnix.h @@ -0,0 +1,138 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * Implementation of nsIFile for ``Unixy'' systems. + */ + +#ifndef _nsLocalFileUNIX_H_ +#define _nsLocalFileUNIX_H_ + +#include +#include +#include + +#include "nscore.h" +#include "nsString.h" +#include "nsReadableUtils.h" +#include "nsIHashable.h" +#include "nsIClassInfoImpl.h" +#include "mozilla/Attributes.h" +#ifdef MOZ_WIDGET_COCOA +#include "nsILocalFileMac.h" +#endif + +/** + * we need these for statfs() + */ +#ifdef HAVE_SYS_STATVFS_H + #if defined(__osf__) && defined(__DECCXX) + extern "C" int statvfs(const char *, struct statvfs *); + #endif + #include +#endif + +#ifdef HAVE_SYS_STATFS_H + #include +#endif + +#ifdef HAVE_SYS_VFS_H + #include +#endif + +#ifdef HAVE_SYS_MOUNT_H + #include + #include +#endif + +#if defined(HAVE_STATVFS64) && (!defined(LINUX) && !defined(__osf__)) + #define STATFS statvfs64 + #define F_BSIZE f_frsize +#elif defined(HAVE_STATVFS) && (!defined(LINUX) && !defined(__osf__)) + #define STATFS statvfs + #define F_BSIZE f_frsize +#elif defined(HAVE_STATFS64) + #define STATFS statfs64 + #define F_BSIZE f_bsize +#elif defined(HAVE_STATFS) + #define STATFS statfs + #define F_BSIZE f_bsize +#endif + +// stat64 and lstat64 are deprecated on OS X. Normal stat and lstat are +// 64-bit by default on OS X 10.6+. +#if defined(HAVE_STAT64) && defined(HAVE_LSTAT64) && !defined(XP_DARWIN) + #if defined (AIX) + #if defined STAT + #undef STAT + #endif + #endif + #define STAT stat64 + #define LSTAT lstat64 + #define HAVE_STATS64 1 +#else + #define STAT stat + #define LSTAT lstat +#endif + + +class nsLocalFile final +#ifdef MOZ_WIDGET_COCOA + : public nsILocalFileMac +#else + : public nsILocalFile +#endif + , public nsIHashable +{ +public: + NS_DEFINE_STATIC_CID_ACCESSOR(NS_LOCAL_FILE_CID) + + nsLocalFile(); + + static nsresult nsLocalFileConstructor(nsISupports* aOuter, + const nsIID& aIID, + void** aInstancePtr); + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIFILE + NS_DECL_NSILOCALFILE +#ifdef MOZ_WIDGET_COCOA + NS_DECL_NSILOCALFILEMAC +#endif + NS_DECL_NSIHASHABLE + +public: + static void GlobalInit(); + static void GlobalShutdown(); + +private: + nsLocalFile(const nsLocalFile& aOther); + ~nsLocalFile() + { + } + +protected: + // This stat cache holds the *last stat* - it does not invalidate. + // Call "FillStatCache" whenever you want to stat our file. + struct STAT mCachedStat; + nsCString mPath; + + void LocateNativeLeafName(nsACString::const_iterator&, + nsACString::const_iterator&); + + nsresult CopyDirectoryTo(nsIFile* aNewParent); + nsresult CreateAllAncestors(uint32_t aPermissions); + nsresult GetNativeTargetPathName(nsIFile* aNewParent, + const nsACString& aNewName, + nsACString& aResult); + + bool FillStatCache(); + + nsresult CreateAndKeepOpen(uint32_t aType, int aFlags, + uint32_t aPermissions, PRFileDesc** aResult); +}; + +#endif /* _nsLocalFileUNIX_H_ */ diff --git a/xpcom/io/nsLocalFileWin.cpp b/xpcom/io/nsLocalFileWin.cpp new file mode 100644 index 000000000..3a7e570f5 --- /dev/null +++ b/xpcom/io/nsLocalFileWin.cpp @@ -0,0 +1,3787 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. 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/DebugOnly.h" +#include "mozilla/UniquePtrExtensions.h" +#include "mozilla/WindowsVersion.h" + +#include "nsCOMPtr.h" +#include "nsAutoPtr.h" +#include "nsMemory.h" +#include "GeckoProfiler.h" + +#include "nsLocalFile.h" +#include "nsIDirectoryEnumerator.h" +#include "nsNativeCharsetUtils.h" + +#include "nsISimpleEnumerator.h" +#include "nsIComponentManager.h" +#include "prio.h" +#include "private/pprio.h" // To get PR_ImportFile +#include "prprf.h" +#include "prmem.h" +#include "nsHashKeys.h" + +#include "nsXPIDLString.h" +#include "nsReadableUtils.h" + +#include +#include +#include +#include + +#include "shellapi.h" +#include "shlguid.h" + +#include +#include +#include +#include + +#include "nsXPIDLString.h" +#include "prproces.h" +#include "prlink.h" + +#include "mozilla/Mutex.h" +#include "SpecialSystemDirectory.h" + +#include "nsTraceRefcnt.h" +#include "nsXPCOMCIDInternal.h" +#include "nsThreadUtils.h" +#include "nsXULAppAPI.h" + +#include "nsIWindowMediator.h" +#include "mozIDOMWindow.h" +#include "nsPIDOMWindow.h" +#include "nsIWidget.h" +#include "mozilla/WidgetUtils.h" + +using namespace mozilla; + +#define CHECK_mWorkingPath() \ + PR_BEGIN_MACRO \ + if (mWorkingPath.IsEmpty()) \ + return NS_ERROR_NOT_INITIALIZED; \ + PR_END_MACRO + +// CopyFileEx only supports unbuffered I/O in Windows Vista and above +#ifndef COPY_FILE_NO_BUFFERING +#define COPY_FILE_NO_BUFFERING 0x00001000 +#endif + +#ifndef FILE_ATTRIBUTE_NOT_CONTENT_INDEXED +#define FILE_ATTRIBUTE_NOT_CONTENT_INDEXED 0x00002000 +#endif + +#ifndef DRIVE_REMOTE +#define DRIVE_REMOTE 4 +#endif + +static HWND +GetMostRecentNavigatorHWND() +{ + nsresult rv; + nsCOMPtr winMediator( + do_GetService(NS_WINDOWMEDIATOR_CONTRACTID, &rv)); + if (NS_FAILED(rv)) { + return nullptr; + } + + nsCOMPtr navWin; + rv = winMediator->GetMostRecentWindow(u"navigator:browser", + getter_AddRefs(navWin)); + if (NS_FAILED(rv) || !navWin) { + return nullptr; + } + + nsPIDOMWindowOuter* win = nsPIDOMWindowOuter::From(navWin); + nsCOMPtr widget = widget::WidgetUtils::DOMWindowToWidget(win); + if (!widget) { + return nullptr; + } + + return reinterpret_cast(widget->GetNativeData(NS_NATIVE_WINDOW)); +} + + +/** + * A runnable to dispatch back to the main thread when + * AsyncRevealOperation completes. +*/ +class AsyncLocalFileWinDone : public Runnable +{ +public: + AsyncLocalFileWinDone() : + mWorkerThread(do_GetCurrentThread()) + { + // Objects of this type must only be created on worker threads + MOZ_ASSERT(!NS_IsMainThread()); + } + + NS_IMETHOD Run() override + { + // This event shuts down the worker thread and so must be main thread. + MOZ_ASSERT(NS_IsMainThread()); + + // If we don't destroy the thread when we're done with it, it will hang + // around forever... and that is bad! + mWorkerThread->Shutdown(); + return NS_OK; + } + +private: + nsCOMPtr mWorkerThread; +}; + +/** + * A runnable to dispatch from the main thread when an async operation should + * be performed. +*/ +class AsyncRevealOperation : public Runnable +{ +public: + explicit AsyncRevealOperation(const nsAString& aResolvedPath) + : mResolvedPath(aResolvedPath) + { + } + + NS_IMETHOD Run() override + { + MOZ_ASSERT(!NS_IsMainThread(), + "AsyncRevealOperation should not be run on the main thread!"); + + bool doCoUninitialize = SUCCEEDED( + CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE)); + Reveal(); + if (doCoUninitialize) { + CoUninitialize(); + } + + // Send the result back to the main thread so that this thread can be + // cleanly shut down + nsCOMPtr resultrunnable = new AsyncLocalFileWinDone(); + NS_DispatchToMainThread(resultrunnable); + return NS_OK; + } + +private: + // Reveals the path in explorer. + nsresult Reveal() + { + DWORD attributes = GetFileAttributesW(mResolvedPath.get()); + if (INVALID_FILE_ATTRIBUTES == attributes) { + return NS_ERROR_FILE_INVALID_PATH; + } + + HRESULT hr; + if (attributes & FILE_ATTRIBUTE_DIRECTORY) { + // We have a directory so we should open the directory itself. + LPITEMIDLIST dir = ILCreateFromPathW(mResolvedPath.get()); + if (!dir) { + return NS_ERROR_FAILURE; + } + + LPCITEMIDLIST selection[] = { dir }; + UINT count = ArrayLength(selection); + + //Perform the open of the directory. + hr = SHOpenFolderAndSelectItems(dir, count, selection, 0); + CoTaskMemFree(dir); + } else { + int32_t len = mResolvedPath.Length(); + // We don't currently handle UNC long paths of the form \\?\ anywhere so + // this should be fine. + if (len > MAX_PATH) { + return NS_ERROR_FILE_INVALID_PATH; + } + WCHAR parentDirectoryPath[MAX_PATH + 1] = { 0 }; + wcsncpy(parentDirectoryPath, mResolvedPath.get(), MAX_PATH); + PathRemoveFileSpecW(parentDirectoryPath); + + // We have a file so we should open the parent directory. + LPITEMIDLIST dir = ILCreateFromPathW(parentDirectoryPath); + if (!dir) { + return NS_ERROR_FAILURE; + } + + // Set the item in the directory to select to the file we want to reveal. + LPITEMIDLIST item = ILCreateFromPathW(mResolvedPath.get()); + if (!item) { + CoTaskMemFree(dir); + return NS_ERROR_FAILURE; + } + + LPCITEMIDLIST selection[] = { item }; + UINT count = ArrayLength(selection); + + //Perform the selection of the file. + hr = SHOpenFolderAndSelectItems(dir, count, selection, 0); + + CoTaskMemFree(dir); + CoTaskMemFree(item); + } + + return SUCCEEDED(hr) ? NS_OK : NS_ERROR_FAILURE; + } + + // Stores the path to perform the operation on + nsString mResolvedPath; +}; + +class nsDriveEnumerator : public nsISimpleEnumerator +{ +public: + nsDriveEnumerator(); + NS_DECL_ISUPPORTS + NS_DECL_NSISIMPLEENUMERATOR + nsresult Init(); +private: + virtual ~nsDriveEnumerator(); + + /* mDrives stores the null-separated drive names. + * Init sets them. + * HasMoreElements checks mStartOfCurrentDrive. + * GetNext advances mStartOfCurrentDrive. + */ + nsString mDrives; + nsAString::const_iterator mStartOfCurrentDrive; + nsAString::const_iterator mEndOfDrivesString; +}; + +//---------------------------------------------------------------------------- +// short cut resolver +//---------------------------------------------------------------------------- +class ShortcutResolver +{ +public: + ShortcutResolver(); + // nonvirtual since we're not subclassed + ~ShortcutResolver(); + + nsresult Init(); + nsresult Resolve(const WCHAR* aIn, WCHAR* aOut); + nsresult SetShortcut(bool aUpdateExisting, + const WCHAR* aShortcutPath, + const WCHAR* aTargetPath, + const WCHAR* aWorkingDir, + const WCHAR* aArgs, + const WCHAR* aDescription, + const WCHAR* aIconFile, + int32_t aIconIndex); + +private: + Mutex mLock; + RefPtr mPersistFile; + RefPtr mShellLink; +}; + +ShortcutResolver::ShortcutResolver() : + mLock("ShortcutResolver.mLock") +{ + CoInitialize(nullptr); +} + +ShortcutResolver::~ShortcutResolver() +{ + CoUninitialize(); +} + +nsresult +ShortcutResolver::Init() +{ + // Get a pointer to the IPersistFile interface. + if (FAILED(CoCreateInstance(CLSID_ShellLink, + nullptr, + CLSCTX_INPROC_SERVER, + IID_IShellLinkW, + getter_AddRefs(mShellLink))) || + FAILED(mShellLink->QueryInterface(IID_IPersistFile, + getter_AddRefs(mPersistFile)))) { + mShellLink = nullptr; + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +// |out| must be an allocated buffer of size MAX_PATH +nsresult +ShortcutResolver::Resolve(const WCHAR* aIn, WCHAR* aOut) +{ + if (!mShellLink) { + return NS_ERROR_FAILURE; + } + + MutexAutoLock lock(mLock); + + if (FAILED(mPersistFile->Load(aIn, STGM_READ)) || + FAILED(mShellLink->Resolve(nullptr, SLR_NO_UI)) || + FAILED(mShellLink->GetPath(aOut, MAX_PATH, nullptr, SLGP_UNCPRIORITY))) { + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +nsresult +ShortcutResolver::SetShortcut(bool aUpdateExisting, + const WCHAR* aShortcutPath, + const WCHAR* aTargetPath, + const WCHAR* aWorkingDir, + const WCHAR* aArgs, + const WCHAR* aDescription, + const WCHAR* aIconPath, + int32_t aIconIndex) +{ + if (!mShellLink) { + return NS_ERROR_FAILURE; + } + + if (!aShortcutPath) { + return NS_ERROR_FAILURE; + } + + MutexAutoLock lock(mLock); + + if (aUpdateExisting) { + if (FAILED(mPersistFile->Load(aShortcutPath, STGM_READWRITE))) { + return NS_ERROR_FAILURE; + } + } else { + if (!aTargetPath) { + return NS_ERROR_FILE_TARGET_DOES_NOT_EXIST; + } + + // Since we reuse our IPersistFile, we have to clear out any values that + // may be left over from previous calls to SetShortcut. + if (FAILED(mShellLink->SetWorkingDirectory(L"")) || + FAILED(mShellLink->SetArguments(L"")) || + FAILED(mShellLink->SetDescription(L"")) || + FAILED(mShellLink->SetIconLocation(L"", 0))) { + return NS_ERROR_FAILURE; + } + } + + if (aTargetPath && FAILED(mShellLink->SetPath(aTargetPath))) { + return NS_ERROR_FAILURE; + } + + if (aWorkingDir && FAILED(mShellLink->SetWorkingDirectory(aWorkingDir))) { + return NS_ERROR_FAILURE; + } + + if (aArgs && FAILED(mShellLink->SetArguments(aArgs))) { + return NS_ERROR_FAILURE; + } + + if (aDescription && FAILED(mShellLink->SetDescription(aDescription))) { + return NS_ERROR_FAILURE; + } + + if (aIconPath && FAILED(mShellLink->SetIconLocation(aIconPath, aIconIndex))) { + return NS_ERROR_FAILURE; + } + + if (FAILED(mPersistFile->Save(aShortcutPath, + TRUE))) { + // Second argument indicates whether the file path specified in the + // first argument should become the "current working file" for this + // IPersistFile + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +static ShortcutResolver* gResolver = nullptr; + +static nsresult +NS_CreateShortcutResolver() +{ + gResolver = new ShortcutResolver(); + return gResolver->Init(); +} + +static void +NS_DestroyShortcutResolver() +{ + delete gResolver; + gResolver = nullptr; +} + + +//----------------------------------------------------------------------------- +// static helper functions +//----------------------------------------------------------------------------- + +// certainly not all the error that can be +// encountered, but many of them common ones +static nsresult +ConvertWinError(DWORD aWinErr) +{ + nsresult rv; + + switch (aWinErr) { + case ERROR_FILE_NOT_FOUND: + case ERROR_PATH_NOT_FOUND: + case ERROR_INVALID_DRIVE: + case ERROR_NOT_READY: + rv = NS_ERROR_FILE_NOT_FOUND; + break; + case ERROR_ACCESS_DENIED: + case ERROR_NOT_SAME_DEVICE: + rv = NS_ERROR_FILE_ACCESS_DENIED; + break; + case ERROR_SHARING_VIOLATION: // CreateFile without sharing flags + case ERROR_LOCK_VIOLATION: // LockFile, LockFileEx + rv = NS_ERROR_FILE_IS_LOCKED; + break; + case ERROR_NOT_ENOUGH_MEMORY: + case ERROR_INVALID_BLOCK: + case ERROR_INVALID_HANDLE: + case ERROR_ARENA_TRASHED: + rv = NS_ERROR_OUT_OF_MEMORY; + break; + case ERROR_CURRENT_DIRECTORY: + rv = NS_ERROR_FILE_DIR_NOT_EMPTY; + break; + case ERROR_WRITE_PROTECT: + rv = NS_ERROR_FILE_READ_ONLY; + break; + case ERROR_HANDLE_DISK_FULL: + rv = NS_ERROR_FILE_TOO_BIG; + break; + case ERROR_FILE_EXISTS: + case ERROR_ALREADY_EXISTS: + case ERROR_CANNOT_MAKE: + rv = NS_ERROR_FILE_ALREADY_EXISTS; + break; + case ERROR_FILENAME_EXCED_RANGE: + rv = NS_ERROR_FILE_NAME_TOO_LONG; + break; + case ERROR_DIRECTORY: + rv = NS_ERROR_FILE_NOT_DIRECTORY; + break; + case 0: + rv = NS_OK; + break; + default: + rv = NS_ERROR_FAILURE; + break; + } + return rv; +} + +// as suggested in the MSDN documentation on SetFilePointer +static __int64 +MyFileSeek64(HANDLE aHandle, __int64 aDistance, DWORD aMoveMethod) +{ + LARGE_INTEGER li; + + li.QuadPart = aDistance; + li.LowPart = SetFilePointer(aHandle, li.LowPart, &li.HighPart, aMoveMethod); + if (li.LowPart == INVALID_SET_FILE_POINTER && GetLastError() != NO_ERROR) { + li.QuadPart = -1; + } + + return li.QuadPart; +} + +static bool +IsShortcutPath(const nsAString& aPath) +{ + // Under Windows, the shortcuts are just files with a ".lnk" extension. + // Note also that we don't resolve links in the middle of paths. + // i.e. "c:\foo.lnk\bar.txt" is invalid. + MOZ_ASSERT(!aPath.IsEmpty(), "don't pass an empty string"); + int32_t len = aPath.Length(); + return len >= 4 && (StringTail(aPath, 4).LowerCaseEqualsASCII(".lnk")); +} + +//----------------------------------------------------------------------------- +// We need the following three definitions to make |OpenFile| convert a file +// handle to an NSPR file descriptor correctly when |O_APPEND| flag is +// specified. It is defined in a private header of NSPR (primpl.h) we can't +// include. As a temporary workaround until we decide how to extend +// |PR_ImportFile|, we define it here. Currently, |_PR_HAVE_PEEK_BUFFER| +// and |PR_STRICT_ADDR_LEN| are not defined for the 'w95'-dependent portion +// of NSPR so that fields of |PRFilePrivate| #ifdef'd by them are not copied. +// Similarly, |_MDFileDesc| is taken from nsprpub/pr/include/md/_win95.h. +// In an unlikely case we switch to 'NT'-dependent NSPR AND this temporary +// workaround last beyond the switch, |PRFilePrivate| and |_MDFileDesc| +// need to be changed to match the definitions for WinNT. +//----------------------------------------------------------------------------- +typedef enum +{ + _PR_TRI_TRUE = 1, + _PR_TRI_FALSE = 0, + _PR_TRI_UNKNOWN = -1 +} _PRTriStateBool; + +struct _MDFileDesc +{ + PROsfd osfd; +}; + +struct PRFilePrivate +{ + int32_t state; + bool nonblocking; + _PRTriStateBool inheritable; + PRFileDesc* next; + int lockCount; /* 0: not locked + * -1: a native lockfile call is in progress + * > 0: # times the file is locked */ + bool appendMode; + _MDFileDesc md; +}; + +//----------------------------------------------------------------------------- +// Six static methods defined below (OpenFile, FileTimeToPRTime, GetFileInfo, +// OpenDir, CloseDir, ReadDir) should go away once the corresponding +// UTF-16 APIs are implemented on all the supported platforms (or at least +// Windows 9x/ME) in NSPR. Currently, they're only implemented on +// Windows NT4 or later. (bug 330665) +//----------------------------------------------------------------------------- + +// copied from nsprpub/pr/src/{io/prfile.c | md/windows/w95io.c} : +// PR_Open and _PR_MD_OPEN +nsresult +OpenFile(const nsAFlatString& aName, + int aOsflags, + int aMode, + bool aShareDelete, + PRFileDesc** aFd) +{ + int32_t access = 0; + + int32_t shareMode = FILE_SHARE_READ | FILE_SHARE_WRITE; + int32_t disposition = 0; + int32_t attributes = 0; + + if (aShareDelete) { + shareMode |= FILE_SHARE_DELETE; + } + + if (aOsflags & PR_SYNC) { + attributes = FILE_FLAG_WRITE_THROUGH; + } + if (aOsflags & PR_RDONLY || aOsflags & PR_RDWR) { + access |= GENERIC_READ; + } + if (aOsflags & PR_WRONLY || aOsflags & PR_RDWR) { + access |= GENERIC_WRITE; + } + + if (aOsflags & PR_CREATE_FILE && aOsflags & PR_EXCL) { + disposition = CREATE_NEW; + } else if (aOsflags & PR_CREATE_FILE) { + if (aOsflags & PR_TRUNCATE) { + disposition = CREATE_ALWAYS; + } else { + disposition = OPEN_ALWAYS; + } + } else { + if (aOsflags & PR_TRUNCATE) { + disposition = TRUNCATE_EXISTING; + } else { + disposition = OPEN_EXISTING; + } + } + + if (aOsflags & nsIFile::DELETE_ON_CLOSE) { + attributes |= FILE_FLAG_DELETE_ON_CLOSE; + } + + if (aOsflags & nsIFile::OS_READAHEAD) { + attributes |= FILE_FLAG_SEQUENTIAL_SCAN; + } + + // If no write permissions are requested, and if we are possibly creating + // the file, then set the new file as read only. + // The flag has no effect if we happen to open the file. + if (!(aMode & (PR_IWUSR | PR_IWGRP | PR_IWOTH)) && + disposition != OPEN_EXISTING) { + attributes |= FILE_ATTRIBUTE_READONLY; + } + + HANDLE file = ::CreateFileW(aName.get(), access, shareMode, + nullptr, disposition, attributes, nullptr); + + if (file == INVALID_HANDLE_VALUE) { + *aFd = nullptr; + return ConvertWinError(GetLastError()); + } + + *aFd = PR_ImportFile((PROsfd) file); + if (*aFd) { + // On Windows, _PR_HAVE_O_APPEND is not defined so that we have to + // add it manually. (see |PR_Open| in nsprpub/pr/src/io/prfile.c) + (*aFd)->secret->appendMode = (PR_APPEND & aOsflags) ? true : false; + return NS_OK; + } + + nsresult rv = NS_ErrorAccordingToNSPR(); + + CloseHandle(file); + + return rv; +} + +// copied from nsprpub/pr/src/{io/prfile.c | md/windows/w95io.c} : +// PR_FileTimeToPRTime and _PR_FileTimeToPRTime +static void +FileTimeToPRTime(const FILETIME* aFiletime, PRTime* aPrtm) +{ +#ifdef __GNUC__ + const PRTime _pr_filetime_offset = 116444736000000000LL; +#else + const PRTime _pr_filetime_offset = 116444736000000000i64; +#endif + + MOZ_ASSERT(sizeof(FILETIME) == sizeof(PRTime)); + ::CopyMemory(aPrtm, aFiletime, sizeof(PRTime)); +#ifdef __GNUC__ + *aPrtm = (*aPrtm - _pr_filetime_offset) / 10LL; +#else + *aPrtm = (*aPrtm - _pr_filetime_offset) / 10i64; +#endif +} + +// copied from nsprpub/pr/src/{io/prfile.c | md/windows/w95io.c} with some +// changes : PR_GetFileInfo64, _PR_MD_GETFILEINFO64 +static nsresult +GetFileInfo(const nsAFlatString& aName, PRFileInfo64* aInfo) +{ + WIN32_FILE_ATTRIBUTE_DATA fileData; + + if (aName.IsEmpty() || aName.FindCharInSet(u"?*") != kNotFound) { + return NS_ERROR_INVALID_ARG; + } + + if (!::GetFileAttributesExW(aName.get(), GetFileExInfoStandard, &fileData)) { + return ConvertWinError(GetLastError()); + } + + if (fileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { + aInfo->type = PR_FILE_DIRECTORY; + } else { + aInfo->type = PR_FILE_FILE; + } + + aInfo->size = fileData.nFileSizeHigh; + aInfo->size = (aInfo->size << 32) + fileData.nFileSizeLow; + + FileTimeToPRTime(&fileData.ftLastWriteTime, &aInfo->modifyTime); + + if (0 == fileData.ftCreationTime.dwLowDateTime && + 0 == fileData.ftCreationTime.dwHighDateTime) { + aInfo->creationTime = aInfo->modifyTime; + } else { + FileTimeToPRTime(&fileData.ftCreationTime, &aInfo->creationTime); + } + + return NS_OK; +} + +struct nsDir +{ + HANDLE handle; + WIN32_FIND_DATAW data; + bool firstEntry; +}; + +static nsresult +OpenDir(const nsAFlatString& aName, nsDir** aDir) +{ + if (NS_WARN_IF(!aDir)) { + return NS_ERROR_INVALID_ARG; + } + + *aDir = nullptr; + if (aName.Length() + 3 >= MAX_PATH) { + return NS_ERROR_FILE_NAME_TOO_LONG; + } + + nsDir* d = new nsDir(); + nsAutoString filename(aName); + + // If |aName| ends in a slash or backslash, do not append another backslash. + if (filename.Last() == L'/' || filename.Last() == L'\\') { + filename.Append('*'); + } else { + filename.AppendLiteral("\\*"); + } + + filename.ReplaceChar(L'/', L'\\'); + + // FindFirstFileW Will have a last error of ERROR_DIRECTORY if + // \* is passed in. If \* is passed in then + // ERROR_PATH_NOT_FOUND will be the last error. + d->handle = ::FindFirstFileW(filename.get(), &(d->data)); + + if (d->handle == INVALID_HANDLE_VALUE) { + delete d; + return ConvertWinError(GetLastError()); + } + d->firstEntry = true; + + *aDir = d; + return NS_OK; +} + +static nsresult +ReadDir(nsDir* aDir, PRDirFlags aFlags, nsString& aName) +{ + aName.Truncate(); + if (NS_WARN_IF(!aDir)) { + return NS_ERROR_INVALID_ARG; + } + + while (1) { + BOOL rv; + if (aDir->firstEntry) { + aDir->firstEntry = false; + rv = 1; + } else { + rv = ::FindNextFileW(aDir->handle, &(aDir->data)); + } + + if (rv == 0) { + break; + } + + const wchar_t* fileName; + fileName = (aDir)->data.cFileName; + + if ((aFlags & PR_SKIP_DOT) && + (fileName[0] == L'.') && (fileName[1] == L'\0')) { + continue; + } + if ((aFlags & PR_SKIP_DOT_DOT) && + (fileName[0] == L'.') && (fileName[1] == L'.') && + (fileName[2] == L'\0')) { + continue; + } + + DWORD attrib = aDir->data.dwFileAttributes; + if ((aFlags & PR_SKIP_HIDDEN) && (attrib & FILE_ATTRIBUTE_HIDDEN)) { + continue; + } + + aName = fileName; + return NS_OK; + } + + DWORD err = GetLastError(); + return err == ERROR_NO_MORE_FILES ? NS_OK : ConvertWinError(err); +} + +static nsresult +CloseDir(nsDir*& aDir) +{ + if (NS_WARN_IF(!aDir)) { + return NS_ERROR_INVALID_ARG; + } + + BOOL isOk = FindClose(aDir->handle); + delete aDir; + aDir = nullptr; + return isOk ? NS_OK : ConvertWinError(GetLastError()); +} + +//----------------------------------------------------------------------------- +// nsDirEnumerator +//----------------------------------------------------------------------------- + +class nsDirEnumerator final + : public nsISimpleEnumerator + , public nsIDirectoryEnumerator +{ +private: + ~nsDirEnumerator() + { + Close(); + } + +public: + NS_DECL_ISUPPORTS + + nsDirEnumerator() : mDir(nullptr) + { + } + + nsresult Init(nsIFile* aParent) + { + nsAutoString filepath; + aParent->GetTarget(filepath); + + if (filepath.IsEmpty()) { + aParent->GetPath(filepath); + } + + if (filepath.IsEmpty()) { + return NS_ERROR_UNEXPECTED; + } + + // IsDirectory is not needed here because OpenDir will return + // NS_ERROR_FILE_NOT_DIRECTORY if the passed in path is a file. + nsresult rv = OpenDir(filepath, &mDir); + if (NS_FAILED(rv)) { + return rv; + } + + mParent = aParent; + return NS_OK; + } + + NS_IMETHOD HasMoreElements(bool* aResult) + { + nsresult rv; + if (!mNext && mDir) { + nsString name; + rv = ReadDir(mDir, PR_SKIP_BOTH, name); + if (NS_FAILED(rv)) { + return rv; + } + if (name.IsEmpty()) { + // end of dir entries + if (NS_FAILED(CloseDir(mDir))) { + return NS_ERROR_FAILURE; + } + + *aResult = false; + return NS_OK; + } + + nsCOMPtr file; + rv = mParent->Clone(getter_AddRefs(file)); + if (NS_FAILED(rv)) { + return rv; + } + + rv = file->Append(name); + if (NS_FAILED(rv)) { + return rv; + } + + mNext = do_QueryInterface(file); + } + *aResult = mNext != nullptr; + if (!*aResult) { + Close(); + } + return NS_OK; + } + + NS_IMETHOD GetNext(nsISupports** aResult) + { + nsresult rv; + bool hasMore; + rv = HasMoreElements(&hasMore); + if (NS_FAILED(rv)) { + return rv; + } + + *aResult = mNext; // might return nullptr + NS_IF_ADDREF(*aResult); + + mNext = nullptr; + return NS_OK; + } + + NS_IMETHOD GetNextFile(nsIFile** aResult) + { + *aResult = nullptr; + bool hasMore = false; + nsresult rv = HasMoreElements(&hasMore); + if (NS_FAILED(rv) || !hasMore) { + return rv; + } + *aResult = mNext; + NS_IF_ADDREF(*aResult); + mNext = nullptr; + return NS_OK; + } + + NS_IMETHOD Close() + { + if (mDir) { + nsresult rv = CloseDir(mDir); + NS_ASSERTION(NS_SUCCEEDED(rv), "close failed"); + if (NS_FAILED(rv)) { + return NS_ERROR_FAILURE; + } + } + return NS_OK; + } + +protected: + nsDir* mDir; + nsCOMPtr mParent; + nsCOMPtr mNext; +}; + +NS_IMPL_ISUPPORTS(nsDirEnumerator, nsISimpleEnumerator, nsIDirectoryEnumerator) + + +//----------------------------------------------------------------------------- +// nsLocalFile +//----------------------------------------------------------------------------- + +nsLocalFile::nsLocalFile() + : mDirty(true) + , mResolveDirty(true) + , mFollowSymlinks(false) +{ +} + +nsresult +nsLocalFile::nsLocalFileConstructor(nsISupports* aOuter, const nsIID& aIID, + void** aInstancePtr) +{ + if (NS_WARN_IF(!aInstancePtr)) { + return NS_ERROR_INVALID_ARG; + } + if (NS_WARN_IF(aOuter)) { + return NS_ERROR_NO_AGGREGATION; + } + + nsLocalFile* inst = new nsLocalFile(); + nsresult rv = inst->QueryInterface(aIID, aInstancePtr); + if (NS_FAILED(rv)) { + delete inst; + return rv; + } + return NS_OK; +} + + +//----------------------------------------------------------------------------- +// nsLocalFile::nsISupports +//----------------------------------------------------------------------------- + +NS_IMPL_ISUPPORTS(nsLocalFile, + nsILocalFile, + nsIFile, + nsILocalFileWin, + nsIHashable) + + +//----------------------------------------------------------------------------- +// nsLocalFile +//----------------------------------------------------------------------------- + +nsLocalFile::nsLocalFile(const nsLocalFile& aOther) + : mDirty(true) + , mResolveDirty(true) + , mFollowSymlinks(aOther.mFollowSymlinks) + , mWorkingPath(aOther.mWorkingPath) +{ +} + +// Resolve the shortcut file from mWorkingPath and write the path +// it points to into mResolvedPath. +nsresult +nsLocalFile::ResolveShortcut() +{ + // we can't do anything without the resolver + if (!gResolver) { + return NS_ERROR_FAILURE; + } + + mResolvedPath.SetLength(MAX_PATH); + if (mResolvedPath.Length() != MAX_PATH) { + return NS_ERROR_OUT_OF_MEMORY; + } + + wchar_t* resolvedPath = wwc(mResolvedPath.BeginWriting()); + + // resolve this shortcut + nsresult rv = gResolver->Resolve(mWorkingPath.get(), resolvedPath); + + size_t len = NS_FAILED(rv) ? 0 : wcslen(resolvedPath); + mResolvedPath.SetLength(len); + + return rv; +} + +// Resolve any shortcuts and stat the resolved path. After a successful return +// the path is guaranteed valid and the members of mFileInfo64 can be used. +nsresult +nsLocalFile::ResolveAndStat() +{ + // if we aren't dirty then we are already done + if (!mDirty) { + return NS_OK; + } + + PROFILER_LABEL_FUNC(js::ProfileEntry::Category::OTHER); + // we can't resolve/stat anything that isn't a valid NSPR addressable path + if (mWorkingPath.IsEmpty()) { + return NS_ERROR_FILE_INVALID_PATH; + } + + // this is usually correct + mResolvedPath.Assign(mWorkingPath); + + // slutty hack designed to work around bug 134796 until it is fixed + nsAutoString nsprPath(mWorkingPath.get()); + if (mWorkingPath.Length() == 2 && mWorkingPath.CharAt(1) == L':') { + nsprPath.Append('\\'); + } + + // first we will see if the working path exists. If it doesn't then + // there is nothing more that can be done + nsresult rv = GetFileInfo(nsprPath, &mFileInfo64); + if (NS_FAILED(rv)) { + return rv; + } + + // if this isn't a shortcut file or we aren't following symlinks then we're done + if (!mFollowSymlinks || + mFileInfo64.type != PR_FILE_FILE || + !IsShortcutPath(mWorkingPath)) { + mDirty = false; + mResolveDirty = false; + return NS_OK; + } + + // we need to resolve this shortcut to what it points to, this will + // set mResolvedPath. Even if it fails we need to have the resolved + // path equal to working path for those functions that always use + // the resolved path. + rv = ResolveShortcut(); + if (NS_FAILED(rv)) { + mResolvedPath.Assign(mWorkingPath); + return rv; + } + mResolveDirty = false; + + // get the details of the resolved path + rv = GetFileInfo(mResolvedPath, &mFileInfo64); + if (NS_FAILED(rv)) { + return rv; + } + + mDirty = false; + return NS_OK; +} + +/** + * Fills the mResolvedPath member variable with the file or symlink target + * if follow symlinks is on. This is a copy of the Resolve parts from + * ResolveAndStat. ResolveAndStat is much slower though because of the stat. + * + * @return NS_OK on success. +*/ +nsresult +nsLocalFile::Resolve() +{ + // if we aren't dirty then we are already done + if (!mResolveDirty) { + return NS_OK; + } + + // we can't resolve/stat anything that isn't a valid NSPR addressable path + if (mWorkingPath.IsEmpty()) { + return NS_ERROR_FILE_INVALID_PATH; + } + + // this is usually correct + mResolvedPath.Assign(mWorkingPath); + + // if this isn't a shortcut file or we aren't following symlinks then + // we're done. + if (!mFollowSymlinks || + !IsShortcutPath(mWorkingPath)) { + mResolveDirty = false; + return NS_OK; + } + + // we need to resolve this shortcut to what it points to, this will + // set mResolvedPath. Even if it fails we need to have the resolved + // path equal to working path for those functions that always use + // the resolved path. + nsresult rv = ResolveShortcut(); + if (NS_FAILED(rv)) { + mResolvedPath.Assign(mWorkingPath); + return rv; + } + + mResolveDirty = false; + return NS_OK; +} + +//----------------------------------------------------------------------------- +// nsLocalFile::nsIFile,nsILocalFile +//----------------------------------------------------------------------------- + +NS_IMETHODIMP +nsLocalFile::Clone(nsIFile** aFile) +{ + // Just copy-construct ourselves + RefPtr file = new nsLocalFile(*this); + file.forget(aFile); + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::InitWithFile(nsIFile* aFile) +{ + if (NS_WARN_IF(!aFile)) { + return NS_ERROR_INVALID_ARG; + } + + nsAutoString path; + aFile->GetPath(path); + if (path.IsEmpty()) { + return NS_ERROR_INVALID_ARG; + } + return InitWithPath(path); +} + +NS_IMETHODIMP +nsLocalFile::InitWithPath(const nsAString& aFilePath) +{ + MakeDirty(); + + nsAString::const_iterator begin, end; + aFilePath.BeginReading(begin); + aFilePath.EndReading(end); + + // input string must not be empty + if (begin == end) { + return NS_ERROR_FAILURE; + } + + char16_t firstChar = *begin; + char16_t secondChar = *(++begin); + + // just do a sanity check. if it has any forward slashes, it is not a Native path + // on windows. Also, it must have a colon at after the first char. + if (FindCharInReadable(L'/', begin, end)) { + return NS_ERROR_FILE_UNRECOGNIZED_PATH; + } + + if (secondChar != L':' && (secondChar != L'\\' || firstChar != L'\\')) { + return NS_ERROR_FILE_UNRECOGNIZED_PATH; + } + + if (secondChar == L':') { + // Make sure we have a valid drive, later code assumes the drive letter + // is a single char a-z or A-Z. + if (PathGetDriveNumberW(aFilePath.Data()) == -1) { + return NS_ERROR_FILE_UNRECOGNIZED_PATH; + } + } + + mWorkingPath = aFilePath; + // kill any trailing '\' + if (mWorkingPath.Last() == L'\\') { + mWorkingPath.Truncate(mWorkingPath.Length() - 1); + } + + return NS_OK; + +} + +// Strip a handler command string of its quotes and parameters. +static void +CleanupHandlerPath(nsString& aPath) +{ + // Example command strings passed into this routine: + + // 1) C:\Program Files\Company\some.exe -foo -bar + // 2) C:\Program Files\Company\some.dll + // 3) C:\Windows\some.dll,-foo -bar + // 4) C:\Windows\some.cpl,-foo -bar + + int32_t lastCommaPos = aPath.RFindChar(','); + if (lastCommaPos != kNotFound) + aPath.Truncate(lastCommaPos); + + aPath.Append(' '); + + // case insensitive + uint32_t index = aPath.Find(".exe ", true); + if (index == kNotFound) + index = aPath.Find(".dll ", true); + if (index == kNotFound) + index = aPath.Find(".cpl ", true); + + if (index != kNotFound) + aPath.Truncate(index + 4); + aPath.Trim(" ", true, true); +} + +// Strip the windows host process bootstrap executable rundll32.exe +// from a handler's command string if it exists. +static void +StripRundll32(nsString& aCommandString) +{ + // Example rundll formats: + // C:\Windows\System32\rundll32.exe "path to dll" + // rundll32.exe "path to dll" + // C:\Windows\System32\rundll32.exe "path to dll", var var + // rundll32.exe "path to dll", var var + + NS_NAMED_LITERAL_STRING(rundllSegment, "rundll32.exe "); + NS_NAMED_LITERAL_STRING(rundllSegmentShort, "rundll32 "); + + // case insensitive + int32_t strLen = rundllSegment.Length(); + int32_t index = aCommandString.Find(rundllSegment, true); + if (index == kNotFound) { + strLen = rundllSegmentShort.Length(); + index = aCommandString.Find(rundllSegmentShort, true); + } + + if (index != kNotFound) { + uint32_t rundllSegmentLength = index + strLen; + aCommandString.Cut(0, rundllSegmentLength); + } +} + +// Returns the fully qualified path to an application handler based on +// a parameterized command string. Note this routine should not be used +// to launch the associated application as it strips parameters and +// rundll.exe from the string. Designed for retrieving display information +// on a particular handler. +/* static */ bool +nsLocalFile::CleanupCmdHandlerPath(nsAString& aCommandHandler) +{ + nsAutoString handlerCommand(aCommandHandler); + + // Straight command path: + // + // %SystemRoot%\system32\NOTEPAD.EXE var + // "C:\Program Files\iTunes\iTunes.exe" var var + // C:\Program Files\iTunes\iTunes.exe var var + // + // Example rundll handlers: + // + // rundll32.exe "%ProgramFiles%\Win...ery\PhotoViewer.dll", var var + // rundll32.exe "%ProgramFiles%\Windows Photo Gallery\PhotoViewer.dll" + // C:\Windows\System32\rundll32.exe "path to dll", var var + // %SystemRoot%\System32\rundll32.exe "%ProgramFiles%\Win...ery\Photo + // Viewer.dll", var var + + // Expand environment variables so we have full path strings. + uint32_t bufLength = ::ExpandEnvironmentStringsW(handlerCommand.get(), + L"", 0); + if (bufLength == 0) // Error + return false; + + auto destination = mozilla::MakeUniqueFallible(bufLength); + if (!destination) + return false; + if (!::ExpandEnvironmentStringsW(handlerCommand.get(), destination.get(), + bufLength)) + return false; + + handlerCommand.Assign(destination.get()); + + // Remove quotes around paths + handlerCommand.StripChars("\""); + + // Strip windows host process bootstrap so we can get to the actual + // handler. + StripRundll32(handlerCommand); + + // Trim any command parameters so that we have a native path we can + // initialize a local file with. + CleanupHandlerPath(handlerCommand); + + aCommandHandler.Assign(handlerCommand); + return true; +} + + +NS_IMETHODIMP +nsLocalFile::InitWithCommandLine(const nsAString& aCommandLine) +{ + nsAutoString commandLine(aCommandLine); + if (!CleanupCmdHandlerPath(commandLine)) { + return NS_ERROR_FILE_UNRECOGNIZED_PATH; + } + return InitWithPath(commandLine); +} + +NS_IMETHODIMP +nsLocalFile::OpenNSPRFileDesc(int32_t aFlags, int32_t aMode, + PRFileDesc** aResult) +{ + nsresult rv = OpenNSPRFileDescMaybeShareDelete(aFlags, aMode, false, aResult); + if (NS_FAILED(rv)) { + return rv; + } + + return NS_OK; +} + + +NS_IMETHODIMP +nsLocalFile::OpenANSIFileDesc(const char* aMode, FILE** aResult) +{ + nsresult rv = ResolveAndStat(); + if (NS_FAILED(rv) && rv != NS_ERROR_FILE_NOT_FOUND) { + return rv; + } + + *aResult = _wfopen(mResolvedPath.get(), NS_ConvertASCIItoUTF16(aMode).get()); + if (*aResult) { + return NS_OK; + } + + return NS_ERROR_FAILURE; +} + + + +NS_IMETHODIMP +nsLocalFile::Create(uint32_t aType, uint32_t aAttributes) +{ + if (aType != NORMAL_FILE_TYPE && aType != DIRECTORY_TYPE) { + return NS_ERROR_FILE_UNKNOWN_TYPE; + } + + nsresult rv = ResolveAndStat(); + if (NS_FAILED(rv) && rv != NS_ERROR_FILE_NOT_FOUND) { + return rv; + } + + // create directories to target + // + // A given local file can be either one of these forms: + // + // - normal: X:\some\path\on\this\drive + // ^--- start here + // + // - UNC path: \\machine\volume\some\path\on\this\drive + // ^--- start here + // + // Skip the first 'X:\' for the first form, and skip the first full + // '\\machine\volume\' segment for the second form. + + wchar_t* path = wwc(mResolvedPath.BeginWriting()); + + if (path[0] == L'\\' && path[1] == L'\\') { + // dealing with a UNC path here; skip past '\\machine\' + path = wcschr(path + 2, L'\\'); + if (!path) { + return NS_ERROR_FILE_INVALID_PATH; + } + ++path; + } + + // search for first slash after the drive (or volume) name + wchar_t* slash = wcschr(path, L'\\'); + + nsresult directoryCreateError = NS_OK; + if (slash) { + // skip the first '\\' + ++slash; + slash = wcschr(slash, L'\\'); + + while (slash) { + *slash = L'\0'; + + if (!::CreateDirectoryW(mResolvedPath.get(), nullptr)) { + rv = ConvertWinError(GetLastError()); + if (NS_ERROR_FILE_NOT_FOUND == rv && + NS_ERROR_FILE_ACCESS_DENIED == directoryCreateError) { + // If a previous CreateDirectory failed due to access, return that. + return NS_ERROR_FILE_ACCESS_DENIED; + } + // perhaps the base path already exists, or perhaps we don't have + // permissions to create the directory. NOTE: access denied could + // occur on a parent directory even though it exists. + else if (rv != NS_ERROR_FILE_ALREADY_EXISTS && + rv != NS_ERROR_FILE_ACCESS_DENIED) { + return rv; + } + + directoryCreateError = rv; + } + *slash = L'\\'; + ++slash; + slash = wcschr(slash, L'\\'); + } + } + + if (aType == NORMAL_FILE_TYPE) { + PRFileDesc* file; + rv = OpenFile(mResolvedPath, + PR_RDONLY | PR_CREATE_FILE | PR_APPEND | PR_EXCL, + aAttributes, false, &file); + if (file) { + PR_Close(file); + } + + if (rv == NS_ERROR_FILE_ACCESS_DENIED) { + // need to return already-exists for directories (bug 452217) + bool isdir; + if (NS_SUCCEEDED(IsDirectory(&isdir)) && isdir) { + rv = NS_ERROR_FILE_ALREADY_EXISTS; + } + } else if (NS_ERROR_FILE_NOT_FOUND == rv && + NS_ERROR_FILE_ACCESS_DENIED == directoryCreateError) { + // If a previous CreateDirectory failed due to access, return that. + return NS_ERROR_FILE_ACCESS_DENIED; + } + return rv; + } + + if (aType == DIRECTORY_TYPE) { + if (!::CreateDirectoryW(mResolvedPath.get(), nullptr)) { + rv = ConvertWinError(GetLastError()); + if (NS_ERROR_FILE_NOT_FOUND == rv && + NS_ERROR_FILE_ACCESS_DENIED == directoryCreateError) { + // If a previous CreateDirectory failed due to access, return that. + return NS_ERROR_FILE_ACCESS_DENIED; + } + return rv; + } + return NS_OK; + } + + return NS_ERROR_FILE_UNKNOWN_TYPE; +} + + +NS_IMETHODIMP +nsLocalFile::Append(const nsAString& aNode) +{ + // append this path, multiple components are not permitted + return AppendInternal(PromiseFlatString(aNode), false); +} + +NS_IMETHODIMP +nsLocalFile::AppendRelativePath(const nsAString& aNode) +{ + // append this path, multiple components are permitted + return AppendInternal(PromiseFlatString(aNode), true); +} + + +nsresult +nsLocalFile::AppendInternal(const nsAFlatString& aNode, + bool aMultipleComponents) +{ + if (aNode.IsEmpty()) { + return NS_OK; + } + + // check the relative path for validity + if (aNode.First() == L'\\' || // can't start with an '\' + aNode.Contains(L'/') || // can't contain / + aNode.EqualsASCII("..")) { // can't be .. + return NS_ERROR_FILE_UNRECOGNIZED_PATH; + } + + if (aMultipleComponents) { + // can't contain .. as a path component. Ensure that the valid components + // "foo..foo", "..foo", and "foo.." are not falsely detected, + // but the invalid paths "..\", "foo\..", "foo\..\foo", + // "..\foo", etc are. + NS_NAMED_LITERAL_STRING(doubleDot, "\\.."); + nsAString::const_iterator start, end, offset; + aNode.BeginReading(start); + aNode.EndReading(end); + offset = end; + while (FindInReadable(doubleDot, start, offset)) { + if (offset == end || *offset == L'\\') { + return NS_ERROR_FILE_UNRECOGNIZED_PATH; + } + start = offset; + offset = end; + } + + // catches the remaining cases of prefixes + if (StringBeginsWith(aNode, NS_LITERAL_STRING("..\\"))) { + return NS_ERROR_FILE_UNRECOGNIZED_PATH; + } + } + // single components can't contain '\' + else if (aNode.Contains(L'\\')) { + return NS_ERROR_FILE_UNRECOGNIZED_PATH; + } + + MakeDirty(); + + mWorkingPath.Append('\\'); + mWorkingPath.Append(aNode); + + return NS_OK; +} + +nsresult +nsLocalFile::OpenNSPRFileDescMaybeShareDelete(int32_t aFlags, + int32_t aMode, + bool aShareDelete, + PRFileDesc** aResult) +{ + nsresult rv = Resolve(); + if (NS_FAILED(rv)) { + return rv; + } + + return OpenFile(mResolvedPath, aFlags, aMode, aShareDelete, aResult); +} + +#define TOUPPER(u) (((u) >= L'a' && (u) <= L'z') ? \ + (u) - (L'a' - L'A') : (u)) + +NS_IMETHODIMP +nsLocalFile::Normalize() +{ + // XXX See bug 187957 comment 18 for possible problems with this implementation. + + if (mWorkingPath.IsEmpty()) { + return NS_OK; + } + + nsAutoString path(mWorkingPath); + + // find the index of the root backslash for the path. Everything before + // this is considered fully normalized and cannot be ascended beyond + // using ".." For a local drive this is the first slash (e.g. "c:\"). + // For a UNC path it is the slash following the share name + // (e.g. "\\server\share\"). + int32_t rootIdx = 2; // default to local drive + if (path.First() == L'\\') { // if a share then calculate the rootIdx + rootIdx = path.FindChar(L'\\', 2); // skip \\ in front of the server + if (rootIdx == kNotFound) { + return NS_OK; // already normalized + } + rootIdx = path.FindChar(L'\\', rootIdx + 1); + if (rootIdx == kNotFound) { + return NS_OK; // already normalized + } + } else if (path.CharAt(rootIdx) != L'\\') { + // The path has been specified relative to the current working directory + // for that drive. To normalize it, the current working directory for + // that drive needs to be inserted before the supplied relative path + // which will provide an absolute path (and the rootIdx will still be 2). + WCHAR cwd[MAX_PATH]; + WCHAR* pcwd = cwd; + int drive = TOUPPER(path.First()) - 'A' + 1; + /* We need to worry about IPH, for details read bug 419326. + * _getdrives - http://msdn2.microsoft.com/en-us/library/xdhk0xd2.aspx + * uses a bitmask, bit 0 is 'a:' + * _chdrive - http://msdn2.microsoft.com/en-us/library/0d1409hb.aspx + * _getdcwd - http://msdn2.microsoft.com/en-us/library/7t2zk3s4.aspx + * take an int, 1 is 'a:'. + * + * Because of this, we need to do some math. Subtract 1 to convert from + * _chdrive/_getdcwd format to _getdrives drive numbering. + * Shift left x bits to convert from integer indexing to bitfield indexing. + * And of course, we need to find out if the drive is in the bitmask. + * + * If we're really unlucky, we can still lose, but only if the user + * manages to eject the drive between our call to _getdrives() and + * our *calls* to _wgetdcwd. + */ + if (!((1 << (drive - 1)) & _getdrives())) { + return NS_ERROR_FILE_INVALID_PATH; + } + if (!_wgetdcwd(drive, pcwd, MAX_PATH)) { + pcwd = _wgetdcwd(drive, 0, 0); + } + if (!pcwd) { + return NS_ERROR_OUT_OF_MEMORY; + } + nsAutoString currentDir(pcwd); + if (pcwd != cwd) { + free(pcwd); + } + + if (currentDir.Last() == '\\') { + path.Replace(0, 2, currentDir); + } else { + path.Replace(0, 2, currentDir + NS_LITERAL_STRING("\\")); + } + } + NS_POSTCONDITION(0 < rootIdx && rootIdx < (int32_t)path.Length(), "rootIdx is invalid"); + NS_POSTCONDITION(path.CharAt(rootIdx) == '\\', "rootIdx is invalid"); + + // if there is nothing following the root path then it is already normalized + if (rootIdx + 1 == (int32_t)path.Length()) { + return NS_OK; + } + + // assign the root + const char16_t* pathBuffer = path.get(); // simplify access to the buffer + mWorkingPath.SetCapacity(path.Length()); // it won't ever grow longer + mWorkingPath.Assign(pathBuffer, rootIdx); + + // Normalize the path components. The actions taken are: + // + // "\\" condense to single backslash + // "." remove from path + // ".." up a directory + // "..." remove from path (any number of dots > 2) + // + // The last form is something that Windows 95 and 98 supported and + // is a shortcut for changing up multiple directories. Windows XP + // and ilk ignore it in a path, as is done here. + int32_t len, begin, end = rootIdx; + while (end < (int32_t)path.Length()) { + // find the current segment (text between the backslashes) to + // be examined, this will set the following variables: + // begin == index of first char in segment + // end == index 1 char after last char in segment + // len == length of segment + begin = end + 1; + end = path.FindChar('\\', begin); + if (end == kNotFound) { + end = path.Length(); + } + len = end - begin; + + // ignore double backslashes + if (len == 0) { + continue; + } + + // len != 0, and interesting paths always begin with a dot + if (pathBuffer[begin] == '.') { + // ignore single dots + if (len == 1) { + continue; + } + + // handle multiple dots + if (len >= 2 && pathBuffer[begin + 1] == L'.') { + // back up a path component on double dot + if (len == 2) { + int32_t prev = mWorkingPath.RFindChar('\\'); + if (prev >= rootIdx) { + mWorkingPath.Truncate(prev); + } + continue; + } + + // length is > 2 and the first two characters are dots. + // if the rest of the string is dots, then ignore it. + int idx = len - 1; + for (; idx >= 2; --idx) { + if (pathBuffer[begin + idx] != L'.') { + break; + } + } + + // this is true if the loop above didn't break + // and all characters in this segment are dots. + if (idx < 2) { + continue; + } + } + } + + // add the current component to the path, including the preceding backslash + mWorkingPath.Append(pathBuffer + begin - 1, len + 1); + } + + // kill trailing dots and spaces. + int32_t filePathLen = mWorkingPath.Length() - 1; + while (filePathLen > 0 && (mWorkingPath[filePathLen] == L' ' || + mWorkingPath[filePathLen] == L'.')) { + mWorkingPath.Truncate(filePathLen--); + } + + MakeDirty(); + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::GetLeafName(nsAString& aLeafName) +{ + aLeafName.Truncate(); + + if (mWorkingPath.IsEmpty()) { + return NS_ERROR_FILE_UNRECOGNIZED_PATH; + } + + int32_t offset = mWorkingPath.RFindChar(L'\\'); + + // if the working path is just a node without any lashes. + if (offset == kNotFound) { + aLeafName = mWorkingPath; + } else { + aLeafName = Substring(mWorkingPath, offset + 1); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::SetLeafName(const nsAString& aLeafName) +{ + MakeDirty(); + + if (mWorkingPath.IsEmpty()) { + return NS_ERROR_FILE_UNRECOGNIZED_PATH; + } + + // cannot use nsCString::RFindChar() due to 0x5c problem + int32_t offset = mWorkingPath.RFindChar(L'\\'); + if (offset) { + mWorkingPath.Truncate(offset + 1); + } + mWorkingPath.Append(aLeafName); + + return NS_OK; +} + + +NS_IMETHODIMP +nsLocalFile::GetPath(nsAString& aResult) +{ + aResult = mWorkingPath; + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::GetCanonicalPath(nsAString& aResult) +{ + EnsureShortPath(); + aResult.Assign(mShortWorkingPath); + return NS_OK; +} + +typedef struct +{ + WORD wLanguage; + WORD wCodePage; +} LANGANDCODEPAGE; + +NS_IMETHODIMP +nsLocalFile::GetVersionInfoField(const char* aField, nsAString& aResult) +{ + nsresult rv = ResolveAndStat(); + if (NS_FAILED(rv)) { + return rv; + } + + rv = NS_ERROR_FAILURE; + + const WCHAR* path = + mFollowSymlinks ? mResolvedPath.get() : mWorkingPath.get(); + + DWORD dummy; + DWORD size = ::GetFileVersionInfoSizeW(path, &dummy); + if (!size) { + return rv; + } + + void* ver = moz_xcalloc(size, 1); + if (::GetFileVersionInfoW(path, 0, size, ver)) { + LANGANDCODEPAGE* translate = nullptr; + UINT pageCount; + BOOL queryResult = ::VerQueryValueW(ver, L"\\VarFileInfo\\Translation", + (void**)&translate, &pageCount); + if (queryResult && translate) { + for (int32_t i = 0; i < 2; ++i) { + wchar_t subBlock[MAX_PATH]; + _snwprintf(subBlock, MAX_PATH, + L"\\StringFileInfo\\%04x%04x\\%s", + (i == 0 ? translate[0].wLanguage : ::GetUserDefaultLangID()), + translate[0].wCodePage, + NS_ConvertASCIItoUTF16( + nsDependentCString(aField)).get()); + subBlock[MAX_PATH - 1] = 0; + LPVOID value = nullptr; + UINT size; + queryResult = ::VerQueryValueW(ver, subBlock, &value, &size); + if (queryResult && value) { + aResult.Assign(static_cast(value)); + if (!aResult.IsEmpty()) { + rv = NS_OK; + break; + } + } + } + } + } + free(ver); + + return rv; +} + +NS_IMETHODIMP +nsLocalFile::SetShortcut(nsIFile* aTargetFile, + nsIFile* aWorkingDir, + const char16_t* aArgs, + const char16_t* aDescription, + nsIFile* aIconFile, + int32_t aIconIndex) +{ + bool exists; + nsresult rv = this->Exists(&exists); + if (NS_FAILED(rv)) { + return rv; + } + + const WCHAR* targetFilePath = nullptr; + const WCHAR* workingDirPath = nullptr; + const WCHAR* iconFilePath = nullptr; + + nsAutoString targetFilePathAuto; + if (aTargetFile) { + rv = aTargetFile->GetPath(targetFilePathAuto); + if (NS_FAILED(rv)) { + return rv; + } + targetFilePath = targetFilePathAuto.get(); + } + + nsAutoString workingDirPathAuto; + if (aWorkingDir) { + rv = aWorkingDir->GetPath(workingDirPathAuto); + if (NS_FAILED(rv)) { + return rv; + } + workingDirPath = workingDirPathAuto.get(); + } + + nsAutoString iconPathAuto; + if (aIconFile) { + rv = aIconFile->GetPath(iconPathAuto); + if (NS_FAILED(rv)) { + return rv; + } + iconFilePath = iconPathAuto.get(); + } + + rv = gResolver->SetShortcut(exists, + mWorkingPath.get(), + targetFilePath, + workingDirPath, + char16ptr_t(aArgs), + char16ptr_t(aDescription), + iconFilePath, + iconFilePath ? aIconIndex : 0); + if (targetFilePath && NS_SUCCEEDED(rv)) { + MakeDirty(); + } + + return rv; +} + +NS_IMETHODIMP +nsLocalFile::OpenNSPRFileDescShareDelete(int32_t aFlags, + int32_t aMode, + PRFileDesc** aResult) +{ + nsresult rv = OpenNSPRFileDescMaybeShareDelete(aFlags, aMode, true, aResult); + if (NS_FAILED(rv)) { + return rv; + } + + return NS_OK; +} + +/** + * Determines if the drive type for the specified file is rmeote or local. + * + * @param path The path of the file to check + * @param remote Out parameter, on function success holds true if the specified + * file path is remote, or false if the file path is local. + * @return true on success. The return value implies absolutely nothing about + * wether the file is local or remote. +*/ +static bool +IsRemoteFilePath(LPCWSTR aPath, bool& aRemote) +{ + // Obtain the parent directory path and make sure it ends with + // a trailing backslash. + WCHAR dirPath[MAX_PATH + 1] = { 0 }; + wcsncpy(dirPath, aPath, MAX_PATH); + if (!PathRemoveFileSpecW(dirPath)) { + return false; + } + size_t len = wcslen(dirPath); + // In case the dirPath holds exaclty MAX_PATH and remains unchanged, we + // recheck the required length here since we need to terminate it with + // a backslash. + if (len >= MAX_PATH) { + return false; + } + + dirPath[len] = L'\\'; + dirPath[len + 1] = L'\0'; + UINT driveType = GetDriveTypeW(dirPath); + aRemote = driveType == DRIVE_REMOTE; + return true; +} + +nsresult +nsLocalFile::CopySingleFile(nsIFile* aSourceFile, nsIFile* aDestParent, + const nsAString& aNewName, uint32_t aOptions) +{ + nsresult rv = NS_OK; + nsAutoString filePath; + + bool move = aOptions & (Move | Rename); + + // get the path that we are going to copy to. + // Since windows does not know how to auto + // resolve shortcuts, we must work with the + // target. + nsAutoString destPath; + aDestParent->GetTarget(destPath); + + destPath.Append('\\'); + + if (aNewName.IsEmpty()) { + nsAutoString aFileName; + aSourceFile->GetLeafName(aFileName); + destPath.Append(aFileName); + } else { + destPath.Append(aNewName); + } + + + if (aOptions & FollowSymlinks) { + rv = aSourceFile->GetTarget(filePath); + if (filePath.IsEmpty()) { + rv = aSourceFile->GetPath(filePath); + } + } else { + rv = aSourceFile->GetPath(filePath); + } + + if (NS_FAILED(rv)) { + return rv; + } + + // Pass the flag COPY_FILE_NO_BUFFERING to CopyFileEx as we may be copying + // to a SMBV2 remote drive. Without this parameter subsequent append mode + // file writes can cause the resultant file to become corrupt. We only need to do + // this if the major version of Windows is > 5(Only Windows Vista and above + // can support SMBV2). With a 7200RPM hard drive: + // Copying a 1KB file with COPY_FILE_NO_BUFFERING takes about 30-60ms. + // Copying a 1KB file without COPY_FILE_NO_BUFFERING takes < 1ms. + // So we only use COPY_FILE_NO_BUFFERING when we have a remote drive. + int copyOK; + DWORD dwCopyFlags = COPY_FILE_ALLOW_DECRYPTED_DESTINATION; + if (IsVistaOrLater()) { + bool path1Remote, path2Remote; + if (!IsRemoteFilePath(filePath.get(), path1Remote) || + !IsRemoteFilePath(destPath.get(), path2Remote) || + path1Remote || path2Remote) { + dwCopyFlags |= COPY_FILE_NO_BUFFERING; + } + } + + if (!move) { + copyOK = ::CopyFileExW(filePath.get(), destPath.get(), nullptr, + nullptr, nullptr, dwCopyFlags); + } else { + copyOK = ::MoveFileExW(filePath.get(), destPath.get(), + MOVEFILE_REPLACE_EXISTING); + + // Check if copying the source file to a different volume, + // as this could be an SMBV2 mapped drive. + if (!copyOK && GetLastError() == ERROR_NOT_SAME_DEVICE) { + if (aOptions & Rename) { + return NS_ERROR_FILE_ACCESS_DENIED; + } + copyOK = CopyFileExW(filePath.get(), destPath.get(), nullptr, + nullptr, nullptr, dwCopyFlags); + + if (copyOK) { + DeleteFileW(filePath.get()); + } + } + } + + if (!copyOK) { // CopyFileEx and MoveFileEx return zero at failure. + rv = ConvertWinError(GetLastError()); + } else if (move && !(aOptions & SkipNtfsAclReset)) { + // Set security permissions to inherit from parent. + // Note: propagates to all children: slow for big file trees + PACL pOldDACL = nullptr; + PSECURITY_DESCRIPTOR pSD = nullptr; + ::GetNamedSecurityInfoW((LPWSTR)destPath.get(), SE_FILE_OBJECT, + DACL_SECURITY_INFORMATION, + nullptr, nullptr, &pOldDACL, nullptr, &pSD); + if (pOldDACL) + ::SetNamedSecurityInfoW((LPWSTR)destPath.get(), SE_FILE_OBJECT, + DACL_SECURITY_INFORMATION | + UNPROTECTED_DACL_SECURITY_INFORMATION, + nullptr, nullptr, pOldDACL, nullptr); + if (pSD) { + LocalFree((HLOCAL)pSD); + } + } + + return rv; +} + +nsresult +nsLocalFile::CopyMove(nsIFile* aParentDir, const nsAString& aNewName, + uint32_t aOptions) +{ + bool move = aOptions & (Move | Rename); + bool followSymlinks = aOptions & FollowSymlinks; + + nsCOMPtr newParentDir = aParentDir; + // check to see if this exists, otherwise return an error. + // we will check this by resolving. If the user wants us + // to follow links, then we are talking about the target, + // hence we can use the |FollowSymlinks| option. + nsresult rv = ResolveAndStat(); + if (NS_FAILED(rv)) { + return rv; + } + + if (!newParentDir) { + // no parent was specified. We must rename. + if (aNewName.IsEmpty()) { + return NS_ERROR_INVALID_ARG; + } + + rv = GetParent(getter_AddRefs(newParentDir)); + if (NS_FAILED(rv)) { + return rv; + } + } + + if (!newParentDir) { + return NS_ERROR_FILE_DESTINATION_NOT_DIR; + } + + // make sure it exists and is a directory. Create it if not there. + bool exists; + newParentDir->Exists(&exists); + if (!exists) { + rv = newParentDir->Create(DIRECTORY_TYPE, 0644); // TODO, what permissions should we use + if (NS_FAILED(rv)) { + return rv; + } + } else { + bool isDir; + newParentDir->IsDirectory(&isDir); + if (!isDir) { + if (followSymlinks) { + bool isLink; + newParentDir->IsSymlink(&isLink); + if (isLink) { + nsAutoString target; + newParentDir->GetTarget(target); + + nsCOMPtr realDest = new nsLocalFile(); + rv = realDest->InitWithPath(target); + + if (NS_FAILED(rv)) { + return rv; + } + + return CopyMove(realDest, aNewName, aOptions); + } + } else { + return NS_ERROR_FILE_DESTINATION_NOT_DIR; + } + } + } + + // Try different ways to move/copy files/directories + bool done = false; + bool isDir; + IsDirectory(&isDir); + bool isSymlink; + IsSymlink(&isSymlink); + + // Try to move the file or directory, or try to copy a single file (or non-followed symlink) + if (move || !isDir || (isSymlink && !followSymlinks)) { + // Copy/Move single file, or move a directory + if (!aParentDir) { + aOptions |= SkipNtfsAclReset; + } + rv = CopySingleFile(this, newParentDir, aNewName, aOptions); + done = NS_SUCCEEDED(rv); + // If we are moving a directory and that fails, fallback on directory + // enumeration. See bug 231300 for details. + if (!done && !(move && isDir)) { + return rv; + } + } + + // Not able to copy or move directly, so enumerate it + if (!done) { + // create a new target destination in the new parentDir; + nsCOMPtr target; + rv = newParentDir->Clone(getter_AddRefs(target)); + + if (NS_FAILED(rv)) { + return rv; + } + + nsAutoString allocatedNewName; + if (aNewName.IsEmpty()) { + bool isLink; + IsSymlink(&isLink); + if (isLink) { + nsAutoString temp; + GetTarget(temp); + int32_t offset = temp.RFindChar(L'\\'); + if (offset == kNotFound) { + allocatedNewName = temp; + } else { + allocatedNewName = Substring(temp, offset + 1); + } + } else { + GetLeafName(allocatedNewName);// this should be the leaf name of the + } + } else { + allocatedNewName = aNewName; + } + + rv = target->Append(allocatedNewName); + if (NS_FAILED(rv)) { + return rv; + } + + allocatedNewName.Truncate(); + + // check if the destination directory already exists + target->Exists(&exists); + if (!exists) { + // if the destination directory cannot be created, return an error + rv = target->Create(DIRECTORY_TYPE, 0644); // TODO, what permissions should we use + if (NS_FAILED(rv)) { + return rv; + } + } else { + // check if the destination directory is writable and empty + bool isWritable; + + target->IsWritable(&isWritable); + if (!isWritable) { + return NS_ERROR_FILE_ACCESS_DENIED; + } + + nsCOMPtr targetIterator; + rv = target->GetDirectoryEntries(getter_AddRefs(targetIterator)); + if (NS_FAILED(rv)) { + return rv; + } + + bool more; + targetIterator->HasMoreElements(&more); + // return error if target directory is not empty + if (more) { + return NS_ERROR_FILE_DIR_NOT_EMPTY; + } + } + + RefPtr dirEnum = new nsDirEnumerator(); + + rv = dirEnum->Init(this); + if (NS_FAILED(rv)) { + NS_WARNING("dirEnum initialization failed"); + return rv; + } + + bool more = false; + while (NS_SUCCEEDED(dirEnum->HasMoreElements(&more)) && more) { + nsCOMPtr item; + nsCOMPtr file; + dirEnum->GetNext(getter_AddRefs(item)); + file = do_QueryInterface(item); + if (file) { + bool isDir, isLink; + + file->IsDirectory(&isDir); + file->IsSymlink(&isLink); + + if (move) { + if (followSymlinks) { + return NS_ERROR_FAILURE; + } + + rv = file->MoveTo(target, EmptyString()); + if (NS_FAILED(rv)) { + return rv; + } + } else { + if (followSymlinks) { + rv = file->CopyToFollowingLinks(target, EmptyString()); + } else { + rv = file->CopyTo(target, EmptyString()); + } + if (NS_FAILED(rv)) { + return rv; + } + } + } + } + // we've finished moving all the children of this directory + // in the new directory. so now delete the directory + // note, we don't need to do a recursive delete. + // MoveTo() is recursive. At this point, + // we've already moved the children of the current folder + // to the new location. nothing should be left in the folder. + if (move) { + rv = Remove(false /* recursive */); + if (NS_FAILED(rv)) { + return rv; + } + } + } + + + // If we moved, we want to adjust this. + if (move) { + MakeDirty(); + + nsAutoString newParentPath; + newParentDir->GetPath(newParentPath); + + if (newParentPath.IsEmpty()) { + return NS_ERROR_FAILURE; + } + + if (aNewName.IsEmpty()) { + nsAutoString aFileName; + GetLeafName(aFileName); + + InitWithPath(newParentPath); + Append(aFileName); + } else { + InitWithPath(newParentPath); + Append(aNewName); + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::CopyTo(nsIFile* aNewParentDir, const nsAString& aNewName) +{ + return CopyMove(aNewParentDir, aNewName, 0); +} + +NS_IMETHODIMP +nsLocalFile::CopyToFollowingLinks(nsIFile* aNewParentDir, + const nsAString& aNewName) +{ + return CopyMove(aNewParentDir, aNewName, FollowSymlinks); +} + +NS_IMETHODIMP +nsLocalFile::MoveTo(nsIFile* aNewParentDir, const nsAString& aNewName) +{ + return CopyMove(aNewParentDir, aNewName, Move); +} + +NS_IMETHODIMP +nsLocalFile::RenameTo(nsIFile* aNewParentDir, const nsAString& aNewName) +{ + nsCOMPtr targetParentDir = aNewParentDir; + // check to see if this exists, otherwise return an error. + // we will check this by resolving. If the user wants us + // to follow links, then we are talking about the target, + // hence we can use the |followSymlinks| parameter. + nsresult rv = ResolveAndStat(); + if (NS_FAILED(rv)) { + return rv; + } + + if (!targetParentDir) { + // no parent was specified. We must rename. + if (aNewName.IsEmpty()) { + return NS_ERROR_INVALID_ARG; + } + rv = GetParent(getter_AddRefs(targetParentDir)); + if (NS_FAILED(rv)) { + return rv; + } + } + + if (!targetParentDir) { + return NS_ERROR_FILE_DESTINATION_NOT_DIR; + } + + // make sure it exists and is a directory. Create it if not there. + bool exists; + targetParentDir->Exists(&exists); + if (!exists) { + rv = targetParentDir->Create(DIRECTORY_TYPE, 0644); + if (NS_FAILED(rv)) { + return rv; + } + } else { + bool isDir; + targetParentDir->IsDirectory(&isDir); + if (!isDir) { + return NS_ERROR_FILE_DESTINATION_NOT_DIR; + } + } + + uint32_t options = Rename; + if (!aNewParentDir) { + options |= SkipNtfsAclReset; + } + // Move single file, or move a directory + return CopySingleFile(this, targetParentDir, aNewName, options); +} + +NS_IMETHODIMP +nsLocalFile::RenameToNative(nsIFile* aNewParentDir, const nsACString& aNewName) +{ + nsAutoString tmp; + nsresult rv = NS_CopyNativeToUnicode(aNewName, tmp); + if (NS_SUCCEEDED(rv)) { + return RenameTo(aNewParentDir, tmp); + } + + return rv; +} + +NS_IMETHODIMP +nsLocalFile::Load(PRLibrary** aResult) +{ + // Check we are correctly initialized. + CHECK_mWorkingPath(); + + bool isFile; + nsresult rv = IsFile(&isFile); + + if (NS_FAILED(rv)) { + return rv; + } + + if (!isFile) { + return NS_ERROR_FILE_IS_DIRECTORY; + } + +#ifdef NS_BUILD_REFCNT_LOGGING + nsTraceRefcnt::SetActivityIsLegal(false); +#endif + + PRLibSpec libSpec; + libSpec.value.pathname_u = mResolvedPath.get(); + libSpec.type = PR_LibSpec_PathnameU; + *aResult = PR_LoadLibraryWithFlags(libSpec, 0); + +#ifdef NS_BUILD_REFCNT_LOGGING + nsTraceRefcnt::SetActivityIsLegal(true); +#endif + + if (*aResult) { + return NS_OK; + } + return NS_ERROR_NULL_POINTER; +} + +NS_IMETHODIMP +nsLocalFile::Remove(bool aRecursive) +{ + // NOTE: + // + // if the working path points to a shortcut, then we will only + // delete the shortcut itself. even if the shortcut points to + // a directory, we will not recurse into that directory or + // delete that directory itself. likewise, if the shortcut + // points to a normal file, we will not delete the real file. + // this is done to be consistent with the other platforms that + // behave this way. we do this even if the followLinks attribute + // is set to true. this helps protect against misuse that could + // lead to security bugs (e.g., bug 210588). + // + // Since shortcut files are no longer permitted to be used as unix-like + // symlinks interspersed in the path (e.g. "c:/file.lnk/foo/bar.txt") + // this processing is a lot simpler. Even if the shortcut file is + // pointing to a directory, only the mWorkingPath value is used and so + // only the shortcut file will be deleted. + + // Check we are correctly initialized. + CHECK_mWorkingPath(); + + bool isDir, isLink; + nsresult rv; + + isDir = false; + rv = IsSymlink(&isLink); + if (NS_FAILED(rv)) { + return rv; + } + + // only check to see if we have a directory if it isn't a link + if (!isLink) { + rv = IsDirectory(&isDir); + if (NS_FAILED(rv)) { + return rv; + } + } + + if (isDir) { + if (aRecursive) { + RefPtr dirEnum = new nsDirEnumerator(); + + rv = dirEnum->Init(this); + if (NS_FAILED(rv)) { + return rv; + } + + bool more = false; + while (NS_SUCCEEDED(dirEnum->HasMoreElements(&more)) && more) { + nsCOMPtr item; + dirEnum->GetNext(getter_AddRefs(item)); + nsCOMPtr file = do_QueryInterface(item); + if (file) { + file->Remove(aRecursive); + } + } + } + if (RemoveDirectoryW(mWorkingPath.get()) == 0) { + return ConvertWinError(GetLastError()); + } + } else { + if (DeleteFileW(mWorkingPath.get()) == 0) { + return ConvertWinError(GetLastError()); + } + } + + MakeDirty(); + return rv; +} + +NS_IMETHODIMP +nsLocalFile::GetLastModifiedTime(PRTime* aLastModifiedTime) +{ + // Check we are correctly initialized. + CHECK_mWorkingPath(); + + if (NS_WARN_IF(!aLastModifiedTime)) { + return NS_ERROR_INVALID_ARG; + } + + // get the modified time of the target as determined by mFollowSymlinks + // If true, then this will be for the target of the shortcut file, + // otherwise it will be for the shortcut file itself (i.e. the same + // results as GetLastModifiedTimeOfLink) + + nsresult rv = ResolveAndStat(); + if (NS_FAILED(rv)) { + return rv; + } + + // microseconds -> milliseconds + *aLastModifiedTime = mFileInfo64.modifyTime / PR_USEC_PER_MSEC; + return NS_OK; +} + + +NS_IMETHODIMP +nsLocalFile::GetLastModifiedTimeOfLink(PRTime* aLastModifiedTime) +{ + // Check we are correctly initialized. + CHECK_mWorkingPath(); + + if (NS_WARN_IF(!aLastModifiedTime)) { + return NS_ERROR_INVALID_ARG; + } + + // The caller is assumed to have already called IsSymlink + // and to have found that this file is a link. + + PRFileInfo64 info; + nsresult rv = GetFileInfo(mWorkingPath, &info); + if (NS_FAILED(rv)) { + return rv; + } + + // microseconds -> milliseconds + *aLastModifiedTime = info.modifyTime / PR_USEC_PER_MSEC; + return NS_OK; +} + + +NS_IMETHODIMP +nsLocalFile::SetLastModifiedTime(PRTime aLastModifiedTime) +{ + // Check we are correctly initialized. + CHECK_mWorkingPath(); + + nsresult rv = ResolveAndStat(); + if (NS_FAILED(rv)) { + return rv; + } + + // set the modified time of the target as determined by mFollowSymlinks + // If true, then this will be for the target of the shortcut file, + // otherwise it will be for the shortcut file itself (i.e. the same + // results as SetLastModifiedTimeOfLink) + + rv = SetModDate(aLastModifiedTime, mResolvedPath.get()); + if (NS_SUCCEEDED(rv)) { + MakeDirty(); + } + + return rv; +} + + +NS_IMETHODIMP +nsLocalFile::SetLastModifiedTimeOfLink(PRTime aLastModifiedTime) +{ + // The caller is assumed to have already called IsSymlink + // and to have found that this file is a link. + + nsresult rv = SetModDate(aLastModifiedTime, mWorkingPath.get()); + if (NS_SUCCEEDED(rv)) { + MakeDirty(); + } + + return rv; +} + +nsresult +nsLocalFile::SetModDate(PRTime aLastModifiedTime, const wchar_t* aFilePath) +{ + // The FILE_FLAG_BACKUP_SEMANTICS is required in order to change the + // modification time for directories. + HANDLE file = ::CreateFileW(aFilePath, // pointer to name of the file + GENERIC_WRITE, // access (write) mode + 0, // share mode + nullptr, // pointer to security attributes + OPEN_EXISTING, // how to create + FILE_FLAG_BACKUP_SEMANTICS, // file attributes + nullptr); + + if (file == INVALID_HANDLE_VALUE) { + return ConvertWinError(GetLastError()); + } + + FILETIME ft; + SYSTEMTIME st; + PRExplodedTime pret; + + // PR_ExplodeTime expects usecs... + PR_ExplodeTime(aLastModifiedTime * PR_USEC_PER_MSEC, PR_GMTParameters, &pret); + st.wYear = pret.tm_year; + st.wMonth = pret.tm_month + 1; // Convert start offset -- Win32: Jan=1; NSPR: Jan=0 + st.wDayOfWeek = pret.tm_wday; + st.wDay = pret.tm_mday; + st.wHour = pret.tm_hour; + st.wMinute = pret.tm_min; + st.wSecond = pret.tm_sec; + st.wMilliseconds = pret.tm_usec / 1000; + + nsresult rv = NS_OK; + // if at least one of these fails... + if (!(SystemTimeToFileTime(&st, &ft) != 0 && + SetFileTime(file, nullptr, &ft, &ft) != 0)) { + rv = ConvertWinError(GetLastError()); + } + + CloseHandle(file); + return rv; +} + +NS_IMETHODIMP +nsLocalFile::GetPermissions(uint32_t* aPermissions) +{ + if (NS_WARN_IF(!aPermissions)) { + return NS_ERROR_INVALID_ARG; + } + + // get the permissions of the target as determined by mFollowSymlinks + // If true, then this will be for the target of the shortcut file, + // otherwise it will be for the shortcut file itself (i.e. the same + // results as GetPermissionsOfLink) + nsresult rv = ResolveAndStat(); + if (NS_FAILED(rv)) { + return rv; + } + + bool isWritable, isExecutable; + IsWritable(&isWritable); + IsExecutable(&isExecutable); + + *aPermissions = PR_IRUSR | PR_IRGRP | PR_IROTH; // all read + if (isWritable) { + *aPermissions |= PR_IWUSR | PR_IWGRP | PR_IWOTH; // all write + } + if (isExecutable) { + *aPermissions |= PR_IXUSR | PR_IXGRP | PR_IXOTH; // all execute + } + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::GetPermissionsOfLink(uint32_t* aPermissions) +{ + // Check we are correctly initialized. + CHECK_mWorkingPath(); + + if (NS_WARN_IF(!aPermissions)) { + return NS_ERROR_INVALID_ARG; + } + + // The caller is assumed to have already called IsSymlink + // and to have found that this file is a link. It is not + // possible for a link file to be executable. + + DWORD word = ::GetFileAttributesW(mWorkingPath.get()); + if (word == INVALID_FILE_ATTRIBUTES) { + return NS_ERROR_FILE_INVALID_PATH; + } + + bool isWritable = !(word & FILE_ATTRIBUTE_READONLY); + *aPermissions = PR_IRUSR | PR_IRGRP | PR_IROTH; // all read + if (isWritable) { + *aPermissions |= PR_IWUSR | PR_IWGRP | PR_IWOTH; // all write + } + + return NS_OK; +} + + +NS_IMETHODIMP +nsLocalFile::SetPermissions(uint32_t aPermissions) +{ + // Check we are correctly initialized. + CHECK_mWorkingPath(); + + // set the permissions of the target as determined by mFollowSymlinks + // If true, then this will be for the target of the shortcut file, + // otherwise it will be for the shortcut file itself (i.e. the same + // results as SetPermissionsOfLink) + nsresult rv = ResolveAndStat(); + if (NS_FAILED(rv)) { + return rv; + } + + // windows only knows about the following permissions + int mode = 0; + if (aPermissions & (PR_IRUSR | PR_IRGRP | PR_IROTH)) { // any read + mode |= _S_IREAD; + } + if (aPermissions & (PR_IWUSR | PR_IWGRP | PR_IWOTH)) { // any write + mode |= _S_IWRITE; + } + + if (_wchmod(mResolvedPath.get(), mode) == -1) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::SetPermissionsOfLink(uint32_t aPermissions) +{ + // The caller is assumed to have already called IsSymlink + // and to have found that this file is a link. + + // windows only knows about the following permissions + int mode = 0; + if (aPermissions & (PR_IRUSR | PR_IRGRP | PR_IROTH)) { // any read + mode |= _S_IREAD; + } + if (aPermissions & (PR_IWUSR | PR_IWGRP | PR_IWOTH)) { // any write + mode |= _S_IWRITE; + } + + if (_wchmod(mWorkingPath.get(), mode) == -1) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + + +NS_IMETHODIMP +nsLocalFile::GetFileSize(int64_t* aFileSize) +{ + if (NS_WARN_IF(!aFileSize)) { + return NS_ERROR_INVALID_ARG; + } + + nsresult rv = ResolveAndStat(); + if (NS_FAILED(rv)) { + return rv; + } + + *aFileSize = mFileInfo64.size; + return NS_OK; +} + + +NS_IMETHODIMP +nsLocalFile::GetFileSizeOfLink(int64_t* aFileSize) +{ + // Check we are correctly initialized. + CHECK_mWorkingPath(); + + if (NS_WARN_IF(!aFileSize)) { + return NS_ERROR_INVALID_ARG; + } + + // The caller is assumed to have already called IsSymlink + // and to have found that this file is a link. + + PRFileInfo64 info; + if (NS_FAILED(GetFileInfo(mWorkingPath, &info))) { + return NS_ERROR_FILE_INVALID_PATH; + } + + *aFileSize = info.size; + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::SetFileSize(int64_t aFileSize) +{ + // Check we are correctly initialized. + CHECK_mWorkingPath(); + + nsresult rv = ResolveAndStat(); + if (NS_FAILED(rv)) { + return rv; + } + + HANDLE hFile = ::CreateFileW(mResolvedPath.get(),// pointer to name of the file + GENERIC_WRITE, // access (write) mode + FILE_SHARE_READ, // share mode + nullptr, // pointer to security attributes + OPEN_EXISTING, // how to create + FILE_ATTRIBUTE_NORMAL, // file attributes + nullptr); + if (hFile == INVALID_HANDLE_VALUE) { + return ConvertWinError(GetLastError()); + } + + // seek the file pointer to the new, desired end of file + // and then truncate the file at that position + rv = NS_ERROR_FAILURE; + aFileSize = MyFileSeek64(hFile, aFileSize, FILE_BEGIN); + if (aFileSize != -1 && SetEndOfFile(hFile)) { + MakeDirty(); + rv = NS_OK; + } + + CloseHandle(hFile); + return rv; +} + +NS_IMETHODIMP +nsLocalFile::GetDiskSpaceAvailable(int64_t* aDiskSpaceAvailable) +{ + // Check we are correctly initialized. + CHECK_mWorkingPath(); + + if (NS_WARN_IF(!aDiskSpaceAvailable)) { + return NS_ERROR_INVALID_ARG; + } + + ResolveAndStat(); + + if (mFileInfo64.type == PR_FILE_FILE) { + // Since GetDiskFreeSpaceExW works only on directories, use the parent. + nsCOMPtr parent; + if (NS_SUCCEEDED(GetParent(getter_AddRefs(parent))) && parent) { + return parent->GetDiskSpaceAvailable(aDiskSpaceAvailable); + } + } + + ULARGE_INTEGER liFreeBytesAvailableToCaller, liTotalNumberOfBytes; + if (::GetDiskFreeSpaceExW(mResolvedPath.get(), &liFreeBytesAvailableToCaller, + &liTotalNumberOfBytes, nullptr)) { + *aDiskSpaceAvailable = liFreeBytesAvailableToCaller.QuadPart; + return NS_OK; + } + *aDiskSpaceAvailable = 0; + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::GetParent(nsIFile** aParent) +{ + // Check we are correctly initialized. + CHECK_mWorkingPath(); + + if (NS_WARN_IF(!aParent)) { + return NS_ERROR_INVALID_ARG; + } + + // A two-character path must be a drive such as C:, so it has no parent + if (mWorkingPath.Length() == 2) { + *aParent = nullptr; + return NS_OK; + } + + int32_t offset = mWorkingPath.RFindChar(char16_t('\\')); + // adding this offset check that was removed in bug 241708 fixes mail + // directories that aren't relative to/underneath the profile dir. + // e.g., on a different drive. Before you remove them, please make + // sure local mail directories that aren't underneath the profile dir work. + if (offset == kNotFound) { + return NS_ERROR_FILE_UNRECOGNIZED_PATH; + } + + // A path of the form \\NAME is a top-level path and has no parent + if (offset == 1 && mWorkingPath[0] == L'\\') { + *aParent = nullptr; + return NS_OK; + } + + nsAutoString parentPath(mWorkingPath); + + if (offset > 0) { + parentPath.Truncate(offset); + } else { + parentPath.AssignLiteral("\\\\."); + } + + nsCOMPtr localFile; + nsresult rv = NS_NewLocalFile(parentPath, mFollowSymlinks, + getter_AddRefs(localFile)); + + if (NS_FAILED(rv)) { + return rv; + } + + localFile.forget(aParent); + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::Exists(bool* aResult) +{ + // Check we are correctly initialized. + CHECK_mWorkingPath(); + + if (NS_WARN_IF(!aResult)) { + return NS_ERROR_INVALID_ARG; + } + *aResult = false; + + MakeDirty(); + nsresult rv = ResolveAndStat(); + *aResult = NS_SUCCEEDED(rv) || rv == NS_ERROR_FILE_IS_LOCKED; + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::IsWritable(bool* aIsWritable) +{ + // Check we are correctly initialized. + CHECK_mWorkingPath(); + + // The read-only attribute on a FAT directory only means that it can't + // be deleted. It is still possible to modify the contents of the directory. + nsresult rv = IsDirectory(aIsWritable); + if (rv == NS_ERROR_FILE_ACCESS_DENIED) { + *aIsWritable = true; + return NS_OK; + } else if (rv == NS_ERROR_FILE_IS_LOCKED) { + // If the file is normally allowed write access + // we should still return that the file is writable. + } else if (NS_FAILED(rv)) { + return rv; + } + if (*aIsWritable) { + return NS_OK; + } + + // writable if the file doesn't have the readonly attribute + rv = HasFileAttribute(FILE_ATTRIBUTE_READONLY, aIsWritable); + if (rv == NS_ERROR_FILE_ACCESS_DENIED) { + *aIsWritable = false; + return NS_OK; + } else if (rv == NS_ERROR_FILE_IS_LOCKED) { + // If the file is normally allowed write access + // we should still return that the file is writable. + } else if (NS_FAILED(rv)) { + return rv; + } + *aIsWritable = !*aIsWritable; + + // If the read only attribute is not set, check to make sure + // we can open the file with write access. + if (*aIsWritable) { + PRFileDesc* file; + rv = OpenFile(mResolvedPath, PR_WRONLY, 0, false, &file); + if (NS_SUCCEEDED(rv)) { + PR_Close(file); + } else if (rv == NS_ERROR_FILE_ACCESS_DENIED) { + *aIsWritable = false; + } else if (rv == NS_ERROR_FILE_IS_LOCKED) { + // If it is locked and read only we would have + // gotten access denied + *aIsWritable = true; + } else { + return rv; + } + } + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::IsReadable(bool* aResult) +{ + // Check we are correctly initialized. + CHECK_mWorkingPath(); + + if (NS_WARN_IF(!aResult)) { + return NS_ERROR_INVALID_ARG; + } + *aResult = false; + + nsresult rv = ResolveAndStat(); + if (NS_FAILED(rv)) { + return rv; + } + + *aResult = true; + return NS_OK; +} + + +NS_IMETHODIMP +nsLocalFile::IsExecutable(bool* aResult) +{ + // Check we are correctly initialized. + CHECK_mWorkingPath(); + + if (NS_WARN_IF(!aResult)) { + return NS_ERROR_INVALID_ARG; + } + *aResult = false; + + nsresult rv; + + // only files can be executables + bool isFile; + rv = IsFile(&isFile); + if (NS_FAILED(rv)) { + return rv; + } + if (!isFile) { + return NS_OK; + } + + //TODO: shouldn't we be checking mFollowSymlinks here? + bool symLink; + rv = IsSymlink(&symLink); + if (NS_FAILED(rv)) { + return rv; + } + + nsAutoString path; + if (symLink) { + GetTarget(path); + } else { + GetPath(path); + } + + // kill trailing dots and spaces. + int32_t filePathLen = path.Length() - 1; + while (filePathLen > 0 && (path[filePathLen] == L' ' || + path[filePathLen] == L'.')) { + path.Truncate(filePathLen--); + } + + // Get extension. + int32_t dotIdx = path.RFindChar(char16_t('.')); + if (dotIdx != kNotFound) { + // Convert extension to lower case. + char16_t* p = path.BeginWriting(); + for (p += dotIdx + 1; *p; ++p) { + *p += (*p >= L'A' && *p <= L'Z') ? 'a' - 'A' : 0; + } + + // Search for any of the set of executable extensions. + static const char* const executableExts[] = { + "ad", + "ade", // access project extension + "adp", + "air", // Adobe AIR installer + "app", // executable application + "application", // from bug 348763 + "asp", + "bas", + "bat", + "chm", + "cmd", + "com", + "cpl", + "crt", + "exe", + "fxp", // FoxPro compiled app + "hlp", + "hta", + "inf", + "ins", + "isp", + "jar", // java application bundle + "js", + "jse", + "lnk", + "mad", // Access Module Shortcut + "maf", // Access + "mag", // Access Diagram Shortcut + "mam", // Access Macro Shortcut + "maq", // Access Query Shortcut + "mar", // Access Report Shortcut + "mas", // Access Stored Procedure + "mat", // Access Table Shortcut + "mau", // Media Attachment Unit + "mav", // Access View Shortcut + "maw", // Access Data Access Page + "mda", // Access Add-in, MDA Access 2 Workgroup + "mdb", + "mde", + "mdt", // Access Add-in Data + "mdw", // Access Workgroup Information + "mdz", // Access Wizard Template + "msc", + "msh", // Microsoft Shell + "mshxml", // Microsoft Shell + "msi", + "msp", + "mst", + "ops", // Office Profile Settings + "pcd", + "pif", + "plg", // Developer Studio Build Log + "prf", // windows system file + "prg", + "pst", + "reg", + "scf", // Windows explorer command + "scr", + "sct", + "shb", + "shs", + "url", + "vb", + "vbe", + "vbs", + "vsd", + "vsmacros", // Visual Studio .NET Binary-based Macro Project + "vss", + "vst", + "vsw", + "ws", + "wsc", + "wsf", + "wsh" + }; + nsDependentSubstring ext = Substring(path, dotIdx + 1); + for (size_t i = 0; i < ArrayLength(executableExts); ++i) { + if (ext.EqualsASCII(executableExts[i])) { + // Found a match. Set result and quit. + *aResult = true; + break; + } + } + } + + return NS_OK; +} + + +NS_IMETHODIMP +nsLocalFile::IsDirectory(bool* aResult) +{ + return HasFileAttribute(FILE_ATTRIBUTE_DIRECTORY, aResult); +} + +NS_IMETHODIMP +nsLocalFile::IsFile(bool* aResult) +{ + nsresult rv = HasFileAttribute(FILE_ATTRIBUTE_DIRECTORY, aResult); + if (NS_SUCCEEDED(rv)) { + *aResult = !*aResult; + } + return rv; +} + +NS_IMETHODIMP +nsLocalFile::IsHidden(bool* aResult) +{ + return HasFileAttribute(FILE_ATTRIBUTE_HIDDEN, aResult); +} + +nsresult +nsLocalFile::HasFileAttribute(DWORD aFileAttrib, bool* aResult) +{ + if (NS_WARN_IF(!aResult)) { + return NS_ERROR_INVALID_ARG; + } + + nsresult rv = Resolve(); + if (NS_FAILED(rv)) { + return rv; + } + + DWORD attributes = GetFileAttributesW(mResolvedPath.get()); + if (INVALID_FILE_ATTRIBUTES == attributes) { + return ConvertWinError(GetLastError()); + } + + *aResult = ((attributes & aFileAttrib) != 0); + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::IsSymlink(bool* aResult) +{ + // Check we are correctly initialized. + CHECK_mWorkingPath(); + + if (NS_WARN_IF(!aResult)) { + return NS_ERROR_INVALID_ARG; + } + + // unless it is a valid shortcut path it's not a symlink + if (!IsShortcutPath(mWorkingPath)) { + *aResult = false; + return NS_OK; + } + + // we need to know if this is a file or directory + nsresult rv = ResolveAndStat(); + if (NS_FAILED(rv)) { + return rv; + } + + // We should not check mFileInfo64.type here for PR_FILE_FILE because lnk + // files can point to directories or files. Important security checks + // depend on correctly identifying lnk files. mFileInfo64 now holds info + // about the target of the lnk file, not the actual lnk file! + *aResult = true; + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::IsSpecial(bool* aResult) +{ + return HasFileAttribute(FILE_ATTRIBUTE_SYSTEM, aResult); +} + +NS_IMETHODIMP +nsLocalFile::Equals(nsIFile* aInFile, bool* aResult) +{ + if (NS_WARN_IF(!aInFile)) { + return NS_ERROR_INVALID_ARG; + } + if (NS_WARN_IF(!aResult)) { + return NS_ERROR_INVALID_ARG; + } + + EnsureShortPath(); + + nsCOMPtr lf(do_QueryInterface(aInFile)); + if (!lf) { + *aResult = false; + return NS_OK; + } + + nsAutoString inFilePath; + lf->GetCanonicalPath(inFilePath); + + // Ok : Win9x + *aResult = _wcsicmp(mShortWorkingPath.get(), inFilePath.get()) == 0; + + return NS_OK; +} + + +NS_IMETHODIMP +nsLocalFile::Contains(nsIFile* aInFile, bool* aResult) +{ + // Check we are correctly initialized. + CHECK_mWorkingPath(); + + *aResult = false; + + nsAutoString myFilePath; + if (NS_FAILED(GetTarget(myFilePath))) { + GetPath(myFilePath); + } + + uint32_t myFilePathLen = myFilePath.Length(); + + nsAutoString inFilePath; + if (NS_FAILED(aInFile->GetTarget(inFilePath))) { + aInFile->GetPath(inFilePath); + } + + // make sure that the |aInFile|'s path has a trailing separator. + if (inFilePath.Length() >= myFilePathLen && + inFilePath[myFilePathLen] == L'\\') { + if (_wcsnicmp(myFilePath.get(), inFilePath.get(), myFilePathLen) == 0) { + *aResult = true; + } + + } + + return NS_OK; +} + + +NS_IMETHODIMP +nsLocalFile::GetTarget(nsAString& aResult) +{ + aResult.Truncate(); +#if STRICT_FAKE_SYMLINKS + bool symLink; + + nsresult rv = IsSymlink(&symLink); + if (NS_FAILED(rv)) { + return rv; + } + + if (!symLink) { + return NS_ERROR_FILE_INVALID_PATH; + } +#endif + ResolveAndStat(); + + aResult = mResolvedPath; + return NS_OK; +} + + +NS_IMETHODIMP +nsLocalFile::GetFollowLinks(bool* aFollowLinks) +{ + *aFollowLinks = mFollowSymlinks; + return NS_OK; +} +NS_IMETHODIMP +nsLocalFile::SetFollowLinks(bool aFollowLinks) +{ + MakeDirty(); + mFollowSymlinks = aFollowLinks; + return NS_OK; +} + + +NS_IMETHODIMP +nsLocalFile::GetDirectoryEntries(nsISimpleEnumerator** aEntries) +{ + nsresult rv; + + *aEntries = nullptr; + if (mWorkingPath.EqualsLiteral("\\\\.")) { + RefPtr drives = new nsDriveEnumerator; + rv = drives->Init(); + if (NS_FAILED(rv)) { + return rv; + } + drives.forget(aEntries); + return NS_OK; + } + + RefPtr dirEnum = new nsDirEnumerator(); + rv = dirEnum->Init(this); + if (NS_FAILED(rv)) { + return rv; + } + + dirEnum.forget(aEntries); + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::GetPersistentDescriptor(nsACString& aPersistentDescriptor) +{ + CopyUTF16toUTF8(mWorkingPath, aPersistentDescriptor); + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::SetPersistentDescriptor(const nsACString& aPersistentDescriptor) +{ + if (IsUTF8(aPersistentDescriptor)) { + return InitWithPath(NS_ConvertUTF8toUTF16(aPersistentDescriptor)); + } else { + return InitWithNativePath(aPersistentDescriptor); + } +} + +NS_IMETHODIMP +nsLocalFile::GetFileAttributesWin(uint32_t* aAttribs) +{ + *aAttribs = 0; + DWORD dwAttrs = GetFileAttributesW(mWorkingPath.get()); + if (dwAttrs == INVALID_FILE_ATTRIBUTES) { + return NS_ERROR_FILE_INVALID_PATH; + } + + if (!(dwAttrs & FILE_ATTRIBUTE_NOT_CONTENT_INDEXED)) { + *aAttribs |= WFA_SEARCH_INDEXED; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::SetFileAttributesWin(uint32_t aAttribs) +{ + DWORD dwAttrs = GetFileAttributesW(mWorkingPath.get()); + if (dwAttrs == INVALID_FILE_ATTRIBUTES) { + return NS_ERROR_FILE_INVALID_PATH; + } + + if (aAttribs & WFA_SEARCH_INDEXED) { + dwAttrs &= ~FILE_ATTRIBUTE_NOT_CONTENT_INDEXED; + } else { + dwAttrs |= FILE_ATTRIBUTE_NOT_CONTENT_INDEXED; + } + + if (aAttribs & WFA_READONLY) { + dwAttrs |= FILE_ATTRIBUTE_READONLY; + } else if ((aAttribs & WFA_READWRITE) && + (dwAttrs & FILE_ATTRIBUTE_READONLY)) { + dwAttrs &= ~FILE_ATTRIBUTE_READONLY; + } + + if (SetFileAttributesW(mWorkingPath.get(), dwAttrs) == 0) { + return NS_ERROR_FAILURE; + } + return NS_OK; +} + + +NS_IMETHODIMP +nsLocalFile::Reveal() +{ + // This API should be main thread only + MOZ_ASSERT(NS_IsMainThread()); + + // make sure mResolvedPath is set + nsresult rv = Resolve(); + if (NS_FAILED(rv) && rv != NS_ERROR_FILE_NOT_FOUND) { + return rv; + } + + // To create a new thread, get the thread manager + nsCOMPtr tm = do_GetService(NS_THREADMANAGER_CONTRACTID); + nsCOMPtr mythread; + rv = tm->NewThread(0, 0, getter_AddRefs(mythread)); + if (NS_FAILED(rv)) { + return rv; + } + + nsCOMPtr runnable = new AsyncRevealOperation(mResolvedPath); + + // After the dispatch, the result runnable will shut down the worker + // thread, so we can let it go. + mythread->Dispatch(runnable, NS_DISPATCH_NORMAL); + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::Launch() +{ + // This API should be main thread only + MOZ_ASSERT(NS_IsMainThread()); + + // make sure mResolvedPath is set + nsresult rv = Resolve(); + if (NS_FAILED(rv)) { + return rv; + } + + // use the app registry name to launch a shell execute.... + SHELLEXECUTEINFOW seinfo; + memset(&seinfo, 0, sizeof(seinfo)); + seinfo.cbSize = sizeof(SHELLEXECUTEINFOW); + seinfo.fMask = SEE_MASK_ASYNCOK; + seinfo.hwnd = GetMostRecentNavigatorHWND(); + seinfo.lpVerb = nullptr; + seinfo.lpFile = mResolvedPath.get(); + seinfo.lpParameters = nullptr; + seinfo.lpDirectory = nullptr; + seinfo.nShow = SW_SHOWNORMAL; + + // Use the directory of the file we're launching as the working + // directory. That way if we have a self extracting EXE it won't + // suggest to extract to the install directory. + WCHAR workingDirectory[MAX_PATH + 1] = { L'\0' }; + wcsncpy(workingDirectory, mResolvedPath.get(), MAX_PATH); + if (PathRemoveFileSpecW(workingDirectory)) { + seinfo.lpDirectory = workingDirectory; + } else { + NS_WARNING("Could not set working directory for launched file."); + } + + if (ShellExecuteExW(&seinfo)) { + return NS_OK; + } + DWORD r = GetLastError(); + // if the file has no association, we launch windows' + // "what do you want to do" dialog + if (r == SE_ERR_NOASSOC) { + nsAutoString shellArg; + shellArg.AssignLiteral(u"shell32.dll,OpenAs_RunDLL "); + shellArg.Append(mResolvedPath); + seinfo.lpFile = L"RUNDLL32.EXE"; + seinfo.lpParameters = shellArg.get(); + if (ShellExecuteExW(&seinfo)) { + return NS_OK; + } + r = GetLastError(); + } + if (r < 32) { + switch (r) { + case 0: + case SE_ERR_OOM: + return NS_ERROR_OUT_OF_MEMORY; + case ERROR_FILE_NOT_FOUND: + return NS_ERROR_FILE_NOT_FOUND; + case ERROR_PATH_NOT_FOUND: + return NS_ERROR_FILE_UNRECOGNIZED_PATH; + case ERROR_BAD_FORMAT: + return NS_ERROR_FILE_CORRUPTED; + case SE_ERR_ACCESSDENIED: + return NS_ERROR_FILE_ACCESS_DENIED; + case SE_ERR_ASSOCINCOMPLETE: + case SE_ERR_NOASSOC: + return NS_ERROR_UNEXPECTED; + case SE_ERR_DDEBUSY: + case SE_ERR_DDEFAIL: + case SE_ERR_DDETIMEOUT: + return NS_ERROR_NOT_AVAILABLE; + case SE_ERR_DLLNOTFOUND: + return NS_ERROR_FAILURE; + case SE_ERR_SHARE: + return NS_ERROR_FILE_IS_LOCKED; + default: + return NS_ERROR_FILE_EXECUTION_FAILED; + } + } + return NS_OK; +} + +nsresult +NS_NewLocalFile(const nsAString& aPath, bool aFollowLinks, nsIFile** aResult) +{ + RefPtr file = new nsLocalFile(); + + file->SetFollowLinks(aFollowLinks); + + if (!aPath.IsEmpty()) { + nsresult rv = file->InitWithPath(aPath); + if (NS_FAILED(rv)) { + return rv; + } + } + + file.forget(aResult); + return NS_OK; +} + +//----------------------------------------------------------------------------- +// Native (lossy) interface +//----------------------------------------------------------------------------- + +NS_IMETHODIMP +nsLocalFile::InitWithNativePath(const nsACString& aFilePath) +{ + nsAutoString tmp; + nsresult rv = NS_CopyNativeToUnicode(aFilePath, tmp); + if (NS_SUCCEEDED(rv)) { + return InitWithPath(tmp); + } + + return rv; +} + +NS_IMETHODIMP +nsLocalFile::AppendNative(const nsACString& aNode) +{ + nsAutoString tmp; + nsresult rv = NS_CopyNativeToUnicode(aNode, tmp); + if (NS_SUCCEEDED(rv)) { + return Append(tmp); + } + + return rv; +} + +NS_IMETHODIMP +nsLocalFile::AppendRelativeNativePath(const nsACString& aNode) +{ + nsAutoString tmp; + nsresult rv = NS_CopyNativeToUnicode(aNode, tmp); + if (NS_SUCCEEDED(rv)) { + return AppendRelativePath(tmp); + } + return rv; +} + + +NS_IMETHODIMP +nsLocalFile::GetNativeLeafName(nsACString& aLeafName) +{ + //NS_WARNING("This API is lossy. Use GetLeafName !"); + nsAutoString tmp; + nsresult rv = GetLeafName(tmp); + if (NS_SUCCEEDED(rv)) { + rv = NS_CopyUnicodeToNative(tmp, aLeafName); + } + + return rv; +} + +NS_IMETHODIMP +nsLocalFile::SetNativeLeafName(const nsACString& aLeafName) +{ + nsAutoString tmp; + nsresult rv = NS_CopyNativeToUnicode(aLeafName, tmp); + if (NS_SUCCEEDED(rv)) { + return SetLeafName(tmp); + } + + return rv; +} + + +NS_IMETHODIMP +nsLocalFile::GetNativePath(nsACString& aResult) +{ + //NS_WARNING("This API is lossy. Use GetPath !"); + nsAutoString tmp; + nsresult rv = GetPath(tmp); + if (NS_SUCCEEDED(rv)) { + rv = NS_CopyUnicodeToNative(tmp, aResult); + } + + return rv; +} + + +NS_IMETHODIMP +nsLocalFile::GetNativeCanonicalPath(nsACString& aResult) +{ + NS_WARNING("This method is lossy. Use GetCanonicalPath !"); + EnsureShortPath(); + NS_CopyUnicodeToNative(mShortWorkingPath, aResult); + return NS_OK; +} + + +NS_IMETHODIMP +nsLocalFile::CopyToNative(nsIFile* aNewParentDir, const nsACString& aNewName) +{ + // Check we are correctly initialized. + CHECK_mWorkingPath(); + + if (aNewName.IsEmpty()) { + return CopyTo(aNewParentDir, EmptyString()); + } + + nsAutoString tmp; + nsresult rv = NS_CopyNativeToUnicode(aNewName, tmp); + if (NS_SUCCEEDED(rv)) { + return CopyTo(aNewParentDir, tmp); + } + + return rv; +} + +NS_IMETHODIMP +nsLocalFile::CopyToFollowingLinksNative(nsIFile* aNewParentDir, + const nsACString& aNewName) +{ + if (aNewName.IsEmpty()) { + return CopyToFollowingLinks(aNewParentDir, EmptyString()); + } + + nsAutoString tmp; + nsresult rv = NS_CopyNativeToUnicode(aNewName, tmp); + if (NS_SUCCEEDED(rv)) { + return CopyToFollowingLinks(aNewParentDir, tmp); + } + + return rv; +} + +NS_IMETHODIMP +nsLocalFile::MoveToNative(nsIFile* aNewParentDir, const nsACString& aNewName) +{ + // Check we are correctly initialized. + CHECK_mWorkingPath(); + + if (aNewName.IsEmpty()) { + return MoveTo(aNewParentDir, EmptyString()); + } + + nsAutoString tmp; + nsresult rv = NS_CopyNativeToUnicode(aNewName, tmp); + if (NS_SUCCEEDED(rv)) { + return MoveTo(aNewParentDir, tmp); + } + + return rv; +} + +NS_IMETHODIMP +nsLocalFile::GetNativeTarget(nsACString& aResult) +{ + // Check we are correctly initialized. + CHECK_mWorkingPath(); + + NS_WARNING("This API is lossy. Use GetTarget !"); + nsAutoString tmp; + nsresult rv = GetTarget(tmp); + if (NS_SUCCEEDED(rv)) { + rv = NS_CopyUnicodeToNative(tmp, aResult); + } + + return rv; +} + +nsresult +NS_NewNativeLocalFile(const nsACString& aPath, bool aFollowLinks, + nsIFile** aResult) +{ + nsAutoString buf; + nsresult rv = NS_CopyNativeToUnicode(aPath, buf); + if (NS_FAILED(rv)) { + *aResult = nullptr; + return rv; + } + return NS_NewLocalFile(buf, aFollowLinks, aResult); +} + +void +nsLocalFile::EnsureShortPath() +{ + if (!mShortWorkingPath.IsEmpty()) { + return; + } + + WCHAR shortPath[MAX_PATH + 1]; + DWORD lengthNeeded = ::GetShortPathNameW(mWorkingPath.get(), shortPath, + ArrayLength(shortPath)); + // If an error occurred then lengthNeeded is set to 0 or the length of the + // needed buffer including null termination. If it succeeds the number of + // wide characters not including null termination is returned. + if (lengthNeeded != 0 && lengthNeeded < ArrayLength(shortPath)) { + mShortWorkingPath.Assign(shortPath); + } else { + mShortWorkingPath.Assign(mWorkingPath); + } +} + +// nsIHashable + +NS_IMETHODIMP +nsLocalFile::Equals(nsIHashable* aOther, bool* aResult) +{ + nsCOMPtr otherfile(do_QueryInterface(aOther)); + if (!otherfile) { + *aResult = false; + return NS_OK; + } + + return Equals(otherfile, aResult); +} + +NS_IMETHODIMP +nsLocalFile::GetHashCode(uint32_t* aResult) +{ + // In order for short and long path names to hash to the same value we + // always hash on the short pathname. + EnsureShortPath(); + + *aResult = HashString(mShortWorkingPath); + return NS_OK; +} + +//----------------------------------------------------------------------------- +// nsLocalFile +//----------------------------------------------------------------------------- + +void +nsLocalFile::GlobalInit() +{ + DebugOnly rv = NS_CreateShortcutResolver(); + NS_ASSERTION(NS_SUCCEEDED(rv), "Shortcut resolver could not be created"); +} + +void +nsLocalFile::GlobalShutdown() +{ + NS_DestroyShortcutResolver(); +} + +NS_IMPL_ISUPPORTS(nsDriveEnumerator, nsISimpleEnumerator) + +nsDriveEnumerator::nsDriveEnumerator() +{ +} + +nsDriveEnumerator::~nsDriveEnumerator() +{ +} + +nsresult +nsDriveEnumerator::Init() +{ + /* If the length passed to GetLogicalDriveStrings is smaller + * than the length of the string it would return, it returns + * the length required for the string. */ + DWORD length = GetLogicalDriveStringsW(0, 0); + /* The string is null terminated */ + if (!mDrives.SetLength(length + 1, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + if (!GetLogicalDriveStringsW(length, wwc(mDrives.BeginWriting()))) { + return NS_ERROR_FAILURE; + } + mDrives.BeginReading(mStartOfCurrentDrive); + mDrives.EndReading(mEndOfDrivesString); + return NS_OK; +} + +NS_IMETHODIMP +nsDriveEnumerator::HasMoreElements(bool* aHasMore) +{ + *aHasMore = *mStartOfCurrentDrive != L'\0'; + return NS_OK; +} + +NS_IMETHODIMP +nsDriveEnumerator::GetNext(nsISupports** aNext) +{ + /* GetLogicalDrives stored in mDrives is a concatenation + * of null terminated strings, followed by a null terminator. + * mStartOfCurrentDrive is an iterator pointing at the first + * character of the current drive. */ + if (*mStartOfCurrentDrive == L'\0') { + *aNext = nullptr; + return NS_OK; + } + + nsAString::const_iterator driveEnd = mStartOfCurrentDrive; + FindCharInReadable(L'\0', driveEnd, mEndOfDrivesString); + nsString drive(Substring(mStartOfCurrentDrive, driveEnd)); + mStartOfCurrentDrive = ++driveEnd; + + nsIFile* file; + nsresult rv = NS_NewLocalFile(drive, false, &file); + + *aNext = file; + return rv; +} diff --git a/xpcom/io/nsLocalFileWin.h b/xpcom/io/nsLocalFileWin.h new file mode 100644 index 000000000..abef2c106 --- /dev/null +++ b/xpcom/io/nsLocalFileWin.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 _nsLocalFileWIN_H_ +#define _nsLocalFileWIN_H_ + +#include "nscore.h" +#include "nsError.h" +#include "nsString.h" +#include "nsCRT.h" +#include "nsIFile.h" +#include "nsIFactory.h" +#include "nsILocalFileWin.h" +#include "nsIHashable.h" +#include "nsIClassInfoImpl.h" +#include "prio.h" + +#include "mozilla/Attributes.h" + +#include "windows.h" +#include "shlobj.h" + +#include + +class nsLocalFile final + : public nsILocalFileWin + , public nsIHashable +{ +public: + NS_DEFINE_STATIC_CID_ACCESSOR(NS_LOCAL_FILE_CID) + + nsLocalFile(); + + static nsresult nsLocalFileConstructor(nsISupports* aOuter, + const nsIID& aIID, + void** aInstancePtr); + + // nsISupports interface + NS_DECL_THREADSAFE_ISUPPORTS + + // nsIFile interface + NS_DECL_NSIFILE + + // nsILocalFile interface + NS_DECL_NSILOCALFILE + + // nsILocalFileWin interface + NS_DECL_NSILOCALFILEWIN + + // nsIHashable interface + NS_DECL_NSIHASHABLE + +public: + static void GlobalInit(); + static void GlobalShutdown(); + + // Removes registry command handler parameters, quotes, and expands environment strings. + static bool CleanupCmdHandlerPath(nsAString& aCommandHandler); + +private: + // CopyMove and CopySingleFile constants for |options| parameter: + enum CopyFileOption { + FollowSymlinks = 1u << 0, + Move = 1u << 1, + SkipNtfsAclReset = 1u << 2, + Rename = 1u << 3 + }; + + nsLocalFile(const nsLocalFile& aOther); + ~nsLocalFile() + { + } + + bool mDirty; // cached information can only be used when this is false + bool mResolveDirty; + bool mFollowSymlinks; // should we follow symlinks when working on this file + + // this string will always be in native format! + nsString mWorkingPath; + + // this will be the resolved path of shortcuts, it will *NEVER* + // be returned to the user + nsString mResolvedPath; + + // this string, if not empty, is the *short* pathname that represents + // mWorkingPath + nsString mShortWorkingPath; + + PRFileInfo64 mFileInfo64; + + void MakeDirty() + { + mDirty = true; + mResolveDirty = true; + mShortWorkingPath.Truncate(); + } + + nsresult ResolveAndStat(); + nsresult Resolve(); + nsresult ResolveShortcut(); + + void EnsureShortPath(); + + nsresult CopyMove(nsIFile* aNewParentDir, const nsAString& aNewName, + uint32_t aOptions); + nsresult CopySingleFile(nsIFile* aSource, nsIFile* aDest, + const nsAString& aNewName, uint32_t aOptions); + + nsresult SetModDate(int64_t aLastModifiedTime, const wchar_t* aFilePath); + nsresult HasFileAttribute(DWORD aFileAttrib, bool* aResult); + nsresult AppendInternal(const nsAFlatString& aNode, + bool aMultipleComponents); + + nsresult OpenNSPRFileDescMaybeShareDelete(int32_t aFlags, + int32_t aMode, + bool aShareDelete, + PRFileDesc** aResult); +}; + +#endif diff --git a/xpcom/io/nsMultiplexInputStream.cpp b/xpcom/io/nsMultiplexInputStream.cpp new file mode 100644 index 000000000..4aa397c37 --- /dev/null +++ b/xpcom/io/nsMultiplexInputStream.cpp @@ -0,0 +1,835 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 multiplex stream concatenates a list of input streams into a single + * stream. + */ + +#include "mozilla/Attributes.h" +#include "mozilla/MathAlgorithms.h" +#include "mozilla/Mutex.h" + +#include "base/basictypes.h" + +#include "nsMultiplexInputStream.h" +#include "nsICloneableInputStream.h" +#include "nsIMultiplexInputStream.h" +#include "nsISeekableStream.h" +#include "nsCOMPtr.h" +#include "nsCOMArray.h" +#include "nsIClassInfoImpl.h" +#include "nsIIPCSerializableInputStream.h" +#include "mozilla/ipc/InputStreamUtils.h" + +using namespace mozilla; +using namespace mozilla::ipc; + +using mozilla::DeprecatedAbs; +using mozilla::Maybe; +using mozilla::Nothing; +using mozilla::Some; + +class nsMultiplexInputStream final + : public nsIMultiplexInputStream + , public nsISeekableStream + , public nsIIPCSerializableInputStream + , public nsICloneableInputStream +{ +public: + nsMultiplexInputStream(); + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIINPUTSTREAM + NS_DECL_NSIMULTIPLEXINPUTSTREAM + NS_DECL_NSISEEKABLESTREAM + NS_DECL_NSIIPCSERIALIZABLEINPUTSTREAM + NS_DECL_NSICLONEABLEINPUTSTREAM + +private: + ~nsMultiplexInputStream() + { + } + + struct MOZ_STACK_CLASS ReadSegmentsState + { + nsCOMPtr mThisStream; + uint32_t mOffset; + nsWriteSegmentFun mWriter; + void* mClosure; + bool mDone; + }; + + static nsresult ReadSegCb(nsIInputStream* aIn, void* aClosure, + const char* aFromRawSegment, uint32_t aToOffset, + uint32_t aCount, uint32_t* aWriteCount); + + Mutex mLock; // Protects access to all data members. + nsTArray> mStreams; + uint32_t mCurrentStream; + bool mStartedReadingCurrent; + nsresult mStatus; +}; + +NS_IMPL_ADDREF(nsMultiplexInputStream) +NS_IMPL_RELEASE(nsMultiplexInputStream) + +NS_IMPL_CLASSINFO(nsMultiplexInputStream, nullptr, nsIClassInfo::THREADSAFE, + NS_MULTIPLEXINPUTSTREAM_CID) + +NS_IMPL_QUERY_INTERFACE_CI(nsMultiplexInputStream, + nsIMultiplexInputStream, + nsIInputStream, + nsISeekableStream, + nsIIPCSerializableInputStream, + nsICloneableInputStream) +NS_IMPL_CI_INTERFACE_GETTER(nsMultiplexInputStream, + nsIMultiplexInputStream, + nsIInputStream, + nsISeekableStream) + +static nsresult +AvailableMaybeSeek(nsIInputStream* aStream, uint64_t* aResult) +{ + nsresult rv = aStream->Available(aResult); + if (rv == NS_BASE_STREAM_CLOSED) { + // Blindly seek to the current position if Available() returns + // NS_BASE_STREAM_CLOSED. + // If nsIFileInputStream is closed in Read() due to CLOSE_ON_EOF flag, + // Seek() could reopen the file if REOPEN_ON_REWIND flag is set. + nsCOMPtr seekable = do_QueryInterface(aStream); + if (seekable) { + nsresult rvSeek = seekable->Seek(nsISeekableStream::NS_SEEK_CUR, 0); + if (NS_SUCCEEDED(rvSeek)) { + rv = aStream->Available(aResult); + } + } + } + return rv; +} + +static nsresult +TellMaybeSeek(nsISeekableStream* aSeekable, int64_t* aResult) +{ + nsresult rv = aSeekable->Tell(aResult); + if (rv == NS_BASE_STREAM_CLOSED) { + // Blindly seek to the current position if Tell() returns + // NS_BASE_STREAM_CLOSED. + // If nsIFileInputStream is closed in Read() due to CLOSE_ON_EOF flag, + // Seek() could reopen the file if REOPEN_ON_REWIND flag is set. + nsresult rvSeek = aSeekable->Seek(nsISeekableStream::NS_SEEK_CUR, 0); + if (NS_SUCCEEDED(rvSeek)) { + rv = aSeekable->Tell(aResult); + } + } + return rv; +} + +nsMultiplexInputStream::nsMultiplexInputStream() + : mLock("nsMultiplexInputStream lock"), + mCurrentStream(0), + mStartedReadingCurrent(false), + mStatus(NS_OK) +{ +} + +NS_IMETHODIMP +nsMultiplexInputStream::GetCount(uint32_t* aCount) +{ + MutexAutoLock lock(mLock); + *aCount = mStreams.Length(); + return NS_OK; +} + +NS_IMETHODIMP +nsMultiplexInputStream::AppendStream(nsIInputStream* aStream) +{ + MutexAutoLock lock(mLock); + return mStreams.AppendElement(aStream) ? NS_OK : NS_ERROR_OUT_OF_MEMORY; +} + +NS_IMETHODIMP +nsMultiplexInputStream::InsertStream(nsIInputStream* aStream, uint32_t aIndex) +{ + MutexAutoLock lock(mLock); + mStreams.InsertElementAt(aIndex, aStream); + if (mCurrentStream > aIndex || + (mCurrentStream == aIndex && mStartedReadingCurrent)) { + ++mCurrentStream; + } + return NS_OK; +} + +NS_IMETHODIMP +nsMultiplexInputStream::RemoveStream(uint32_t aIndex) +{ + MutexAutoLock lock(mLock); + mStreams.RemoveElementAt(aIndex); + if (mCurrentStream > aIndex) { + --mCurrentStream; + } else if (mCurrentStream == aIndex) { + mStartedReadingCurrent = false; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsMultiplexInputStream::GetStream(uint32_t aIndex, nsIInputStream** aResult) +{ + MutexAutoLock lock(mLock); + *aResult = mStreams.SafeElementAt(aIndex, nullptr); + if (NS_WARN_IF(!*aResult)) { + return NS_ERROR_NOT_AVAILABLE; + } + + NS_ADDREF(*aResult); + return NS_OK; +} + +NS_IMETHODIMP +nsMultiplexInputStream::Close() +{ + MutexAutoLock lock(mLock); + mStatus = NS_BASE_STREAM_CLOSED; + + nsresult rv = NS_OK; + + uint32_t len = mStreams.Length(); + for (uint32_t i = 0; i < len; ++i) { + nsresult rv2 = mStreams[i]->Close(); + // We still want to close all streams, but we should return an error + if (NS_FAILED(rv2)) { + rv = rv2; + } + } + return rv; +} + +NS_IMETHODIMP +nsMultiplexInputStream::Available(uint64_t* aResult) +{ + MutexAutoLock lock(mLock); + if (NS_FAILED(mStatus)) { + return mStatus; + } + + uint64_t avail = 0; + + uint32_t len = mStreams.Length(); + for (uint32_t i = mCurrentStream; i < len; i++) { + uint64_t streamAvail; + mStatus = AvailableMaybeSeek(mStreams[i], &streamAvail); + if (NS_WARN_IF(NS_FAILED(mStatus))) { + return mStatus; + } + avail += streamAvail; + } + *aResult = avail; + return NS_OK; +} + +NS_IMETHODIMP +nsMultiplexInputStream::Read(char* aBuf, uint32_t aCount, uint32_t* aResult) +{ + MutexAutoLock lock(mLock); + // It is tempting to implement this method in terms of ReadSegments, but + // that would prevent this class from being used with streams that only + // implement Read (e.g., file streams). + + *aResult = 0; + + if (mStatus == NS_BASE_STREAM_CLOSED) { + return NS_OK; + } + if (NS_FAILED(mStatus)) { + return mStatus; + } + + nsresult rv = NS_OK; + + uint32_t len = mStreams.Length(); + while (mCurrentStream < len && aCount) { + uint32_t read; + rv = mStreams[mCurrentStream]->Read(aBuf, aCount, &read); + + // XXX some streams return NS_BASE_STREAM_CLOSED to indicate EOF. + // (This is a bug in those stream implementations) + if (rv == NS_BASE_STREAM_CLOSED) { + NS_NOTREACHED("Input stream's Read method returned NS_BASE_STREAM_CLOSED"); + rv = NS_OK; + read = 0; + } else if (NS_FAILED(rv)) { + break; + } + + if (read == 0) { + ++mCurrentStream; + mStartedReadingCurrent = false; + } else { + NS_ASSERTION(aCount >= read, "Read more than requested"); + *aResult += read; + aCount -= read; + aBuf += read; + mStartedReadingCurrent = true; + } + } + return *aResult ? NS_OK : rv; +} + +NS_IMETHODIMP +nsMultiplexInputStream::ReadSegments(nsWriteSegmentFun aWriter, void* aClosure, + uint32_t aCount, uint32_t* aResult) +{ + MutexAutoLock lock(mLock); + + if (mStatus == NS_BASE_STREAM_CLOSED) { + *aResult = 0; + return NS_OK; + } + if (NS_FAILED(mStatus)) { + return mStatus; + } + + NS_ASSERTION(aWriter, "missing aWriter"); + + nsresult rv = NS_OK; + ReadSegmentsState state; + state.mThisStream = this; + state.mOffset = 0; + state.mWriter = aWriter; + state.mClosure = aClosure; + state.mDone = false; + + uint32_t len = mStreams.Length(); + while (mCurrentStream < len && aCount) { + uint32_t read; + rv = mStreams[mCurrentStream]->ReadSegments(ReadSegCb, &state, aCount, &read); + + // XXX some streams return NS_BASE_STREAM_CLOSED to indicate EOF. + // (This is a bug in those stream implementations) + if (rv == NS_BASE_STREAM_CLOSED) { + NS_NOTREACHED("Input stream's Read method returned NS_BASE_STREAM_CLOSED"); + rv = NS_OK; + read = 0; + } + + // if |aWriter| decided to stop reading segments... + if (state.mDone || NS_FAILED(rv)) { + break; + } + + // if stream is empty, then advance to the next stream. + if (read == 0) { + ++mCurrentStream; + mStartedReadingCurrent = false; + } else { + NS_ASSERTION(aCount >= read, "Read more than requested"); + state.mOffset += read; + aCount -= read; + mStartedReadingCurrent = true; + } + } + + // if we successfully read some data, then this call succeeded. + *aResult = state.mOffset; + return state.mOffset ? NS_OK : rv; +} + +nsresult +nsMultiplexInputStream::ReadSegCb(nsIInputStream* aIn, void* aClosure, + const char* aFromRawSegment, + uint32_t aToOffset, uint32_t aCount, + uint32_t* aWriteCount) +{ + nsresult rv; + ReadSegmentsState* state = (ReadSegmentsState*)aClosure; + rv = (state->mWriter)(state->mThisStream, + state->mClosure, + aFromRawSegment, + aToOffset + state->mOffset, + aCount, + aWriteCount); + if (NS_FAILED(rv)) { + state->mDone = true; + } + return rv; +} + +NS_IMETHODIMP +nsMultiplexInputStream::IsNonBlocking(bool* aNonBlocking) +{ + MutexAutoLock lock(mLock); + + uint32_t len = mStreams.Length(); + if (len == 0) { + // Claim to be non-blocking, since we won't block the caller. + // On the other hand we'll never return NS_BASE_STREAM_WOULD_BLOCK, + // so maybe we should claim to be blocking? It probably doesn't + // matter in practice. + *aNonBlocking = true; + return NS_OK; + } + for (uint32_t i = 0; i < len; ++i) { + nsresult rv = mStreams[i]->IsNonBlocking(aNonBlocking); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + // If one is non-blocking the entire stream becomes non-blocking + // (except that we don't implement nsIAsyncInputStream, so there's + // not much for the caller to do if Read returns "would block") + if (*aNonBlocking) { + return NS_OK; + } + } + return NS_OK; +} + +NS_IMETHODIMP +nsMultiplexInputStream::Seek(int32_t aWhence, int64_t aOffset) +{ + MutexAutoLock lock(mLock); + + if (NS_FAILED(mStatus)) { + return mStatus; + } + + nsresult rv; + + uint32_t oldCurrentStream = mCurrentStream; + bool oldStartedReadingCurrent = mStartedReadingCurrent; + + if (aWhence == NS_SEEK_SET) { + int64_t remaining = aOffset; + if (aOffset == 0) { + mCurrentStream = 0; + } + for (uint32_t i = 0; i < mStreams.Length(); ++i) { + nsCOMPtr stream = + do_QueryInterface(mStreams[i]); + if (!stream) { + return NS_ERROR_FAILURE; + } + + // See if all remaining streams should be rewound + if (remaining == 0) { + if (i < oldCurrentStream || + (i == oldCurrentStream && oldStartedReadingCurrent)) { + rv = stream->Seek(NS_SEEK_SET, 0); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + continue; + } else { + break; + } + } + + // Get position in current stream + int64_t streamPos; + if (i > oldCurrentStream || + (i == oldCurrentStream && !oldStartedReadingCurrent)) { + streamPos = 0; + } else { + rv = TellMaybeSeek(stream, &streamPos); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + + // See if we need to seek current stream forward or backward + if (remaining < streamPos) { + rv = stream->Seek(NS_SEEK_SET, remaining); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + mCurrentStream = i; + mStartedReadingCurrent = remaining != 0; + + remaining = 0; + } else if (remaining > streamPos) { + if (i < oldCurrentStream) { + // We're already at end so no need to seek this stream + remaining -= streamPos; + NS_ASSERTION(remaining >= 0, "Remaining invalid"); + } else { + uint64_t avail; + rv = AvailableMaybeSeek(mStreams[i], &avail); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + int64_t newPos = XPCOM_MIN(remaining, streamPos + (int64_t)avail); + + rv = stream->Seek(NS_SEEK_SET, newPos); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + mCurrentStream = i; + mStartedReadingCurrent = true; + + remaining -= newPos; + NS_ASSERTION(remaining >= 0, "Remaining invalid"); + } + } else { + NS_ASSERTION(remaining == streamPos, "Huh?"); + remaining = 0; + } + } + + return NS_OK; + } + + if (aWhence == NS_SEEK_CUR && aOffset > 0) { + int64_t remaining = aOffset; + for (uint32_t i = mCurrentStream; remaining && i < mStreams.Length(); ++i) { + nsCOMPtr stream = + do_QueryInterface(mStreams[i]); + + uint64_t avail; + rv = AvailableMaybeSeek(mStreams[i], &avail); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + int64_t seek = XPCOM_MIN((int64_t)avail, remaining); + + rv = stream->Seek(NS_SEEK_CUR, seek); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + mCurrentStream = i; + mStartedReadingCurrent = true; + + remaining -= seek; + } + + return NS_OK; + } + + if (aWhence == NS_SEEK_CUR && aOffset < 0) { + int64_t remaining = -aOffset; + for (uint32_t i = mCurrentStream; remaining && i != (uint32_t)-1; --i) { + nsCOMPtr stream = + do_QueryInterface(mStreams[i]); + + int64_t pos; + rv = TellMaybeSeek(stream, &pos); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + int64_t seek = XPCOM_MIN(pos, remaining); + + rv = stream->Seek(NS_SEEK_CUR, -seek); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + mCurrentStream = i; + mStartedReadingCurrent = seek != -pos; + + remaining -= seek; + } + + return NS_OK; + } + + if (aWhence == NS_SEEK_CUR) { + NS_ASSERTION(aOffset == 0, "Should have handled all non-zero values"); + + return NS_OK; + } + + if (aWhence == NS_SEEK_END) { + if (aOffset > 0) { + return NS_ERROR_INVALID_ARG; + } + int64_t remaining = aOffset; + for (uint32_t i = mStreams.Length() - 1; i != (uint32_t)-1; --i) { + nsCOMPtr stream = + do_QueryInterface(mStreams[i]); + + // See if all remaining streams should be seeked to end + if (remaining == 0) { + if (i >= oldCurrentStream) { + rv = stream->Seek(NS_SEEK_END, 0); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } else { + break; + } + } + + // Get position in current stream + int64_t streamPos; + if (i < oldCurrentStream) { + streamPos = 0; + } else { + uint64_t avail; + rv = AvailableMaybeSeek(mStreams[i], &avail); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + streamPos = avail; + } + + // See if we have enough data in the current stream. + if (DeprecatedAbs(remaining) < streamPos) { + rv = stream->Seek(NS_SEEK_END, remaining); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + mCurrentStream = i; + mStartedReadingCurrent = true; + + remaining = 0; + } else if (DeprecatedAbs(remaining) > streamPos) { + if (i > oldCurrentStream || + (i == oldCurrentStream && !oldStartedReadingCurrent)) { + // We're already at start so no need to seek this stream + remaining += streamPos; + } else { + int64_t avail; + rv = TellMaybeSeek(stream, &avail); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + int64_t newPos = streamPos + XPCOM_MIN(avail, DeprecatedAbs(remaining)); + + rv = stream->Seek(NS_SEEK_END, -newPos); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + mCurrentStream = i; + mStartedReadingCurrent = true; + + remaining += newPos; + } + } else { + NS_ASSERTION(remaining == streamPos, "Huh?"); + remaining = 0; + } + } + + return NS_OK; + } + + // other Seeks not implemented yet + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsMultiplexInputStream::Tell(int64_t* aResult) +{ + MutexAutoLock lock(mLock); + + if (NS_FAILED(mStatus)) { + return mStatus; + } + + nsresult rv; + int64_t ret64 = 0; + uint32_t i, last; + last = mStartedReadingCurrent ? mCurrentStream + 1 : mCurrentStream; + for (i = 0; i < last; ++i) { + nsCOMPtr stream = do_QueryInterface(mStreams[i]); + if (NS_WARN_IF(!stream)) { + return NS_ERROR_NO_INTERFACE; + } + + int64_t pos; + rv = TellMaybeSeek(stream, &pos); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + ret64 += pos; + } + *aResult = ret64; + + return NS_OK; +} + +NS_IMETHODIMP +nsMultiplexInputStream::SetEOF() +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +nsresult +nsMultiplexInputStreamConstructor(nsISupports* aOuter, + REFNSIID aIID, + void** aResult) +{ + *aResult = nullptr; + + if (aOuter) { + return NS_ERROR_NO_AGGREGATION; + } + + RefPtr inst = new nsMultiplexInputStream(); + + return inst->QueryInterface(aIID, aResult); +} + +void +nsMultiplexInputStream::Serialize(InputStreamParams& aParams, + FileDescriptorArray& aFileDescriptors) +{ + MutexAutoLock lock(mLock); + + MultiplexInputStreamParams params; + + uint32_t streamCount = mStreams.Length(); + + if (streamCount) { + InfallibleTArray& streams = params.streams(); + + streams.SetCapacity(streamCount); + for (uint32_t index = 0; index < streamCount; index++) { + InputStreamParams childStreamParams; + SerializeInputStream(mStreams[index], childStreamParams, + aFileDescriptors); + + streams.AppendElement(childStreamParams); + } + } + + params.currentStream() = mCurrentStream; + params.status() = mStatus; + params.startedReadingCurrent() = mStartedReadingCurrent; + + aParams = params; +} + +bool +nsMultiplexInputStream::Deserialize(const InputStreamParams& aParams, + const FileDescriptorArray& aFileDescriptors) +{ + if (aParams.type() != + InputStreamParams::TMultiplexInputStreamParams) { + NS_ERROR("Received unknown parameters from the other process!"); + return false; + } + + const MultiplexInputStreamParams& params = + aParams.get_MultiplexInputStreamParams(); + + const InfallibleTArray& streams = params.streams(); + + uint32_t streamCount = streams.Length(); + for (uint32_t index = 0; index < streamCount; index++) { + nsCOMPtr stream = + DeserializeInputStream(streams[index], aFileDescriptors); + if (!stream) { + NS_WARNING("Deserialize failed!"); + return false; + } + + if (NS_FAILED(AppendStream(stream))) { + NS_WARNING("AppendStream failed!"); + return false; + } + } + + mCurrentStream = params.currentStream(); + mStatus = params.status(); + mStartedReadingCurrent = params.startedReadingCurrent(); + + return true; +} + +Maybe +nsMultiplexInputStream::ExpectedSerializedLength() +{ + MutexAutoLock lock(mLock); + + bool lengthValueExists = false; + uint64_t expectedLength = 0; + uint32_t streamCount = mStreams.Length(); + for (uint32_t index = 0; index < streamCount; index++) { + nsCOMPtr stream = do_QueryInterface(mStreams[index]); + if (!stream) { + continue; + } + Maybe length = stream->ExpectedSerializedLength(); + if (length.isNothing()) { + continue; + } + lengthValueExists = true; + expectedLength += length.value(); + } + return lengthValueExists ? Some(expectedLength) : Nothing(); +} + +NS_IMETHODIMP +nsMultiplexInputStream::GetCloneable(bool* aCloneable) +{ + MutexAutoLock lock(mLock); + //XXXnsm Cloning a multiplex stream which has started reading is not permitted + //right now. + if (mCurrentStream > 0 || mStartedReadingCurrent) { + *aCloneable = false; + return NS_OK; + } + + uint32_t len = mStreams.Length(); + for (uint32_t i = 0; i < len; ++i) { + nsCOMPtr cis = do_QueryInterface(mStreams[i]); + if (!cis || !cis->GetCloneable()) { + *aCloneable = false; + return NS_OK; + } + } + + *aCloneable = true; + return NS_OK; +} + +NS_IMETHODIMP +nsMultiplexInputStream::Clone(nsIInputStream** aClone) +{ + MutexAutoLock lock(mLock); + + //XXXnsm Cloning a multiplex stream which has started reading is not permitted + //right now. + if (mCurrentStream > 0 || mStartedReadingCurrent) { + return NS_ERROR_FAILURE; + } + + RefPtr clone = new nsMultiplexInputStream(); + + nsresult rv; + uint32_t len = mStreams.Length(); + for (uint32_t i = 0; i < len; ++i) { + nsCOMPtr substream = do_QueryInterface(mStreams[i]); + if (NS_WARN_IF(!substream)) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr clonedSubstream; + rv = substream->Clone(getter_AddRefs(clonedSubstream)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = clone->AppendStream(clonedSubstream); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + + clone.forget(aClone); + return NS_OK; +} diff --git a/xpcom/io/nsMultiplexInputStream.h b/xpcom/io/nsMultiplexInputStream.h new file mode 100644 index 000000000..381051dca --- /dev/null +++ b/xpcom/io/nsMultiplexInputStream.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/. */ + +/** + * The multiplex stream concatenates a list of input streams into a single + * stream. + */ + +#ifndef _nsMultiplexInputStream_h_ +#define _nsMultiplexInputStream_h_ + +#include "nsIMultiplexInputStream.h" + +#define NS_MULTIPLEXINPUTSTREAM_CONTRACTID "@mozilla.org/io/multiplex-input-stream;1" +#define NS_MULTIPLEXINPUTSTREAM_CID \ + { /* 565e3a2c-1dd2-11b2-8da1-b4cef17e568d */ \ + 0x565e3a2c, \ + 0x1dd2, \ + 0x11b2, \ + {0x8d, 0xa1, 0xb4, 0xce, 0xf1, 0x7e, 0x56, 0x8d} \ + } + +extern nsresult nsMultiplexInputStreamConstructor(nsISupports* aOuter, + REFNSIID aIID, + void** aResult); + +#endif // _nsMultiplexInputStream_h_ diff --git a/xpcom/io/nsNativeCharsetUtils.cpp b/xpcom/io/nsNativeCharsetUtils.cpp new file mode 100644 index 000000000..e53307af5 --- /dev/null +++ b/xpcom/io/nsNativeCharsetUtils.cpp @@ -0,0 +1,1044 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "xpcom-private.h" + +//----------------------------------------------------------------------------- +// XP_MACOSX or ANDROID +//----------------------------------------------------------------------------- +#if defined(XP_MACOSX) || defined(ANDROID) + +#include "nsAString.h" +#include "nsReadableUtils.h" +#include "nsString.h" + +nsresult +NS_CopyNativeToUnicode(const nsACString& aInput, nsAString& aOutput) +{ + CopyUTF8toUTF16(aInput, aOutput); + return NS_OK; +} + +nsresult +NS_CopyUnicodeToNative(const nsAString& aInput, nsACString& aOutput) +{ + CopyUTF16toUTF8(aInput, aOutput); + return NS_OK; +} + +void +NS_StartupNativeCharsetUtils() +{ +} + +void +NS_ShutdownNativeCharsetUtils() +{ +} + + +//----------------------------------------------------------------------------- +// XP_UNIX +//----------------------------------------------------------------------------- +#elif defined(XP_UNIX) + +#include // mbtowc, wctomb +#include // setlocale +#include "mozilla/Mutex.h" +#include "nscore.h" +#include "nsAString.h" +#include "nsReadableUtils.h" + +using namespace mozilla; + +// +// choose a conversion library. we used to use mbrtowc/wcrtomb under Linux, +// but that doesn't work for non-BMP characters whether we use '-fshort-wchar' +// or not (see bug 206811 and +// news://news.mozilla.org:119/bajml3$fvr1@ripley.netscape.com). we now use +// iconv for all platforms where nltypes.h and nllanginfo.h are present +// along with iconv. +// +#if defined(HAVE_ICONV) && defined(HAVE_NL_TYPES_H) && defined(HAVE_LANGINFO_CODESET) +#define USE_ICONV 1 +#else +#define USE_STDCONV 1 +#endif + +static void +isolatin1_to_utf16(const char** aInput, uint32_t* aInputLeft, + char16_t** aOutput, uint32_t* aOutputLeft) +{ + while (*aInputLeft && *aOutputLeft) { + **aOutput = (unsigned char)** aInput; + (*aInput)++; + (*aInputLeft)--; + (*aOutput)++; + (*aOutputLeft)--; + } +} + +static void +utf16_to_isolatin1(const char16_t** aInput, uint32_t* aInputLeft, + char** aOutput, uint32_t* aOutputLeft) +{ + while (*aInputLeft && *aOutputLeft) { + **aOutput = (unsigned char)**aInput; + (*aInput)++; + (*aInputLeft)--; + (*aOutput)++; + (*aOutputLeft)--; + } +} + +//----------------------------------------------------------------------------- +// conversion using iconv +//----------------------------------------------------------------------------- +#if defined(USE_ICONV) +#include // CODESET +#include // nl_langinfo +#include // iconv_open, iconv, iconv_close +#include +#include "plstr.h" + +#if defined(HAVE_ICONV_WITH_CONST_INPUT) +#define ICONV_INPUT(x) (x) +#else +#define ICONV_INPUT(x) ((char **)x) +#endif + +// solaris definitely needs this, but we'll enable it by default +// just in case... but we know for sure that iconv(3) in glibc +// doesn't need this. +#if !defined(__GLIBC__) +#define ENABLE_UTF8_FALLBACK_SUPPORT +#endif + +#define INVALID_ICONV_T ((iconv_t)-1) + +static inline size_t +xp_iconv(iconv_t converter, + const char** aInput, size_t* aInputLeft, + char** aOutput, size_t* aOutputLeft) +{ + size_t res, outputAvail = *aOutputLeft; + res = iconv(converter, ICONV_INPUT(aInput), aInputLeft, aOutput, aOutputLeft); + if (res == (size_t)-1) { + // on some platforms (e.g., linux) iconv will fail with + // E2BIG if it cannot convert _all_ of its input. it'll + // still adjust all of the in/out params correctly, so we + // can ignore this error. the assumption is that we will + // be called again to complete the conversion. + if ((errno == E2BIG) && (*aOutputLeft < outputAvail)) { + res = 0; + } + } + return res; +} + +static inline void +xp_iconv_reset(iconv_t converter) +{ + // NOTE: the man pages on Solaris claim that you can pass nullptr + // for all parameter to reset the converter, but beware the + // evil Solaris crash if you go down this route >:-) + + const char* zero_char_in_ptr = nullptr; + char* zero_char_out_ptr = nullptr; + size_t zero_size_in = 0; + size_t zero_size_out = 0; + + xp_iconv(converter, + &zero_char_in_ptr, + &zero_size_in, + &zero_char_out_ptr, + &zero_size_out); +} + +static inline iconv_t +xp_iconv_open(const char** to_list, const char** from_list) +{ + iconv_t res; + const char** from_name; + const char** to_name; + + // try all possible combinations to locate a converter. + to_name = to_list; + while (*to_name) { + if (**to_name) { + from_name = from_list; + while (*from_name) { + if (**from_name) { + res = iconv_open(*to_name, *from_name); + if (res != INVALID_ICONV_T) { + return res; + } + } + from_name++; + } + } + to_name++; + } + + return INVALID_ICONV_T; +} + +/* + * char16_t[] is NOT a UCS-2 array BUT a UTF-16 string. Therefore, we + * have to use UTF-16 with iconv(3) on platforms where it's supported. + * However, the way UTF-16 and UCS-2 are interpreted varies across platforms + * and implementations of iconv(3). On Tru64, it also depends on the environment + * variable. To avoid the trouble arising from byte-swapping + * (bug 208809), we have to try UTF-16LE/BE and UCS-2LE/BE before falling + * back to UTF-16 and UCS-2 and variants. We assume that UTF-16 and UCS-2 + * on systems without UTF-16LE/BE and UCS-2LE/BE have the native endianness, + * which isn't the case of glibc 2.1.x, for which we use 'UNICODELITTLE' + * and 'UNICODEBIG'. It's also not true of Tru64 V4 when the environment + * variable ICONV_BYTEORDER is set to 'big-endian', about which not much + * can be done other than adding a note in the release notes. (bug 206811) + */ +static const char* UTF_16_NAMES[] = { +#if defined(IS_LITTLE_ENDIAN) + "UTF-16LE", +#if defined(__GLIBC__) + "UNICODELITTLE", +#endif + "UCS-2LE", +#else + "UTF-16BE", +#if defined(__GLIBC__) + "UNICODEBIG", +#endif + "UCS-2BE", +#endif + "UTF-16", + "UCS-2", + "UCS2", + "UCS_2", + "ucs-2", + "ucs2", + "ucs_2", + nullptr +}; + +#if defined(ENABLE_UTF8_FALLBACK_SUPPORT) +static const char* UTF_8_NAMES[] = { + "UTF-8", + "UTF8", + "UTF_8", + "utf-8", + "utf8", + "utf_8", + nullptr +}; +#endif + +static const char* ISO_8859_1_NAMES[] = { + "ISO-8859-1", +#if !defined(__GLIBC__) + "ISO8859-1", + "ISO88591", + "ISO_8859_1", + "ISO8859_1", + "iso-8859-1", + "iso8859-1", + "iso88591", + "iso_8859_1", + "iso8859_1", +#endif + nullptr +}; + +class nsNativeCharsetConverter +{ +public: + nsNativeCharsetConverter(); + ~nsNativeCharsetConverter(); + + nsresult NativeToUnicode(const char** aInput, uint32_t* aInputLeft, + char16_t** aOutput, uint32_t* aOutputLeft); + nsresult UnicodeToNative(const char16_t** aInput, uint32_t* aInputLeft, + char** aOutput, uint32_t* aOutputLeft); + + static void GlobalInit(); + static void GlobalShutdown(); + static bool IsNativeUTF8(); + +private: + static iconv_t gNativeToUnicode; + static iconv_t gUnicodeToNative; +#if defined(ENABLE_UTF8_FALLBACK_SUPPORT) + static iconv_t gNativeToUTF8; + static iconv_t gUTF8ToNative; + static iconv_t gUnicodeToUTF8; + static iconv_t gUTF8ToUnicode; +#endif + static Mutex* gLock; + static bool gInitialized; + static bool gIsNativeUTF8; + + static void LazyInit(); + + static void Lock() + { + if (gLock) { + gLock->Lock(); + } + } + static void Unlock() + { + if (gLock) { + gLock->Unlock(); + } + } +}; + +iconv_t nsNativeCharsetConverter::gNativeToUnicode = INVALID_ICONV_T; +iconv_t nsNativeCharsetConverter::gUnicodeToNative = INVALID_ICONV_T; +#if defined(ENABLE_UTF8_FALLBACK_SUPPORT) +iconv_t nsNativeCharsetConverter::gNativeToUTF8 = INVALID_ICONV_T; +iconv_t nsNativeCharsetConverter::gUTF8ToNative = INVALID_ICONV_T; +iconv_t nsNativeCharsetConverter::gUnicodeToUTF8 = INVALID_ICONV_T; +iconv_t nsNativeCharsetConverter::gUTF8ToUnicode = INVALID_ICONV_T; +#endif +Mutex* nsNativeCharsetConverter::gLock = nullptr; +bool nsNativeCharsetConverter::gInitialized = false; +bool nsNativeCharsetConverter::gIsNativeUTF8 = false; + +void +nsNativeCharsetConverter::LazyInit() +{ + // LazyInit may be called before NS_StartupNativeCharsetUtils, but + // the setlocale it does has to be called before nl_langinfo. Like in + // NS_StartupNativeCharsetUtils, assume we are called early enough that + // we are the first to care about the locale's charset. + if (!gLock) { + setlocale(LC_CTYPE, ""); + } + const char* blank_list[] = { "", nullptr }; + const char** native_charset_list = blank_list; + const char* native_charset = nl_langinfo(CODESET); + if (!native_charset) { + NS_ERROR("native charset is unknown"); + // fallback to ISO-8859-1 + native_charset_list = ISO_8859_1_NAMES; + } else { + native_charset_list[0] = native_charset; + } + + // Most, if not all, Unixen supporting UTF-8 and nl_langinfo(CODESET) + // return 'UTF-8' (or 'utf-8') + if (!PL_strcasecmp(native_charset, "UTF-8")) { + gIsNativeUTF8 = true; + } + + gNativeToUnicode = xp_iconv_open(UTF_16_NAMES, native_charset_list); + gUnicodeToNative = xp_iconv_open(native_charset_list, UTF_16_NAMES); + +#if defined(ENABLE_UTF8_FALLBACK_SUPPORT) + if (gNativeToUnicode == INVALID_ICONV_T) { + gNativeToUTF8 = xp_iconv_open(UTF_8_NAMES, native_charset_list); + gUTF8ToUnicode = xp_iconv_open(UTF_16_NAMES, UTF_8_NAMES); + NS_ASSERTION(gNativeToUTF8 != INVALID_ICONV_T, "no native to utf-8 converter"); + NS_ASSERTION(gUTF8ToUnicode != INVALID_ICONV_T, "no utf-8 to utf-16 converter"); + } + if (gUnicodeToNative == INVALID_ICONV_T) { + gUnicodeToUTF8 = xp_iconv_open(UTF_8_NAMES, UTF_16_NAMES); + gUTF8ToNative = xp_iconv_open(native_charset_list, UTF_8_NAMES); + NS_ASSERTION(gUnicodeToUTF8 != INVALID_ICONV_T, "no utf-16 to utf-8 converter"); + NS_ASSERTION(gUTF8ToNative != INVALID_ICONV_T, "no utf-8 to native converter"); + } +#else + NS_ASSERTION(gNativeToUnicode != INVALID_ICONV_T, "no native to utf-16 converter"); + NS_ASSERTION(gUnicodeToNative != INVALID_ICONV_T, "no utf-16 to native converter"); +#endif + + /* + * On Solaris 8 (and newer?), the iconv modules converting to UCS-2 + * prepend a byte order mark unicode character (BOM, u+FEFF) during + * the first use of the iconv converter. The same is the case of + * glibc 2.2.9x and Tru64 V5 (see bug 208809) when 'UTF-16' is used. + * However, we use 'UTF-16LE/BE' in both cases, instead so that we + * should be safe. But just in case... + * + * This dummy conversion gets rid of the BOMs and fixes bug 153562. + */ + char dummy_input[1] = { ' ' }; + char dummy_output[4]; + + if (gNativeToUnicode != INVALID_ICONV_T) { + const char* input = dummy_input; + size_t input_left = sizeof(dummy_input); + char* output = dummy_output; + size_t output_left = sizeof(dummy_output); + + xp_iconv(gNativeToUnicode, &input, &input_left, &output, &output_left); + } +#if defined(ENABLE_UTF8_FALLBACK_SUPPORT) + if (gUTF8ToUnicode != INVALID_ICONV_T) { + const char* input = dummy_input; + size_t input_left = sizeof(dummy_input); + char* output = dummy_output; + size_t output_left = sizeof(dummy_output); + + xp_iconv(gUTF8ToUnicode, &input, &input_left, &output, &output_left); + } +#endif + + gInitialized = true; +} + +void +nsNativeCharsetConverter::GlobalInit() +{ + gLock = new Mutex("nsNativeCharsetConverter.gLock"); +} + +void +nsNativeCharsetConverter::GlobalShutdown() +{ + delete gLock; + gLock = nullptr; + + if (gNativeToUnicode != INVALID_ICONV_T) { + iconv_close(gNativeToUnicode); + gNativeToUnicode = INVALID_ICONV_T; + } + + if (gUnicodeToNative != INVALID_ICONV_T) { + iconv_close(gUnicodeToNative); + gUnicodeToNative = INVALID_ICONV_T; + } + +#if defined(ENABLE_UTF8_FALLBACK_SUPPORT) + if (gNativeToUTF8 != INVALID_ICONV_T) { + iconv_close(gNativeToUTF8); + gNativeToUTF8 = INVALID_ICONV_T; + } + if (gUTF8ToNative != INVALID_ICONV_T) { + iconv_close(gUTF8ToNative); + gUTF8ToNative = INVALID_ICONV_T; + } + if (gUnicodeToUTF8 != INVALID_ICONV_T) { + iconv_close(gUnicodeToUTF8); + gUnicodeToUTF8 = INVALID_ICONV_T; + } + if (gUTF8ToUnicode != INVALID_ICONV_T) { + iconv_close(gUTF8ToUnicode); + gUTF8ToUnicode = INVALID_ICONV_T; + } +#endif + + gInitialized = false; +} + +nsNativeCharsetConverter::nsNativeCharsetConverter() +{ + Lock(); + if (!gInitialized) { + LazyInit(); + } +} + +nsNativeCharsetConverter::~nsNativeCharsetConverter() +{ + // reset converters for next time + if (gNativeToUnicode != INVALID_ICONV_T) { + xp_iconv_reset(gNativeToUnicode); + } + if (gUnicodeToNative != INVALID_ICONV_T) { + xp_iconv_reset(gUnicodeToNative); + } +#if defined(ENABLE_UTF8_FALLBACK_SUPPORT) + if (gNativeToUTF8 != INVALID_ICONV_T) { + xp_iconv_reset(gNativeToUTF8); + } + if (gUTF8ToNative != INVALID_ICONV_T) { + xp_iconv_reset(gUTF8ToNative); + } + if (gUnicodeToUTF8 != INVALID_ICONV_T) { + xp_iconv_reset(gUnicodeToUTF8); + } + if (gUTF8ToUnicode != INVALID_ICONV_T) { + xp_iconv_reset(gUTF8ToUnicode); + } +#endif + Unlock(); +} + +nsresult +nsNativeCharsetConverter::NativeToUnicode(const char** aInput, + uint32_t* aInputLeft, + char16_t** aOutput, + uint32_t* aOutputLeft) +{ + size_t res = 0; + size_t inLeft = (size_t)*aInputLeft; + size_t outLeft = (size_t)*aOutputLeft * 2; + + if (gNativeToUnicode != INVALID_ICONV_T) { + + res = xp_iconv(gNativeToUnicode, aInput, &inLeft, (char**)aOutput, &outLeft); + + *aInputLeft = inLeft; + *aOutputLeft = outLeft / 2; + if (res != (size_t)-1) { + return NS_OK; + } + + NS_WARNING("conversion from native to utf-16 failed"); + + // reset converter + xp_iconv_reset(gNativeToUnicode); + } +#if defined(ENABLE_UTF8_FALLBACK_SUPPORT) + else if ((gNativeToUTF8 != INVALID_ICONV_T) && + (gUTF8ToUnicode != INVALID_ICONV_T)) { + // convert first to UTF8, then from UTF8 to UCS2 + const char* in = *aInput; + + char ubuf[1024]; + + // we assume we're always called with enough space in |aOutput|, + // so convert many chars at a time... + while (inLeft) { + char* p = ubuf; + size_t n = sizeof(ubuf); + res = xp_iconv(gNativeToUTF8, &in, &inLeft, &p, &n); + if (res == (size_t)-1) { + NS_ERROR("conversion from native to utf-8 failed"); + break; + } + NS_ASSERTION(outLeft > 0, "bad assumption"); + p = ubuf; + n = sizeof(ubuf) - n; + res = xp_iconv(gUTF8ToUnicode, (const char**)&p, &n, + (char**)aOutput, &outLeft); + if (res == (size_t)-1) { + NS_ERROR("conversion from utf-8 to utf-16 failed"); + break; + } + } + + (*aInput) += (*aInputLeft - inLeft); + *aInputLeft = inLeft; + *aOutputLeft = outLeft / 2; + + if (res != (size_t)-1) { + return NS_OK; + } + + // reset converters + xp_iconv_reset(gNativeToUTF8); + xp_iconv_reset(gUTF8ToUnicode); + } +#endif + + // fallback: zero-pad and hope for the best + // XXX This is lame and we have to do better. + isolatin1_to_utf16(aInput, aInputLeft, aOutput, aOutputLeft); + + return NS_OK; +} + +nsresult +nsNativeCharsetConverter::UnicodeToNative(const char16_t** aInput, + uint32_t* aInputLeft, + char** aOutput, + uint32_t* aOutputLeft) +{ + size_t res = 0; + size_t inLeft = (size_t)*aInputLeft * 2; + size_t outLeft = (size_t)*aOutputLeft; + + if (gUnicodeToNative != INVALID_ICONV_T) { + res = xp_iconv(gUnicodeToNative, (const char**)aInput, &inLeft, + aOutput, &outLeft); + + *aInputLeft = inLeft / 2; + *aOutputLeft = outLeft; + if (res != (size_t)-1) { + return NS_OK; + } + + NS_ERROR("iconv failed"); + + // reset converter + xp_iconv_reset(gUnicodeToNative); + } +#if defined(ENABLE_UTF8_FALLBACK_SUPPORT) + else if ((gUnicodeToUTF8 != INVALID_ICONV_T) && + (gUTF8ToNative != INVALID_ICONV_T)) { + const char* in = (const char*)*aInput; + + char ubuf[6]; // max utf-8 char length (really only needs to be 4 bytes) + + // convert one uchar at a time... + while (inLeft && outLeft) { + char* p = ubuf; + size_t n = sizeof(ubuf), one_uchar = sizeof(char16_t); + res = xp_iconv(gUnicodeToUTF8, &in, &one_uchar, &p, &n); + if (res == (size_t)-1) { + NS_ERROR("conversion from utf-16 to utf-8 failed"); + break; + } + p = ubuf; + n = sizeof(ubuf) - n; + res = xp_iconv(gUTF8ToNative, (const char**)&p, &n, aOutput, &outLeft); + if (res == (size_t)-1) { + if (errno == E2BIG) { + // not enough room for last uchar... back up and return. + in -= sizeof(char16_t); + res = 0; + } else { + NS_ERROR("conversion from utf-8 to native failed"); + } + break; + } + inLeft -= sizeof(char16_t); + } + + (*aInput) += (*aInputLeft - inLeft / 2); + *aInputLeft = inLeft / 2; + *aOutputLeft = outLeft; + if (res != (size_t)-1) { + return NS_OK; + } + + // reset converters + xp_iconv_reset(gUnicodeToUTF8); + xp_iconv_reset(gUTF8ToNative); + } +#endif + + // fallback: truncate and hope for the best + // XXX This is lame and we have to do better. + utf16_to_isolatin1(aInput, aInputLeft, aOutput, aOutputLeft); + + return NS_OK; +} + +bool +nsNativeCharsetConverter::IsNativeUTF8() +{ + if (!gInitialized) { + Lock(); + if (!gInitialized) { + LazyInit(); + } + Unlock(); + } + return gIsNativeUTF8; +} + +#endif // USE_ICONV + +//----------------------------------------------------------------------------- +// conversion using mb[r]towc/wc[r]tomb +//----------------------------------------------------------------------------- +#if defined(USE_STDCONV) +#if defined(HAVE_WCRTOMB) || defined(HAVE_MBRTOWC) +#include // mbrtowc, wcrtomb +#endif + +class nsNativeCharsetConverter +{ +public: + nsNativeCharsetConverter(); + + nsresult NativeToUnicode(const char** aInput, uint32_t* aInputLeft, + char16_t** aOutput, uint32_t* aOutputLeft); + nsresult UnicodeToNative(const char16_t** aInput, uint32_t* aInputLeft, + char** aOutput, uint32_t* aOutputLeft); + + static void GlobalInit(); + static void GlobalShutdown() { } + static bool IsNativeUTF8(); + +private: + static bool gWCharIsUnicode; + +#if defined(HAVE_WCRTOMB) || defined(HAVE_MBRTOWC) + mbstate_t ps; +#endif +}; + +bool nsNativeCharsetConverter::gWCharIsUnicode = false; + +nsNativeCharsetConverter::nsNativeCharsetConverter() +{ +#if defined(HAVE_WCRTOMB) || defined(HAVE_MBRTOWC) + memset(&ps, 0, sizeof(ps)); +#endif +} + +void +nsNativeCharsetConverter::GlobalInit() +{ + // verify that wchar_t for the current locale is actually unicode. + // if it is not, then we should avoid calling mbtowc/wctomb and + // just fallback on zero-pad/truncation conversion. + // + // this test cannot be done at build time because the encoding of + // wchar_t may depend on the runtime locale. sad, but true!! + // + // so, if wchar_t is unicode then converting an ASCII character + // to wchar_t should not change its numeric value. we'll just + // check what happens with the ASCII 'a' character. + // + // this test is not perfect... obviously, it could yield false + // positives, but then at least ASCII text would be converted + // properly (or maybe just the 'a' character) -- oh well :( + + char a = 'a'; + unsigned int w = 0; + + int res = mbtowc((wchar_t*)&w, &a, 1); + + gWCharIsUnicode = (res != -1 && w == 'a'); + +#ifdef DEBUG + if (!gWCharIsUnicode) { + NS_WARNING("wchar_t is not unicode (unicode conversion will be lossy)"); + } +#endif +} + +nsresult +nsNativeCharsetConverter::NativeToUnicode(const char** aInput, + uint32_t* aInputLeft, + char16_t** aOutput, + uint32_t* aOutputLeft) +{ + if (gWCharIsUnicode) { + int incr; + + // cannot use wchar_t here since it may have been redefined (e.g., + // via -fshort-wchar). hopefully, sizeof(tmp) is sufficient XP. + unsigned int tmp = 0; + while (*aInputLeft && *aOutputLeft) { +#ifdef HAVE_MBRTOWC + incr = (int)mbrtowc((wchar_t*)&tmp, *aInput, *aInputLeft, &ps); +#else + // XXX is this thread-safe? + incr = (int)mbtowc((wchar_t*)&tmp, *aInput, *aInputLeft); +#endif + if (incr < 0) { + NS_WARNING("mbtowc failed: possible charset mismatch"); + // zero-pad and hope for the best + tmp = (unsigned char)**aInput; + incr = 1; + } + ** aOutput = (char16_t)tmp; + (*aInput) += incr; + (*aInputLeft) -= incr; + (*aOutput)++; + (*aOutputLeft)--; + } + } else { + // wchar_t isn't unicode, so the best we can do is treat the + // input as if it is isolatin1 :( + isolatin1_to_utf16(aInput, aInputLeft, aOutput, aOutputLeft); + } + + return NS_OK; +} + +nsresult +nsNativeCharsetConverter::UnicodeToNative(const char16_t** aInput, + uint32_t* aInputLeft, + char** aOutput, + uint32_t* aOutputLeft) +{ + if (gWCharIsUnicode) { + int incr; + + while (*aInputLeft && *aOutputLeft >= MB_CUR_MAX) { +#ifdef HAVE_WCRTOMB + incr = (int)wcrtomb(*aOutput, (wchar_t)**aInput, &ps); +#else + // XXX is this thread-safe? + incr = (int)wctomb(*aOutput, (wchar_t)**aInput); +#endif + if (incr < 0) { + NS_WARNING("mbtowc failed: possible charset mismatch"); + ** aOutput = (unsigned char)**aInput; // truncate + incr = 1; + } + // most likely we're dead anyways if this assertion should fire + NS_ASSERTION(uint32_t(incr) <= *aOutputLeft, "wrote beyond end of string"); + (*aOutput) += incr; + (*aOutputLeft) -= incr; + (*aInput)++; + (*aInputLeft)--; + } + } else { + // wchar_t isn't unicode, so the best we can do is treat the + // input as if it is isolatin1 :( + utf16_to_isolatin1(aInput, aInputLeft, aOutput, aOutputLeft); + } + + return NS_OK; +} + +// XXX : for now, return false +bool +nsNativeCharsetConverter::IsNativeUTF8() +{ + return false; +} + +#endif // USE_STDCONV + +//----------------------------------------------------------------------------- +// API implementation +//----------------------------------------------------------------------------- + +nsresult +NS_CopyNativeToUnicode(const nsACString& aInput, nsAString& aOutput) +{ + aOutput.Truncate(); + + uint32_t inputLen = aInput.Length(); + + nsACString::const_iterator iter; + aInput.BeginReading(iter); + + // + // OPTIMIZATION: preallocate space for largest possible result; convert + // directly into the result buffer to avoid intermediate buffer copy. + // + // this will generally result in a larger allocation, but that seems + // better than an extra buffer copy. + // + if (!aOutput.SetLength(inputLen, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + nsAString::iterator out_iter; + aOutput.BeginWriting(out_iter); + + char16_t* result = out_iter.get(); + uint32_t resultLeft = inputLen; + + const char* buf = iter.get(); + uint32_t bufLeft = inputLen; + + nsNativeCharsetConverter conv; + nsresult rv = conv.NativeToUnicode(&buf, &bufLeft, &result, &resultLeft); + if (NS_SUCCEEDED(rv)) { + NS_ASSERTION(bufLeft == 0, "did not consume entire input buffer"); + aOutput.SetLength(inputLen - resultLeft); + } + return rv; +} + +nsresult +NS_CopyUnicodeToNative(const nsAString& aInput, nsACString& aOutput) +{ + aOutput.Truncate(); + + nsAString::const_iterator iter, end; + aInput.BeginReading(iter); + aInput.EndReading(end); + + // cannot easily avoid intermediate buffer copy. + char temp[4096]; + + nsNativeCharsetConverter conv; + + const char16_t* buf = iter.get(); + uint32_t bufLeft = Distance(iter, end); + while (bufLeft) { + char* p = temp; + uint32_t tempLeft = sizeof(temp); + + nsresult rv = conv.UnicodeToNative(&buf, &bufLeft, &p, &tempLeft); + if (NS_FAILED(rv)) { + return rv; + } + + if (tempLeft < sizeof(temp)) { + aOutput.Append(temp, sizeof(temp) - tempLeft); + } + } + return NS_OK; +} + +bool +NS_IsNativeUTF8() +{ + return nsNativeCharsetConverter::IsNativeUTF8(); +} + +void +NS_StartupNativeCharsetUtils() +{ + // + // need to initialize the locale or else charset conversion will fail. + // better not delay this in case some other component alters the locale + // settings. + // + // XXX we assume that we are called early enough that we should + // always be the first to care about the locale's charset. + // + setlocale(LC_CTYPE, ""); + + nsNativeCharsetConverter::GlobalInit(); +} + +void +NS_ShutdownNativeCharsetUtils() +{ + nsNativeCharsetConverter::GlobalShutdown(); +} + +//----------------------------------------------------------------------------- +// XP_WIN +//----------------------------------------------------------------------------- +#elif defined(XP_WIN) + +#include +#include "nsString.h" +#include "nsAString.h" +#include "nsReadableUtils.h" + +using namespace mozilla; + +nsresult +NS_CopyNativeToUnicode(const nsACString& aInput, nsAString& aOutput) +{ + uint32_t inputLen = aInput.Length(); + + nsACString::const_iterator iter; + aInput.BeginReading(iter); + + const char* buf = iter.get(); + + // determine length of result + uint32_t resultLen = 0; + int n = ::MultiByteToWideChar(CP_ACP, 0, buf, inputLen, nullptr, 0); + if (n > 0) { + resultLen += n; + } + + // allocate sufficient space + if (!aOutput.SetLength(resultLen, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + if (resultLen > 0) { + nsAString::iterator out_iter; + aOutput.BeginWriting(out_iter); + + char16_t* result = out_iter.get(); + + ::MultiByteToWideChar(CP_ACP, 0, buf, inputLen, wwc(result), resultLen); + } + return NS_OK; +} + +nsresult +NS_CopyUnicodeToNative(const nsAString& aInput, nsACString& aOutput) +{ + uint32_t inputLen = aInput.Length(); + + nsAString::const_iterator iter; + aInput.BeginReading(iter); + + char16ptr_t buf = iter.get(); + + // determine length of result + uint32_t resultLen = 0; + + int n = ::WideCharToMultiByte(CP_ACP, 0, buf, inputLen, nullptr, 0, + nullptr, nullptr); + if (n > 0) { + resultLen += n; + } + + // allocate sufficient space + if (!aOutput.SetLength(resultLen, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + if (resultLen > 0) { + nsACString::iterator out_iter; + aOutput.BeginWriting(out_iter); + + // default "defaultChar" is '?', which is an illegal character on windows + // file system. That will cause file uncreatable. Change it to '_' + const char defaultChar = '_'; + + char* result = out_iter.get(); + + ::WideCharToMultiByte(CP_ACP, 0, buf, inputLen, result, resultLen, + &defaultChar, nullptr); + } + return NS_OK; +} + +// moved from widget/windows/nsToolkit.cpp +int32_t +NS_ConvertAtoW(const char* aStrInA, int aBufferSize, char16_t* aStrOutW) +{ + return MultiByteToWideChar(CP_ACP, 0, aStrInA, -1, wwc(aStrOutW), aBufferSize); +} + +int32_t +NS_ConvertWtoA(const char16_t* aStrInW, int aBufferSizeOut, + char* aStrOutA, const char* aDefault) +{ + if ((!aStrInW) || (!aStrOutA) || (aBufferSizeOut <= 0)) { + return 0; + } + + int numCharsConverted = WideCharToMultiByte(CP_ACP, 0, char16ptr_t(aStrInW), -1, + aStrOutA, aBufferSizeOut, + aDefault, nullptr); + + if (!numCharsConverted) { + if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) { + // Overflow, add missing null termination but return 0 + aStrOutA[aBufferSizeOut - 1] = '\0'; + } else { + // Other error, clear string and return 0 + aStrOutA[0] = '\0'; + } + } else if (numCharsConverted < aBufferSizeOut) { + // Add 2nd null (really necessary?) + aStrOutA[numCharsConverted] = '\0'; + } + + return numCharsConverted; +} + +#else + +#include "nsReadableUtils.h" + +nsresult +NS_CopyNativeToUnicode(const nsACString& aInput, nsAString& aOutput) +{ + CopyASCIItoUTF16(aInput, aOutput); + return NS_OK; +} + +nsresult +NS_CopyUnicodeToNative(const nsAString& aInput, nsACString& aOutput) +{ + LossyCopyUTF16toASCII(aInput, aOutput); + return NS_OK; +} + +void +NS_StartupNativeCharsetUtils() +{ +} + +void +NS_ShutdownNativeCharsetUtils() +{ +} + +#endif diff --git a/xpcom/io/nsNativeCharsetUtils.h b/xpcom/io/nsNativeCharsetUtils.h new file mode 100644 index 000000000..5c1e670d5 --- /dev/null +++ b/xpcom/io/nsNativeCharsetUtils.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 nsNativeCharsetUtils_h__ +#define nsNativeCharsetUtils_h__ + + +/*****************************************************************************\ + * * + * **** NOTICE **** * + * * + * *** THESE ARE NOT GENERAL PURPOSE CONVERTERS *** * + * * + * NS_CopyNativeToUnicode / NS_CopyUnicodeToNative should only be used * + * for converting *FILENAMES* between native and unicode. They are not * + * designed or tested for general encoding converter use. * + * * +\*****************************************************************************/ + +/** + * thread-safe conversion routines that do not depend on uconv libraries. + */ +nsresult NS_CopyNativeToUnicode(const nsACString& aInput, nsAString& aOutput); +nsresult NS_CopyUnicodeToNative(const nsAString& aInput, nsACString& aOutput); + +/* + * This function indicates whether the character encoding used in the file + * system (more exactly what's used for |GetNativeFoo| and |SetNativeFoo| + * of |nsIFile|) is UTF-8 or not. Knowing that helps us avoid an + * unncessary encoding conversion in some cases. For instance, to get the leaf + * name in UTF-8 out of nsIFile, we can just use |GetNativeLeafName| rather + * than using |GetLeafName| and converting the result to UTF-8 if the file + * system encoding is UTF-8. + * On Unix (but not on Mac OS X), it depends on the locale and is not known + * in advance (at the compilation time) so that this function needs to be + * a real function. On Mac OS X it's always UTF-8 while on Windows + * and other platforms (e.g. OS2), it's never UTF-8. + */ +#if defined(XP_UNIX) && !defined(XP_MACOSX) && !defined(ANDROID) +bool NS_IsNativeUTF8(); +#else +inline bool +NS_IsNativeUTF8() +{ +#if defined(XP_MACOSX) || defined(ANDROID) + return true; +#else + return false; +#endif +} +#endif + + +/** + * internal + */ +void NS_StartupNativeCharsetUtils(); +void NS_ShutdownNativeCharsetUtils(); + +#endif // nsNativeCharsetUtils_h__ diff --git a/xpcom/io/nsPipe.h b/xpcom/io/nsPipe.h new file mode 100644 index 000000000..29ce0ce96 --- /dev/null +++ b/xpcom/io/nsPipe.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 nsPipe_h__ +#define nsPipe_h__ + +#define NS_PIPE_CONTRACTID \ + "@mozilla.org/pipe;1" +#define NS_PIPE_CID \ +{ /* e4a0ee4e-0775-457b-9118-b3ae97a7c758 */ \ + 0xe4a0ee4e, \ + 0x0775, \ + 0x457b, \ + {0x91,0x18,0xb3,0xae,0x97,0xa7,0xc7,0x58} \ +} + +// Generic factory constructor for the nsPipe class +nsresult +nsPipeConstructor(nsISupports* outer, REFNSIID iid, void** result); + +#endif // !defined(nsPipe_h__) diff --git a/xpcom/io/nsPipe3.cpp b/xpcom/io/nsPipe3.cpp new file mode 100644 index 000000000..56932adfc --- /dev/null +++ b/xpcom/io/nsPipe3.cpp @@ -0,0 +1,2007 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. 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/Attributes.h" +#include "mozilla/ReentrantMonitor.h" +#include "nsIBufferedStreams.h" +#include "nsICloneableInputStream.h" +#include "nsIPipe.h" +#include "nsIEventTarget.h" +#include "nsISeekableStream.h" +#include "mozilla/RefPtr.h" +#include "nsSegmentedBuffer.h" +#include "nsStreamUtils.h" +#include "nsCOMPtr.h" +#include "nsCRT.h" +#include "mozilla/Logging.h" +#include "nsIClassInfoImpl.h" +#include "nsAlgorithm.h" +#include "nsMemory.h" +#include "nsIAsyncInputStream.h" +#include "nsIAsyncOutputStream.h" + +using namespace mozilla; + +#ifdef LOG +#undef LOG +#endif +// +// set MOZ_LOG=nsPipe:5 +// +static LazyLogModule sPipeLog("nsPipe"); +#define LOG(args) MOZ_LOG(sPipeLog, mozilla::LogLevel::Debug, args) + +#define DEFAULT_SEGMENT_SIZE 4096 +#define DEFAULT_SEGMENT_COUNT 16 + +class nsPipe; +class nsPipeEvents; +class nsPipeInputStream; +class nsPipeOutputStream; +class AutoReadSegment; + +namespace { + +enum MonitorAction +{ + DoNotNotifyMonitor, + NotifyMonitor +}; + +enum SegmentChangeResult +{ + SegmentNotChanged, + SegmentAdvanceBufferRead +}; + +} // namespace + +//----------------------------------------------------------------------------- + +// this class is used to delay notifications until the end of a particular +// scope. it helps avoid the complexity of issuing callbacks while inside +// a critical section. +class nsPipeEvents +{ +public: + nsPipeEvents() { } + ~nsPipeEvents(); + + inline void NotifyInputReady(nsIAsyncInputStream* aStream, + nsIInputStreamCallback* aCallback) + { + mInputList.AppendElement(InputEntry(aStream, aCallback)); + } + + inline void NotifyOutputReady(nsIAsyncOutputStream* aStream, + nsIOutputStreamCallback* aCallback) + { + NS_ASSERTION(!mOutputCallback, "already have an output event"); + mOutputStream = aStream; + mOutputCallback = aCallback; + } + +private: + struct InputEntry + { + InputEntry(nsIAsyncInputStream* aStream, nsIInputStreamCallback* aCallback) + : mStream(aStream) + , mCallback(aCallback) + { + MOZ_ASSERT(mStream); + MOZ_ASSERT(mCallback); + } + + nsCOMPtr mStream; + nsCOMPtr mCallback; + }; + + nsTArray mInputList; + + nsCOMPtr mOutputStream; + nsCOMPtr mOutputCallback; +}; + +//----------------------------------------------------------------------------- + +// This class is used to maintain input stream state. Its broken out from the +// nsPipeInputStream class because generally the nsPipe should be modifying +// this state and not the input stream itself. +struct nsPipeReadState +{ + nsPipeReadState() + : mReadCursor(nullptr) + , mReadLimit(nullptr) + , mSegment(0) + , mAvailable(0) + , mActiveRead(false) + , mNeedDrain(false) + { } + + char* mReadCursor; + char* mReadLimit; + int32_t mSegment; + uint32_t mAvailable; + + // This flag is managed using the AutoReadSegment RAII stack class. + bool mActiveRead; + + // Set to indicate that the input stream has closed and should be drained, + // but that drain has been delayed due to an active read. When the read + // completes, this flag indicate the drain should then be performed. + bool mNeedDrain; +}; + +//----------------------------------------------------------------------------- + +// an input end of a pipe (maintained as a list of refs within the pipe) +class nsPipeInputStream final + : public nsIAsyncInputStream + , public nsISeekableStream + , public nsISearchableInputStream + , public nsICloneableInputStream + , public nsIClassInfo + , public nsIBufferedInputStream +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIINPUTSTREAM + NS_DECL_NSIASYNCINPUTSTREAM + NS_DECL_NSISEEKABLESTREAM + NS_DECL_NSISEARCHABLEINPUTSTREAM + NS_DECL_NSICLONEABLEINPUTSTREAM + NS_DECL_NSICLASSINFO + NS_DECL_NSIBUFFEREDINPUTSTREAM + + explicit nsPipeInputStream(nsPipe* aPipe) + : mPipe(aPipe) + , mLogicalOffset(0) + , mInputStatus(NS_OK) + , mBlocking(true) + , mBlocked(false) + , mCallbackFlags(0) + { } + + explicit nsPipeInputStream(const nsPipeInputStream& aOther) + : mPipe(aOther.mPipe) + , mLogicalOffset(aOther.mLogicalOffset) + , mInputStatus(aOther.mInputStatus) + , mBlocking(aOther.mBlocking) + , mBlocked(false) + , mCallbackFlags(0) + , mReadState(aOther.mReadState) + { } + + nsresult Fill(); + void SetNonBlocking(bool aNonBlocking) + { + mBlocking = !aNonBlocking; + } + + uint32_t Available(); + + // synchronously wait for the pipe to become readable. + nsresult Wait(); + + // These two don't acquire the monitor themselves. Instead they + // expect their caller to have done so and to pass the monitor as + // evidence. + MonitorAction OnInputReadable(uint32_t aBytesWritten, nsPipeEvents&, + const ReentrantMonitorAutoEnter& ev); + MonitorAction OnInputException(nsresult, nsPipeEvents&, + const ReentrantMonitorAutoEnter& ev); + + nsPipeReadState& ReadState() + { + return mReadState; + } + + const nsPipeReadState& ReadState() const + { + return mReadState; + } + + nsresult Status() const; + + // A version of Status() that doesn't acquire the monitor. + nsresult Status(const ReentrantMonitorAutoEnter& ev) const; + +private: + virtual ~nsPipeInputStream(); + + RefPtr mPipe; + + int64_t mLogicalOffset; + // Individual input streams can be closed without effecting the rest of the + // pipe. So track individual input stream status separately. |mInputStatus| + // is protected by |mPipe->mReentrantMonitor|. + nsresult mInputStatus; + bool mBlocking; + + // these variables can only be accessed while inside the pipe's monitor + bool mBlocked; + nsCOMPtr mCallback; + uint32_t mCallbackFlags; + + // requires pipe's monitor; usually treat as an opaque token to pass to nsPipe + nsPipeReadState mReadState; +}; + +//----------------------------------------------------------------------------- + +// the output end of a pipe (allocated as a member of the pipe). +class nsPipeOutputStream + : public nsIAsyncOutputStream + , public nsIClassInfo +{ +public: + // since this class will be allocated as a member of the pipe, we do not + // need our own ref count. instead, we share the lifetime (the ref count) + // of the entire pipe. this macro is just convenience since it does not + // declare a mRefCount variable; however, don't let the name fool you... + // we are not inheriting from nsPipe ;-) + NS_DECL_ISUPPORTS_INHERITED + + NS_DECL_NSIOUTPUTSTREAM + NS_DECL_NSIASYNCOUTPUTSTREAM + NS_DECL_NSICLASSINFO + + explicit nsPipeOutputStream(nsPipe* aPipe) + : mPipe(aPipe) + , mWriterRefCnt(0) + , mLogicalOffset(0) + , mBlocking(true) + , mBlocked(false) + , mWritable(true) + , mCallbackFlags(0) + { } + + void SetNonBlocking(bool aNonBlocking) + { + mBlocking = !aNonBlocking; + } + void SetWritable(bool aWritable) + { + mWritable = aWritable; + } + + // synchronously wait for the pipe to become writable. + nsresult Wait(); + + MonitorAction OnOutputWritable(nsPipeEvents&); + MonitorAction OnOutputException(nsresult, nsPipeEvents&); + +private: + nsPipe* mPipe; + + // separate refcnt so that we know when to close the producer + mozilla::ThreadSafeAutoRefCnt mWriterRefCnt; + int64_t mLogicalOffset; + bool mBlocking; + + // these variables can only be accessed while inside the pipe's monitor + bool mBlocked; + bool mWritable; + nsCOMPtr mCallback; + uint32_t mCallbackFlags; +}; + +//----------------------------------------------------------------------------- + +class nsPipe final : public nsIPipe +{ +public: + friend class nsPipeInputStream; + friend class nsPipeOutputStream; + friend class AutoReadSegment; + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIPIPE + + // nsPipe methods: + nsPipe(); + +private: + ~nsPipe(); + + // + // Methods below may only be called while inside the pipe's monitor. Some + // of these methods require passing a ReentrantMonitorAutoEnter to prove the + // monitor is held. + // + + void PeekSegment(const nsPipeReadState& aReadState, uint32_t aIndex, + char*& aCursor, char*& aLimit); + SegmentChangeResult AdvanceReadSegment(nsPipeReadState& aReadState, + const ReentrantMonitorAutoEnter &ev); + bool ReadSegmentBeingWritten(nsPipeReadState& aReadState); + uint32_t CountSegmentReferences(int32_t aSegment); + void SetAllNullReadCursors(); + bool AllReadCursorsMatchWriteCursor(); + void RollBackAllReadCursors(char* aWriteCursor); + void UpdateAllReadCursors(char* aWriteCursor); + void ValidateAllReadCursors(); + uint32_t GetBufferSegmentCount(const nsPipeReadState& aReadState, + const ReentrantMonitorAutoEnter& ev) const; + bool IsAdvanceBufferFull(const ReentrantMonitorAutoEnter& ev) const; + + // + // methods below may be called while outside the pipe's monitor + // + + void DrainInputStream(nsPipeReadState& aReadState, nsPipeEvents& aEvents); + nsresult GetWriteSegment(char*& aSegment, uint32_t& aSegmentLen); + void AdvanceWriteCursor(uint32_t aCount); + + void OnInputStreamException(nsPipeInputStream* aStream, nsresult aReason); + void OnPipeException(nsresult aReason, bool aOutputOnly = false); + + nsresult CloneInputStream(nsPipeInputStream* aOriginal, + nsIInputStream** aCloneOut); + + // methods below should only be called by AutoReadSegment + nsresult GetReadSegment(nsPipeReadState& aReadState, const char*& aSegment, + uint32_t& aLength); + void ReleaseReadSegment(nsPipeReadState& aReadState, + nsPipeEvents& aEvents); + void AdvanceReadCursor(nsPipeReadState& aReadState, uint32_t aCount); + + // We can't inherit from both nsIInputStream and nsIOutputStream + // because they collide on their Close method. Consequently we nest their + // implementations to avoid the extra object allocation. + nsPipeOutputStream mOutput; + + // Since the input stream can be cloned, we may have more than one. Use + // a weak reference as the streams will clear their entry here in their + // destructor. Using a strong reference would create a reference cycle. + // Only usable while mReentrantMonitor is locked. + nsTArray mInputList; + + // But hold a strong ref to our original input stream. For backward + // compatibility we need to be able to consistently return this same + // object from GetInputStream(). Note, mOriginalInput is also stored + // in mInputList as a weak ref. + RefPtr mOriginalInput; + + ReentrantMonitor mReentrantMonitor; + nsSegmentedBuffer mBuffer; + + // The maximum number of segments to allow to be buffered in advance + // of the fastest reader. This is collection of segments is called + // the "advance buffer". + uint32_t mMaxAdvanceBufferSegmentCount; + + int32_t mWriteSegment; + char* mWriteCursor; + char* mWriteLimit; + + // |mStatus| is protected by |mReentrantMonitor|. + nsresult mStatus; + bool mInited; +}; + +//----------------------------------------------------------------------------- + +// RAII class representing an active read segment. When it goes out of scope +// it automatically updates the read cursor and releases the read segment. +class MOZ_STACK_CLASS AutoReadSegment final +{ +public: + AutoReadSegment(nsPipe* aPipe, nsPipeReadState& aReadState, + uint32_t aMaxLength) + : mPipe(aPipe) + , mReadState(aReadState) + , mStatus(NS_ERROR_FAILURE) + , mSegment(nullptr) + , mLength(0) + , mOffset(0) + { + MOZ_ASSERT(mPipe); + MOZ_ASSERT(!mReadState.mActiveRead); + mStatus = mPipe->GetReadSegment(mReadState, mSegment, mLength); + if (NS_SUCCEEDED(mStatus)) { + MOZ_ASSERT(mReadState.mActiveRead); + MOZ_ASSERT(mSegment); + mLength = std::min(mLength, aMaxLength); + MOZ_ASSERT(mLength); + } + } + + ~AutoReadSegment() + { + if (NS_SUCCEEDED(mStatus)) { + if (mOffset) { + mPipe->AdvanceReadCursor(mReadState, mOffset); + } else { + nsPipeEvents events; + mPipe->ReleaseReadSegment(mReadState, events); + } + } + MOZ_ASSERT(!mReadState.mActiveRead); + } + + nsresult Status() const + { + return mStatus; + } + + const char* Data() const + { + MOZ_ASSERT(NS_SUCCEEDED(mStatus)); + MOZ_ASSERT(mSegment); + return mSegment + mOffset; + } + + uint32_t Length() const + { + MOZ_ASSERT(NS_SUCCEEDED(mStatus)); + MOZ_ASSERT(mLength >= mOffset); + return mLength - mOffset; + } + + void + Advance(uint32_t aCount) + { + MOZ_ASSERT(NS_SUCCEEDED(mStatus)); + MOZ_ASSERT(aCount <= (mLength - mOffset)); + mOffset += aCount; + } + + nsPipeReadState& + ReadState() const + { + return mReadState; + } + +private: + // guaranteed to remain alive due to limited stack lifetime of AutoReadSegment + nsPipe* mPipe; + nsPipeReadState& mReadState; + nsresult mStatus; + const char* mSegment; + uint32_t mLength; + uint32_t mOffset; +}; + +// +// NOTES on buffer architecture: +// +// +-----------------+ - - mBuffer.GetSegment(0) +// | | +// + - - - - - - - - + - - nsPipeReadState.mReadCursor +// |/////////////////| +// |/////////////////| +// |/////////////////| +// |/////////////////| +// +-----------------+ - - nsPipeReadState.mReadLimit +// | +// +-----------------+ +// |/////////////////| +// |/////////////////| +// |/////////////////| +// |/////////////////| +// |/////////////////| +// |/////////////////| +// +-----------------+ +// | +// +-----------------+ - - mBuffer.GetSegment(mWriteSegment) +// |/////////////////| +// |/////////////////| +// |/////////////////| +// + - - - - - - - - + - - mWriteCursor +// | | +// | | +// +-----------------+ - - mWriteLimit +// +// (shaded region contains data) +// +// NOTE: Each input stream produced by the nsPipe contains its own, separate +// nsPipeReadState. This means there are multiple mReadCursor and +// mReadLimit values in play. The pipe cannot discard old data until +// all mReadCursors have moved beyond that point in the stream. +// +// Likewise, each input stream reader will have it's own amount of +// buffered data. The pipe size threshold, however, is only applied +// to the input stream that is being read fastest. We call this +// the "advance buffer" in that its in advance of all readers. We +// allow slower input streams to buffer more data so that we don't +// stall processing of the faster input stream. +// +// NOTE: on some systems (notably OS/2), the heap allocator uses an arena for +// small allocations (e.g., 64 byte allocations). this means that buffers may +// be allocated back-to-back. in the diagram above, for example, mReadLimit +// would actually be pointing at the beginning of the next segment. when +// making changes to this file, please keep this fact in mind. +// + +//----------------------------------------------------------------------------- +// nsPipe methods: +//----------------------------------------------------------------------------- + +nsPipe::nsPipe() + : mOutput(this) + , mOriginalInput(new nsPipeInputStream(this)) + , mReentrantMonitor("nsPipe.mReentrantMonitor") + , mMaxAdvanceBufferSegmentCount(0) + , mWriteSegment(-1) + , mWriteCursor(nullptr) + , mWriteLimit(nullptr) + , mStatus(NS_OK) + , mInited(false) +{ + mInputList.AppendElement(mOriginalInput); +} + +nsPipe::~nsPipe() +{ +} + +NS_IMPL_ADDREF(nsPipe) +NS_IMPL_QUERY_INTERFACE(nsPipe, nsIPipe) + +NS_IMETHODIMP_(MozExternalRefCountType) +nsPipe::Release() +{ + MOZ_ASSERT(int32_t(mRefCnt) > 0, "dup release"); + nsrefcnt count = --mRefCnt; + NS_LOG_RELEASE(this, count, "nsPipe"); + if (count == 0) { + delete (this); + return 0; + } + // Avoid racing on |mOriginalInput| by only looking at it when + // the refcount is 1, that is, we are the only pointer (hence only + // thread) to access it. + if (count == 1 && mOriginalInput) { + mOriginalInput = nullptr; + return 1; + } + return count; +} + +NS_IMETHODIMP +nsPipe::Init(bool aNonBlockingIn, + bool aNonBlockingOut, + uint32_t aSegmentSize, + uint32_t aSegmentCount) +{ + mInited = true; + + if (aSegmentSize == 0) { + aSegmentSize = DEFAULT_SEGMENT_SIZE; + } + if (aSegmentCount == 0) { + aSegmentCount = DEFAULT_SEGMENT_COUNT; + } + + // protect against overflow + uint32_t maxCount = uint32_t(-1) / aSegmentSize; + if (aSegmentCount > maxCount) { + aSegmentCount = maxCount; + } + + // The internal buffer is always "infinite" so that we can allow + // the size to expand when cloned streams are read at different + // rates. We enforce a limit on how much data can be buffered + // ahead of the fastest reader in GetWriteSegment(). + nsresult rv = mBuffer.Init(aSegmentSize, UINT32_MAX); + if (NS_FAILED(rv)) { + return rv; + } + + mMaxAdvanceBufferSegmentCount = aSegmentCount; + + mOutput.SetNonBlocking(aNonBlockingOut); + mOriginalInput->SetNonBlocking(aNonBlockingIn); + + return NS_OK; +} + +NS_IMETHODIMP +nsPipe::GetInputStream(nsIAsyncInputStream** aInputStream) +{ + if (NS_WARN_IF(!mInited)) { + return NS_ERROR_NOT_INITIALIZED; + } + RefPtr ref = mOriginalInput; + ref.forget(aInputStream); + return NS_OK; +} + +NS_IMETHODIMP +nsPipe::GetOutputStream(nsIAsyncOutputStream** aOutputStream) +{ + if (NS_WARN_IF(!mInited)) { + return NS_ERROR_NOT_INITIALIZED; + } + NS_ADDREF(*aOutputStream = &mOutput); + return NS_OK; +} + +void +nsPipe::PeekSegment(const nsPipeReadState& aReadState, uint32_t aIndex, + char*& aCursor, char*& aLimit) +{ + if (aIndex == 0) { + NS_ASSERTION(!aReadState.mReadCursor || mBuffer.GetSegmentCount(), + "unexpected state"); + aCursor = aReadState.mReadCursor; + aLimit = aReadState.mReadLimit; + } else { + uint32_t absoluteIndex = aReadState.mSegment + aIndex; + uint32_t numSegments = mBuffer.GetSegmentCount(); + if (absoluteIndex >= numSegments) { + aCursor = aLimit = nullptr; + } else { + aCursor = mBuffer.GetSegment(absoluteIndex); + if (mWriteSegment == (int32_t)absoluteIndex) { + aLimit = mWriteCursor; + } else { + aLimit = aCursor + mBuffer.GetSegmentSize(); + } + } + } +} + +nsresult +nsPipe::GetReadSegment(nsPipeReadState& aReadState, const char*& aSegment, + uint32_t& aLength) +{ + ReentrantMonitorAutoEnter mon(mReentrantMonitor); + + if (aReadState.mReadCursor == aReadState.mReadLimit) { + return NS_FAILED(mStatus) ? mStatus : NS_BASE_STREAM_WOULD_BLOCK; + } + + // The input stream locks the pipe while getting the buffer to read from, + // but then unlocks while actual data copying is taking place. In + // order to avoid deleting the buffer out from under this lockless read + // set a flag to indicate a read is active. This flag is only modified + // while the lock is held. + MOZ_ASSERT(!aReadState.mActiveRead); + aReadState.mActiveRead = true; + + aSegment = aReadState.mReadCursor; + aLength = aReadState.mReadLimit - aReadState.mReadCursor; + + return NS_OK; +} + +void +nsPipe::ReleaseReadSegment(nsPipeReadState& aReadState, nsPipeEvents& aEvents) +{ + ReentrantMonitorAutoEnter mon(mReentrantMonitor); + + MOZ_ASSERT(aReadState.mActiveRead); + aReadState.mActiveRead = false; + + // When a read completes and releases the mActiveRead flag, we may have blocked + // a drain from completing. This occurs when the input stream is closed during + // the read. In these cases, we need to complete the drain as soon as the + // active read completes. + if (aReadState.mNeedDrain) { + aReadState.mNeedDrain = false; + DrainInputStream(aReadState, aEvents); + } +} + +void +nsPipe::AdvanceReadCursor(nsPipeReadState& aReadState, uint32_t aBytesRead) +{ + NS_ASSERTION(aBytesRead, "don't call if no bytes read"); + + nsPipeEvents events; + { + ReentrantMonitorAutoEnter mon(mReentrantMonitor); + + LOG(("III advancing read cursor by %u\n", aBytesRead)); + NS_ASSERTION(aBytesRead <= mBuffer.GetSegmentSize(), "read too much"); + + aReadState.mReadCursor += aBytesRead; + NS_ASSERTION(aReadState.mReadCursor <= aReadState.mReadLimit, + "read cursor exceeds limit"); + + MOZ_ASSERT(aReadState.mAvailable >= aBytesRead); + aReadState.mAvailable -= aBytesRead; + + // Check to see if we're at the end of the available read data. If we + // are, and this segment is not still being written, then we can possibly + // free up the segment. + if (aReadState.mReadCursor == aReadState.mReadLimit && + !ReadSegmentBeingWritten(aReadState)) { + + // Advance the segment position. If we have read any segments from the + // advance buffer then we can potentially notify blocked writers. + if (AdvanceReadSegment(aReadState, mon) == SegmentAdvanceBufferRead && + mOutput.OnOutputWritable(events) == NotifyMonitor) { + mon.NotifyAll(); + } + } + + ReleaseReadSegment(aReadState, events); + } +} + +SegmentChangeResult +nsPipe::AdvanceReadSegment(nsPipeReadState& aReadState, + const ReentrantMonitorAutoEnter &ev) +{ + // Calculate how many segments are buffered for this stream to start. + uint32_t startBufferSegments = GetBufferSegmentCount(aReadState, ev); + + int32_t currentSegment = aReadState.mSegment; + + // Move to the next segment to read + aReadState.mSegment += 1; + + // If this was the last reference to the first segment, then remove it. + if (currentSegment == 0 && CountSegmentReferences(currentSegment) == 0) { + + // shift write and read segment index (-1 indicates an empty buffer). + mWriteSegment -= 1; + + // Directly modify the current read state. If the associated input + // stream is closed simultaneous with reading, then it may not be + // in the mInputList any more. + aReadState.mSegment -= 1; + + for (uint32_t i = 0; i < mInputList.Length(); ++i) { + // Skip the current read state structure since we modify it manually + // before entering this loop. + if (&mInputList[i]->ReadState() == &aReadState) { + continue; + } + mInputList[i]->ReadState().mSegment -= 1; + } + + // done with this segment + mBuffer.DeleteFirstSegment(); + LOG(("III deleting first segment\n")); + } + + if (mWriteSegment < aReadState.mSegment) { + // read cursor has hit the end of written data, so reset it + MOZ_ASSERT(mWriteSegment == (aReadState.mSegment - 1)); + aReadState.mReadCursor = nullptr; + aReadState.mReadLimit = nullptr; + // also, the buffer is completely empty, so reset the write cursor + if (mWriteSegment == -1) { + mWriteCursor = nullptr; + mWriteLimit = nullptr; + } + } else { + // advance read cursor and limit to next buffer segment + aReadState.mReadCursor = mBuffer.GetSegment(aReadState.mSegment); + if (mWriteSegment == aReadState.mSegment) { + aReadState.mReadLimit = mWriteCursor; + } else { + aReadState.mReadLimit = aReadState.mReadCursor + mBuffer.GetSegmentSize(); + } + } + + // Calculate how many segments are buffered for the stream after + // reading. + uint32_t endBufferSegments = GetBufferSegmentCount(aReadState, ev); + + // If the stream has read a segment out of the set of advanced buffer + // segments, then the writer may advance. + if (startBufferSegments >= mMaxAdvanceBufferSegmentCount && + endBufferSegments < mMaxAdvanceBufferSegmentCount) { + return SegmentAdvanceBufferRead; + } + + // Otherwise there are no significant changes to the segment structure. + return SegmentNotChanged; +} + +void +nsPipe::DrainInputStream(nsPipeReadState& aReadState, nsPipeEvents& aEvents) +{ + ReentrantMonitorAutoEnter mon(mReentrantMonitor); + + // If a segment is actively being read in ReadSegments() for this input + // stream, then we cannot drain the stream. This can happen because + // ReadSegments() does not hold the lock while copying from the buffer. + // If we detect this condition, simply note that we need a drain once + // the read completes and return immediately. + if (aReadState.mActiveRead) { + MOZ_ASSERT(!aReadState.mNeedDrain); + aReadState.mNeedDrain = true; + return; + } + + aReadState.mAvailable = 0; + + while(mWriteSegment >= aReadState.mSegment) { + + // If the last segment to free is still being written to, we're done + // draining. We can't free any more. + if (ReadSegmentBeingWritten(aReadState)) { + break; + } + + // Don't bother checking if this results in an advance buffer segment + // read. Since we are draining the entire stream we will read an + // advance buffer segment no matter what. + AdvanceReadSegment(aReadState, mon); + } + + // If we have read any segments from the advance buffer then we can + // potentially notify blocked writers. + if (!IsAdvanceBufferFull(mon) && + mOutput.OnOutputWritable(aEvents) == NotifyMonitor) { + mon.NotifyAll(); + } +} + +bool +nsPipe::ReadSegmentBeingWritten(nsPipeReadState& aReadState) +{ + mReentrantMonitor.AssertCurrentThreadIn(); + bool beingWritten = mWriteSegment == aReadState.mSegment && + mWriteLimit > mWriteCursor; + NS_ASSERTION(!beingWritten || aReadState.mReadLimit == mWriteCursor, + "unexpected state"); + return beingWritten; +} + +nsresult +nsPipe::GetWriteSegment(char*& aSegment, uint32_t& aSegmentLen) +{ + ReentrantMonitorAutoEnter mon(mReentrantMonitor); + + if (NS_FAILED(mStatus)) { + return mStatus; + } + + // write cursor and limit may both be null indicating an empty buffer. + if (mWriteCursor == mWriteLimit) { + // The pipe is full if we have hit our limit on advance data buffering. + // This means the fastest reader is still reading slower than data is + // being written into the pipe. + if (IsAdvanceBufferFull(mon)) { + return NS_BASE_STREAM_WOULD_BLOCK; + } + + // The nsSegmentedBuffer is configured to be "infinite", so this + // should never return nullptr here. + char* seg = mBuffer.AppendNewSegment(); + if (!seg) { + return NS_ERROR_OUT_OF_MEMORY; + } + + LOG(("OOO appended new segment\n")); + mWriteCursor = seg; + mWriteLimit = mWriteCursor + mBuffer.GetSegmentSize(); + ++mWriteSegment; + } + + // make sure read cursor is initialized + SetAllNullReadCursors(); + + // check to see if we can roll-back our read and write cursors to the + // beginning of the current/first segment. this is purely an optimization. + if (mWriteSegment == 0 && AllReadCursorsMatchWriteCursor()) { + char* head = mBuffer.GetSegment(0); + LOG(("OOO rolling back write cursor %u bytes\n", mWriteCursor - head)); + RollBackAllReadCursors(head); + mWriteCursor = head; + } + + aSegment = mWriteCursor; + aSegmentLen = mWriteLimit - mWriteCursor; + return NS_OK; +} + +void +nsPipe::AdvanceWriteCursor(uint32_t aBytesWritten) +{ + NS_ASSERTION(aBytesWritten, "don't call if no bytes written"); + + nsPipeEvents events; + { + ReentrantMonitorAutoEnter mon(mReentrantMonitor); + + LOG(("OOO advancing write cursor by %u\n", aBytesWritten)); + + char* newWriteCursor = mWriteCursor + aBytesWritten; + NS_ASSERTION(newWriteCursor <= mWriteLimit, "write cursor exceeds limit"); + + // update read limit if reading in the same segment + UpdateAllReadCursors(newWriteCursor); + + mWriteCursor = newWriteCursor; + + ValidateAllReadCursors(); + + // update the writable flag on the output stream + if (mWriteCursor == mWriteLimit) { + mOutput.SetWritable(!IsAdvanceBufferFull(mon)); + } + + // notify input stream that pipe now contains additional data + bool needNotify = false; + for (uint32_t i = 0; i < mInputList.Length(); ++i) { + if (mInputList[i]->OnInputReadable(aBytesWritten, events, mon) + == NotifyMonitor) { + needNotify = true; + } + } + + if (needNotify) { + mon.NotifyAll(); + } + } +} + +void +nsPipe::OnInputStreamException(nsPipeInputStream* aStream, nsresult aReason) +{ + MOZ_ASSERT(NS_FAILED(aReason)); + + nsPipeEvents events; + { + ReentrantMonitorAutoEnter mon(mReentrantMonitor); + + // Its possible to re-enter this method when we call OnPipeException() or + // OnInputExection() below. If there is a caller stuck in our synchronous + // Wait() method, then they will get woken up with a failure code which + // re-enters this method. Therefore, gracefully handle unknown streams + // here. + + // If we only have one stream open and it is the given stream, then shut + // down the entire pipe. + if (mInputList.Length() == 1) { + if (mInputList[0] == aStream) { + OnPipeException(aReason); + } + return; + } + + // Otherwise just close the particular stream that hit an exception. + for (uint32_t i = 0; i < mInputList.Length(); ++i) { + if (mInputList[i] != aStream) { + continue; + } + + MonitorAction action = mInputList[i]->OnInputException(aReason, events, + mon); + mInputList.RemoveElementAt(i); + + // Notify after element is removed in case we re-enter as a result. + if (action == NotifyMonitor) { + mon.NotifyAll(); + } + + return; + } + } +} + +void +nsPipe::OnPipeException(nsresult aReason, bool aOutputOnly) +{ + LOG(("PPP nsPipe::OnPipeException [reason=%x output-only=%d]\n", + aReason, aOutputOnly)); + + nsPipeEvents events; + { + ReentrantMonitorAutoEnter mon(mReentrantMonitor); + + // if we've already hit an exception, then ignore this one. + if (NS_FAILED(mStatus)) { + return; + } + + mStatus = aReason; + + bool needNotify = false; + + nsTArray tmpInputList; + for (uint32_t i = 0; i < mInputList.Length(); ++i) { + // an output-only exception applies to the input end if the pipe has + // zero bytes available. + if (aOutputOnly && mInputList[i]->Available()) { + tmpInputList.AppendElement(mInputList[i]); + continue; + } + + if (mInputList[i]->OnInputException(aReason, events, mon) + == NotifyMonitor) { + needNotify = true; + } + } + mInputList = tmpInputList; + + if (mOutput.OnOutputException(aReason, events) == NotifyMonitor) { + needNotify = true; + } + + // Notify after we have removed any input streams from mInputList + if (needNotify) { + mon.NotifyAll(); + } + } +} + +nsresult +nsPipe::CloneInputStream(nsPipeInputStream* aOriginal, + nsIInputStream** aCloneOut) +{ + ReentrantMonitorAutoEnter mon(mReentrantMonitor); + RefPtr ref = new nsPipeInputStream(*aOriginal); + mInputList.AppendElement(ref); + nsCOMPtr downcast = ref.forget(); + downcast.forget(aCloneOut); + return NS_OK; +} + +uint32_t +nsPipe::CountSegmentReferences(int32_t aSegment) +{ + mReentrantMonitor.AssertCurrentThreadIn(); + uint32_t count = 0; + for (uint32_t i = 0; i < mInputList.Length(); ++i) { + if (aSegment >= mInputList[i]->ReadState().mSegment) { + count += 1; + } + } + return count; +} + +void +nsPipe::SetAllNullReadCursors() +{ + mReentrantMonitor.AssertCurrentThreadIn(); + for (uint32_t i = 0; i < mInputList.Length(); ++i) { + nsPipeReadState& readState = mInputList[i]->ReadState(); + if (!readState.mReadCursor) { + NS_ASSERTION(mWriteSegment == readState.mSegment, + "unexpected null read cursor"); + readState.mReadCursor = readState.mReadLimit = mWriteCursor; + } + } +} + +bool +nsPipe::AllReadCursorsMatchWriteCursor() +{ + mReentrantMonitor.AssertCurrentThreadIn(); + for (uint32_t i = 0; i < mInputList.Length(); ++i) { + const nsPipeReadState& readState = mInputList[i]->ReadState(); + if (readState.mSegment != mWriteSegment || + readState.mReadCursor != mWriteCursor) { + return false; + } + } + return true; +} + +void +nsPipe::RollBackAllReadCursors(char* aWriteCursor) +{ + mReentrantMonitor.AssertCurrentThreadIn(); + for (uint32_t i = 0; i < mInputList.Length(); ++i) { + nsPipeReadState& readState = mInputList[i]->ReadState(); + MOZ_ASSERT(mWriteSegment == readState.mSegment); + MOZ_ASSERT(mWriteCursor == readState.mReadCursor); + MOZ_ASSERT(mWriteCursor == readState.mReadLimit); + readState.mReadCursor = aWriteCursor; + readState.mReadLimit = aWriteCursor; + } +} + +void +nsPipe::UpdateAllReadCursors(char* aWriteCursor) +{ + mReentrantMonitor.AssertCurrentThreadIn(); + for (uint32_t i = 0; i < mInputList.Length(); ++i) { + nsPipeReadState& readState = mInputList[i]->ReadState(); + if (mWriteSegment == readState.mSegment && + readState.mReadLimit == mWriteCursor) { + readState.mReadLimit = aWriteCursor; + } + } +} + +void +nsPipe::ValidateAllReadCursors() +{ + mReentrantMonitor.AssertCurrentThreadIn(); + // The only way mReadCursor == mWriteCursor is if: + // + // - mReadCursor is at the start of a segment (which, based on how + // nsSegmentedBuffer works, means that this segment is the "first" + // segment) + // - mWriteCursor points at the location past the end of the current + // write segment (so the current write filled the current write + // segment, so we've incremented mWriteCursor to point past the end + // of it) + // - the segment to which data has just been written is located + // exactly one segment's worth of bytes before the first segment + // where mReadCursor is located + // + // Consequently, the byte immediately after the end of the current + // write segment is the first byte of the first segment, so + // mReadCursor == mWriteCursor. (Another way to think about this is + // to consider the buffer architecture diagram above, but consider it + // with an arena allocator which allocates from the *end* of the + // arena to the *beginning* of the arena.) +#ifdef DEBUG + for (uint32_t i = 0; i < mInputList.Length(); ++i) { + const nsPipeReadState& state = mInputList[i]->ReadState(); + NS_ASSERTION(state.mReadCursor != mWriteCursor || + (mBuffer.GetSegment(state.mSegment) == state.mReadCursor && + mWriteCursor == mWriteLimit), + "read cursor is bad"); + } +#endif +} + +uint32_t +nsPipe::GetBufferSegmentCount(const nsPipeReadState& aReadState, + const ReentrantMonitorAutoEnter& ev) const +{ + // The write segment can be smaller than the current reader position + // in some cases. For example, when the first write segment has not + // been allocated yet mWriteSegment is negative. In these cases + // the stream is effectively using zero segments. + if (mWriteSegment < aReadState.mSegment) { + return 0; + } + + MOZ_ASSERT(mWriteSegment >= 0); + MOZ_ASSERT(aReadState.mSegment >= 0); + + // Otherwise at least one segment is being used. We add one here + // since a single segment is being used when the write and read + // segment indices are the same. + return 1 + mWriteSegment - aReadState.mSegment; +} + +bool +nsPipe::IsAdvanceBufferFull(const ReentrantMonitorAutoEnter& ev) const +{ + // If we have fewer total segments than the limit we can immediately + // determine we are not full. Note, we must add one to mWriteSegment + // to convert from a index to a count. + MOZ_DIAGNOSTIC_ASSERT(mWriteSegment >= -1); + MOZ_DIAGNOSTIC_ASSERT(mWriteSegment < INT32_MAX); + uint32_t totalWriteSegments = mWriteSegment + 1; + if (totalWriteSegments < mMaxAdvanceBufferSegmentCount) { + return false; + } + + // Otherwise we must inspect all of our reader streams. We need + // to determine the buffer depth of the fastest reader. + uint32_t minBufferSegments = UINT32_MAX; + for (uint32_t i = 0; i < mInputList.Length(); ++i) { + // Only count buffer segments from input streams that are open. + if (NS_FAILED(mInputList[i]->Status(ev))) { + continue; + } + const nsPipeReadState& state = mInputList[i]->ReadState(); + uint32_t bufferSegments = GetBufferSegmentCount(state, ev); + minBufferSegments = std::min(minBufferSegments, bufferSegments); + // We only care if any reader has fewer segments buffered than + // our threshold. We can stop once we hit that threshold. + if (minBufferSegments < mMaxAdvanceBufferSegmentCount) { + return false; + } + } + + // Note, its possible for minBufferSegments to exceed our + // mMaxAdvanceBufferSegmentCount here. This happens when a cloned + // reader gets far behind, but then the fastest reader stream is + // closed. This leaves us with a single stream that is buffered + // beyond our max. Naturally we continue to indicate the pipe + // is full at this point. + + return true; +} + +//----------------------------------------------------------------------------- +// nsPipeEvents methods: +//----------------------------------------------------------------------------- + +nsPipeEvents::~nsPipeEvents() +{ + // dispatch any pending events + + for (uint32_t i = 0; i < mInputList.Length(); ++i) { + mInputList[i].mCallback->OnInputStreamReady(mInputList[i].mStream); + } + mInputList.Clear(); + + if (mOutputCallback) { + mOutputCallback->OnOutputStreamReady(mOutputStream); + mOutputCallback = nullptr; + mOutputStream = nullptr; + } +} + +//----------------------------------------------------------------------------- +// nsPipeInputStream methods: +//----------------------------------------------------------------------------- + +NS_IMPL_ADDREF(nsPipeInputStream); +NS_IMPL_RELEASE(nsPipeInputStream); + +NS_INTERFACE_TABLE_HEAD(nsPipeInputStream) + NS_INTERFACE_TABLE_BEGIN + NS_INTERFACE_TABLE_ENTRY(nsPipeInputStream, nsIAsyncInputStream) + NS_INTERFACE_TABLE_ENTRY(nsPipeInputStream, nsISeekableStream) + NS_INTERFACE_TABLE_ENTRY(nsPipeInputStream, nsISearchableInputStream) + NS_INTERFACE_TABLE_ENTRY(nsPipeInputStream, nsICloneableInputStream) + NS_INTERFACE_TABLE_ENTRY(nsPipeInputStream, nsIBufferedInputStream) + NS_INTERFACE_TABLE_ENTRY(nsPipeInputStream, nsIClassInfo) + NS_INTERFACE_TABLE_ENTRY_AMBIGUOUS(nsPipeInputStream, nsIInputStream, + nsIAsyncInputStream) + NS_INTERFACE_TABLE_ENTRY_AMBIGUOUS(nsPipeInputStream, nsISupports, + nsIAsyncInputStream) + NS_INTERFACE_TABLE_END +NS_INTERFACE_TABLE_TAIL + +NS_IMPL_CI_INTERFACE_GETTER(nsPipeInputStream, + nsIInputStream, + nsIAsyncInputStream, + nsISeekableStream, + nsISearchableInputStream, + nsICloneableInputStream, + nsIBufferedInputStream) + +NS_IMPL_THREADSAFE_CI(nsPipeInputStream) + +NS_IMETHODIMP +nsPipeInputStream::Init(nsIInputStream*, uint32_t) +{ + MOZ_CRASH("nsPipeInputStream should never be initialized with " + "nsIBufferedInputStream::Init!\n"); +} + +uint32_t +nsPipeInputStream::Available() +{ + mPipe->mReentrantMonitor.AssertCurrentThreadIn(); + return mReadState.mAvailable; +} + +nsresult +nsPipeInputStream::Wait() +{ + NS_ASSERTION(mBlocking, "wait on non-blocking pipe input stream"); + + ReentrantMonitorAutoEnter mon(mPipe->mReentrantMonitor); + + while (NS_SUCCEEDED(Status(mon)) && (mReadState.mAvailable == 0)) { + LOG(("III pipe input: waiting for data\n")); + + mBlocked = true; + mon.Wait(); + mBlocked = false; + + LOG(("III pipe input: woke up [status=%x available=%u]\n", + Status(mon), mReadState.mAvailable)); + } + + return Status(mon) == NS_BASE_STREAM_CLOSED ? NS_OK : Status(mon); +} + +MonitorAction +nsPipeInputStream::OnInputReadable(uint32_t aBytesWritten, + nsPipeEvents& aEvents, + const ReentrantMonitorAutoEnter& ev) +{ + MonitorAction result = DoNotNotifyMonitor; + + mPipe->mReentrantMonitor.AssertCurrentThreadIn(); + mReadState.mAvailable += aBytesWritten; + + if (mCallback && !(mCallbackFlags & WAIT_CLOSURE_ONLY)) { + aEvents.NotifyInputReady(this, mCallback); + mCallback = nullptr; + mCallbackFlags = 0; + } else if (mBlocked) { + result = NotifyMonitor; + } + + return result; +} + +MonitorAction +nsPipeInputStream::OnInputException(nsresult aReason, nsPipeEvents& aEvents, + const ReentrantMonitorAutoEnter& ev) +{ + LOG(("nsPipeInputStream::OnInputException [this=%x reason=%x]\n", + this, aReason)); + + MonitorAction result = DoNotNotifyMonitor; + + NS_ASSERTION(NS_FAILED(aReason), "huh? successful exception"); + + if (NS_SUCCEEDED(mInputStatus)) { + mInputStatus = aReason; + } + + // force count of available bytes to zero. + mPipe->DrainInputStream(mReadState, aEvents); + + if (mCallback) { + aEvents.NotifyInputReady(this, mCallback); + mCallback = nullptr; + mCallbackFlags = 0; + } else if (mBlocked) { + result = NotifyMonitor; + } + + return result; +} + +NS_IMETHODIMP +nsPipeInputStream::CloseWithStatus(nsresult aReason) +{ + LOG(("III CloseWithStatus [this=%x reason=%x]\n", this, aReason)); + + ReentrantMonitorAutoEnter mon(mPipe->mReentrantMonitor); + + if (NS_FAILED(mInputStatus)) { + return NS_OK; + } + + if (NS_SUCCEEDED(aReason)) { + aReason = NS_BASE_STREAM_CLOSED; + } + + mPipe->OnInputStreamException(this, aReason); + return NS_OK; +} + +NS_IMETHODIMP +nsPipeInputStream::Close() +{ + return CloseWithStatus(NS_BASE_STREAM_CLOSED); +} + +NS_IMETHODIMP +nsPipeInputStream::Available(uint64_t* aResult) +{ + // nsPipeInputStream supports under 4GB stream only + ReentrantMonitorAutoEnter mon(mPipe->mReentrantMonitor); + + // return error if closed + if (!mReadState.mAvailable && NS_FAILED(Status(mon))) { + return Status(mon); + } + + *aResult = (uint64_t)mReadState.mAvailable; + return NS_OK; +} + +NS_IMETHODIMP +nsPipeInputStream::ReadSegments(nsWriteSegmentFun aWriter, + void* aClosure, + uint32_t aCount, + uint32_t* aReadCount) +{ + LOG(("III ReadSegments [this=%x count=%u]\n", this, aCount)); + + nsresult rv = NS_OK; + + *aReadCount = 0; + while (aCount) { + AutoReadSegment segment(mPipe, mReadState, aCount); + rv = segment.Status(); + if (NS_FAILED(rv)) { + // ignore this error if we've already read something. + if (*aReadCount > 0) { + rv = NS_OK; + break; + } + if (rv == NS_BASE_STREAM_WOULD_BLOCK) { + // pipe is empty + if (!mBlocking) { + break; + } + // wait for some data to be written to the pipe + rv = Wait(); + if (NS_SUCCEEDED(rv)) { + continue; + } + } + // ignore this error, just return. + if (rv == NS_BASE_STREAM_CLOSED) { + rv = NS_OK; + break; + } + mPipe->OnInputStreamException(this, rv); + break; + } + + uint32_t writeCount; + while (segment.Length()) { + writeCount = 0; + + rv = aWriter(static_cast(this), aClosure, + segment.Data(), *aReadCount, segment.Length(), &writeCount); + + if (NS_FAILED(rv) || writeCount == 0) { + aCount = 0; + // any errors returned from the writer end here: do not + // propagate to the caller of ReadSegments. + rv = NS_OK; + break; + } + + NS_ASSERTION(writeCount <= segment.Length(), "wrote more than expected"); + segment.Advance(writeCount); + aCount -= writeCount; + *aReadCount += writeCount; + mLogicalOffset += writeCount; + } + } + + return rv; +} + +NS_IMETHODIMP +nsPipeInputStream::Read(char* aToBuf, uint32_t aBufLen, uint32_t* aReadCount) +{ + return ReadSegments(NS_CopySegmentToBuffer, aToBuf, aBufLen, aReadCount); +} + +NS_IMETHODIMP +nsPipeInputStream::IsNonBlocking(bool* aNonBlocking) +{ + *aNonBlocking = !mBlocking; + return NS_OK; +} + +NS_IMETHODIMP +nsPipeInputStream::AsyncWait(nsIInputStreamCallback* aCallback, + uint32_t aFlags, + uint32_t aRequestedCount, + nsIEventTarget* aTarget) +{ + LOG(("III AsyncWait [this=%x]\n", this)); + + nsPipeEvents pipeEvents; + { + ReentrantMonitorAutoEnter mon(mPipe->mReentrantMonitor); + + // replace a pending callback + mCallback = nullptr; + mCallbackFlags = 0; + + if (!aCallback) { + return NS_OK; + } + + nsCOMPtr proxy; + if (aTarget) { + proxy = NS_NewInputStreamReadyEvent(aCallback, aTarget); + aCallback = proxy; + } + + if (NS_FAILED(Status(mon)) || + (mReadState.mAvailable && !(aFlags & WAIT_CLOSURE_ONLY))) { + // stream is already closed or readable; post event. + pipeEvents.NotifyInputReady(this, aCallback); + } else { + // queue up callback object to be notified when data becomes available + mCallback = aCallback; + mCallbackFlags = aFlags; + } + } + return NS_OK; +} + +NS_IMETHODIMP +nsPipeInputStream::Seek(int32_t aWhence, int64_t aOffset) +{ + NS_NOTREACHED("nsPipeInputStream::Seek"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsPipeInputStream::Tell(int64_t* aOffset) +{ + ReentrantMonitorAutoEnter mon(mPipe->mReentrantMonitor); + + // return error if closed + if (!mReadState.mAvailable && NS_FAILED(Status(mon))) { + return Status(mon); + } + + *aOffset = mLogicalOffset; + return NS_OK; +} + +NS_IMETHODIMP +nsPipeInputStream::SetEOF() +{ + NS_NOTREACHED("nsPipeInputStream::SetEOF"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +static bool strings_equal(bool aIgnoreCase, + const char* aS1, const char* aS2, uint32_t aLen) +{ + return aIgnoreCase + ? !nsCRT::strncasecmp(aS1, aS2, aLen) : !nsCRT::strncmp(aS1, aS2, aLen); +} + +NS_IMETHODIMP +nsPipeInputStream::Search(const char* aForString, + bool aIgnoreCase, + bool* aFound, + uint32_t* aOffsetSearchedTo) +{ + LOG(("III Search [for=%s ic=%u]\n", aForString, aIgnoreCase)); + + ReentrantMonitorAutoEnter mon(mPipe->mReentrantMonitor); + + char* cursor1; + char* limit1; + uint32_t index = 0, offset = 0; + uint32_t strLen = strlen(aForString); + + mPipe->PeekSegment(mReadState, 0, cursor1, limit1); + if (cursor1 == limit1) { + *aFound = false; + *aOffsetSearchedTo = 0; + LOG((" result [aFound=%u offset=%u]\n", *aFound, *aOffsetSearchedTo)); + return NS_OK; + } + + while (true) { + uint32_t i, len1 = limit1 - cursor1; + + // check if the string is in the buffer segment + for (i = 0; i < len1 - strLen + 1; i++) { + if (strings_equal(aIgnoreCase, &cursor1[i], aForString, strLen)) { + *aFound = true; + *aOffsetSearchedTo = offset + i; + LOG((" result [aFound=%u offset=%u]\n", *aFound, *aOffsetSearchedTo)); + return NS_OK; + } + } + + // get the next segment + char* cursor2; + char* limit2; + uint32_t len2; + + index++; + offset += len1; + + mPipe->PeekSegment(mReadState, index, cursor2, limit2); + if (cursor2 == limit2) { + *aFound = false; + *aOffsetSearchedTo = offset - strLen + 1; + LOG((" result [aFound=%u offset=%u]\n", *aFound, *aOffsetSearchedTo)); + return NS_OK; + } + len2 = limit2 - cursor2; + + // check if the string is straddling the next buffer segment + uint32_t lim = XPCOM_MIN(strLen, len2 + 1); + for (i = 0; i < lim; ++i) { + uint32_t strPart1Len = strLen - i - 1; + uint32_t strPart2Len = strLen - strPart1Len; + const char* strPart2 = &aForString[strLen - strPart2Len]; + uint32_t bufSeg1Offset = len1 - strPart1Len; + if (strings_equal(aIgnoreCase, &cursor1[bufSeg1Offset], aForString, strPart1Len) && + strings_equal(aIgnoreCase, cursor2, strPart2, strPart2Len)) { + *aFound = true; + *aOffsetSearchedTo = offset - strPart1Len; + LOG((" result [aFound=%u offset=%u]\n", *aFound, *aOffsetSearchedTo)); + return NS_OK; + } + } + + // finally continue with the next buffer + cursor1 = cursor2; + limit1 = limit2; + } + + NS_NOTREACHED("can't get here"); + return NS_ERROR_UNEXPECTED; // keep compiler happy +} + +NS_IMETHODIMP +nsPipeInputStream::GetCloneable(bool* aCloneableOut) +{ + *aCloneableOut = true; + return NS_OK; +} + +NS_IMETHODIMP +nsPipeInputStream::Clone(nsIInputStream** aCloneOut) +{ + return mPipe->CloneInputStream(this, aCloneOut); +} + +nsresult +nsPipeInputStream::Status(const ReentrantMonitorAutoEnter& ev) const +{ + if (NS_FAILED(mInputStatus)) { + return mInputStatus; + } + + if (mReadState.mAvailable) { + // Still something to read and this input stream state is OK. + return NS_OK; + } + + // Nothing to read, just fall through to the pipe's state that + // may reflect state of its output stream side (already closed). + return mPipe->mStatus; +} + +nsresult +nsPipeInputStream::Status() const +{ + ReentrantMonitorAutoEnter mon(mPipe->mReentrantMonitor); + return Status(mon); +} + +nsPipeInputStream::~nsPipeInputStream() +{ + Close(); +} + +//----------------------------------------------------------------------------- +// nsPipeOutputStream methods: +//----------------------------------------------------------------------------- + +NS_IMPL_QUERY_INTERFACE(nsPipeOutputStream, + nsIOutputStream, + nsIAsyncOutputStream, + nsIClassInfo) + +NS_IMPL_CI_INTERFACE_GETTER(nsPipeOutputStream, + nsIOutputStream, + nsIAsyncOutputStream) + +NS_IMPL_THREADSAFE_CI(nsPipeOutputStream) + +nsresult +nsPipeOutputStream::Wait() +{ + NS_ASSERTION(mBlocking, "wait on non-blocking pipe output stream"); + + ReentrantMonitorAutoEnter mon(mPipe->mReentrantMonitor); + + if (NS_SUCCEEDED(mPipe->mStatus) && !mWritable) { + LOG(("OOO pipe output: waiting for space\n")); + mBlocked = true; + mon.Wait(); + mBlocked = false; + LOG(("OOO pipe output: woke up [pipe-status=%x writable=%u]\n", + mPipe->mStatus, mWritable)); + } + + return mPipe->mStatus == NS_BASE_STREAM_CLOSED ? NS_OK : mPipe->mStatus; +} + +MonitorAction +nsPipeOutputStream::OnOutputWritable(nsPipeEvents& aEvents) +{ + MonitorAction result = DoNotNotifyMonitor; + + mWritable = true; + + if (mCallback && !(mCallbackFlags & WAIT_CLOSURE_ONLY)) { + aEvents.NotifyOutputReady(this, mCallback); + mCallback = nullptr; + mCallbackFlags = 0; + } else if (mBlocked) { + result = NotifyMonitor; + } + + return result; +} + +MonitorAction +nsPipeOutputStream::OnOutputException(nsresult aReason, nsPipeEvents& aEvents) +{ + LOG(("nsPipeOutputStream::OnOutputException [this=%x reason=%x]\n", + this, aReason)); + + MonitorAction result = DoNotNotifyMonitor; + + NS_ASSERTION(NS_FAILED(aReason), "huh? successful exception"); + mWritable = false; + + if (mCallback) { + aEvents.NotifyOutputReady(this, mCallback); + mCallback = nullptr; + mCallbackFlags = 0; + } else if (mBlocked) { + result = NotifyMonitor; + } + + return result; +} + + +NS_IMETHODIMP_(MozExternalRefCountType) +nsPipeOutputStream::AddRef() +{ + ++mWriterRefCnt; + return mPipe->AddRef(); +} + +NS_IMETHODIMP_(MozExternalRefCountType) +nsPipeOutputStream::Release() +{ + if (--mWriterRefCnt == 0) { + Close(); + } + return mPipe->Release(); +} + +NS_IMETHODIMP +nsPipeOutputStream::CloseWithStatus(nsresult aReason) +{ + LOG(("OOO CloseWithStatus [this=%x reason=%x]\n", this, aReason)); + + if (NS_SUCCEEDED(aReason)) { + aReason = NS_BASE_STREAM_CLOSED; + } + + // input stream may remain open + mPipe->OnPipeException(aReason, true); + return NS_OK; +} + +NS_IMETHODIMP +nsPipeOutputStream::Close() +{ + return CloseWithStatus(NS_BASE_STREAM_CLOSED); +} + +NS_IMETHODIMP +nsPipeOutputStream::WriteSegments(nsReadSegmentFun aReader, + void* aClosure, + uint32_t aCount, + uint32_t* aWriteCount) +{ + LOG(("OOO WriteSegments [this=%x count=%u]\n", this, aCount)); + + nsresult rv = NS_OK; + + char* segment; + uint32_t segmentLen; + + *aWriteCount = 0; + while (aCount) { + rv = mPipe->GetWriteSegment(segment, segmentLen); + if (NS_FAILED(rv)) { + if (rv == NS_BASE_STREAM_WOULD_BLOCK) { + // pipe is full + if (!mBlocking) { + // ignore this error if we've already written something + if (*aWriteCount > 0) { + rv = NS_OK; + } + break; + } + // wait for the pipe to have an empty segment. + rv = Wait(); + if (NS_SUCCEEDED(rv)) { + continue; + } + } + mPipe->OnPipeException(rv); + break; + } + + // write no more than aCount + if (segmentLen > aCount) { + segmentLen = aCount; + } + + uint32_t readCount, originalLen = segmentLen; + while (segmentLen) { + readCount = 0; + + rv = aReader(this, aClosure, segment, *aWriteCount, segmentLen, &readCount); + + if (NS_FAILED(rv) || readCount == 0) { + aCount = 0; + // any errors returned from the aReader end here: do not + // propagate to the caller of WriteSegments. + rv = NS_OK; + break; + } + + NS_ASSERTION(readCount <= segmentLen, "read more than expected"); + segment += readCount; + segmentLen -= readCount; + aCount -= readCount; + *aWriteCount += readCount; + mLogicalOffset += readCount; + } + + if (segmentLen < originalLen) { + mPipe->AdvanceWriteCursor(originalLen - segmentLen); + } + } + + return rv; +} + +static nsresult +nsReadFromRawBuffer(nsIOutputStream* aOutStr, + void* aClosure, + char* aToRawSegment, + uint32_t aOffset, + uint32_t aCount, + uint32_t* aReadCount) +{ + const char* fromBuf = (const char*)aClosure; + memcpy(aToRawSegment, &fromBuf[aOffset], aCount); + *aReadCount = aCount; + return NS_OK; +} + +NS_IMETHODIMP +nsPipeOutputStream::Write(const char* aFromBuf, + uint32_t aBufLen, + uint32_t* aWriteCount) +{ + return WriteSegments(nsReadFromRawBuffer, (void*)aFromBuf, aBufLen, aWriteCount); +} + +NS_IMETHODIMP +nsPipeOutputStream::Flush(void) +{ + // nothing to do + return NS_OK; +} + +static nsresult +nsReadFromInputStream(nsIOutputStream* aOutStr, + void* aClosure, + char* aToRawSegment, + uint32_t aOffset, + uint32_t aCount, + uint32_t* aReadCount) +{ + nsIInputStream* fromStream = (nsIInputStream*)aClosure; + return fromStream->Read(aToRawSegment, aCount, aReadCount); +} + +NS_IMETHODIMP +nsPipeOutputStream::WriteFrom(nsIInputStream* aFromStream, + uint32_t aCount, + uint32_t* aWriteCount) +{ + return WriteSegments(nsReadFromInputStream, aFromStream, aCount, aWriteCount); +} + +NS_IMETHODIMP +nsPipeOutputStream::IsNonBlocking(bool* aNonBlocking) +{ + *aNonBlocking = !mBlocking; + return NS_OK; +} + +NS_IMETHODIMP +nsPipeOutputStream::AsyncWait(nsIOutputStreamCallback* aCallback, + uint32_t aFlags, + uint32_t aRequestedCount, + nsIEventTarget* aTarget) +{ + LOG(("OOO AsyncWait [this=%x]\n", this)); + + nsPipeEvents pipeEvents; + { + ReentrantMonitorAutoEnter mon(mPipe->mReentrantMonitor); + + // replace a pending callback + mCallback = nullptr; + mCallbackFlags = 0; + + if (!aCallback) { + return NS_OK; + } + + nsCOMPtr proxy; + if (aTarget) { + proxy = NS_NewOutputStreamReadyEvent(aCallback, aTarget); + aCallback = proxy; + } + + if (NS_FAILED(mPipe->mStatus) || + (mWritable && !(aFlags & WAIT_CLOSURE_ONLY))) { + // stream is already closed or writable; post event. + pipeEvents.NotifyOutputReady(this, aCallback); + } else { + // queue up callback object to be notified when data becomes available + mCallback = aCallback; + mCallbackFlags = aFlags; + } + } + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////////////// + +nsresult +NS_NewPipe(nsIInputStream** aPipeIn, + nsIOutputStream** aPipeOut, + uint32_t aSegmentSize, + uint32_t aMaxSize, + bool aNonBlockingInput, + bool aNonBlockingOutput) +{ + if (aSegmentSize == 0) { + aSegmentSize = DEFAULT_SEGMENT_SIZE; + } + + // Handle aMaxSize of UINT32_MAX as a special case + uint32_t segmentCount; + if (aMaxSize == UINT32_MAX) { + segmentCount = UINT32_MAX; + } else { + segmentCount = aMaxSize / aSegmentSize; + } + + nsIAsyncInputStream* in; + nsIAsyncOutputStream* out; + nsresult rv = NS_NewPipe2(&in, &out, aNonBlockingInput, aNonBlockingOutput, + aSegmentSize, segmentCount); + if (NS_FAILED(rv)) { + return rv; + } + + *aPipeIn = in; + *aPipeOut = out; + return NS_OK; +} + +nsresult +NS_NewPipe2(nsIAsyncInputStream** aPipeIn, + nsIAsyncOutputStream** aPipeOut, + bool aNonBlockingInput, + bool aNonBlockingOutput, + uint32_t aSegmentSize, + uint32_t aSegmentCount) +{ + nsPipe* pipe = new nsPipe(); + nsresult rv = pipe->Init(aNonBlockingInput, + aNonBlockingOutput, + aSegmentSize, + aSegmentCount); + if (NS_FAILED(rv)) { + NS_ADDREF(pipe); + NS_RELEASE(pipe); + return rv; + } + + // These always succeed because the pipe is initialized above. + MOZ_ALWAYS_SUCCEEDS(pipe->GetInputStream(aPipeIn)); + MOZ_ALWAYS_SUCCEEDS(pipe->GetOutputStream(aPipeOut)); + return NS_OK; +} + +nsresult +nsPipeConstructor(nsISupports* aOuter, REFNSIID aIID, void** aResult) +{ + if (aOuter) { + return NS_ERROR_NO_AGGREGATION; + } + nsPipe* pipe = new nsPipe(); + NS_ADDREF(pipe); + nsresult rv = pipe->QueryInterface(aIID, aResult); + NS_RELEASE(pipe); + return rv; +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/xpcom/io/nsScriptableBase64Encoder.cpp b/xpcom/io/nsScriptableBase64Encoder.cpp new file mode 100644 index 000000000..a8ffce8cd --- /dev/null +++ b/xpcom/io/nsScriptableBase64Encoder.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 "nsScriptableBase64Encoder.h" +#include "mozilla/Base64.h" + +using namespace mozilla; + +NS_IMPL_ISUPPORTS(nsScriptableBase64Encoder, nsIScriptableBase64Encoder) + +NS_IMETHODIMP +nsScriptableBase64Encoder::EncodeToCString(nsIInputStream* aStream, + uint32_t aLength, + nsACString& aResult) +{ + return Base64EncodeInputStream(aStream, aResult, aLength); +} + +NS_IMETHODIMP +nsScriptableBase64Encoder::EncodeToString(nsIInputStream* aStream, + uint32_t aLength, + nsAString& aResult) +{ + return Base64EncodeInputStream(aStream, aResult, aLength); +} diff --git a/xpcom/io/nsScriptableBase64Encoder.h b/xpcom/io/nsScriptableBase64Encoder.h new file mode 100644 index 000000000..d9121e5de --- /dev/null +++ b/xpcom/io/nsScriptableBase64Encoder.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 nsScriptableBase64Encoder_h__ +#define nsScriptableBase64Encoder_h__ + +#include "nsIScriptableBase64Encoder.h" +#include "mozilla/Attributes.h" + +#define NS_SCRIPTABLEBASE64ENCODER_CID \ + {0xaaf68860, 0xf849, 0x40ee, \ + {0xbb, 0x7a, 0xb2, 0x29, 0xbc, 0xe0, 0x36, 0xa3} } +#define NS_SCRIPTABLEBASE64ENCODER_CONTRACTID \ + "@mozilla.org/scriptablebase64encoder;1" + +class nsScriptableBase64Encoder final : public nsIScriptableBase64Encoder +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSISCRIPTABLEBASE64ENCODER +private: + ~nsScriptableBase64Encoder() + { + } +}; + +#endif diff --git a/xpcom/io/nsScriptableInputStream.cpp b/xpcom/io/nsScriptableInputStream.cpp new file mode 100644 index 000000000..6f8022238 --- /dev/null +++ b/xpcom/io/nsScriptableInputStream.cpp @@ -0,0 +1,134 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsScriptableInputStream.h" +#include "nsMemory.h" +#include "nsString.h" + +NS_IMPL_ISUPPORTS(nsScriptableInputStream, nsIScriptableInputStream) + +// nsIScriptableInputStream methods +NS_IMETHODIMP +nsScriptableInputStream::Close() +{ + if (!mInputStream) { + return NS_ERROR_NOT_INITIALIZED; + } + return mInputStream->Close(); +} + +NS_IMETHODIMP +nsScriptableInputStream::Init(nsIInputStream* aInputStream) +{ + if (!aInputStream) { + return NS_ERROR_NULL_POINTER; + } + mInputStream = aInputStream; + return NS_OK; +} + +NS_IMETHODIMP +nsScriptableInputStream::Available(uint64_t* aResult) +{ + if (!mInputStream) { + return NS_ERROR_NOT_INITIALIZED; + } + return mInputStream->Available(aResult); +} + +NS_IMETHODIMP +nsScriptableInputStream::Read(uint32_t aCount, char** aResult) +{ + nsresult rv = NS_OK; + uint64_t count64 = 0; + char* buffer = nullptr; + + if (!mInputStream) { + return NS_ERROR_NOT_INITIALIZED; + } + + rv = mInputStream->Available(&count64); + if (NS_FAILED(rv)) { + return rv; + } + + // bug716556 - Ensure count+1 doesn't overflow + uint32_t count = + XPCOM_MIN((uint32_t)XPCOM_MIN(count64, aCount), UINT32_MAX - 1); + buffer = (char*)malloc(count + 1); // make room for '\0' + if (!buffer) { + return NS_ERROR_OUT_OF_MEMORY; + } + + rv = ReadHelper(buffer, count); + if (NS_FAILED(rv)) { + free(buffer); + return rv; + } + + buffer[count] = '\0'; + *aResult = buffer; + return NS_OK; +} + +NS_IMETHODIMP +nsScriptableInputStream::ReadBytes(uint32_t aCount, nsACString& aResult) +{ + if (!mInputStream) { + return NS_ERROR_NOT_INITIALIZED; + } + + if (!aResult.SetLength(aCount, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + MOZ_ASSERT(aResult.Length() == aCount); + char* ptr = aResult.BeginWriting(); + nsresult rv = ReadHelper(ptr, aCount); + if (NS_FAILED(rv)) { + aResult.Truncate(); + } + return rv; +} + +nsresult +nsScriptableInputStream::ReadHelper(char* aBuffer, uint32_t aCount) +{ + uint32_t totalBytesRead = 0; + while (1) { + uint32_t bytesRead; + nsresult rv = mInputStream->Read(aBuffer + totalBytesRead, + aCount - totalBytesRead, + &bytesRead); + if (NS_FAILED(rv)) { + return rv; + } + + totalBytesRead += bytesRead; + if (totalBytesRead == aCount) { + break; + } + + // If we have read zero bytes, we have hit EOF. + if (bytesRead == 0) { + return NS_ERROR_FAILURE; + } + + } + return NS_OK; +} + +nsresult +nsScriptableInputStream::Create(nsISupports* aOuter, REFNSIID aIID, + void** aResult) +{ + if (aOuter) { + return NS_ERROR_NO_AGGREGATION; + } + + RefPtr sis = new nsScriptableInputStream(); + return sis->QueryInterface(aIID, aResult); +} diff --git a/xpcom/io/nsScriptableInputStream.h b/xpcom/io/nsScriptableInputStream.h new file mode 100644 index 000000000..a84facd94 --- /dev/null +++ b/xpcom/io/nsScriptableInputStream.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 ___nsscriptableinputstream___h_ +#define ___nsscriptableinputstream___h_ + +#include "nsIScriptableInputStream.h" +#include "nsIInputStream.h" +#include "nsCOMPtr.h" +#include "mozilla/Attributes.h" + +#define NS_SCRIPTABLEINPUTSTREAM_CID \ +{ 0x7225c040, 0xa9bf, 0x11d3, { 0xa1, 0x97, 0x0, 0x50, 0x4, 0x1c, 0xaf, 0x44 } } + +#define NS_SCRIPTABLEINPUTSTREAM_CONTRACTID "@mozilla.org/scriptableinputstream;1" + +class nsScriptableInputStream final : public nsIScriptableInputStream +{ +public: + // nsISupports methods + NS_DECL_ISUPPORTS + + // nsIScriptableInputStream methods + NS_DECL_NSISCRIPTABLEINPUTSTREAM + + // nsScriptableInputStream methods + nsScriptableInputStream() + { + } + + static nsresult + Create(nsISupports* aOuter, REFNSIID aIID, void** aResult); + +private: + ~nsScriptableInputStream() + { + } + + nsresult ReadHelper(char* aBuffer, uint32_t aCount); + + nsCOMPtr mInputStream; +}; + +#endif // ___nsscriptableinputstream___h_ diff --git a/xpcom/io/nsSegmentedBuffer.cpp b/xpcom/io/nsSegmentedBuffer.cpp new file mode 100644 index 000000000..ab42a73c7 --- /dev/null +++ b/xpcom/io/nsSegmentedBuffer.cpp @@ -0,0 +1,169 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsSegmentedBuffer.h" +#include "nsMemory.h" + +nsresult +nsSegmentedBuffer::Init(uint32_t aSegmentSize, uint32_t aMaxSize) +{ + if (mSegmentArrayCount != 0) { + return NS_ERROR_FAILURE; // initialized more than once + } + mSegmentSize = aSegmentSize; + mMaxSize = aMaxSize; +#if 0 // testing... + mSegmentArrayCount = 2; +#else + mSegmentArrayCount = NS_SEGMENTARRAY_INITIAL_COUNT; +#endif + return NS_OK; +} + +char* +nsSegmentedBuffer::AppendNewSegment() +{ + if (GetSize() >= mMaxSize) { + return nullptr; + } + + if (!mSegmentArray) { + uint32_t bytes = mSegmentArrayCount * sizeof(char*); + mSegmentArray = (char**)moz_xmalloc(bytes); + if (!mSegmentArray) { + return nullptr; + } + memset(mSegmentArray, 0, bytes); + } + + if (IsFull()) { + uint32_t newArraySize = mSegmentArrayCount * 2; + uint32_t bytes = newArraySize * sizeof(char*); + char** newSegArray = (char**)moz_xrealloc(mSegmentArray, bytes); + if (!newSegArray) { + return nullptr; + } + mSegmentArray = newSegArray; + // copy wrapped content to new extension + if (mFirstSegmentIndex > mLastSegmentIndex) { + // deal with wrap around case + memcpy(&mSegmentArray[mSegmentArrayCount], + mSegmentArray, + mLastSegmentIndex * sizeof(char*)); + memset(mSegmentArray, 0, mLastSegmentIndex * sizeof(char*)); + mLastSegmentIndex += mSegmentArrayCount; + memset(&mSegmentArray[mLastSegmentIndex], 0, + (newArraySize - mLastSegmentIndex) * sizeof(char*)); + } else { + memset(&mSegmentArray[mLastSegmentIndex], 0, + (newArraySize - mLastSegmentIndex) * sizeof(char*)); + } + mSegmentArrayCount = newArraySize; + } + + char* seg = (char*)malloc(mSegmentSize); + if (!seg) { + return nullptr; + } + mSegmentArray[mLastSegmentIndex] = seg; + mLastSegmentIndex = ModSegArraySize(mLastSegmentIndex + 1); + return seg; +} + +bool +nsSegmentedBuffer::DeleteFirstSegment() +{ + NS_ASSERTION(mSegmentArray[mFirstSegmentIndex] != nullptr, "deleting bad segment"); + free(mSegmentArray[mFirstSegmentIndex]); + mSegmentArray[mFirstSegmentIndex] = nullptr; + int32_t last = ModSegArraySize(mLastSegmentIndex - 1); + if (mFirstSegmentIndex == last) { + mLastSegmentIndex = last; + return true; + } else { + mFirstSegmentIndex = ModSegArraySize(mFirstSegmentIndex + 1); + return false; + } +} + +bool +nsSegmentedBuffer::DeleteLastSegment() +{ + int32_t last = ModSegArraySize(mLastSegmentIndex - 1); + NS_ASSERTION(mSegmentArray[last] != nullptr, "deleting bad segment"); + free(mSegmentArray[last]); + mSegmentArray[last] = nullptr; + mLastSegmentIndex = last; + return (bool)(mLastSegmentIndex == mFirstSegmentIndex); +} + +bool +nsSegmentedBuffer::ReallocLastSegment(size_t aNewSize) +{ + int32_t last = ModSegArraySize(mLastSegmentIndex - 1); + NS_ASSERTION(mSegmentArray[last] != nullptr, "realloc'ing bad segment"); + char* newSegment = (char*)realloc(mSegmentArray[last], aNewSize); + if (newSegment) { + mSegmentArray[last] = newSegment; + return true; + } + return false; +} + +void +nsSegmentedBuffer::Empty() +{ + if (mSegmentArray) { + for (uint32_t i = 0; i < mSegmentArrayCount; i++) { + if (mSegmentArray[i]) { + free(mSegmentArray[i]); + } + } + free(mSegmentArray); + mSegmentArray = nullptr; + } + mSegmentArrayCount = NS_SEGMENTARRAY_INITIAL_COUNT; + mFirstSegmentIndex = mLastSegmentIndex = 0; +} + +#if 0 +void +TestSegmentedBuffer() +{ + nsSegmentedBuffer* buf = new nsSegmentedBuffer(); + NS_ASSERTION(buf, "out of memory"); + buf->Init(4, 16); + char* seg; + bool empty; + seg = buf->AppendNewSegment(); + NS_ASSERTION(seg, "AppendNewSegment failed"); + seg = buf->AppendNewSegment(); + NS_ASSERTION(seg, "AppendNewSegment failed"); + seg = buf->AppendNewSegment(); + NS_ASSERTION(seg, "AppendNewSegment failed"); + empty = buf->DeleteFirstSegment(); + NS_ASSERTION(!empty, "DeleteFirstSegment failed"); + empty = buf->DeleteFirstSegment(); + NS_ASSERTION(!empty, "DeleteFirstSegment failed"); + seg = buf->AppendNewSegment(); + NS_ASSERTION(seg, "AppendNewSegment failed"); + seg = buf->AppendNewSegment(); + NS_ASSERTION(seg, "AppendNewSegment failed"); + seg = buf->AppendNewSegment(); + NS_ASSERTION(seg, "AppendNewSegment failed"); + empty = buf->DeleteFirstSegment(); + NS_ASSERTION(!empty, "DeleteFirstSegment failed"); + empty = buf->DeleteFirstSegment(); + NS_ASSERTION(!empty, "DeleteFirstSegment failed"); + empty = buf->DeleteFirstSegment(); + NS_ASSERTION(!empty, "DeleteFirstSegment failed"); + empty = buf->DeleteFirstSegment(); + NS_ASSERTION(empty, "DeleteFirstSegment failed"); + delete buf; +} +#endif + +//////////////////////////////////////////////////////////////////////////////// diff --git a/xpcom/io/nsSegmentedBuffer.h b/xpcom/io/nsSegmentedBuffer.h new file mode 100644 index 000000000..37b3a8e4f --- /dev/null +++ b/xpcom/io/nsSegmentedBuffer.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 nsSegmentedBuffer_h__ +#define nsSegmentedBuffer_h__ + +#include "nsIMemory.h" + +class nsSegmentedBuffer +{ +public: + nsSegmentedBuffer() + : mSegmentSize(0) + , mMaxSize(0) + , mSegmentArray(nullptr) + , mSegmentArrayCount(0) + , mFirstSegmentIndex(0) + , mLastSegmentIndex(0) + { + } + + ~nsSegmentedBuffer() + { + Empty(); + } + + + nsresult Init(uint32_t aSegmentSize, uint32_t aMaxSize); + + char* AppendNewSegment(); // pushes at end + + // returns true if no more segments remain: + bool DeleteFirstSegment(); // pops from beginning + + // returns true if no more segments remain: + bool DeleteLastSegment(); // pops from beginning + + // Call Realloc() on last segment. This is used to reduce memory + // consumption when data is not an exact multiple of segment size. + bool ReallocLastSegment(size_t aNewSize); + + void Empty(); // frees all segments + + inline uint32_t GetSegmentCount() + { + if (mFirstSegmentIndex <= mLastSegmentIndex) { + return mLastSegmentIndex - mFirstSegmentIndex; + } else { + return mSegmentArrayCount + mLastSegmentIndex - mFirstSegmentIndex; + } + } + + inline uint32_t GetSegmentSize() + { + return mSegmentSize; + } + inline uint32_t GetMaxSize() + { + return mMaxSize; + } + inline uint32_t GetSize() + { + return GetSegmentCount() * mSegmentSize; + } + + inline char* GetSegment(uint32_t aIndex) + { + NS_ASSERTION(aIndex < GetSegmentCount(), "index out of bounds"); + int32_t i = ModSegArraySize(mFirstSegmentIndex + (int32_t)aIndex); + return mSegmentArray[i]; + } + +protected: + inline int32_t ModSegArraySize(int32_t aIndex) + { + uint32_t result = aIndex & (mSegmentArrayCount - 1); + NS_ASSERTION(result == aIndex % mSegmentArrayCount, + "non-power-of-2 mSegmentArrayCount"); + return result; + } + + inline bool IsFull() + { + return ModSegArraySize(mLastSegmentIndex + 1) == mFirstSegmentIndex; + } + +protected: + uint32_t mSegmentSize; + uint32_t mMaxSize; + char** mSegmentArray; + uint32_t mSegmentArrayCount; + int32_t mFirstSegmentIndex; + int32_t mLastSegmentIndex; +}; + +// NS_SEGMENTARRAY_INITIAL_SIZE: This number needs to start out as a +// power of 2 given how it gets used. We double the segment array +// when we overflow it, and use that fact that it's a power of 2 +// to compute a fast modulus operation in IsFull. +// +// 32 segment array entries can accommodate 128k of data if segments +// are 4k in size. That seems like a reasonable amount that will avoid +// needing to grow the segment array. +#define NS_SEGMENTARRAY_INITIAL_COUNT 32 + +#endif // nsSegmentedBuffer_h__ diff --git a/xpcom/io/nsStorageStream.cpp b/xpcom/io/nsStorageStream.cpp new file mode 100644 index 000000000..3af7bb853 --- /dev/null +++ b/xpcom/io/nsStorageStream.cpp @@ -0,0 +1,648 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 storage stream provides an internal buffer that can be filled by a + * client using a single output stream. One or more independent input streams + * can be created to read the data out non-destructively. The implementation + * uses a segmented buffer internally to avoid realloc'ing of large buffers, + * with the attendant performance loss and heap fragmentation. + */ + +#include "nsAlgorithm.h" +#include "nsStorageStream.h" +#include "nsSegmentedBuffer.h" +#include "nsStreamUtils.h" +#include "nsCOMPtr.h" +#include "nsICloneableInputStream.h" +#include "nsIInputStream.h" +#include "nsIIPCSerializableInputStream.h" +#include "nsISeekableStream.h" +#include "mozilla/Logging.h" +#include "mozilla/Attributes.h" +#include "mozilla/Likely.h" +#include "mozilla/MathAlgorithms.h" +#include "mozilla/ipc/InputStreamUtils.h" + +using mozilla::ipc::InputStreamParams; +using mozilla::ipc::StringInputStreamParams; +using mozilla::Maybe; +using mozilla::Some; + +// +// Log module for StorageStream logging... +// +// To enable logging (see prlog.h for full details): +// +// set MOZ_LOG=StorageStreamLog:5 +// set MOZ_LOG_FILE=storage.log +// +// This enables LogLevel::Debug level information and places all output in +// the file storage.log. +// +static LazyLogModule sStorageStreamLog("nsStorageStream"); +#ifdef LOG +#undef LOG +#endif +#define LOG(args) MOZ_LOG(sStorageStreamLog, mozilla::LogLevel::Debug, args) + +nsStorageStream::nsStorageStream() + : mSegmentedBuffer(0), mSegmentSize(0), mWriteInProgress(false), + mLastSegmentNum(-1), mWriteCursor(0), mSegmentEnd(0), mLogicalLength(0) +{ + LOG(("Creating nsStorageStream [%p].\n", this)); +} + +nsStorageStream::~nsStorageStream() +{ + delete mSegmentedBuffer; +} + +NS_IMPL_ISUPPORTS(nsStorageStream, + nsIStorageStream, + nsIOutputStream) + +NS_IMETHODIMP +nsStorageStream::Init(uint32_t aSegmentSize, uint32_t aMaxSize) +{ + mSegmentedBuffer = new nsSegmentedBuffer(); + mSegmentSize = aSegmentSize; + mSegmentSizeLog2 = mozilla::FloorLog2(aSegmentSize); + + // Segment size must be a power of two + if (mSegmentSize != ((uint32_t)1 << mSegmentSizeLog2)) { + return NS_ERROR_INVALID_ARG; + } + + return mSegmentedBuffer->Init(aSegmentSize, aMaxSize); +} + +NS_IMETHODIMP +nsStorageStream::GetOutputStream(int32_t aStartingOffset, + nsIOutputStream** aOutputStream) +{ + if (NS_WARN_IF(!aOutputStream)) { + return NS_ERROR_INVALID_ARG; + } + if (NS_WARN_IF(!mSegmentedBuffer)) { + return NS_ERROR_NOT_INITIALIZED; + } + + if (mWriteInProgress) { + return NS_ERROR_NOT_AVAILABLE; + } + + nsresult rv = Seek(aStartingOffset); + if (NS_FAILED(rv)) { + return rv; + } + + // Enlarge the last segment in the buffer so that it is the same size as + // all the other segments in the buffer. (It may have been realloc'ed + // smaller in the Close() method.) + if (mLastSegmentNum >= 0) + if (mSegmentedBuffer->ReallocLastSegment(mSegmentSize)) { + // Need to re-Seek, since realloc changed segment base pointer + rv = Seek(aStartingOffset); + if (NS_FAILED(rv)) { + return rv; + } + } + + NS_ADDREF(this); + *aOutputStream = static_cast(this); + mWriteInProgress = true; + return NS_OK; +} + +NS_IMETHODIMP +nsStorageStream::Close() +{ + if (NS_WARN_IF(!mSegmentedBuffer)) { + return NS_ERROR_NOT_INITIALIZED; + } + + mWriteInProgress = false; + + int32_t segmentOffset = SegOffset(mLogicalLength); + + // Shrink the final segment in the segmented buffer to the minimum size + // needed to contain the data, so as to conserve memory. + if (segmentOffset) { + mSegmentedBuffer->ReallocLastSegment(segmentOffset); + } + + mWriteCursor = 0; + mSegmentEnd = 0; + + LOG(("nsStorageStream [%p] Close mWriteCursor=%x mSegmentEnd=%x\n", + this, mWriteCursor, mSegmentEnd)); + + return NS_OK; +} + +NS_IMETHODIMP +nsStorageStream::Flush() +{ + return NS_OK; +} + +NS_IMETHODIMP +nsStorageStream::Write(const char* aBuffer, uint32_t aCount, + uint32_t* aNumWritten) +{ + if (NS_WARN_IF(!aNumWritten) || NS_WARN_IF(!aBuffer)) { + return NS_ERROR_INVALID_ARG; + } + if (NS_WARN_IF(!mSegmentedBuffer)) { + return NS_ERROR_NOT_INITIALIZED; + } + + const char* readCursor; + uint32_t count, availableInSegment, remaining; + nsresult rv = NS_OK; + + LOG(("nsStorageStream [%p] Write mWriteCursor=%x mSegmentEnd=%x aCount=%d\n", + this, mWriteCursor, mSegmentEnd, aCount)); + + remaining = aCount; + readCursor = aBuffer; + // If no segments have been created yet, create one even if we don't have + // to write any data; this enables creating an input stream which reads from + // the very end of the data for any amount of data in the stream (i.e. + // this stream contains N bytes of data and newInputStream(N) is called), + // even for N=0 (with the caveat that we require .write("", 0) be called to + // initialize internal buffers). + bool firstTime = mSegmentedBuffer->GetSegmentCount() == 0; + while (remaining || MOZ_UNLIKELY(firstTime)) { + firstTime = false; + availableInSegment = mSegmentEnd - mWriteCursor; + if (!availableInSegment) { + mWriteCursor = mSegmentedBuffer->AppendNewSegment(); + if (!mWriteCursor) { + mSegmentEnd = 0; + rv = NS_ERROR_OUT_OF_MEMORY; + goto out; + } + mLastSegmentNum++; + mSegmentEnd = mWriteCursor + mSegmentSize; + availableInSegment = mSegmentEnd - mWriteCursor; + LOG(("nsStorageStream [%p] Write (new seg) mWriteCursor=%x mSegmentEnd=%x\n", + this, mWriteCursor, mSegmentEnd)); + } + + count = XPCOM_MIN(availableInSegment, remaining); + memcpy(mWriteCursor, readCursor, count); + remaining -= count; + readCursor += count; + mWriteCursor += count; + LOG(("nsStorageStream [%p] Writing mWriteCursor=%x mSegmentEnd=%x count=%d\n", + this, mWriteCursor, mSegmentEnd, count)); + } + +out: + *aNumWritten = aCount - remaining; + mLogicalLength += *aNumWritten; + + LOG(("nsStorageStream [%p] Wrote mWriteCursor=%x mSegmentEnd=%x numWritten=%d\n", + this, mWriteCursor, mSegmentEnd, *aNumWritten)); + return rv; +} + +NS_IMETHODIMP +nsStorageStream::WriteFrom(nsIInputStream* aInStr, uint32_t aCount, + uint32_t* aResult) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsStorageStream::WriteSegments(nsReadSegmentFun aReader, void* aClosure, + uint32_t aCount, uint32_t* aResult) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsStorageStream::IsNonBlocking(bool* aNonBlocking) +{ + *aNonBlocking = false; + return NS_OK; +} + +NS_IMETHODIMP +nsStorageStream::GetLength(uint32_t* aLength) +{ + *aLength = mLogicalLength; + return NS_OK; +} + +// Truncate the buffer by deleting the end segments +NS_IMETHODIMP +nsStorageStream::SetLength(uint32_t aLength) +{ + if (NS_WARN_IF(!mSegmentedBuffer)) { + return NS_ERROR_NOT_INITIALIZED; + } + + if (mWriteInProgress) { + return NS_ERROR_NOT_AVAILABLE; + } + + if (aLength > mLogicalLength) { + return NS_ERROR_INVALID_ARG; + } + + int32_t newLastSegmentNum = SegNum(aLength); + int32_t segmentOffset = SegOffset(aLength); + if (segmentOffset == 0) { + newLastSegmentNum--; + } + + while (newLastSegmentNum < mLastSegmentNum) { + mSegmentedBuffer->DeleteLastSegment(); + mLastSegmentNum--; + } + + mLogicalLength = aLength; + return NS_OK; +} + +NS_IMETHODIMP +nsStorageStream::GetWriteInProgress(bool* aWriteInProgress) +{ + *aWriteInProgress = mWriteInProgress; + return NS_OK; +} + +nsresult +nsStorageStream::Seek(int32_t aPosition) +{ + if (NS_WARN_IF(!mSegmentedBuffer)) { + return NS_ERROR_NOT_INITIALIZED; + } + + // An argument of -1 means "seek to end of stream" + if (aPosition == -1) { + aPosition = mLogicalLength; + } + + // Seeking beyond the buffer end is illegal + if ((uint32_t)aPosition > mLogicalLength) { + return NS_ERROR_INVALID_ARG; + } + + // Seeking backwards in the write stream results in truncation + SetLength(aPosition); + + // Special handling for seek to start-of-buffer + if (aPosition == 0) { + mWriteCursor = 0; + mSegmentEnd = 0; + LOG(("nsStorageStream [%p] Seek mWriteCursor=%x mSegmentEnd=%x\n", + this, mWriteCursor, mSegmentEnd)); + return NS_OK; + } + + // Segment may have changed, so reset pointers + mWriteCursor = mSegmentedBuffer->GetSegment(mLastSegmentNum); + NS_ASSERTION(mWriteCursor, "null mWriteCursor"); + mSegmentEnd = mWriteCursor + mSegmentSize; + + // Adjust write cursor for current segment offset. This test is necessary + // because SegNum may reference the next-to-be-allocated segment, in which + // case we need to be pointing at the end of the last segment. + int32_t segmentOffset = SegOffset(aPosition); + if (segmentOffset == 0 && (SegNum(aPosition) > (uint32_t) mLastSegmentNum)) { + mWriteCursor = mSegmentEnd; + } else { + mWriteCursor += segmentOffset; + } + + LOG(("nsStorageStream [%p] Seek mWriteCursor=%x mSegmentEnd=%x\n", + this, mWriteCursor, mSegmentEnd)); + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////////////// + +// There can be many nsStorageInputStreams for a single nsStorageStream +class nsStorageInputStream final + : public nsIInputStream + , public nsISeekableStream + , public nsIIPCSerializableInputStream + , public nsICloneableInputStream +{ +public: + nsStorageInputStream(nsStorageStream* aStorageStream, uint32_t aSegmentSize) + : mStorageStream(aStorageStream), mReadCursor(0), + mSegmentEnd(0), mSegmentNum(0), + mSegmentSize(aSegmentSize), mLogicalCursor(0), + mStatus(NS_OK) + { + } + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIINPUTSTREAM + NS_DECL_NSISEEKABLESTREAM + NS_DECL_NSIIPCSERIALIZABLEINPUTSTREAM + NS_DECL_NSICLONEABLEINPUTSTREAM + +private: + ~nsStorageInputStream() + { + } + +protected: + nsresult Seek(uint32_t aPosition); + + friend class nsStorageStream; + +private: + RefPtr mStorageStream; + uint32_t mReadCursor; // Next memory location to read byte, or 0 + uint32_t mSegmentEnd; // One byte past end of current buffer segment + uint32_t mSegmentNum; // Segment number containing read cursor + uint32_t mSegmentSize; // All segments, except the last, are of this size + uint32_t mLogicalCursor; // Logical offset into stream + nsresult mStatus; + + uint32_t SegNum(uint32_t aPosition) + { + return aPosition >> mStorageStream->mSegmentSizeLog2; + } + uint32_t SegOffset(uint32_t aPosition) + { + return aPosition & (mSegmentSize - 1); + } +}; + +NS_IMPL_ISUPPORTS(nsStorageInputStream, + nsIInputStream, + nsISeekableStream, + nsIIPCSerializableInputStream, + nsICloneableInputStream) + +NS_IMETHODIMP +nsStorageStream::NewInputStream(int32_t aStartingOffset, + nsIInputStream** aInputStream) +{ + if (NS_WARN_IF(!mSegmentedBuffer)) { + return NS_ERROR_NOT_INITIALIZED; + } + + RefPtr inputStream = + new nsStorageInputStream(this, mSegmentSize); + + nsresult rv = inputStream->Seek(aStartingOffset); + if (NS_FAILED(rv)) { + return rv; + } + + inputStream.forget(aInputStream); + return NS_OK; +} + +NS_IMETHODIMP +nsStorageInputStream::Close() +{ + mStatus = NS_BASE_STREAM_CLOSED; + return NS_OK; +} + +NS_IMETHODIMP +nsStorageInputStream::Available(uint64_t* aAvailable) +{ + if (NS_FAILED(mStatus)) { + return mStatus; + } + + *aAvailable = mStorageStream->mLogicalLength - mLogicalCursor; + return NS_OK; +} + +NS_IMETHODIMP +nsStorageInputStream::Read(char* aBuffer, uint32_t aCount, uint32_t* aNumRead) +{ + return ReadSegments(NS_CopySegmentToBuffer, aBuffer, aCount, aNumRead); +} + +NS_IMETHODIMP +nsStorageInputStream::ReadSegments(nsWriteSegmentFun aWriter, void* aClosure, + uint32_t aCount, uint32_t* aNumRead) +{ + *aNumRead = 0; + if (mStatus == NS_BASE_STREAM_CLOSED) { + return NS_OK; + } + if (NS_FAILED(mStatus)) { + return mStatus; + } + + uint32_t count, availableInSegment, remainingCapacity, bytesConsumed; + nsresult rv; + + remainingCapacity = aCount; + while (remainingCapacity) { + availableInSegment = mSegmentEnd - mReadCursor; + if (!availableInSegment) { + uint32_t available = mStorageStream->mLogicalLength - mLogicalCursor; + if (!available) { + goto out; + } + + // We have data in the stream, but if mSegmentEnd is zero, then we + // were likely constructed prior to any data being written into + // the stream. Therefore, if mSegmentEnd is non-zero, we should + // move into the next segment; otherwise, we should stay in this + // segment so our input state can be updated and we can properly + // perform the initial read. + if (mSegmentEnd > 0) { + mSegmentNum++; + } + mReadCursor = 0; + mSegmentEnd = XPCOM_MIN(mSegmentSize, available); + availableInSegment = mSegmentEnd; + } + const char* cur = mStorageStream->mSegmentedBuffer->GetSegment(mSegmentNum); + + count = XPCOM_MIN(availableInSegment, remainingCapacity); + rv = aWriter(this, aClosure, cur + mReadCursor, aCount - remainingCapacity, + count, &bytesConsumed); + if (NS_FAILED(rv) || (bytesConsumed == 0)) { + break; + } + remainingCapacity -= bytesConsumed; + mReadCursor += bytesConsumed; + mLogicalCursor += bytesConsumed; + } + +out: + *aNumRead = aCount - remainingCapacity; + + bool isWriteInProgress = false; + if (NS_FAILED(mStorageStream->GetWriteInProgress(&isWriteInProgress))) { + isWriteInProgress = false; + } + + if (*aNumRead == 0 && isWriteInProgress) { + return NS_BASE_STREAM_WOULD_BLOCK; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsStorageInputStream::IsNonBlocking(bool* aNonBlocking) +{ + // TODO: This class should implement nsIAsyncInputStream so that callers + // have some way of dealing with NS_BASE_STREAM_WOULD_BLOCK errors. + + *aNonBlocking = true; + return NS_OK; +} + +NS_IMETHODIMP +nsStorageInputStream::Seek(int32_t aWhence, int64_t aOffset) +{ + if (NS_FAILED(mStatus)) { + return mStatus; + } + + int64_t pos = aOffset; + + switch (aWhence) { + case NS_SEEK_SET: + break; + case NS_SEEK_CUR: + pos += mLogicalCursor; + break; + case NS_SEEK_END: + pos += mStorageStream->mLogicalLength; + break; + default: + NS_NOTREACHED("unexpected whence value"); + return NS_ERROR_UNEXPECTED; + } + if (pos == int64_t(mLogicalCursor)) { + return NS_OK; + } + + return Seek(pos); +} + +NS_IMETHODIMP +nsStorageInputStream::Tell(int64_t* aResult) +{ + if (NS_FAILED(mStatus)) { + return mStatus; + } + + *aResult = mLogicalCursor; + return NS_OK; +} + +NS_IMETHODIMP +nsStorageInputStream::SetEOF() +{ + NS_NOTREACHED("nsStorageInputStream::SetEOF"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +nsresult +nsStorageInputStream::Seek(uint32_t aPosition) +{ + uint32_t length = mStorageStream->mLogicalLength; + if (aPosition > length) { + return NS_ERROR_INVALID_ARG; + } + + if (length == 0) { + return NS_OK; + } + + mSegmentNum = SegNum(aPosition); + mReadCursor = SegOffset(aPosition); + uint32_t available = length - aPosition; + mSegmentEnd = mReadCursor + XPCOM_MIN(mSegmentSize - mReadCursor, available); + mLogicalCursor = aPosition; + return NS_OK; +} + +void +nsStorageInputStream::Serialize(InputStreamParams& aParams, FileDescriptorArray&) +{ + nsCString combined; + int64_t offset; + mozilla::DebugOnly rv = Tell(&offset); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + + uint64_t remaining; + rv = Available(&remaining); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + + combined.SetCapacity(remaining); + uint32_t numRead = 0; + + rv = Read(combined.BeginWriting(), remaining, &numRead); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + MOZ_ASSERT(numRead == remaining); + combined.SetLength(numRead); + + rv = Seek(NS_SEEK_SET, offset); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + + StringInputStreamParams params; + params.data() = combined; + aParams = params; +} + +Maybe +nsStorageInputStream::ExpectedSerializedLength() +{ + uint64_t remaining = 0; + DebugOnly rv = Available(&remaining); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + return Some(remaining); +} + +bool +nsStorageInputStream::Deserialize(const InputStreamParams& aParams, + const FileDescriptorArray&) +{ + NS_NOTREACHED("We should never attempt to deserialize a storage input stream."); + return false; +} + +NS_IMETHODIMP +nsStorageInputStream::GetCloneable(bool* aCloneableOut) +{ + *aCloneableOut = true; + return NS_OK; +} + +NS_IMETHODIMP +nsStorageInputStream::Clone(nsIInputStream** aCloneOut) +{ + return mStorageStream->NewInputStream(mLogicalCursor, aCloneOut); +} + +nsresult +NS_NewStorageStream(uint32_t aSegmentSize, uint32_t aMaxSize, + nsIStorageStream** aResult) +{ + RefPtr storageStream = new nsStorageStream(); + nsresult rv = storageStream->Init(aSegmentSize, aMaxSize); + if (NS_FAILED(rv)) { + return rv; + } + storageStream.forget(aResult); + return NS_OK; +} + +// Undefine LOG, so that other .cpp files (or their includes) won't complain +// about it already being defined, when we build in unified mode. +#undef LOG diff --git a/xpcom/io/nsStorageStream.h b/xpcom/io/nsStorageStream.h new file mode 100644 index 000000000..7fddd683e --- /dev/null +++ b/xpcom/io/nsStorageStream.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/. */ + +/* + * The storage stream provides an internal buffer that can be filled by a + * client using a single output stream. One or more independent input streams + * can be created to read the data out non-destructively. The implementation + * uses a segmented buffer internally to avoid realloc'ing of large buffers, + * with the attendant performance loss and heap fragmentation. + */ + +#ifndef _nsStorageStream_h_ +#define _nsStorageStream_h_ + +#include "nsIStorageStream.h" +#include "nsIOutputStream.h" +#include "nsMemory.h" +#include "mozilla/Attributes.h" + +#define NS_STORAGESTREAM_CID \ +{ /* 669a9795-6ff7-4ed4-9150-c34ce2971b63 */ \ + 0x669a9795, \ + 0x6ff7, \ + 0x4ed4, \ + {0x91, 0x50, 0xc3, 0x4c, 0xe2, 0x97, 0x1b, 0x63} \ +} + +#define NS_STORAGESTREAM_CONTRACTID "@mozilla.org/storagestream;1" + +class nsSegmentedBuffer; + +class nsStorageStream final + : public nsIStorageStream + , public nsIOutputStream +{ +public: + nsStorageStream(); + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSISTORAGESTREAM + NS_DECL_NSIOUTPUTSTREAM + + friend class nsStorageInputStream; + +private: + ~nsStorageStream(); + + nsSegmentedBuffer* mSegmentedBuffer; + uint32_t mSegmentSize; // All segments, except possibly the last, are of this size + // Must be power-of-2 + uint32_t mSegmentSizeLog2; // log2(mSegmentSize) + bool mWriteInProgress; // true, if an un-Close'ed output stream exists + int32_t mLastSegmentNum; // Last segment # in use, -1 initially + char* mWriteCursor; // Pointer to next byte to be written + char* mSegmentEnd; // Pointer to one byte after end of segment + // containing the write cursor + uint32_t mLogicalLength; // Number of bytes written to stream + + nsresult Seek(int32_t aPosition); + uint32_t SegNum(uint32_t aPosition) + { + return aPosition >> mSegmentSizeLog2; + } + uint32_t SegOffset(uint32_t aPosition) + { + return aPosition & (mSegmentSize - 1); + } +}; + +#endif // _nsStorageStream_h_ diff --git a/xpcom/io/nsStreamUtils.cpp b/xpcom/io/nsStreamUtils.cpp new file mode 100644 index 000000000..891dad59f --- /dev/null +++ b/xpcom/io/nsStreamUtils.cpp @@ -0,0 +1,957 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. 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/Mutex.h" +#include "mozilla/Attributes.h" +#include "nsStreamUtils.h" +#include "nsAutoPtr.h" +#include "nsCOMPtr.h" +#include "nsIPipe.h" +#include "nsICloneableInputStream.h" +#include "nsIEventTarget.h" +#include "nsICancelableRunnable.h" +#include "nsISafeOutputStream.h" +#include "nsString.h" +#include "nsIAsyncInputStream.h" +#include "nsIAsyncOutputStream.h" +#include "nsIBufferedStreams.h" +#include "nsNetCID.h" +#include "nsServiceManagerUtils.h" +#include "nsThreadUtils.h" + +using namespace mozilla; + +//----------------------------------------------------------------------------- + +// 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 nsInputStreamReadyEvent final + : public CancelableRunnable + , public nsIInputStreamCallback +{ +public: + NS_DECL_ISUPPORTS_INHERITED + + nsInputStreamReadyEvent(nsIInputStreamCallback* aCallback, + nsIEventTarget* aTarget) + : mCallback(aCallback) + , mTarget(aTarget) + { + } + +private: + ~nsInputStreamReadyEvent() + { + if (!mCallback) { + return; + } + // + // whoa!! looks like we never posted this event. take care to + // release mCallback on the correct thread. if mTarget lives on the + // calling thread, then we are ok. otherwise, we have to try to + // proxy the Release over the right thread. if that thread is dead, + // then there's nothing we can do... better to leak than crash. + // + bool val; + nsresult rv = mTarget->IsOnCurrentThread(&val); + if (NS_FAILED(rv) || !val) { + nsCOMPtr event = + NS_NewInputStreamReadyEvent(mCallback, mTarget); + mCallback = nullptr; + if (event) { + rv = event->OnInputStreamReady(nullptr); + if (NS_FAILED(rv)) { + NS_NOTREACHED("leaking stream event"); + nsISupports* sup = event; + NS_ADDREF(sup); + } + } + } + } + +public: + NS_IMETHOD OnInputStreamReady(nsIAsyncInputStream* aStream) override + { + mStream = aStream; + + nsresult rv = + mTarget->Dispatch(this, NS_DISPATCH_NORMAL); + if (NS_FAILED(rv)) { + NS_WARNING("Dispatch failed"); + return NS_ERROR_FAILURE; + } + + return NS_OK; + } + + NS_IMETHOD Run() override + { + if (mCallback) { + if (mStream) { + mCallback->OnInputStreamReady(mStream); + } + mCallback = nullptr; + } + return NS_OK; + } + + nsresult Cancel() override + { + mCallback = nullptr; + return NS_OK; + } + +private: + nsCOMPtr mStream; + nsCOMPtr mCallback; + nsCOMPtr mTarget; +}; + +NS_IMPL_ISUPPORTS_INHERITED(nsInputStreamReadyEvent, CancelableRunnable, + nsIInputStreamCallback) + +//----------------------------------------------------------------------------- + +// 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 nsOutputStreamReadyEvent final + : public CancelableRunnable + , public nsIOutputStreamCallback +{ +public: + NS_DECL_ISUPPORTS_INHERITED + + nsOutputStreamReadyEvent(nsIOutputStreamCallback* aCallback, + nsIEventTarget* aTarget) + : mCallback(aCallback) + , mTarget(aTarget) + { + } + +private: + ~nsOutputStreamReadyEvent() + { + if (!mCallback) { + return; + } + // + // whoa!! looks like we never posted this event. take care to + // release mCallback on the correct thread. if mTarget lives on the + // calling thread, then we are ok. otherwise, we have to try to + // proxy the Release over the right thread. if that thread is dead, + // then there's nothing we can do... better to leak than crash. + // + bool val; + nsresult rv = mTarget->IsOnCurrentThread(&val); + if (NS_FAILED(rv) || !val) { + nsCOMPtr event = + NS_NewOutputStreamReadyEvent(mCallback, mTarget); + mCallback = nullptr; + if (event) { + rv = event->OnOutputStreamReady(nullptr); + if (NS_FAILED(rv)) { + NS_NOTREACHED("leaking stream event"); + nsISupports* sup = event; + NS_ADDREF(sup); + } + } + } + } + +public: + NS_IMETHOD OnOutputStreamReady(nsIAsyncOutputStream* aStream) override + { + mStream = aStream; + + nsresult rv = + mTarget->Dispatch(this, NS_DISPATCH_NORMAL); + if (NS_FAILED(rv)) { + NS_WARNING("PostEvent failed"); + return NS_ERROR_FAILURE; + } + + return NS_OK; + } + + NS_IMETHOD Run() override + { + if (mCallback) { + if (mStream) { + mCallback->OnOutputStreamReady(mStream); + } + mCallback = nullptr; + } + return NS_OK; + } + + nsresult Cancel() override + { + mCallback = nullptr; + return NS_OK; + } + +private: + nsCOMPtr mStream; + nsCOMPtr mCallback; + nsCOMPtr mTarget; +}; + +NS_IMPL_ISUPPORTS_INHERITED(nsOutputStreamReadyEvent, CancelableRunnable, + nsIOutputStreamCallback) + +//----------------------------------------------------------------------------- + +already_AddRefed +NS_NewInputStreamReadyEvent(nsIInputStreamCallback* aCallback, + nsIEventTarget* aTarget) +{ + NS_ASSERTION(aCallback, "null callback"); + NS_ASSERTION(aTarget, "null target"); + RefPtr ev = + new nsInputStreamReadyEvent(aCallback, aTarget); + return ev.forget(); +} + +already_AddRefed +NS_NewOutputStreamReadyEvent(nsIOutputStreamCallback* aCallback, + nsIEventTarget* aTarget) +{ + NS_ASSERTION(aCallback, "null callback"); + NS_ASSERTION(aTarget, "null target"); + RefPtr ev = + new nsOutputStreamReadyEvent(aCallback, aTarget); + return ev.forget(); +} + +//----------------------------------------------------------------------------- +// NS_AsyncCopy implementation + +// abstract stream copier... +class nsAStreamCopier + : public nsIInputStreamCallback + , public nsIOutputStreamCallback + , public CancelableRunnable +{ +public: + NS_DECL_ISUPPORTS_INHERITED + + nsAStreamCopier() + : mLock("nsAStreamCopier.mLock") + , mCallback(nullptr) + , mProgressCallback(nullptr) + , mClosure(nullptr) + , mChunkSize(0) + , mEventInProcess(false) + , mEventIsPending(false) + , mCloseSource(true) + , mCloseSink(true) + , mCanceled(false) + , mCancelStatus(NS_OK) + { + } + + // kick off the async copy... + nsresult Start(nsIInputStream* aSource, + nsIOutputStream* aSink, + nsIEventTarget* aTarget, + nsAsyncCopyCallbackFun aCallback, + void* aClosure, + uint32_t aChunksize, + bool aCloseSource, + bool aCloseSink, + nsAsyncCopyProgressFun aProgressCallback) + { + mSource = aSource; + mSink = aSink; + mTarget = aTarget; + mCallback = aCallback; + mClosure = aClosure; + mChunkSize = aChunksize; + mCloseSource = aCloseSource; + mCloseSink = aCloseSink; + mProgressCallback = aProgressCallback; + + mAsyncSource = do_QueryInterface(mSource); + mAsyncSink = do_QueryInterface(mSink); + + return PostContinuationEvent(); + } + + // implemented by subclasses, returns number of bytes copied and + // sets source and sink condition before returning. + virtual uint32_t DoCopy(nsresult* aSourceCondition, + nsresult* aSinkCondition) = 0; + + void Process() + { + if (!mSource || !mSink) { + return; + } + + nsresult cancelStatus; + bool canceled; + { + MutexAutoLock lock(mLock); + canceled = mCanceled; + cancelStatus = mCancelStatus; + } + + // If the copy was canceled before Process() was even called, then + // sourceCondition and sinkCondition should be set to error results to + // ensure we don't call Finish() on a canceled nsISafeOutputStream. + MOZ_ASSERT(NS_FAILED(cancelStatus) == canceled, "cancel needs an error"); + nsresult sourceCondition = cancelStatus; + nsresult sinkCondition = cancelStatus; + + // Copy data from the source to the sink until we hit failure or have + // copied all the data. + for (;;) { + // Note: copyFailed will be true if the source or the sink have + // reported an error, or if we failed to write any bytes + // because we have consumed all of our data. + bool copyFailed = false; + if (!canceled) { + uint32_t n = DoCopy(&sourceCondition, &sinkCondition); + if (n > 0 && mProgressCallback) { + mProgressCallback(mClosure, n); + } + copyFailed = NS_FAILED(sourceCondition) || + NS_FAILED(sinkCondition) || n == 0; + + MutexAutoLock lock(mLock); + canceled = mCanceled; + cancelStatus = mCancelStatus; + } + if (copyFailed && !canceled) { + if (sourceCondition == NS_BASE_STREAM_WOULD_BLOCK && mAsyncSource) { + // need to wait for more data from source. while waiting for + // more source data, be sure to observe failures on output end. + mAsyncSource->AsyncWait(this, 0, 0, nullptr); + + if (mAsyncSink) + mAsyncSink->AsyncWait(this, + nsIAsyncOutputStream::WAIT_CLOSURE_ONLY, + 0, nullptr); + break; + } else if (sinkCondition == NS_BASE_STREAM_WOULD_BLOCK && mAsyncSink) { + // need to wait for more room in the sink. while waiting for + // more room in the sink, be sure to observer failures on the + // input end. + mAsyncSink->AsyncWait(this, 0, 0, nullptr); + + if (mAsyncSource) + mAsyncSource->AsyncWait(this, + nsIAsyncInputStream::WAIT_CLOSURE_ONLY, + 0, nullptr); + break; + } + } + if (copyFailed || canceled) { + if (mCloseSource) { + // close source + if (mAsyncSource) + mAsyncSource->CloseWithStatus( + canceled ? cancelStatus : sinkCondition); + else { + mSource->Close(); + } + } + mAsyncSource = nullptr; + mSource = nullptr; + + if (mCloseSink) { + // close sink + if (mAsyncSink) + mAsyncSink->CloseWithStatus( + canceled ? cancelStatus : sourceCondition); + else { + // If we have an nsISafeOutputStream, and our + // sourceCondition and sinkCondition are not set to a + // failure state, finish writing. + nsCOMPtr sostream = + do_QueryInterface(mSink); + if (sostream && NS_SUCCEEDED(sourceCondition) && + NS_SUCCEEDED(sinkCondition)) { + sostream->Finish(); + } else { + mSink->Close(); + } + } + } + mAsyncSink = nullptr; + mSink = nullptr; + + // notify state complete... + if (mCallback) { + nsresult status; + if (!canceled) { + status = sourceCondition; + if (NS_SUCCEEDED(status)) { + status = sinkCondition; + } + if (status == NS_BASE_STREAM_CLOSED) { + status = NS_OK; + } + } else { + status = cancelStatus; + } + mCallback(mClosure, status); + } + break; + } + } + } + + nsresult Cancel(nsresult aReason) + { + MutexAutoLock lock(mLock); + if (mCanceled) { + return NS_ERROR_FAILURE; + } + + if (NS_SUCCEEDED(aReason)) { + NS_WARNING("cancel with non-failure status code"); + aReason = NS_BASE_STREAM_CLOSED; + } + + mCanceled = true; + mCancelStatus = aReason; + return NS_OK; + } + + NS_IMETHOD OnInputStreamReady(nsIAsyncInputStream* aSource) override + { + PostContinuationEvent(); + return NS_OK; + } + + NS_IMETHOD OnOutputStreamReady(nsIAsyncOutputStream* aSink) override + { + PostContinuationEvent(); + return NS_OK; + } + + // continuation event handler + NS_IMETHOD Run() override + { + Process(); + + // clear "in process" flag and post any pending continuation event + MutexAutoLock lock(mLock); + mEventInProcess = false; + if (mEventIsPending) { + mEventIsPending = false; + PostContinuationEvent_Locked(); + } + + return NS_OK; + } + + nsresult Cancel() MOZ_MUST_OVERRIDE override = 0; + + nsresult PostContinuationEvent() + { + // we cannot post a continuation event if there is currently + // an event in process. doing so could result in Process being + // run simultaneously on multiple threads, so we mark the event + // as pending, and if an event is already in process then we + // just let that existing event take care of posting the real + // continuation event. + + MutexAutoLock lock(mLock); + return PostContinuationEvent_Locked(); + } + + nsresult PostContinuationEvent_Locked() + { + nsresult rv = NS_OK; + if (mEventInProcess) { + mEventIsPending = true; + } else { + rv = mTarget->Dispatch(this, NS_DISPATCH_NORMAL); + if (NS_SUCCEEDED(rv)) { + mEventInProcess = true; + } else { + NS_WARNING("unable to post continuation event"); + } + } + return rv; + } + +protected: + nsCOMPtr mSource; + nsCOMPtr mSink; + nsCOMPtr mAsyncSource; + nsCOMPtr mAsyncSink; + nsCOMPtr mTarget; + Mutex mLock; + nsAsyncCopyCallbackFun mCallback; + nsAsyncCopyProgressFun mProgressCallback; + void* mClosure; + uint32_t mChunkSize; + bool mEventInProcess; + bool mEventIsPending; + bool mCloseSource; + bool mCloseSink; + bool mCanceled; + nsresult mCancelStatus; + + // virtual since subclasses call superclass Release() + virtual ~nsAStreamCopier() + { + } +}; + +NS_IMPL_ISUPPORTS_INHERITED(nsAStreamCopier, + CancelableRunnable, + nsIInputStreamCallback, + nsIOutputStreamCallback) + +class nsStreamCopierIB final : public nsAStreamCopier +{ +public: + nsStreamCopierIB() : nsAStreamCopier() + { + } + virtual ~nsStreamCopierIB() + { + } + + struct MOZ_STACK_CLASS ReadSegmentsState + { + // the nsIOutputStream will outlive the ReadSegmentsState on the stack + nsIOutputStream* MOZ_NON_OWNING_REF mSink; + nsresult mSinkCondition; + }; + + static nsresult ConsumeInputBuffer(nsIInputStream* aInStr, + void* aClosure, + const char* aBuffer, + uint32_t aOffset, + uint32_t aCount, + uint32_t* aCountWritten) + { + ReadSegmentsState* state = (ReadSegmentsState*)aClosure; + + nsresult rv = state->mSink->Write(aBuffer, aCount, aCountWritten); + if (NS_FAILED(rv)) { + state->mSinkCondition = rv; + } else if (*aCountWritten == 0) { + state->mSinkCondition = NS_BASE_STREAM_CLOSED; + } + + return state->mSinkCondition; + } + + uint32_t DoCopy(nsresult* aSourceCondition, + nsresult* aSinkCondition) override + { + ReadSegmentsState state; + state.mSink = mSink; + state.mSinkCondition = NS_OK; + + uint32_t n; + *aSourceCondition = + mSource->ReadSegments(ConsumeInputBuffer, &state, mChunkSize, &n); + *aSinkCondition = state.mSinkCondition; + return n; + } + + nsresult Cancel() override + { + return NS_OK; + } +}; + +class nsStreamCopierOB final : public nsAStreamCopier +{ +public: + nsStreamCopierOB() : nsAStreamCopier() + { + } + virtual ~nsStreamCopierOB() + { + } + + struct MOZ_STACK_CLASS WriteSegmentsState + { + // the nsIInputStream will outlive the WriteSegmentsState on the stack + nsIInputStream* MOZ_NON_OWNING_REF mSource; + nsresult mSourceCondition; + }; + + static nsresult FillOutputBuffer(nsIOutputStream* aOutStr, + void* aClosure, + char* aBuffer, + uint32_t aOffset, + uint32_t aCount, + uint32_t* aCountRead) + { + WriteSegmentsState* state = (WriteSegmentsState*)aClosure; + + nsresult rv = state->mSource->Read(aBuffer, aCount, aCountRead); + if (NS_FAILED(rv)) { + state->mSourceCondition = rv; + } else if (*aCountRead == 0) { + state->mSourceCondition = NS_BASE_STREAM_CLOSED; + } + + return state->mSourceCondition; + } + + uint32_t DoCopy(nsresult* aSourceCondition, + nsresult* aSinkCondition) override + { + WriteSegmentsState state; + state.mSource = mSource; + state.mSourceCondition = NS_OK; + + uint32_t n; + *aSinkCondition = + mSink->WriteSegments(FillOutputBuffer, &state, mChunkSize, &n); + *aSourceCondition = state.mSourceCondition; + return n; + } + + nsresult Cancel() override + { + return NS_OK; + } +}; + +//----------------------------------------------------------------------------- + +nsresult +NS_AsyncCopy(nsIInputStream* aSource, + nsIOutputStream* aSink, + nsIEventTarget* aTarget, + nsAsyncCopyMode aMode, + uint32_t aChunkSize, + nsAsyncCopyCallbackFun aCallback, + void* aClosure, + bool aCloseSource, + bool aCloseSink, + nsISupports** aCopierCtx, + nsAsyncCopyProgressFun aProgressCallback) +{ + NS_ASSERTION(aTarget, "non-null target required"); + + nsresult rv; + nsAStreamCopier* copier; + + if (aMode == NS_ASYNCCOPY_VIA_READSEGMENTS) { + copier = new nsStreamCopierIB(); + } else { + copier = new nsStreamCopierOB(); + } + + // Start() takes an owning ref to the copier... + NS_ADDREF(copier); + rv = copier->Start(aSource, aSink, aTarget, aCallback, aClosure, aChunkSize, + aCloseSource, aCloseSink, aProgressCallback); + + if (aCopierCtx) { + *aCopierCtx = static_cast(static_cast(copier)); + NS_ADDREF(*aCopierCtx); + } + NS_RELEASE(copier); + + return rv; +} + +//----------------------------------------------------------------------------- + +nsresult +NS_CancelAsyncCopy(nsISupports* aCopierCtx, nsresult aReason) +{ + nsAStreamCopier* copier = + static_cast(static_cast(aCopierCtx)); + return copier->Cancel(aReason); +} + +//----------------------------------------------------------------------------- + +nsresult +NS_ConsumeStream(nsIInputStream* aStream, uint32_t aMaxCount, + nsACString& aResult) +{ + nsresult rv = NS_OK; + aResult.Truncate(); + + while (aMaxCount) { + uint64_t avail64; + rv = aStream->Available(&avail64); + if (NS_FAILED(rv)) { + if (rv == NS_BASE_STREAM_CLOSED) { + rv = NS_OK; + } + break; + } + if (avail64 == 0) { + break; + } + + uint32_t avail = (uint32_t)XPCOM_MIN(avail64, aMaxCount); + + // resize aResult buffer + uint32_t length = aResult.Length(); + if (avail > UINT32_MAX - length) { + return NS_ERROR_FILE_TOO_BIG; + } + + aResult.SetLength(length + avail); + if (aResult.Length() != (length + avail)) { + return NS_ERROR_OUT_OF_MEMORY; + } + char* buf = aResult.BeginWriting() + length; + + uint32_t n; + rv = aStream->Read(buf, avail, &n); + if (NS_FAILED(rv)) { + break; + } + if (n != avail) { + aResult.SetLength(length + n); + } + if (n == 0) { + break; + } + aMaxCount -= n; + } + + return rv; +} + +//----------------------------------------------------------------------------- + +static nsresult +TestInputStream(nsIInputStream* aInStr, + void* aClosure, + const char* aBuffer, + uint32_t aOffset, + uint32_t aCount, + uint32_t* aCountWritten) +{ + bool* result = static_cast(aClosure); + *result = true; + return NS_ERROR_ABORT; // don't call me anymore +} + +bool +NS_InputStreamIsBuffered(nsIInputStream* aStream) +{ + nsCOMPtr bufferedIn = do_QueryInterface(aStream); + if (bufferedIn) { + return true; + } + + bool result = false; + uint32_t n; + nsresult rv = aStream->ReadSegments(TestInputStream, &result, 1, &n); + return result || NS_SUCCEEDED(rv); +} + +static nsresult +TestOutputStream(nsIOutputStream* aOutStr, + void* aClosure, + char* aBuffer, + uint32_t aOffset, + uint32_t aCount, + uint32_t* aCountRead) +{ + bool* result = static_cast(aClosure); + *result = true; + return NS_ERROR_ABORT; // don't call me anymore +} + +bool +NS_OutputStreamIsBuffered(nsIOutputStream* aStream) +{ + nsCOMPtr bufferedOut = do_QueryInterface(aStream); + if (bufferedOut) { + return true; + } + + bool result = false; + uint32_t n; + aStream->WriteSegments(TestOutputStream, &result, 1, &n); + return result; +} + +//----------------------------------------------------------------------------- + +nsresult +NS_CopySegmentToStream(nsIInputStream* aInStr, + void* aClosure, + const char* aBuffer, + uint32_t aOffset, + uint32_t aCount, + uint32_t* aCountWritten) +{ + nsIOutputStream* outStr = static_cast(aClosure); + *aCountWritten = 0; + while (aCount) { + uint32_t n; + nsresult rv = outStr->Write(aBuffer, aCount, &n); + if (NS_FAILED(rv)) { + return rv; + } + aBuffer += n; + aCount -= n; + *aCountWritten += n; + } + return NS_OK; +} + +nsresult +NS_CopySegmentToBuffer(nsIInputStream* aInStr, + void* aClosure, + const char* aBuffer, + uint32_t aOffset, + uint32_t aCount, + uint32_t* aCountWritten) +{ + char* toBuf = static_cast(aClosure); + memcpy(&toBuf[aOffset], aBuffer, aCount); + *aCountWritten = aCount; + return NS_OK; +} + +nsresult +NS_CopySegmentToBuffer(nsIOutputStream* aOutStr, + void* aClosure, + char* aBuffer, + uint32_t aOffset, + uint32_t aCount, + uint32_t* aCountRead) +{ + const char* fromBuf = static_cast(aClosure); + memcpy(aBuffer, &fromBuf[aOffset], aCount); + *aCountRead = aCount; + return NS_OK; +} + +nsresult +NS_DiscardSegment(nsIInputStream* aInStr, + void* aClosure, + const char* aBuffer, + uint32_t aOffset, + uint32_t aCount, + uint32_t* aCountWritten) +{ + *aCountWritten = aCount; + return NS_OK; +} + +//----------------------------------------------------------------------------- + +nsresult +NS_WriteSegmentThunk(nsIInputStream* aInStr, + void* aClosure, + const char* aBuffer, + uint32_t aOffset, + uint32_t aCount, + uint32_t* aCountWritten) +{ + nsWriteSegmentThunk* thunk = static_cast(aClosure); + return thunk->mFun(thunk->mStream, thunk->mClosure, aBuffer, aOffset, aCount, + aCountWritten); +} + +nsresult +NS_FillArray(FallibleTArray& aDest, nsIInputStream* aInput, + uint32_t aKeep, uint32_t* aNewBytes) +{ + MOZ_ASSERT(aInput, "null stream"); + MOZ_ASSERT(aKeep <= aDest.Length(), "illegal keep count"); + + char* aBuffer = aDest.Elements(); + int64_t keepOffset = int64_t(aDest.Length()) - aKeep; + if (aKeep != 0 && keepOffset > 0) { + memmove(aBuffer, aBuffer + keepOffset, aKeep); + } + + nsresult rv = + aInput->Read(aBuffer + aKeep, aDest.Capacity() - aKeep, aNewBytes); + if (NS_FAILED(rv)) { + *aNewBytes = 0; + } + // NOTE: we rely on the fact that the new slots are NOT initialized by + // SetLengthAndRetainStorage here, see nsTArrayElementTraits::Construct() + // in nsTArray.h: + aDest.SetLengthAndRetainStorage(aKeep + *aNewBytes); + + MOZ_ASSERT(aDest.Length() <= aDest.Capacity(), "buffer overflow"); + return rv; +} + +bool +NS_InputStreamIsCloneable(nsIInputStream* aSource) +{ + if (!aSource) { + return false; + } + + nsCOMPtr cloneable = do_QueryInterface(aSource); + return cloneable && cloneable->GetCloneable(); +} + +nsresult +NS_CloneInputStream(nsIInputStream* aSource, nsIInputStream** aCloneOut, + nsIInputStream** aReplacementOut) +{ + if (NS_WARN_IF(!aSource)) { + return NS_ERROR_FAILURE; + } + + // Attempt to perform the clone directly on the source stream + nsCOMPtr cloneable = do_QueryInterface(aSource); + if (cloneable && cloneable->GetCloneable()) { + if (aReplacementOut) { + *aReplacementOut = nullptr; + } + return cloneable->Clone(aCloneOut); + } + + // If we failed the clone and the caller does not want to replace their + // original stream, then we are done. Return error. + if (!aReplacementOut) { + return NS_ERROR_FAILURE; + } + + // The caller has opted-in to the fallback clone support that replaces + // the original stream. Copy the data to a pipe and return two cloned + // input streams. + + nsCOMPtr reader; + nsCOMPtr readerClone; + nsCOMPtr writer; + + nsresult rv = NS_NewPipe(getter_AddRefs(reader), getter_AddRefs(writer), + 0, 0, // default segment size and max size + true, true); // non-blocking + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + cloneable = do_QueryInterface(reader); + MOZ_ASSERT(cloneable && cloneable->GetCloneable()); + + rv = cloneable->Clone(getter_AddRefs(readerClone)); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + nsCOMPtr target = + do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + rv = NS_AsyncCopy(aSource, writer, target, NS_ASYNCCOPY_VIA_WRITESEGMENTS); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + readerClone.forget(aCloneOut); + reader.forget(aReplacementOut); + + return NS_OK; +} diff --git a/xpcom/io/nsStreamUtils.h b/xpcom/io/nsStreamUtils.h new file mode 100644 index 000000000..957eb2713 --- /dev/null +++ b/xpcom/io/nsStreamUtils.h @@ -0,0 +1,295 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 nsStreamUtils_h__ +#define nsStreamUtils_h__ + +#include "nsCOMPtr.h" +#include "nsStringFwd.h" +#include "nsIInputStream.h" +#include "nsTArray.h" + +class nsIOutputStream; +class nsIInputStreamCallback; +class nsIOutputStreamCallback; +class nsIEventTarget; + +/** + * A "one-shot" proxy of the OnInputStreamReady callback. The resulting + * proxy object's OnInputStreamReady function may only be called once! The + * proxy object ensures that the real notify object will be free'd on the + * thread corresponding to the given event target regardless of what thread + * the proxy object is destroyed on. + * + * This function is designed to be used to implement AsyncWait when the + * aTarget parameter is non-null. + */ +extern already_AddRefed +NS_NewInputStreamReadyEvent(nsIInputStreamCallback* aNotify, + nsIEventTarget* aTarget); + +/** + * A "one-shot" proxy of the OnOutputStreamReady callback. The resulting + * proxy object's OnOutputStreamReady function may only be called once! The + * proxy object ensures that the real notify object will be free'd on the + * thread corresponding to the given event target regardless of what thread + * the proxy object is destroyed on. + * + * This function is designed to be used to implement AsyncWait when the + * aTarget parameter is non-null. + */ +extern already_AddRefed +NS_NewOutputStreamReadyEvent(nsIOutputStreamCallback* aNotify, + nsIEventTarget* aTarget); + +/* ------------------------------------------------------------------------- */ + +enum nsAsyncCopyMode { + NS_ASYNCCOPY_VIA_READSEGMENTS, + NS_ASYNCCOPY_VIA_WRITESEGMENTS +}; + +/** + * This function is called when a new chunk of data has been copied. The + * reported count is the size of the current chunk. + */ +typedef void (* nsAsyncCopyProgressFun)(void* closure, uint32_t count); + +/** + * This function is called when the async copy process completes. The reported + * status is NS_OK on success and some error code on failure. + */ +typedef void (* nsAsyncCopyCallbackFun)(void* closure, nsresult status); + +/** + * This function asynchronously copies data from the source to the sink. All + * data transfer occurs on the thread corresponding to the given event target. + * A null event target is not permitted. + * + * The copier handles blocking or non-blocking streams transparently. If a + * stream operation returns NS_BASE_STREAM_WOULD_BLOCK, then the stream will + * be QI'd to nsIAsync{In,Out}putStream and its AsyncWait method will be used + * to determine when to resume copying. + * + * Source and sink are closed by default when copying finishes or when error + * occurs. Caller can prevent closing source or sink by setting aCloseSource + * or aCloseSink to false. + * + * Caller can obtain aCopierCtx to be able to cancel copying. + */ +extern nsresult +NS_AsyncCopy(nsIInputStream* aSource, + nsIOutputStream* aSink, + nsIEventTarget* aTarget, + nsAsyncCopyMode aMode = NS_ASYNCCOPY_VIA_READSEGMENTS, + uint32_t aChunkSize = 4096, + nsAsyncCopyCallbackFun aCallbackFun = nullptr, + void* aCallbackClosure = nullptr, + bool aCloseSource = true, + bool aCloseSink = true, + nsISupports** aCopierCtx = nullptr, + nsAsyncCopyProgressFun aProgressCallbackFun = nullptr); + +/** + * This function cancels copying started by function NS_AsyncCopy. + * + * @param aCopierCtx + * Copier context returned by NS_AsyncCopy. + * @param aReason + * A failure code indicating why the operation is being canceled. + * It is an error to pass a success code. + */ +extern nsresult +NS_CancelAsyncCopy(nsISupports* aCopierCtx, nsresult aReason); + +/** + * This function copies all of the available data from the stream (up to at + * most aMaxCount bytes) into the given buffer. The buffer is truncated at + * the start of the function. + * + * If an error occurs while reading from the stream or while attempting to + * resize the buffer, then the corresponding error code is returned from this + * function, and any data that has already been read will be returned in the + * output buffer. This allows one to use this function with a non-blocking + * input stream that may return NS_BASE_STREAM_WOULD_BLOCK if it only has + * partial data available. + * + * @param aSource + * The input stream to read. + * @param aMaxCount + * The maximum number of bytes to consume from the stream. Pass the + * value UINT32_MAX to consume the entire stream. The number of + * bytes actually read is given by the length of aBuffer upon return. + * @param aBuffer + * The string object that will contain the stream data upon return. + * Note: The data copied to the string may contain null bytes and may + * contain non-ASCII values. + */ +extern nsresult +NS_ConsumeStream(nsIInputStream* aSource, uint32_t aMaxCount, + nsACString& aBuffer); + +/** + * This function tests whether or not the input stream is buffered. A buffered + * input stream is one that implements readSegments. The test for this is to + * 1/ check whether the input stream implements nsIBufferedInputStream; + * 2/ if not, call readSegments, without actually consuming any data from the + * stream, to verify that it functions. + * + * NOTE: If the stream is non-blocking and has no data available yet, then this + * test will fail. In that case, we return false even though the test is not + * really conclusive. + * + * PERFORMANCE NOTE: If the stream does not implement nsIBufferedInputStream, + * calling readSegments may cause I/O. Therefore, you should avoid calling + * this function from the main thread. + * + * @param aInputStream + * The input stream to test. + */ +extern bool +NS_InputStreamIsBuffered(nsIInputStream* aInputStream); + +/** + * This function tests whether or not the output stream is buffered. A + * buffered output stream is one that implements writeSegments. The test for + * this is to: + * 1/ check whether the output stream implements nsIBufferedOutputStream; + * 2/ if not, call writeSegments, without actually writing any data into + * the stream, to verify that it functions. + * + * NOTE: If the stream is non-blocking and has no available space yet, then + * this test will fail. In that case, we return false even though the test is + * not really conclusive. + * + * PERFORMANCE NOTE: If the stream does not implement nsIBufferedOutputStream, + * calling writeSegments may cause I/O. Therefore, you should avoid calling + * this function from the main thread. + * + * @param aOutputStream + * The output stream to test. + */ +extern bool +NS_OutputStreamIsBuffered(nsIOutputStream* aOutputStream); + +/** + * This function is intended to be passed to nsIInputStream::ReadSegments to + * copy data from the nsIInputStream into a nsIOutputStream passed as the + * aClosure parameter to the ReadSegments function. + * + * @see nsIInputStream.idl for a description of this function's parameters. + */ +extern nsresult +NS_CopySegmentToStream(nsIInputStream* aInputStream, void* aClosure, + const char* aFromSegment, uint32_t aToOffset, + uint32_t aCount, uint32_t* aWriteCount); + +/** + * This function is intended to be passed to nsIInputStream::ReadSegments to + * copy data from the nsIInputStream into a character buffer passed as the + * aClosure parameter to the ReadSegments function. The character buffer + * must be at least as large as the aCount parameter passed to ReadSegments. + * + * @see nsIInputStream.idl for a description of this function's parameters. + */ +extern nsresult +NS_CopySegmentToBuffer(nsIInputStream* aInputStream, void* aClosure, + const char* aFromSegment, uint32_t aToOffset, + uint32_t aCount, uint32_t* aWriteCount); + +/** + * This function is intended to be passed to nsIOutputStream::WriteSegments to + * copy data into the nsIOutputStream from a character buffer passed as the + * aClosure parameter to the WriteSegments function. + * + * @see nsIOutputStream.idl for a description of this function's parameters. + */ +extern nsresult +NS_CopySegmentToBuffer(nsIOutputStream* aOutputStream, void* aClosure, + char* aToSegment, uint32_t aFromOffset, + uint32_t aCount, uint32_t* aReadCount); + +/** + * This function is intended to be passed to nsIInputStream::ReadSegments to + * discard data from the nsIInputStream. This can be used to efficiently read + * data from the stream without actually copying any bytes. + * + * @see nsIInputStream.idl for a description of this function's parameters. + */ +extern nsresult +NS_DiscardSegment(nsIInputStream* aInputStream, void* aClosure, + const char* aFromSegment, uint32_t aToOffset, + uint32_t aCount, uint32_t* aWriteCount); + +/** + * This function is intended to be passed to nsIInputStream::ReadSegments to + * adjust the aInputStream parameter passed to a consumer's WriteSegmentFun. + * The aClosure parameter must be a pointer to a nsWriteSegmentThunk object. + * The mStream and mClosure members of that object will be passed to the mFun + * function, with the remainder of the parameters being what are passed to + * NS_WriteSegmentThunk. + * + * This function comes in handy when implementing ReadSegments in terms of an + * inner stream's ReadSegments. + */ +extern nsresult +NS_WriteSegmentThunk(nsIInputStream* aInputStream, void* aClosure, + const char* aFromSegment, uint32_t aToOffset, + uint32_t aCount, uint32_t* aWriteCount); + +struct MOZ_STACK_CLASS nsWriteSegmentThunk +{ + nsCOMPtr mStream; + nsWriteSegmentFun mFun; + void* mClosure; +}; + +/** + * Read data from aInput and store in aDest. A non-zero aKeep will keep that + * many bytes from aDest (from the end). New data is appended after the kept + * bytes (if any). aDest's new length on returning from this function is + * aKeep + aNewBytes and is guaranteed to be less than or equal to aDest's + * current capacity. + * @param aDest the array to fill + * @param aInput the stream to read from + * @param aKeep number of bytes to keep (0 <= aKeep <= aDest.Length()) + * @param aNewBytes (out) number of bytes read from aInput or zero if Read() + * failed + * @return the result from aInput->Read(...) + */ +extern nsresult +NS_FillArray(FallibleTArray& aDest, nsIInputStream* aInput, + uint32_t aKeep, uint32_t* aNewBytes); + +/** + * Return true if the given stream can be directly cloned. + */ +extern bool +NS_InputStreamIsCloneable(nsIInputStream* aSource); + +/** + * Clone the provided source stream in the most efficient way possible. This + * first attempts to QI to nsICloneableInputStream to use Clone(). If that is + * not supported or its cloneable attribute is false, then a fallback clone is + * provided by copying the source to a pipe. In this case the caller must + * replace the source stream with the resulting replacement stream. The clone + * and the replacement stream are then cloneable using nsICloneableInputStream + * without duplicating memory. This fallback clone using the pipe is only + * performed if a replacement stream parameter is also passed in. + * @param aSource The input stream to clone. + * @param aCloneOut Required out parameter to hold resulting clone. + * @param aReplacementOut Optional out parameter to hold stream to replace + * original source stream after clone. If not + * provided then the fallback clone process is not + * supported and a non-cloneable source will result + * in failure. Replacement streams are non-blocking. + * @return NS_OK on successful clone. Error otherwise. + */ +extern nsresult +NS_CloneInputStream(nsIInputStream* aSource, nsIInputStream** aCloneOut, + nsIInputStream** aReplacementOut = nullptr); + +#endif // !nsStreamUtils_h__ diff --git a/xpcom/io/nsStringStream.cpp b/xpcom/io/nsStringStream.cpp new file mode 100644 index 000000000..b65242c14 --- /dev/null +++ b/xpcom/io/nsStringStream.cpp @@ -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/. */ + +/** + * Based on original code from nsIStringStream.cpp + */ + +#include "ipc/IPCMessageUtils.h" + +#include "nsStringStream.h" +#include "nsStreamUtils.h" +#include "nsReadableUtils.h" +#include "nsICloneableInputStream.h" +#include "nsISeekableStream.h" +#include "nsISupportsPrimitives.h" +#include "nsCRT.h" +#include "prerror.h" +#include "plstr.h" +#include "nsIClassInfoImpl.h" +#include "mozilla/Attributes.h" +#include "mozilla/ipc/InputStreamUtils.h" +#include "nsIIPCSerializableInputStream.h" + +using namespace mozilla::ipc; +using mozilla::Maybe; +using mozilla::Some; + +//----------------------------------------------------------------------------- +// nsIStringInputStream implementation +//----------------------------------------------------------------------------- + +class nsStringInputStream final + : public nsIStringInputStream + , public nsISeekableStream + , public nsISupportsCString + , public nsIIPCSerializableInputStream + , public nsICloneableInputStream +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIINPUTSTREAM + NS_DECL_NSISTRINGINPUTSTREAM + NS_DECL_NSISEEKABLESTREAM + NS_DECL_NSISUPPORTSPRIMITIVE + NS_DECL_NSISUPPORTSCSTRING + NS_DECL_NSIIPCSERIALIZABLEINPUTSTREAM + NS_DECL_NSICLONEABLEINPUTSTREAM + + nsStringInputStream() + { + Clear(); + } + + explicit nsStringInputStream(const nsStringInputStream& aOther) + : mOffset(aOther.mOffset) + { + // Use Assign() here because we don't want the life of the clone to be + // dependent on the life of the original stream. + mData.Assign(aOther.mData); + } + +private: + ~nsStringInputStream() + { + } + + uint32_t Length() const + { + return mData.Length(); + } + + uint32_t LengthRemaining() const + { + return Length() - mOffset; + } + + void Clear() + { + mData.SetIsVoid(true); + } + + bool Closed() + { + return mData.IsVoid(); + } + + nsDependentCSubstring mData; + uint32_t mOffset; +}; + +// This class needs to support threadsafe refcounting since people often +// allocate a string stream, and then read it from a background thread. +NS_IMPL_ADDREF(nsStringInputStream) +NS_IMPL_RELEASE(nsStringInputStream) + +NS_IMPL_CLASSINFO(nsStringInputStream, nullptr, nsIClassInfo::THREADSAFE, + NS_STRINGINPUTSTREAM_CID) +NS_IMPL_QUERY_INTERFACE_CI(nsStringInputStream, + nsIStringInputStream, + nsIInputStream, + nsISupportsCString, + nsISeekableStream, + nsIIPCSerializableInputStream, + nsICloneableInputStream) +NS_IMPL_CI_INTERFACE_GETTER(nsStringInputStream, + nsIStringInputStream, + nsIInputStream, + nsISupportsCString, + nsISeekableStream, + nsICloneableInputStream) + +///////// +// nsISupportsCString implementation +///////// + +NS_IMETHODIMP +nsStringInputStream::GetType(uint16_t* aType) +{ + *aType = TYPE_CSTRING; + return NS_OK; +} + +NS_IMETHODIMP +nsStringInputStream::GetData(nsACString& data) +{ + // The stream doesn't have any data when it is closed. We could fake it + // and return an empty string here, but it seems better to keep this return + // value consistent with the behavior of the other 'getter' methods. + if (NS_WARN_IF(Closed())) { + return NS_BASE_STREAM_CLOSED; + } + + data.Assign(mData); + return NS_OK; +} + +NS_IMETHODIMP +nsStringInputStream::SetData(const nsACString& aData) +{ + mData.Assign(aData); + mOffset = 0; + return NS_OK; +} + +NS_IMETHODIMP +nsStringInputStream::ToString(char** aResult) +{ + // NOTE: This method may result in data loss, so we do not implement it. + return NS_ERROR_NOT_IMPLEMENTED; +} + +///////// +// nsIStringInputStream implementation +///////// + +NS_IMETHODIMP +nsStringInputStream::SetData(const char* aData, int32_t aDataLen) +{ + if (NS_WARN_IF(!aData)) { + return NS_ERROR_INVALID_ARG; + } + mData.Assign(aData, aDataLen); + mOffset = 0; + return NS_OK; +} + +NS_IMETHODIMP +nsStringInputStream::AdoptData(char* aData, int32_t aDataLen) +{ + if (NS_WARN_IF(!aData)) { + return NS_ERROR_INVALID_ARG; + } + mData.Adopt(aData, aDataLen); + mOffset = 0; + return NS_OK; +} + +NS_IMETHODIMP +nsStringInputStream::ShareData(const char* aData, int32_t aDataLen) +{ + if (NS_WARN_IF(!aData)) { + return NS_ERROR_INVALID_ARG; + } + + if (aDataLen < 0) { + aDataLen = strlen(aData); + } + + mData.Rebind(aData, aDataLen); + mOffset = 0; + return NS_OK; +} + +NS_IMETHODIMP_(size_t) +nsStringInputStream::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) +{ + size_t n = aMallocSizeOf(this); + n += mData.SizeOfExcludingThisIfUnshared(aMallocSizeOf); + return n; +} + +///////// +// nsIInputStream implementation +///////// + +NS_IMETHODIMP +nsStringInputStream::Close() +{ + Clear(); + return NS_OK; +} + +NS_IMETHODIMP +nsStringInputStream::Available(uint64_t* aLength) +{ + NS_ASSERTION(aLength, "null ptr"); + + if (Closed()) { + return NS_BASE_STREAM_CLOSED; + } + + *aLength = LengthRemaining(); + return NS_OK; +} + +NS_IMETHODIMP +nsStringInputStream::Read(char* aBuf, uint32_t aCount, uint32_t* aReadCount) +{ + NS_ASSERTION(aBuf, "null ptr"); + return ReadSegments(NS_CopySegmentToBuffer, aBuf, aCount, aReadCount); +} + +NS_IMETHODIMP +nsStringInputStream::ReadSegments(nsWriteSegmentFun aWriter, void* aClosure, + uint32_t aCount, uint32_t* aResult) +{ + NS_ASSERTION(aResult, "null ptr"); + NS_ASSERTION(Length() >= mOffset, "bad stream state"); + + if (Closed()) { + return NS_BASE_STREAM_CLOSED; + } + + // We may be at end-of-file + uint32_t maxCount = LengthRemaining(); + if (maxCount == 0) { + *aResult = 0; + return NS_OK; + } + + if (aCount > maxCount) { + aCount = maxCount; + } + nsresult rv = aWriter(this, aClosure, mData.BeginReading() + mOffset, 0, + aCount, aResult); + if (NS_SUCCEEDED(rv)) { + NS_ASSERTION(*aResult <= aCount, + "writer should not write more than we asked it to write"); + mOffset += *aResult; + } + + // errors returned from the writer end here! + return NS_OK; +} + +NS_IMETHODIMP +nsStringInputStream::IsNonBlocking(bool* aNonBlocking) +{ + *aNonBlocking = true; + return NS_OK; +} + +///////// +// nsISeekableStream implementation +///////// + +NS_IMETHODIMP +nsStringInputStream::Seek(int32_t aWhence, int64_t aOffset) +{ + if (Closed()) { + return NS_BASE_STREAM_CLOSED; + } + + // Compute new stream position. The given offset may be a negative value. + + int64_t newPos = aOffset; + switch (aWhence) { + case NS_SEEK_SET: + break; + case NS_SEEK_CUR: + newPos += mOffset; + break; + case NS_SEEK_END: + newPos += Length(); + break; + default: + NS_ERROR("invalid aWhence"); + return NS_ERROR_INVALID_ARG; + } + + if (NS_WARN_IF(newPos < 0) || NS_WARN_IF(newPos > Length())) { + return NS_ERROR_INVALID_ARG; + } + + mOffset = (uint32_t)newPos; + return NS_OK; +} + +NS_IMETHODIMP +nsStringInputStream::Tell(int64_t* aOutWhere) +{ + if (Closed()) { + return NS_BASE_STREAM_CLOSED; + } + + *aOutWhere = mOffset; + return NS_OK; +} + +NS_IMETHODIMP +nsStringInputStream::SetEOF() +{ + if (Closed()) { + return NS_BASE_STREAM_CLOSED; + } + + mOffset = Length(); + return NS_OK; +} + +///////// +// nsIIPCSerializableInputStream implementation +///////// + +void +nsStringInputStream::Serialize(InputStreamParams& aParams, + FileDescriptorArray& /* aFDs */) +{ + StringInputStreamParams params; + params.data() = PromiseFlatCString(mData); + aParams = params; +} + +bool +nsStringInputStream::Deserialize(const InputStreamParams& aParams, + const FileDescriptorArray& /* aFDs */) +{ + if (aParams.type() != InputStreamParams::TStringInputStreamParams) { + NS_ERROR("Received unknown parameters from the other process!"); + return false; + } + + const StringInputStreamParams& params = + aParams.get_StringInputStreamParams(); + + if (NS_FAILED(SetData(params.data()))) { + NS_WARNING("SetData failed!"); + return false; + } + + return true; +} + +Maybe +nsStringInputStream::ExpectedSerializedLength() +{ + return Some(static_cast(Length())); +} + +///////// +// nsICloneableInputStream implementation +///////// + +NS_IMETHODIMP +nsStringInputStream::GetCloneable(bool* aCloneableOut) +{ + *aCloneableOut = true; + return NS_OK; +} + +NS_IMETHODIMP +nsStringInputStream::Clone(nsIInputStream** aCloneOut) +{ + RefPtr ref = new nsStringInputStream(*this); + ref.forget(aCloneOut); + return NS_OK; +} + +nsresult +NS_NewByteInputStream(nsIInputStream** aStreamResult, + const char* aStringToRead, int32_t aLength, + nsAssignmentType aAssignment) +{ + NS_PRECONDITION(aStreamResult, "null out ptr"); + + RefPtr stream = new nsStringInputStream(); + + nsresult rv; + switch (aAssignment) { + case NS_ASSIGNMENT_COPY: + rv = stream->SetData(aStringToRead, aLength); + break; + case NS_ASSIGNMENT_DEPEND: + rv = stream->ShareData(aStringToRead, aLength); + break; + case NS_ASSIGNMENT_ADOPT: + rv = stream->AdoptData(const_cast(aStringToRead), aLength); + break; + default: + NS_ERROR("invalid assignment type"); + rv = NS_ERROR_INVALID_ARG; + } + + if (NS_FAILED(rv)) { + return rv; + } + + stream.forget(aStreamResult); + return NS_OK; +} + +nsresult +NS_NewCStringInputStream(nsIInputStream** aStreamResult, + const nsACString& aStringToRead) +{ + NS_PRECONDITION(aStreamResult, "null out ptr"); + + RefPtr stream = new nsStringInputStream(); + + stream->SetData(aStringToRead); + + stream.forget(aStreamResult); + return NS_OK; +} + +// factory method for constructing a nsStringInputStream object +nsresult +nsStringInputStreamConstructor(nsISupports* aOuter, REFNSIID aIID, + void** aResult) +{ + *aResult = nullptr; + + if (NS_WARN_IF(aOuter)) { + return NS_ERROR_NO_AGGREGATION; + } + + RefPtr inst = new nsStringInputStream(); + return inst->QueryInterface(aIID, aResult); +} diff --git a/xpcom/io/nsStringStream.h b/xpcom/io/nsStringStream.h new file mode 100644 index 000000000..8c09530eb --- /dev/null +++ b/xpcom/io/nsStringStream.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 nsStringStream_h__ +#define nsStringStream_h__ + +#include "nsIStringStream.h" +#include "nsStringGlue.h" +#include "nsMemory.h" + +/** + * Implements: + * nsIStringInputStream + * nsIInputStream + * nsISeekableStream + * nsISupportsCString + */ +#define NS_STRINGINPUTSTREAM_CONTRACTID "@mozilla.org/io/string-input-stream;1" +#define NS_STRINGINPUTSTREAM_CID \ +{ /* 0abb0835-5000-4790-af28-61b3ba17c295 */ \ + 0x0abb0835, \ + 0x5000, \ + 0x4790, \ + {0xaf, 0x28, 0x61, 0xb3, 0xba, 0x17, 0xc2, 0x95} \ +} + +/** + * Factory method to get an nsInputStream from a byte buffer. Result will + * implement nsIStringInputStream and nsISeekableStream. + * + * If aAssignment is NS_ASSIGNMENT_COPY, then the resulting stream holds a copy + * of the given buffer (aStringToRead), and the caller is free to discard + * aStringToRead after this function returns. + * + * If aAssignment is NS_ASSIGNMENT_DEPEND, then the resulting stream refers + * directly to the given buffer (aStringToRead), so the caller must ensure that + * the buffer remains valid for the lifetime of the stream object. Use with + * care!! + * + * If aAssignment is NS_ASSIGNMENT_ADOPT, then the resulting stream refers + * directly to the given buffer (aStringToRead) and will free aStringToRead + * once the stream is closed. + * + * If aLength is less than zero, then the length of aStringToRead will be + * determined by scanning the buffer for the first null byte. + */ +extern nsresult +NS_NewByteInputStream(nsIInputStream** aStreamResult, + const char* aStringToRead, int32_t aLength = -1, + nsAssignmentType aAssignment = NS_ASSIGNMENT_DEPEND); + +/** + * Factory method to get an nsInputStream from an nsACString. Result will + * implement nsIStringInputStream and nsISeekableStream. + */ +extern nsresult +NS_NewCStringInputStream(nsIInputStream** aStreamResult, + const nsACString& aStringToRead); + +#endif // nsStringStream_h__ diff --git a/xpcom/io/nsUnicharInputStream.cpp b/xpcom/io/nsUnicharInputStream.cpp new file mode 100644 index 000000000..27c074c09 --- /dev/null +++ b/xpcom/io/nsUnicharInputStream.cpp @@ -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/. */ + +#include "nsUnicharInputStream.h" +#include "nsIInputStream.h" +#include "nsIServiceManager.h" +#include "nsString.h" +#include "nsTArray.h" +#include "nsAutoPtr.h" +#include "nsCRT.h" +#include "nsStreamUtils.h" +#include "nsUTF8Utils.h" +#include "mozilla/Attributes.h" +#include +#if defined(XP_WIN) +#include +#else +#include +#endif + +#define STRING_BUFFER_SIZE 8192 + +class StringUnicharInputStream final : public nsIUnicharInputStream +{ +public: + explicit StringUnicharInputStream(const nsAString& aString) : + mString(aString), mPos(0), mLen(aString.Length()) { } + + NS_DECL_ISUPPORTS + NS_DECL_NSIUNICHARINPUTSTREAM + + nsString mString; + uint32_t mPos; + uint32_t mLen; + +private: + ~StringUnicharInputStream() { } +}; + +NS_IMETHODIMP +StringUnicharInputStream::Read(char16_t* aBuf, + uint32_t aCount, + uint32_t* aReadCount) +{ + if (mPos >= mLen) { + *aReadCount = 0; + return NS_OK; + } + nsAString::const_iterator iter; + mString.BeginReading(iter); + const char16_t* us = iter.get(); + uint32_t amount = mLen - mPos; + if (amount > aCount) { + amount = aCount; + } + memcpy(aBuf, us + mPos, sizeof(char16_t) * amount); + mPos += amount; + *aReadCount = amount; + return NS_OK; +} + +NS_IMETHODIMP +StringUnicharInputStream::ReadSegments(nsWriteUnicharSegmentFun aWriter, + void* aClosure, + uint32_t aCount, uint32_t* aReadCount) +{ + uint32_t bytesWritten; + uint32_t totalBytesWritten = 0; + + nsresult rv; + aCount = XPCOM_MIN(mString.Length() - mPos, aCount); + + nsAString::const_iterator iter; + mString.BeginReading(iter); + + while (aCount) { + rv = aWriter(this, aClosure, iter.get() + mPos, + totalBytesWritten, aCount, &bytesWritten); + + if (NS_FAILED(rv)) { + // don't propagate errors to the caller + break; + } + + aCount -= bytesWritten; + totalBytesWritten += bytesWritten; + mPos += bytesWritten; + } + + *aReadCount = totalBytesWritten; + + return NS_OK; +} + +NS_IMETHODIMP +StringUnicharInputStream::ReadString(uint32_t aCount, nsAString& aString, + uint32_t* aReadCount) +{ + if (mPos >= mLen) { + *aReadCount = 0; + return NS_OK; + } + uint32_t amount = mLen - mPos; + if (amount > aCount) { + amount = aCount; + } + aString = Substring(mString, mPos, amount); + mPos += amount; + *aReadCount = amount; + return NS_OK; +} + +nsresult +StringUnicharInputStream::Close() +{ + mPos = mLen; + return NS_OK; +} + +NS_IMPL_ISUPPORTS(StringUnicharInputStream, nsIUnicharInputStream) + +//---------------------------------------------------------------------- + +class UTF8InputStream final : public nsIUnicharInputStream +{ +public: + UTF8InputStream(); + nsresult Init(nsIInputStream* aStream); + + NS_DECL_ISUPPORTS + NS_DECL_NSIUNICHARINPUTSTREAM + +private: + ~UTF8InputStream(); + +protected: + int32_t Fill(nsresult* aErrorCode); + + static void CountValidUTF8Bytes(const char* aBuf, uint32_t aMaxBytes, + uint32_t& aValidUTF8bytes, + uint32_t& aValidUTF16CodeUnits); + + nsCOMPtr mInput; + FallibleTArray mByteData; + FallibleTArray mUnicharData; + + uint32_t mByteDataOffset; + uint32_t mUnicharDataOffset; + uint32_t mUnicharDataLength; +}; + +UTF8InputStream::UTF8InputStream() : + mByteDataOffset(0), + mUnicharDataOffset(0), + mUnicharDataLength(0) +{ +} + +nsresult +UTF8InputStream::Init(nsIInputStream* aStream) +{ + if (!mByteData.SetCapacity(STRING_BUFFER_SIZE, mozilla::fallible) || + !mUnicharData.SetCapacity(STRING_BUFFER_SIZE, mozilla::fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + mInput = aStream; + + return NS_OK; +} + +NS_IMPL_ISUPPORTS(UTF8InputStream, nsIUnicharInputStream) + +UTF8InputStream::~UTF8InputStream() +{ + Close(); +} + +nsresult +UTF8InputStream::Close() +{ + mInput = nullptr; + mByteData.Clear(); + mUnicharData.Clear(); + return NS_OK; +} + +nsresult +UTF8InputStream::Read(char16_t* aBuf, uint32_t aCount, uint32_t* aReadCount) +{ + NS_ASSERTION(mUnicharDataLength >= mUnicharDataOffset, "unsigned madness"); + uint32_t readCount = mUnicharDataLength - mUnicharDataOffset; + nsresult errorCode; + if (0 == readCount) { + // Fill the unichar buffer + int32_t bytesRead = Fill(&errorCode); + if (bytesRead <= 0) { + *aReadCount = 0; + return errorCode; + } + readCount = bytesRead; + } + if (readCount > aCount) { + readCount = aCount; + } + memcpy(aBuf, mUnicharData.Elements() + mUnicharDataOffset, + readCount * sizeof(char16_t)); + mUnicharDataOffset += readCount; + *aReadCount = readCount; + return NS_OK; +} + +NS_IMETHODIMP +UTF8InputStream::ReadSegments(nsWriteUnicharSegmentFun aWriter, + void* aClosure, + uint32_t aCount, uint32_t* aReadCount) +{ + NS_ASSERTION(mUnicharDataLength >= mUnicharDataOffset, "unsigned madness"); + uint32_t bytesToWrite = mUnicharDataLength - mUnicharDataOffset; + nsresult rv = NS_OK; + if (0 == bytesToWrite) { + // Fill the unichar buffer + int32_t bytesRead = Fill(&rv); + if (bytesRead <= 0) { + *aReadCount = 0; + return rv; + } + bytesToWrite = bytesRead; + } + + if (bytesToWrite > aCount) { + bytesToWrite = aCount; + } + + uint32_t bytesWritten; + uint32_t totalBytesWritten = 0; + + while (bytesToWrite) { + rv = aWriter(this, aClosure, + mUnicharData.Elements() + mUnicharDataOffset, + totalBytesWritten, bytesToWrite, &bytesWritten); + + if (NS_FAILED(rv)) { + // don't propagate errors to the caller + break; + } + + bytesToWrite -= bytesWritten; + totalBytesWritten += bytesWritten; + mUnicharDataOffset += bytesWritten; + } + + *aReadCount = totalBytesWritten; + + return NS_OK; +} + +NS_IMETHODIMP +UTF8InputStream::ReadString(uint32_t aCount, nsAString& aString, + uint32_t* aReadCount) +{ + NS_ASSERTION(mUnicharDataLength >= mUnicharDataOffset, "unsigned madness"); + uint32_t readCount = mUnicharDataLength - mUnicharDataOffset; + nsresult errorCode; + if (0 == readCount) { + // Fill the unichar buffer + int32_t bytesRead = Fill(&errorCode); + if (bytesRead <= 0) { + *aReadCount = 0; + return errorCode; + } + readCount = bytesRead; + } + if (readCount > aCount) { + readCount = aCount; + } + const char16_t* buf = mUnicharData.Elements() + mUnicharDataOffset; + aString.Assign(buf, readCount); + + mUnicharDataOffset += readCount; + *aReadCount = readCount; + return NS_OK; +} + +int32_t +UTF8InputStream::Fill(nsresult* aErrorCode) +{ + if (!mInput) { + // We already closed the stream! + *aErrorCode = NS_BASE_STREAM_CLOSED; + return -1; + } + + NS_ASSERTION(mByteData.Length() >= mByteDataOffset, "unsigned madness"); + uint32_t remainder = mByteData.Length() - mByteDataOffset; + mByteDataOffset = remainder; + uint32_t nb; + *aErrorCode = NS_FillArray(mByteData, mInput, remainder, &nb); + if (nb == 0) { + // Because we assume a many to one conversion, the lingering data + // in the byte buffer must be a partial conversion + // fragment. Because we know that we have received no more new + // data to add to it, we can't convert it. Therefore, we discard + // it. + return nb; + } + NS_ASSERTION(remainder + nb == mByteData.Length(), "bad nb"); + + // Now convert as much of the byte buffer to unicode as possible + uint32_t srcLen, dstLen; + CountValidUTF8Bytes(mByteData.Elements(), remainder + nb, srcLen, dstLen); + + // the number of UCS2 characters should always be <= the number of + // UTF8 chars + NS_ASSERTION(remainder + nb >= srcLen, "cannot be longer than out buffer"); + NS_ASSERTION(dstLen <= mUnicharData.Capacity(), + "Ouch. I would overflow my buffer if I wasn't so careful."); + if (dstLen > mUnicharData.Capacity()) { + return 0; + } + + ConvertUTF8toUTF16 converter(mUnicharData.Elements()); + + nsASingleFragmentCString::const_char_iterator start = mByteData.Elements(); + nsASingleFragmentCString::const_char_iterator end = mByteData.Elements() + srcLen; + + copy_string(start, end, converter); + if (converter.Length() != dstLen) { + *aErrorCode = NS_BASE_STREAM_BAD_CONVERSION; + return -1; + } + + mUnicharDataOffset = 0; + mUnicharDataLength = dstLen; + mByteDataOffset = srcLen; + + return dstLen; +} + +void +UTF8InputStream::CountValidUTF8Bytes(const char* aBuffer, uint32_t aMaxBytes, + uint32_t& aValidUTF8bytes, + uint32_t& aValidUTF16CodeUnits) +{ + const char* c = aBuffer; + const char* end = aBuffer + aMaxBytes; + const char* lastchar = c; // pre-initialize in case of 0-length buffer + uint32_t utf16length = 0; + while (c < end && *c) { + lastchar = c; + utf16length++; + + if (UTF8traits::isASCII(*c)) { + c++; + } else if (UTF8traits::is2byte(*c)) { + c += 2; + } else if (UTF8traits::is3byte(*c)) { + c += 3; + } else if (UTF8traits::is4byte(*c)) { + c += 4; + utf16length++; // add 1 more because this will be converted to a + // surrogate pair. + } else if (UTF8traits::is5byte(*c)) { + c += 5; + } else if (UTF8traits::is6byte(*c)) { + c += 6; + } else { + NS_WARNING("Unrecognized UTF8 string in UTF8InputStream::CountValidUTF8Bytes()"); + break; // Otherwise we go into an infinite loop. But what happens now? + } + } + if (c > end) { + c = lastchar; + utf16length--; + } + + aValidUTF8bytes = c - aBuffer; + aValidUTF16CodeUnits = utf16length; +} + +nsresult +NS_NewUnicharInputStream(nsIInputStream* aStreamToWrap, + nsIUnicharInputStream** aResult) +{ + *aResult = nullptr; + + // Create converter input stream + RefPtr it = new UTF8InputStream(); + nsresult rv = it->Init(aStreamToWrap); + if (NS_FAILED(rv)) { + return rv; + } + + it.forget(aResult); + return NS_OK; +} diff --git a/xpcom/io/nsUnicharInputStream.h b/xpcom/io/nsUnicharInputStream.h new file mode 100644 index 000000000..d4631af7e --- /dev/null +++ b/xpcom/io/nsUnicharInputStream.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 nsUnicharInputStream_h__ +#define nsUnicharInputStream_h__ + +#include "nsIUnicharInputStream.h" + +nsresult NS_NewUnicharInputStream(nsIInputStream* aStreamToWrap, + nsIUnicharInputStream** aResult); + +#endif // nsUnicharInputStream_h__ diff --git a/xpcom/io/nsWildCard.cpp b/xpcom/io/nsWildCard.cpp new file mode 100644 index 000000000..9125cbbb8 --- /dev/null +++ b/xpcom/io/nsWildCard.cpp @@ -0,0 +1,481 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* * + * + * + * nsWildCard.cpp: shell-like wildcard match routines + * + * See nsIZipReader.findEntries documentation in nsIZipReader.idl for + * a description of the syntax supported by the routines in this file. + * + * Rob McCool + * + */ + +#include "nsWildCard.h" +#include "nsXPCOM.h" +#include "nsCRTGlue.h" +#include "nsCharTraits.h" + +/* -------------------- ASCII-specific character methods ------------------- */ + +typedef int static_assert_character_code_arrangement['a' > 'A' ? 1 : -1]; + +template +static int +alpha(T aChar) +{ + return ('a' <= aChar && aChar <= 'z') || + ('A' <= aChar && aChar <= 'Z'); +} + +template +static int +alphanumeric(T aChar) +{ + return ('0' <= aChar && aChar <= '9') || ::alpha(aChar); +} + +template +static int +lower(T aChar) +{ + return ('A' <= aChar && aChar <= 'Z') ? aChar + ('a' - 'A') : aChar; +} + +template +static int +upper(T aChar) +{ + return ('a' <= aChar && aChar <= 'z') ? aChar - ('a' - 'A') : aChar; +} + +/* ----------------------------- _valid_subexp ---------------------------- */ + +template +static int +_valid_subexp(const T* aExpr, T aStop1, T aStop2) +{ + int x; + int nsc = 0; /* Number of special characters */ + int np; /* Number of pipe characters in union */ + int tld = 0; /* Number of tilde characters */ + + for (x = 0; aExpr[x] && (aExpr[x] != aStop1) && (aExpr[x] != aStop2); ++x) { + switch (aExpr[x]) { + case '~': + if (tld) { /* at most one exclusion */ + return INVALID_SXP; + } + if (aStop1) { /* no exclusions within unions */ + return INVALID_SXP; + } + if (!aExpr[x + 1]) { /* exclusion cannot be last character */ + return INVALID_SXP; + } + if (!x) { /* exclusion cannot be first character */ + return INVALID_SXP; + } + ++tld; + MOZ_FALLTHROUGH; + case '*': + case '?': + case '$': + ++nsc; + break; + case '[': + ++nsc; + if ((!aExpr[++x]) || (aExpr[x] == ']')) { + return INVALID_SXP; + } + for (; aExpr[x] && (aExpr[x] != ']'); ++x) { + if (aExpr[x] == '\\' && !aExpr[++x]) { + return INVALID_SXP; + } + } + if (!aExpr[x]) { + return INVALID_SXP; + } + break; + case '(': + ++nsc; + if (aStop1) { /* no nested unions */ + return INVALID_SXP; + } + np = -1; + do { + int t = ::_valid_subexp(&aExpr[++x], T(')'), T('|')); + if (t == 0 || t == INVALID_SXP) { + return INVALID_SXP; + } + x += t; + if (!aExpr[x]) { + return INVALID_SXP; + } + ++np; + } while (aExpr[x] == '|'); + if (np < 1) { /* must be at least one pipe */ + return INVALID_SXP; + } + break; + case ')': + case ']': + case '|': + return INVALID_SXP; + case '\\': + ++nsc; + if (!aExpr[++x]) { + return INVALID_SXP; + } + break; + default: + break; + } + } + if (!aStop1 && !nsc) { /* must be at least one special character */ + return NON_SXP; + } + return ((aExpr[x] == aStop1 || aExpr[x] == aStop2) ? x : INVALID_SXP); +} + + +template +int +NS_WildCardValid_(const T* aExpr) +{ + int x = ::_valid_subexp(aExpr, T('\0'), T('\0')); + return (x < 0 ? x : VALID_SXP); +} + +int +NS_WildCardValid(const char* aExpr) +{ + return NS_WildCardValid_(aExpr); +} + +int +NS_WildCardValid(const char16_t* aExpr) +{ + return NS_WildCardValid_(aExpr); +} + +/* ----------------------------- _shexp_match ----------------------------- */ + + +#define MATCH 0 +#define NOMATCH 1 +#define ABORTED -1 + +template +static int +_shexp_match(const T* aStr, const T* aExpr, bool aCaseInsensitive, + unsigned int aLevel); + +/** + * Count characters until we reach a NUL character or either of the + * two delimiter characters, stop1 or stop2. If we encounter a bracketed + * expression, look only for NUL or ']' inside it. Do not look for stop1 + * or stop2 inside it. Return ABORTED if bracketed expression is unterminated. + * Handle all escaping. + * Return index in input string of first stop found, or ABORTED if not found. + * If "dest" is non-nullptr, copy counted characters to it and null terminate. + */ +template +static int +_scan_and_copy(const T* aExpr, T aStop1, T aStop2, T* aDest) +{ + int sx; /* source index */ + T cc; + + for (sx = 0; (cc = aExpr[sx]) && cc != aStop1 && cc != aStop2; ++sx) { + if (cc == '\\') { + if (!aExpr[++sx]) { + return ABORTED; /* should be impossible */ + } + } else if (cc == '[') { + while ((cc = aExpr[++sx]) && cc != ']') { + if (cc == '\\' && !aExpr[++sx]) { + return ABORTED; + } + } + if (!cc) { + return ABORTED; /* should be impossible */ + } + } + } + if (aDest && sx) { + /* Copy all but the closing delimiter. */ + memcpy(aDest, aExpr, sx * sizeof(T)); + aDest[sx] = 0; + } + return cc ? sx : ABORTED; /* index of closing delimiter */ +} + +/* On input, expr[0] is the opening parenthesis of a union. + * See if any of the alternatives in the union matches as a pattern. + * The strategy is to take each of the alternatives, in turn, and append + * the rest of the expression (after the closing ')' that marks the end of + * this union) to that alternative, and then see if the resultant expression + * matches the input string. Repeat this until some alternative matches, + * or we have an abort. + */ +template +static int +_handle_union(const T* aStr, const T* aExpr, bool aCaseInsensitive, + unsigned int aLevel) +{ + int sx; /* source index */ + int cp; /* source index of closing parenthesis */ + int count; + int ret = NOMATCH; + T* e2; + + /* Find the closing parenthesis that ends this union in the expression */ + cp = ::_scan_and_copy(aExpr, T(')'), T('\0'), static_cast(nullptr)); + if (cp == ABORTED || cp < 4) { /* must be at least "(a|b" before ')' */ + return ABORTED; + } + ++cp; /* now index of char after closing parenthesis */ + e2 = (T*)moz_xmalloc((1 + nsCharTraits::length(aExpr)) * sizeof(T)); + if (!e2) { + return ABORTED; + } + for (sx = 1; ; ++sx) { + /* Here, aExpr[sx] is one character past the preceding '(' or '|'. */ + /* Copy everything up to the next delimiter to e2 */ + count = ::_scan_and_copy(aExpr + sx, T(')'), T('|'), e2); + if (count == ABORTED || !count) { + ret = ABORTED; + break; + } + sx += count; + /* Append everything after closing parenthesis to e2. This is safe. */ + nsCharTraits::copy(e2 + count, aExpr + cp, + nsCharTraits::length(aExpr + cp) + 1); + ret = ::_shexp_match(aStr, e2, aCaseInsensitive, aLevel + 1); + if (ret != NOMATCH || !aExpr[sx] || aExpr[sx] == ')') { + break; + } + } + free(e2); + if (sx < 2) { + ret = ABORTED; + } + return ret; +} + +/* returns 1 if val is in range from start..end, case insensitive. */ +static int +_is_char_in_range(unsigned char aStart, unsigned char aEnd, unsigned char aVal) +{ + char map[256]; + memset(map, 0, sizeof(map)); + while (aStart <= aEnd) { + map[lower(aStart++)] = 1; + } + return map[lower(aVal)]; +} + +template +static int +_shexp_match(const T* aStr, const T* aExpr, bool aCaseInsensitive, + unsigned int aLevel) +{ + int x; /* input string index */ + int y; /* expression index */ + int ret, neg; + + if (aLevel > 20) { /* Don't let the stack get too deep. */ + return ABORTED; + } + for (x = 0, y = 0; aExpr[y]; ++y, ++x) { + if (!aStr[x] && aExpr[y] != '$' && aExpr[y] != '*') { + return NOMATCH; + } + switch (aExpr[y]) { + case '$': + if (aStr[x]) { + return NOMATCH; + } + --x; /* we don't want loop to increment x */ + break; + case '*': + while (aExpr[++y] == '*') { + } + if (!aExpr[y]) { + return MATCH; + } + while (aStr[x]) { + ret = ::_shexp_match(&aStr[x++], &aExpr[y], aCaseInsensitive, + aLevel + 1); + switch (ret) { + case NOMATCH: + continue; + case ABORTED: + return ABORTED; + default: + return MATCH; + } + } + if (aExpr[y] == '$' && aExpr[y + 1] == '\0' && !aStr[x]) { + return MATCH; + } else { + return NOMATCH; + } + case '[': { + T start, end = 0; + int i; + ++y; + neg = (aExpr[y] == '^' && aExpr[y + 1] != ']'); + if (neg) { + ++y; + } + i = y; + start = aExpr[i++]; + if (start == '\\') { + start = aExpr[i++]; + } + if (::alphanumeric(start) && aExpr[i++] == '-') { + end = aExpr[i++]; + if (end == '\\') { + end = aExpr[i++]; + } + } + if (::alphanumeric(end) && aExpr[i] == ']') { + /* This is a range form: a-b */ + T val = aStr[x]; + if (end < start) { /* swap them */ + T tmp = end; + end = start; + start = tmp; + } + if (aCaseInsensitive && ::alpha(val)) { + val = ::_is_char_in_range((unsigned char)start, + (unsigned char)end, + (unsigned char)val); + if (neg == val) { + return NOMATCH; + } + } else if (neg != (val < start || val > end)) { + return NOMATCH; + } + y = i; + } else { + /* Not range form */ + int matched = 0; + for (; aExpr[y] != ']'; ++y) { + if (aExpr[y] == '\\') { + ++y; + } + if (aCaseInsensitive) { + matched |= (::upper(aStr[x]) == ::upper(aExpr[y])); + } else { + matched |= (aStr[x] == aExpr[y]); + } + } + if (neg == matched) { + return NOMATCH; + } + } + } + break; + case '(': + if (!aExpr[y + 1]) { + return ABORTED; + } + return ::_handle_union(&aStr[x], &aExpr[y], aCaseInsensitive, + aLevel + 1); + case '?': + break; + case ')': + case ']': + case '|': + return ABORTED; + case '\\': + ++y; + MOZ_FALLTHROUGH; + default: + if (aCaseInsensitive) { + if (::upper(aStr[x]) != ::upper(aExpr[y])) { + return NOMATCH; + } + } else { + if (aStr[x] != aExpr[y]) { + return NOMATCH; + } + } + break; + } + } + return (aStr[x] ? NOMATCH : MATCH); +} + +template +static int +ns_WildCardMatch(const T* aStr, const T* aXp, bool aCaseInsensitive) +{ + T* expr = nullptr; + int ret = MATCH; + + if (!nsCharTraits::find(aXp, nsCharTraits::length(aXp), T('~'))) { + return ::_shexp_match(aStr, aXp, aCaseInsensitive, 0); + } + + expr = (T*)moz_xmalloc((nsCharTraits::length(aXp) + 1) * sizeof(T)); + if (!expr) { + return NOMATCH; + } + memcpy(expr, aXp, (nsCharTraits::length(aXp) + 1) * sizeof(T)); + + int x = ::_scan_and_copy(expr, T('~'), T('\0'), static_cast(nullptr)); + if (x != ABORTED && expr[x] == '~') { + expr[x++] = '\0'; + ret = ::_shexp_match(aStr, &expr[x], aCaseInsensitive, 0); + switch (ret) { + case NOMATCH: + ret = MATCH; + break; + case MATCH: + ret = NOMATCH; + break; + default: + break; + } + } + if (ret == MATCH) { + ret = ::_shexp_match(aStr, expr, aCaseInsensitive, 0); + } + + free(expr); + return ret; +} + +template +int +NS_WildCardMatch_(const T* aStr, const T* aExpr, bool aCaseInsensitive) +{ + int is_valid = NS_WildCardValid(aExpr); + switch (is_valid) { + case INVALID_SXP: + return -1; + default: + return ::ns_WildCardMatch(aStr, aExpr, aCaseInsensitive); + } +} + +int +NS_WildCardMatch(const char* aStr, const char* aXp, bool aCaseInsensitive) +{ + return NS_WildCardMatch_(aStr, aXp, aCaseInsensitive); +} + +int +NS_WildCardMatch(const char16_t* aStr, const char16_t* aXp, + bool aCaseInsensitive) +{ + return NS_WildCardMatch_(aStr, aXp, aCaseInsensitive); +} diff --git a/xpcom/io/nsWildCard.h b/xpcom/io/nsWildCard.h new file mode 100644 index 000000000..a077382bb --- /dev/null +++ b/xpcom/io/nsWildCard.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/. */ + +/* + * nsWildCard.h: Defines and prototypes for shell exp. match routines + * + * See nsIZipReader.findEntries docs in nsIZipReader.idl for a description of + * the supported expression syntax. + * + * Note that the syntax documentation explicitly says the results of certain + * expressions are undefined. This is intentional to require less robustness + * in the code. Regular expression parsing is hard; the smaller the set of + * features and interactions this code must support, the easier it is to + * ensure it works. + * + */ + +#ifndef nsWildCard_h__ +#define nsWildCard_h__ + +#include "nscore.h" + +/* --------------------------- Public routines ---------------------------- */ + + +/* + * NS_WildCardValid takes a shell expression exp as input. It returns: + * + * NON_SXP if exp is a standard string + * INVALID_SXP if exp is a shell expression, but invalid + * VALID_SXP if exp is a valid shell expression + */ + +#define NON_SXP -1 +#define INVALID_SXP -2 +#define VALID_SXP 1 + +int NS_WildCardValid(const char* aExpr); + +int NS_WildCardValid(const char16_t* aExpr); + +/* return values for the search routines */ +#define MATCH 0 +#define NOMATCH 1 +#define ABORTED -1 + +/* + * NS_WildCardMatch + * + * Takes a prevalidated shell expression exp, and a string str. + * + * Returns 0 on match and 1 on non-match. + */ + +int NS_WildCardMatch(const char* aStr, const char* aExpr, + bool aCaseInsensitive); + +int NS_WildCardMatch(const char16_t* aStr, const char16_t* aExpr, + bool aCaseInsensitive); + +#endif /* nsWildCard_h__ */ diff --git a/xpcom/libxpt/xptcall/porting.html b/xpcom/libxpt/xptcall/porting.html new file mode 100644 index 000000000..84465306e --- /dev/null +++ b/xpcom/libxpt/xptcall/porting.html @@ -0,0 +1,17 @@ + + + + +Document Moved! + + +
The xptcall porting document has been moved to: +

+http://lxr.mozilla.org/mozilla/source/xpcom/reflect/xptcall/porting.html +

+Please update your links. +

+ + \ No newline at end of file diff --git a/xpcom/libxpt/xptcall/status.html b/xpcom/libxpt/xptcall/status.html new file mode 100644 index 000000000..ba7fe008f --- /dev/null +++ b/xpcom/libxpt/xptcall/status.html @@ -0,0 +1,17 @@ + + + + +Document Moved! + + +
The xptcall status document has been moved to: +

+http://lxr.mozilla.org/mozilla/source/xpcom/reflect/xptcall/status.html +

+Please update your links. +

+ + diff --git a/xpcom/moz.build b/xpcom/moz.build new file mode 100644 index 000000000..45a83c5c8 --- /dev/null +++ b/xpcom/moz.build @@ -0,0 +1,50 @@ +# -*- 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', 'XPCOM') + +DIRS += [ + 'idl-parser/xpidl', +] + +DIRS += [ + 'typelib', + 'string', + 'glue', + 'base', + 'ds', + 'io', + 'components', + 'threads', + 'reflect', + 'system', + '../chrome', + 'build', +] + +if CONFIG['OS_ARCH'] == 'WINNT' and CONFIG['MOZ_DEBUG']: + DIRS += ['windbgdlg'] + +TEST_DIRS += [ + 'tests', + 'rust/nsstring/gtest', +] + +# Can't build internal xptcall tests that use symbols which are not exported. +#TEST_DIRS += [ +# 'reflect/xptinfo/tests', +# 'reflect/xptcall/tests, +#] + +CONFIGURE_DEFINE_FILES += [ + 'xpcom-config.h', + 'xpcom-private.h', +] + +EXPORTS += [ + '!xpcom-config.h', +] diff --git a/xpcom/reflect/moz.build b/xpcom/reflect/moz.build new file mode 100644 index 000000000..c04892d00 --- /dev/null +++ b/xpcom/reflect/moz.build @@ -0,0 +1,8 @@ +# -*- 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/. + +DIRS += ['xptinfo', 'xptcall'] + diff --git a/xpcom/reflect/xptcall/README b/xpcom/reflect/xptcall/README new file mode 100644 index 000000000..0c401fe88 --- /dev/null +++ b/xpcom/reflect/xptcall/README @@ -0,0 +1,6 @@ +see: + +http://www.mozilla.org/scriptable/xptcall-faq.html +and +http://lxr.mozilla.org/mozilla/source/xpcom/reflect/xptcall/porting.html + diff --git a/xpcom/reflect/xptcall/genstubs.pl b/xpcom/reflect/xptcall/genstubs.pl new file mode 100644 index 000000000..16ae2006a --- /dev/null +++ b/xpcom/reflect/xptcall/genstubs.pl @@ -0,0 +1,88 @@ +#!/usr/local/bin/perl +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.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 used to generate stub entry points. We generate a file to +# be included in the declaraion and a file to be used for expanding macros +# to represent the implementation of the stubs. + +# +# if "$entry_count" is ever changed and the .inc files regenerated then +# the following issues need to be addressed: +# +# 1) The current Linux ARM code has a limitation of only having 256-3 stubs, +# as a result of the limitations of immediate values in ARM assembly. +# +# This number is verified by the IDL parser in xpcom/idl-parser/xpidl.py, as +# well as in xpcom/reflect/xptinfo/xptiInterfaceInfoManager.cpp, to +# prevent generating interfaces or loading xpt files that would cause the +# stubs to run off the entries. +# If you change this number, please update that location. + +# 3 entries are already 'used' by the 3 methods of nsISupports. +# 3+247+5=255 This should get us in under the Linux ARM limitation +$entry_count = 247; +$sentinel_count = 5; + +$decl_name = "xptcstubsdecl.inc"; +$def_name = "xptcstubsdef.inc"; + +## +## Write the declarations include file +## + +die "Can't open $decl_name" if !open(OUTFILE, ">$decl_name"); + +print OUTFILE "/* generated file - DO NOT EDIT */\n\n"; +print OUTFILE "/* includes ",$entry_count," stub entries, and ", + $sentinel_count," sentinel entries */\n\n"; +print OUTFILE "/*\n"; +print OUTFILE "* declarations of normal stubs...\n"; +print OUTFILE "* 0 is QueryInterface\n"; +print OUTFILE "* 1 is AddRef\n"; +print OUTFILE "* 2 is Release\n"; +print OUTFILE "*/\n"; +print OUTFILE "#if !defined(__ia64) || (!defined(__hpux) && !defined(__linux__) && !defined(__FreeBSD__))\n"; +for($i = 0; $i < $entry_count; $i++) { + print OUTFILE "NS_IMETHOD Stub",$i+3,"();\n"; +} +print OUTFILE "#else\n"; +for($i = 0; $i < $entry_count; $i++) { + print OUTFILE "NS_IMETHOD Stub",$i+3,"(uint64_t,uint64_t,\n"; + print OUTFILE " uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t);\n"; + +} +print OUTFILE "#endif\n"; + +print OUTFILE "\n/* declarations of sentinel stubs */\n"; + +for($i = 0; $i < $sentinel_count; $i++) { + print OUTFILE "NS_IMETHOD Sentinel",$i,"();\n"; +} +close(OUTFILE); + + +## +## Write the definitions include file. This assumes a macro will be used to +## expand the entries written... +## + +die "Can't open $def_name" if !open(OUTFILE, ">$def_name"); + +## Disabled for bug 275004 - followup to fix is Bug 419604 +my $warn_inc_is_generated = 0; +if ($warn_inc_is_generated) { +print OUTFILE "/* generated file - DO NOT EDIT */\n\n"; +print OUTFILE "/* includes ",$entry_count," stub entries, and ", + $sentinel_count," sentinel entries */\n\n"; +} + +for($i = 0; $i < $entry_count; $i++) { + print OUTFILE "STUB_ENTRY(",$i+3,")\n"; +} + +for($i = 0; $i < $sentinel_count; $i++) { + print OUTFILE "SENTINEL_ENTRY(",$i,")\n"; +} diff --git a/xpcom/reflect/xptcall/md/moz.build b/xpcom/reflect/xptcall/md/moz.build new file mode 100644 index 000000000..a4b59aaf0 --- /dev/null +++ b/xpcom/reflect/xptcall/md/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/. + +arch = CONFIG['OS_ARCH'] + +if arch == 'WINNT': + DIRS += ['win32'] +else: + DIRS += ['unix'] diff --git a/xpcom/reflect/xptcall/md/test/README b/xpcom/reflect/xptcall/md/test/README new file mode 100644 index 000000000..04850b2e0 --- /dev/null +++ b/xpcom/reflect/xptcall/md/test/README @@ -0,0 +1,6 @@ +These are just simple test programs in which stripped down versions of the +XPConnect invoke and stubs code can be built and tested as the code is brought +up on various platforms. These probrams do not test the param sizing and copying +functionality of the routines. However, they do supply a place where the lowest +level assembly language code can be developed and debugged in the simplest of +contexts before it is moved into the real routines. \ No newline at end of file diff --git a/xpcom/reflect/xptcall/md/test/clean.bat b/xpcom/reflect/xptcall/md/test/clean.bat new file mode 100755 index 000000000..f320e222c --- /dev/null +++ b/xpcom/reflect/xptcall/md/test/clean.bat @@ -0,0 +1,5 @@ +@echo off +echo deleting intermediate files +if exist *.obj del *.obj > NUL +if exist *.ilk del *.ilk > NUL +if exist *.pdb del *.pdb > NUL \ No newline at end of file diff --git a/xpcom/reflect/xptcall/md/test/invoke_test.cpp b/xpcom/reflect/xptcall/md/test/invoke_test.cpp new file mode 100644 index 000000000..068db8b2f --- /dev/null +++ b/xpcom/reflect/xptcall/md/test/invoke_test.cpp @@ -0,0 +1,207 @@ +/* -*- 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 + +typedef unsigned nsresult; +typedef unsigned uint32_t; +typedef unsigned nsXPCVariant; + + +#if defined(WIN32) +#define NS_IMETHOD virtual nsresult __stdcall +#define NS_IMETHODIMP nsresult __stdcall +#else +#define NS_IMETHOD virtual nsresult +#define NS_IMETHODIMP nsresult +#endif + + +class base{ +public: + NS_IMETHOD ignored() = 0; +}; + +class foo : public base { +public: + NS_IMETHOD callme1(int i, int j) = 0; + NS_IMETHOD callme2(int i, int j) = 0; + NS_IMETHOD callme3(int i, int j) = 0; +}; + +class bar : public foo{ +public: + NS_IMETHOD ignored(); + NS_IMETHOD callme1(int i, int j); + NS_IMETHOD callme2(int i, int j); + NS_IMETHOD callme3(int i, int j); +}; + +/* +class baz : public base { +public: + NS_IMETHOD ignored(); + NS_IMETHOD callme1(); + NS_IMETHOD callme2(); + NS_IMETHOD callme3(); + void setfoo(foo* f) {other = f;} + + foo* other; +}; +NS_IMETHODIMP baz::ignored(){return 0;} +*/ + +NS_IMETHODIMP bar::ignored(){return 0;} + +NS_IMETHODIMP bar::callme1(int i, int j) +{ + printf("called bar::callme1 with: %d %d\n", i, j); + return 5; +} + +NS_IMETHODIMP bar::callme2(int i, int j) +{ + printf("called bar::callme2 with: %d %d\n", i, j); + return 5; +} + +NS_IMETHODIMP bar::callme3(int i, int j) +{ + printf("called bar::callme3 with: %d %d\n", i, j); + return 5; +} + +void docall(foo* f, int i, int j){ + f->callme1(i, j); +} + +/***************************************************************************/ +#if defined(WIN32) + +static uint32_t __stdcall +invoke_count_words(uint32_t paramCount, nsXPCVariant* s) +{ + return paramCount; +} + +static void __stdcall +invoke_copy_to_stack(uint32_t* d, uint32_t paramCount, nsXPCVariant* s) +{ + for(uint32_t i = 0; i < paramCount; i++, d++, s++) + { + *((uint32_t*)d) = *((uint32_t*)s); + } +} + +static nsresult __stdcall +DoInvoke(void* that, uint32_t index, + uint32_t paramCount, nsXPCVariant* params) +{ + __asm { + push params + push paramCount + call invoke_count_words // stdcall, result in eax + shl eax,2 // *= 4 + sub esp,eax // make space for params + mov edx,esp + push params + push paramCount + push edx + call invoke_copy_to_stack // stdcall + mov ecx,that // instance in ecx + push ecx // push this + mov edx,[ecx] // vtable in edx + mov eax,index + shl eax,2 // *= 4 + add edx,eax + call [edx] // stdcall, i.e. callee cleans up stack. + } +} + +#else +/***************************************************************************/ +// just Linux_x86 now. Add other later... + +static uint32_t +invoke_count_words(uint32_t paramCount, nsXPCVariant* s) +{ + return paramCount; +} + +static void +invoke_copy_to_stack(uint32_t* d, uint32_t paramCount, nsXPCVariant* s) +{ + for(uint32_t i = 0; i < paramCount; i++, d++, s++) + { + *((uint32_t*)d) = *((uint32_t*)s); + } +} + +static nsresult +DoInvoke(void* that, uint32_t index, + uint32_t paramCount, nsXPCVariant* params) +{ + uint32_t result; + void* fn_count = invoke_count_words; + void* fn_copy = invoke_copy_to_stack; + + __asm__ __volatile__( + "pushl %4\n\t" + "pushl %3\n\t" + "movl %5, %%eax\n\t" + "call *%%eax\n\t" /* count words */ + "addl $0x8, %%esp\n\t" + "shl $2, %%eax\n\t" /* *= 4 */ + "subl %%eax, %%esp\n\t" /* make room for params */ + "movl %%esp, %%edx\n\t" + "pushl %4\n\t" + "pushl %3\n\t" + "pushl %%edx\n\t" + "movl %6, %%eax\n\t" + "call *%%eax\n\t" /* copy params */ + "addl $0xc, %%esp\n\t" + "movl %1, %%ecx\n\t" + "pushl %%ecx\n\t" + "movl (%%ecx), %%edx\n\t" + "movl %2, %%eax\n\t" /* function index */ + "shl $2, %%eax\n\t" /* *= 4 */ + "addl $8, %%eax\n\t" /* += 8 */ + "addl %%eax, %%edx\n\t" + "call *(%%edx)\n\t" /* safe to not cleanup esp */ + "movl %%eax, %0" + : "=g" (result) /* %0 */ + : "g" (that), /* %1 */ + "g" (index), /* %2 */ + "g" (paramCount), /* %3 */ + "g" (params), /* %4 */ + "g" (fn_count), /* %5 */ + "g" (fn_copy) /* %6 */ + : "ax", "cx", "dx", "memory" + ); + + return result; +} + +#endif +/***************************************************************************/ + +int main() +{ + nsXPCVariant params1[2] = {1,2}; + nsXPCVariant params2[2] = {2,4}; + nsXPCVariant params3[2] = {3,6}; + + foo* a = new bar(); + +// printf("calling via C++...\n"); +// docall(a, 12, 24); + + printf("calling via ASM...\n"); + DoInvoke(a, 1, 2, params1); + DoInvoke(a, 2, 2, params2); + DoInvoke(a, 3, 2, params3); + + return 0; +} diff --git a/xpcom/reflect/xptcall/md/test/mk_invoke.bat b/xpcom/reflect/xptcall/md/test/mk_invoke.bat new file mode 100755 index 000000000..10a9be51d --- /dev/null +++ b/xpcom/reflect/xptcall/md/test/mk_invoke.bat @@ -0,0 +1,9 @@ +@echo off +@echo deleing old output +if exist invoke_test.obj del invoke_test.obj > NUL +if exist invoke_test.ilk del invoke_test.ilk > NUL +if exist *.pdb del *.pdb > NUL +if exist invoke_test.exe del invoke_test.exe > NUL + +@echo building... +cl /nologo -Zi -DWIN32 invoke_test.cpp \ No newline at end of file diff --git a/xpcom/reflect/xptcall/md/test/mk_stub.bat b/xpcom/reflect/xptcall/md/test/mk_stub.bat new file mode 100755 index 000000000..f9af17aff --- /dev/null +++ b/xpcom/reflect/xptcall/md/test/mk_stub.bat @@ -0,0 +1,9 @@ +@echo off +@echo deleing old output +if exist stub_test.obj del stub_test.obj > NUL +if exist stub_test.ilk del stub_test.ilk > NUL +if exist *.pdb del *.pdb > NUL +if exist stub_test.exe del stub_test.exe > NUL + +@echo building... +cl /nologo -Zi -DWIN32 stub_test.cpp \ No newline at end of file diff --git a/xpcom/reflect/xptcall/md/test/moz.build b/xpcom/reflect/xptcall/md/test/moz.build new file mode 100644 index 000000000..2261c3ce6 --- /dev/null +++ b/xpcom/reflect/xptcall/md/test/moz.build @@ -0,0 +1,9 @@ +# -*- 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([ + 'stub_test', +]) diff --git a/xpcom/reflect/xptcall/md/test/stub_test.cpp b/xpcom/reflect/xptcall/md/test/stub_test.cpp new file mode 100644 index 000000000..6c9559c65 --- /dev/null +++ b/xpcom/reflect/xptcall/md/test/stub_test.cpp @@ -0,0 +1,213 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include + +typedef unsigned nsresult; +typedef unsigned uint32_t; +typedef unsigned nsXPCVariant; + + +#if defined(WIN32) +#define NS_IMETHOD virtual nsresult __stdcall +#define NS_IMETHODIMP nsresult __stdcall +#else +#define NS_IMETHOD virtual nsresult +#define NS_IMETHODIMP nsresult +#endif + + +class base{ +public: + NS_IMETHOD ignored() = 0; +}; + +class foo : public base { +public: + NS_IMETHOD callme1(int i, int j) = 0; + NS_IMETHOD callme2(int i, int j) = 0; + NS_IMETHOD callme3(int i, int j) = 0; +}; + +class bar : public foo{ +public: + NS_IMETHOD ignored(); + NS_IMETHOD callme1(int i, int j); + NS_IMETHOD callme2(int i, int j); + NS_IMETHOD callme3(int i, int j); +}; + +class baz : public base { +public: + NS_IMETHOD ignored(); + NS_IMETHOD callme1(); + NS_IMETHOD callme2(); + NS_IMETHOD callme3(); + void setfoo(foo* f) {other = f;} + + foo* other; +}; +NS_IMETHODIMP baz::ignored(){return 0;} + +NS_IMETHODIMP bar::ignored(){return 0;} + +NS_IMETHODIMP bar::callme1(int i, int j) +{ + printf("called bar::callme1 with: %d %d\n", i, j); + return 15; +} + +NS_IMETHODIMP bar::callme2(int i, int j) +{ + printf("called bar::callme2 with: %d %d\n", i, j); + return 25; +} + +NS_IMETHODIMP bar::callme3(int i, int j) +{ + printf("called bar::callme3 with: %d %d\n", i, j); + return 35; +} + +void docall(foo* f, int i, int j){ + f->callme1(i, j); +} + +/***************************************************************************/ +#if defined(WIN32) + +static int __stdcall +PrepareAndDispatch(baz* self, uint32_t methodIndex, + uint32_t* args, uint32_t* stackBytesToPop) +{ + fprintf(stdout, "PrepareAndDispatch (%p, %d, %p)\n", + (void*)self, methodIndex, (void*)args); + foo* a = self->other; + int p1 = (int) *args; + int p2 = (int) *(args+1); + int out = 0; + switch(methodIndex) + { + case 1: out = a->callme1(p1, p2); break; + case 2: out = a->callme2(p1, p2); break; + case 3: out = a->callme3(p1, p2); break; + } + *stackBytesToPop = 2*4; + return out; +} + +#ifndef __GNUC__ +static __declspec(naked) void SharedStub(void) +{ + __asm { + push ebp // set up simple stack frame + mov ebp, esp // stack has: ebp/vtbl_index/retaddr/this/args + push ecx // make room for a ptr + lea eax, [ebp-4] // pointer to stackBytesToPop + push eax + lea ecx, [ebp+16] // pointer to args + push ecx + mov edx, [ebp+4] // vtbl_index + push edx + mov eax, [ebp+12] // this + push eax + call PrepareAndDispatch + mov edx, [ebp+8] // return address + mov ecx, [ebp-4] // stackBytesToPop + add ecx, 12 // for this, the index, and ret address + mov esp, ebp + pop ebp + add esp, ecx // fix up stack pointer + jmp edx // simulate __stdcall return + } +} + +// these macros get expanded (many times) in the file #included below +#define STUB_ENTRY(n) \ +__declspec(naked) nsresult __stdcall baz::callme##n() \ +{ __asm push n __asm jmp SharedStub } + +#else /* __GNUC__ */ + +#define STUB_ENTRY(n) \ +nsresult __stdcall baz::callme##n() \ +{ \ + uint32_t *args, stackBytesToPop; \ + int result = 0; \ + baz *obj; \ + __asm__ __volatile__ ( \ + "leal 0x0c(%%ebp), %0\n\t" /* args */ \ + "movl 0x08(%%ebp), %1\n\t" /* this */ \ + : "=r" (args), \ + "=r" (obj)); \ + result = PrepareAndDispatch(obj, n, args,&stackBytesToPop); \ + fprintf(stdout, "stub returning: %d\n", result); \ + fprintf(stdout, "bytes to pop: %d\n", stackBytesToPop); \ + return result; \ +} + +#endif /* ! __GNUC__ */ + +#else +/***************************************************************************/ +// just Linux_x86 now. Add other later... + +static int +PrepareAndDispatch(baz* self, uint32_t methodIndex, uint32_t* args) +{ + foo* a = self->other; + int p1 = (int) *args; + int p2 = (int) *(args+1); + switch(methodIndex) + { + case 1: a->callme1(p1, p2); break; + case 2: a->callme2(p1, p2); break; + case 3: a->callme3(p1, p2); break; + } + return 1; +} + +#define STUB_ENTRY(n) \ +nsresult baz::callme##n() \ +{ \ + void* method = PrepareAndDispatch; \ + nsresult result; \ + __asm__ __volatile__( \ + "leal 0x0c(%%ebp), %%ecx\n\t" /* args */ \ + "pushl %%ecx\n\t" \ + "pushl $"#n"\n\t" /* method index */ \ + "movl 0x08(%%ebp), %%ecx\n\t" /* this */ \ + "pushl %%ecx\n\t" \ + "call *%%edx" /* PrepareAndDispatch */ \ + : "=a" (result) /* %0 */ \ + : "d" (method) /* %1 */ \ + : "memory" ); \ + return result; \ +} + +#endif +/***************************************************************************/ + +STUB_ENTRY(1) +STUB_ENTRY(2) +STUB_ENTRY(3) + +int main() +{ + foo* a = new bar(); + baz* b = new baz(); + + /* here we make the global 'check for alloc failure' checker happy */ + if(!a || !b) + return 1; + + foo* c = (foo*)b; + + b->setfoo(a); + c->callme1(1,2); + c->callme2(2,4); + c->callme3(3,6); + + return 0; +} diff --git a/xpcom/reflect/xptcall/md/unix/Makefile.in b/xpcom/reflect/xptcall/md/unix/Makefile.in new file mode 100644 index 000000000..e4cdc389b --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/Makefile.in @@ -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/. + +###################################################################### +# HPPA +###################################################################### +# +# HP-UX/PA32 +# +# for gas and gcc, check comment in xptcinvoke_asm_pa32.s +ifeq ($(OS_ARCH),HP-UX) +ifneq ($(CC),gcc) +# #18875 Building the CPP's (CXX) optimized causes a crash +CXXFLAGS := $(filter-out $(MOZ_OPTIMIZE_FLAGS), $(CXXFLAGS)) +endif +endif + +# +# Linux/HPPA/gcc +# +ifeq ($(OS_ARCH),Linux) +ifneq (,$(filter hppa hppa2.0 hppa1.1,$(OS_TEST))) +ifndef GNU_CXX +else +# #434190 optimized builds crash +CXXFLAGS := $(filter-out $(MOZ_OPTIMIZE_FLAGS), $(CXXFLAGS)) +endif +endif +endif + + +###################################################################### +# PowerPC +###################################################################### +# +# AIX/PPC +# +ifeq ($(OS_ARCH),AIX) +# #24617 Building the CPP's (CXX) optimized causes a crash +CXXFLAGS := $(filter-out $(MOZ_OPTIMIZE_FLAGS), $(CXXFLAGS)) +endif + +include $(topsrcdir)/config/rules.mk + +ifeq ($(OS_ARCH),Linux) +ifneq (,$(findstring mips, $(OS_TEST))) +xptcstubs_asm_mips.o: $(DIST)/include/xptcstubsdef.inc +endif +endif + +ifeq ($(OS_ARCH),Darwin) +xptcstubs_asm_ppc_darwin.s: xptcstubs_asm_ppc_darwin.s.m4 $(DIST)/include/xptcstubsdef.inc Makefile + gm4 $(INCLUDES) $< > $@ +endif + +ifeq ($(OS_ARCH),AIX) +ifdef HAVE_64BIT_BUILD +xptcstubs_asm_ppc_aix64.s: xptcstubs_asm_ppc_aix64.s.m4 $(DIST)/include/xptcstubsdef.inc Makefile + m4 -DAIX_OBJMODEL=$(AIX_OBJMODEL) $(INCLUDES) -I. $< > $@ +else +xptcstubs_asm_ppc_aix.s: xptcstubs_asm_ppc_aix.s.m4 $(DIST)/include/xptcstubsdef.inc Makefile + m4 -DAIX_OBJMODEL=$(AIX_OBJMODEL) $(INCLUDES) -I. $< > $@ +endif +endif + +ifeq ($(OS_ARCH),SunOS) +ifeq (86,$(findstring 86,$(OS_TEST))) +ifndef GNU_CC +xptcstubsdef_asm.solx86: $(DIST)/include/xptcstubsdef.inc + sed \ + -e 's/^\(STUB_ENTRY\)(\([0-9]\))/\11\(\2\)/' \ + -e 's/^\(STUB_ENTRY\)(\([0-9][0-9]\))/\12\(\2\)/' \ + -e 's/^\(STUB_ENTRY\)(\([0-9][0-9][0-9]\))/\13\(\2\)/' \ + $(DIST)/include/xptcstubsdef.inc > $@ +endif +endif +endif diff --git a/xpcom/reflect/xptcall/md/unix/moz.build b/xpcom/reflect/xptcall/md/unix/moz.build new file mode 100644 index 000000000..1d182bbd6 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/moz.build @@ -0,0 +1,327 @@ +# -*- 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/. + +if CONFIG['OS_ARCH'] == 'Darwin': + SOURCES += [ + 'xptcinvoke_darwin.cpp', + 'xptcstubs_darwin.cpp', + ] + if CONFIG['OS_TEST'] == 'powerpc': + SOURCES += [ + '!xptcstubs_asm_ppc_darwin.s', + 'xptcinvoke_asm_ppc_rhapsody.s', + ] + if '86' in CONFIG['OS_TEST'] and CONFIG['OS_TEST'] != 'x86_64': + DEFINES['MOZ_NEED_LEADING_UNDERSCORE'] = True + +if CONFIG['OS_ARCH'] == 'GNU': + if CONFIG['CPU_ARCH'] == 'x86': + SOURCES += [ + 'xptcinvoke_gcc_x86_unix.cpp', + 'xptcstubs_gcc_x86_unix.cpp' + ] + +if CONFIG['OS_ARCH'] in ('Linux', 'Bitrig', 'DragonFly', 'FreeBSD', 'NetBSD', 'OpenBSD') or \ + CONFIG['OS_ARCH'].startswith('GNU_'): + if CONFIG['OS_TEST'] == 'x86_64': + SOURCES += [ + 'xptcinvoke_x86_64_unix.cpp', + 'xptcstubs_x86_64_linux.cpp', + ] + elif '86' in CONFIG['OS_TEST']: + SOURCES += [ + 'xptcinvoke_gcc_x86_unix.cpp', + 'xptcstubs_gcc_x86_unix.cpp' + ] + +if CONFIG['OS_ARCH'] in ('Linux', 'FreeBSD'): + if 'ia64' in CONFIG['OS_TEST']: + SOURCES += [ + 'xptcinvoke_asm_ipf64.s', + 'xptcinvoke_ipf64.cpp', + 'xptcstubs_asm_ipf64.s', + 'xptcstubs_ipf64.cpp' + ] + +if CONFIG['OS_ARCH'] == 'SunOS' and '86' in CONFIG['OS_TEST']: + GENERATED_FILES = [ + 'xptcstubsdef_asm.solx86', + ] + if CONFIG['OS_TEST'] == 'x86_64': + if CONFIG['GNU_CC']: + SOURCES += [ + 'xptcinvoke_x86_64_unix.cpp', + 'xptcstubs_x86_64_linux.cpp' + ] + else: + ASFLAGS += ['-xarch=amd64'] + SOURCES += [ + 'xptcinvoke_x86_64_solaris.cpp', + 'xptcstubs_asm_x86_64_solaris_SUNW.s', + 'xptcstubs_x86_64_solaris.cpp', + ] + else: + if CONFIG['GNU_CC']: + SOURCES += [ + 'xptcinvoke_gcc_x86_unix.cpp', + 'xptcstubs_gcc_x86_unix.cpp' + ] + else: + SOURCES += [ + 'xptcinvoke_asm_x86_solaris_SUNW.s', + 'xptcinvoke_x86_solaris.cpp', + 'xptcstubs_asm_x86_solaris_SUNW.s', + 'xptcstubs_x86_solaris.cpp' + ] + +if CONFIG['OS_TEST'] == 'alpha': + if CONFIG['OS_ARCH'] in ('Linux', 'FreeBSD', 'NetBSD'): + SOURCES += [ + 'xptcinvoke_linux_alpha.cpp', + 'xptcstubs_linux_alpha.cpp', + ] + elif CONFIG['OS_ARCH'] == 'OpenBSD': + SOURCES += [ + 'xptcinvoke_alpha_openbsd.cpp', + 'xptcstubs_alpha_openbsd.cpp', + ] + +if CONFIG['CPU_ARCH'] == 'arm' or CONFIG['OS_TEST'] == 'sa110': + if CONFIG['OS_ARCH'] == 'Linux': + SOURCES += [ + 'xptcinvoke_arm.cpp', + 'xptcstubs_arm.cpp' + ] + CXXFLAGS += ['-O2'] + elif CONFIG['OS_ARCH'] == 'NetBSD': + SOURCES += [ + 'xptcinvoke_arm_netbsd.cpp', + 'xptcstubs_arm_netbsd.cpp', + ] + +if CONFIG['CPU_ARCH'] == 'arm' and CONFIG['OS_ARCH'] in ('Bitrig', 'OpenBSD'): + SOURCES += [ + 'xptcinvoke_arm_openbsd.cpp', + 'xptcstubs_arm_openbsd.cpp', + ] + +if CONFIG['OS_ARCH'] == 'HP-UX': + if CONFIG['CC'] != 'gcc': + if CONFIG['OS_TEST'] == 'ia64': + SOURCES += [ + 'xptcinvoke_asm_ipf32.s', + 'xptcinvoke_ipf32.cpp', + 'xptcstubs_asm_ipf32.s', + 'xptcstubs_ipf32.cpp', + ] + else: + SOURCES += [ + 'xptcinvoke_asm_pa32.s', + 'xptcinvoke_pa32.cpp', + 'xptcstubs_asm_pa32.s', + 'xptcstubs_pa32.cpp' + ] + +if CONFIG['OS_ARCH'] == 'Linux': + if CONFIG['OS_TEST'] in ('hppa', 'hppa2.0', 'hppa1.1'): + if CONFIG['GNU_CXX']: + SOURCES += [ + 'xptcinvoke_asm_parisc_linux.s', + 'xptcinvoke_pa32.cpp', + 'xptcstubs_asm_parisc_linux.s', + 'xptcstubs_pa32.cpp', + ] + elif CONFIG['COMPILE_ENVIRONMENT']: + error('Unknown C++ compiler, xptcall assembly will probably be incorrect.') + +if CONFIG['OS_ARCH'] == 'NetBSD': + if CONFIG['OS_TEST'] in ('amiga', 'atari', 'hp300', 'mac68k', 'mvme68k', + 'next68k', 'sun3', 'sun3x', 'x68k'): + SOURCES += [ + 'xptcinvoke_netbsd_m68k.cpp', + 'xptcstubs_netbsd_m68k.cpp' + ] + +if CONFIG['OS_ARCH'] in ('Linux', 'FreeBSD', 'NetBSD', 'OpenBSD'): + if CONFIG['OS_TEST'] == 'aarch64': + SOURCES += [ + 'xptcinvoke_aarch64.cpp', + 'xptcinvoke_asm_aarch64.s', + 'xptcstubs_aarch64.cpp', + 'xptcstubs_asm_aarch64.s', + ] + if CONFIG['OS_TEST'] == 'm68k': + SOURCES += [ + 'xptcinvoke_linux_m68k.cpp', + 'xptcstubs_linux_m68k.cpp', + ] + if 'mips' in CONFIG['OS_TEST']: + if 'mips64' in CONFIG['OS_TEST']: + SOURCES += [ + 'xptcinvoke_asm_mips64.S', + 'xptcinvoke_mips64.cpp', + 'xptcstubs_asm_mips64.S', + 'xptcstubs_mips64.cpp', + ] + else: + SOURCES += [ + 'xptcinvoke_asm_mips.S', + 'xptcinvoke_mips.cpp', + 'xptcstubs_asm_mips.S', + 'xptcstubs_mips.cpp', + ] + +if CONFIG['OS_ARCH'] == 'AIX': + if CONFIG['HAVE_64BIT_BUILD']: + SOURCES += [ + '!xptcstubs_asm_ppc_aix64.s', + 'xptcinvoke_asm_ppc_aix64.s', + 'xptcinvoke_ppc_aix64.cpp', + 'xptcstubs_ppc_aix64.cpp', + ] + else: + SOURCES += [ + '!xptcstubs_asm_ppc_aix.s', + 'xptcinvoke_ppc_aix.cpp', + 'xptcstubs_ppc_aix.cpp', + ] + if CONFIG['AIX_OBJMODEL'] == 'ibm': + SOURCES += [ + 'xptcinvoke_asm_ppc_ibmobj_aix.s', + ] + else: + SOURCES += [ + 'xptcinvoke_asm_ppc_aix.s', + ] + +if CONFIG['OS_TEST'] == 'powerpc': + if CONFIG['OS_ARCH'] in ('Linux', 'FreeBSD'): + SOURCES += [ + 'xptcinvoke_asm_ppc_linux.S', + 'xptcinvoke_ppc_linux.cpp', + 'xptcstubs_asm_ppc_linux.S', + 'xptcstubs_ppc_linux.cpp', + ] + +if CONFIG['OS_TEST'] in ('powerpc64', 'powerpc64le'): + if CONFIG['OS_ARCH'] in ('Linux', 'FreeBSD'): + SOURCES += [ + 'xptcinvoke_asm_ppc64_linux.S', + 'xptcinvoke_ppc64_linux.cpp', + 'xptcstubs_asm_ppc64_linux.S', + 'xptcstubs_ppc64_linux.cpp', + ] + +if CONFIG['OS_TEST'] in ('macppc', 'bebox', 'ofppc', 'prep', 'amigappc'): + if CONFIG['OS_ARCH'] == 'NetBSD': + SOURCES += [ + 'xptcinvoke_asm_ppc_netbsd.s', + 'xptcinvoke_ppc_netbsd.cpp', + 'xptcstubs_asm_ppc_netbsd.s', + 'xptcstubs_ppc_netbsd.cpp', + ] + +if CONFIG['OS_ARCH'] == 'OpenBSD' and CONFIG['OS_TEST'] == 'powerpc': + SOURCES += [ + 'xptcinvoke_asm_ppc_openbsd.S', + 'xptcinvoke_ppc_openbsd.cpp', + 'xptcstubs_asm_ppc_openbsd.S', + 'xptcstubs_ppc_openbsd.cpp', + ] + +if CONFIG['OS_ARCH'] == 'Linux' and 'sparc' in CONFIG['OS_TEST']: + SOURCES += [ + 'xptcinvoke_asm_sparc_linux_GCC3.s', + 'xptcinvoke_sparc_solaris.cpp', + 'xptcstubs_asm_sparc_solaris.s', + 'xptcstubs_sparc_solaris.cpp', + ] + +if CONFIG['OS_ARCH'] == 'NetBSD' and CONFIG['OS_TEST'] == 'sparc': + SOURCES += [ + 'xptcinvoke_asm_sparc_netbsd.s', + 'xptcinvoke_sparc_netbsd.cpp', + 'xptcstubs_asm_sparc_netbsd.s', + 'xptcstubs_sparc_netbsd.cpp', + ] + +if CONFIG['OS_ARCH'] == 'OpenBSD' and CONFIG['OS_TEST'] == 'sparc': + SOURCES += [ + 'xptcinvoke_asm_sparc_openbsd.s', + 'xptcinvoke_sparc_openbsd.cpp', + 'xptcstubs_asm_sparc_openbsd.s', + 'xptcstubs_sparc_openbsd.cpp', + ] + +if CONFIG['OS_ARCH'] in ('OpenBSD', 'FreeBSD') and CONFIG['OS_TEST'] == 'sparc64': + SOURCES += [ + 'xptcinvoke_asm_sparc64_openbsd.s', + 'xptcinvoke_sparc64_openbsd.cpp', + 'xptcstubs_asm_sparc64_openbsd.s', + 'xptcstubs_sparc64_openbsd.cpp', + ] + +if CONFIG['OS_ARCH'] == 'SunOS' and '86' not in CONFIG['OS_TEST']: + if CONFIG['HAVE_64BIT_BUILD']: + ASFLAGS += ['-xarch=v9'] + SOURCES += [ + 'xptcinvoke_sparcv9_solaris.cpp', + 'xptcstubs_sparcv9_solaris.cpp', + ] + else: + SOURCES += [ + 'xptcinvoke_sparc_solaris.cpp', + 'xptcstubs_sparc_solaris.cpp', + ] + if CONFIG['GNU_CC']: + SOURCES += [ + 'xptcinvoke_asm_sparc_solaris_GCC3.s', + 'xptcstubs_asm_sparc_solaris.s', + ] + else: + if CONFIG['HAVE_64BIT_BUILD']: + SOURCES += [ + 'xptcinvoke_asm_sparcv9_solaris_SUNW.s', + 'xptcstubs_asm_sparcv9_solaris.s', + ] + else: + SOURCES += [ + 'xptcinvoke_asm_sparc_solaris_SUNW.s', + 'xptcstubs_asm_sparc_solaris.s', + ] + +if CONFIG['OS_ARCH'] == 'Linux': + if CONFIG['OS_TEST'] == 's390': + SOURCES += [ + 'xptcinvoke_linux_s390.cpp', + 'xptcstubs_linux_s390.cpp', + ] + CXXFLAGS += [ + '-fno-strict-aliasing', + '-fno-inline', + '-fomit-frame-pointer', + '-mbackchain', + ] + elif CONFIG['OS_TEST'] == 's390x': + SOURCES += [ + 'xptcinvoke_linux_s390x.cpp', + 'xptcstubs_linux_s390x.cpp', + ] + CXXFLAGS += [ + '-fno-strict-aliasing', + '-fno-inline', + '-fomit-frame-pointer', + '-mbackchain', + ] + +FINAL_LIBRARY = 'xul' + +LOCAL_INCLUDES += [ + '../..', + '/xpcom/reflect/xptinfo', +] + +NO_PGO = True diff --git a/xpcom/reflect/xptcall/md/unix/vtable_layout_x86.cpp b/xpcom/reflect/xptcall/md/unix/vtable_layout_x86.cpp new file mode 100644 index 000000000..0cb327a3d --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/vtable_layout_x86.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/. */ + +/* this code contributed by Bert Driehuis */ + +#include + +// Try to determine the vtable layout generated by G++ +// Produces the offset at which the first vtable entry can be +// found, and the factor to apply for subsequent entries on stdout. +// Example output: +// #define GCC_VTABLE_START 0xc +// #define GCC_VTABLE_FACTOR 0x8 + +class test { +public: + virtual int t1(void); + virtual int t2(void); + int x; +}; + +test::test() { this->x = 0x12121212; }; + +int test::t1(void) { return 1; } +int test::t2(void) { return 2; } + +void die(char *x) { + fprintf(stderr, "%s\n", x); + exit(1); +} + +int +main() +{ + int i; + test *t = new test(); + int *tp = (int *) t; + int off1 = -1; + int off2 = -1; + int factor; + int factorshift; + + if (*tp++ != 0x12121212) + die("Integer element test::x not found!"); + tp = (int *) *tp; + for (i = 0; i < 10; i++) { + if (tp[i] == (int) t->t1) + off1 = i; + if (tp[i] == (int) t->t2) + off2 = i; + } + if (off1 == -1 || off2 == -1) + die("Could not determine offset into vtable!"); + factor = (off2 - off1) * 4; + factorshift = -1; + while (factor) { + factorshift++; + factor >>= 1; + } + printf("/* Automatically generated by vtable_layout_x86.cpp */\n"); + printf("#define GCC_VTABLE_START\t0x%x\n", off1 * 4); + printf("#define GCC_VTABLE_FACTOR\t0x%x\n", (off2 - off1) * 4); + printf("#define GCC_VTABLE_SHIFT\t0x%x\n", factorshift); + exit(0); +} diff --git a/xpcom/reflect/xptcall/md/unix/xptc_gcc_x86_unix.h b/xpcom/reflect/xptcall/md/unix/xptc_gcc_x86_unix.h new file mode 100644 index 000000000..733646c49 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptc_gcc_x86_unix.h @@ -0,0 +1,17 @@ +/* -*- 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/. */ + +/* Special include file for xptc*_gcc_x86_unix.cpp */ + +// +// this may improve the static function calls, but may not. +// + +#ifdef MOZ_NEED_LEADING_UNDERSCORE +#define SYMBOL_UNDERSCORE "_" +#else +#define SYMBOL_UNDERSCORE +#endif + diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_aarch64.cpp b/xpcom/reflect/xptcall/md/unix/xptcinvoke_aarch64.cpp new file mode 100644 index 000000000..e5807dbcd --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_aarch64.cpp @@ -0,0 +1,140 @@ +/* -*- 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/. */ + +/* Platform specific code to invoke XPCOM methods on native objects */ + +#include "xptcprivate.h" + +#if !defined(__aarch64__) +#error "This code is for Linux AArch64 only." +#endif + + +/* "Procedure Call Standard for the ARM 64-bit Architecture" document, sections + * "5.4 Parameter Passing" and "6.1.2 Procedure Calling" contain all the + * needed information. + * + * http://infocenter.arm.com/help/topic/com.arm.doc.ihi0042d/IHI0042D_aapcs.pdf + */ + +#ifndef __AARCH64EL__ +#error "Only little endian compatibility was tested" +#endif + +/* + * Allocation of integer function arguments initially to registers r1-r7 + * and then to stack. Handling of 'that' argument which goes to register r0 + * is handled separately and does not belong here. + * + * 'ireg_args' - pointer to the current position in the buffer, + * corresponding to the register arguments + * 'stack_args' - pointer to the current position in the buffer, + * corresponding to the arguments on stack + * 'end' - pointer to the end of the registers argument + * buffer. + */ +static inline void alloc_word(uint64_t* &ireg_args, + uint64_t* &stack_args, + uint64_t* end, + uint64_t data) +{ + if (ireg_args < end) { + *ireg_args = data; + ireg_args++; + } else { + *stack_args = data; + stack_args++; + } +} + +static inline void alloc_double(double* &freg_args, + uint64_t* &stack_args, + double* end, + double data) +{ + if (freg_args < end) { + *freg_args = data; + freg_args++; + } else { + memcpy(stack_args, &data, sizeof(data)); + stack_args++; + } +} + +static inline void alloc_float(double* &freg_args, + uint64_t* &stack_args, + double* end, + float data) +{ + if (freg_args < end) { + memcpy(freg_args, &data, sizeof(data)); + freg_args++; + } else { + memcpy(stack_args, &data, sizeof(data)); + stack_args++; + } +} + + +extern "C" void +invoke_copy_to_stack(uint64_t* stk, uint64_t *end, + uint32_t paramCount, nsXPTCVariant* s) +{ + uint64_t *ireg_args = stk; + uint64_t *ireg_end = ireg_args + 8; + double *freg_args = (double *)ireg_end; + double *freg_end = freg_args + 8; + uint64_t *stack_args = (uint64_t *)freg_end; + + // leave room for 'that' argument in x0 + ++ireg_args; + + for (uint32_t i = 0; i < paramCount; i++, s++) { + uint64_t word; + + if (s->IsPtrData()) { + word = (uint64_t)s->ptr; + } else { + // According to the ABI, integral types that are smaller than 8 + // bytes are to be passed in 8-byte registers or 8-byte stack + // slots. + switch (s->type) { + case nsXPTType::T_FLOAT: + alloc_float(freg_args, stack_args, freg_end, s->val.f); + continue; + case nsXPTType::T_DOUBLE: + alloc_double(freg_args, stack_args, freg_end, s->val.d); + continue; + case nsXPTType::T_I8: word = s->val.i8; break; + case nsXPTType::T_I16: word = s->val.i16; break; + case nsXPTType::T_I32: word = s->val.i32; break; + case nsXPTType::T_I64: word = s->val.i64; break; + case nsXPTType::T_U8: word = s->val.u8; break; + case nsXPTType::T_U16: word = s->val.u16; break; + case nsXPTType::T_U32: word = s->val.u32; break; + case nsXPTType::T_U64: word = s->val.u64; break; + case nsXPTType::T_BOOL: word = s->val.b; break; + case nsXPTType::T_CHAR: word = s->val.c; break; + case nsXPTType::T_WCHAR: word = s->val.wc; break; + default: + // all the others are plain pointer types + word = reinterpret_cast(s->val.p); + break; + } + } + + alloc_word(ireg_args, stack_args, ireg_end, word); + } +} + +extern "C" nsresult _NS_InvokeByIndex(nsISupports* that, uint32_t methodIndex, + uint32_t paramCount, nsXPTCVariant* params); + +EXPORT_XPCOM_API(nsresult) +NS_InvokeByIndex(nsISupports* that, uint32_t methodIndex, + uint32_t paramCount, nsXPTCVariant* params) +{ + return _NS_InvokeByIndex(that, methodIndex, paramCount, params); +} diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_alpha_openbsd.cpp b/xpcom/reflect/xptcall/md/unix/xptcinvoke_alpha_openbsd.cpp new file mode 100644 index 000000000..dc111e435 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_alpha_openbsd.cpp @@ -0,0 +1,144 @@ +/* -*- 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/. */ + +/* Platform specific code to invoke XPCOM methods on native objects */ + +#include "xptcprivate.h" + +/* Prototype specifies unmangled function name and disables unused warning */ +static void +invoke_copy_to_stack(uint64_t* d, uint32_t paramCount, nsXPTCVariant* s) +__asm__("invoke_copy_to_stack") __attribute__((used)); + +static void +invoke_copy_to_stack(uint64_t* d, uint32_t paramCount, nsXPTCVariant* s) +{ + const uint8_t NUM_ARG_REGS = 6-1; // -1 for "this" pointer + + for(uint32_t i = 0; i < paramCount; i++, d++, s++) + { + if(s->IsPtrData()) + { + *d = (uint64_t)s->ptr; + continue; + } + switch(s->type) + { + case nsXPTType::T_I8 : *d = (uint64_t)s->val.i8; break; + case nsXPTType::T_I16 : *d = (uint64_t)s->val.i16; break; + case nsXPTType::T_I32 : *d = (uint64_t)s->val.i32; break; + case nsXPTType::T_I64 : *d = (uint64_t)s->val.i64; break; + case nsXPTType::T_U8 : *d = (uint64_t)s->val.u8; break; + case nsXPTType::T_U16 : *d = (uint64_t)s->val.u16; break; + case nsXPTType::T_U32 : *d = (uint64_t)s->val.u32; break; + case nsXPTType::T_U64 : *d = (uint64_t)s->val.u64; break; + case nsXPTType::T_FLOAT : + if(i < NUM_ARG_REGS) + { + // convert floats to doubles if they are to be passed + // via registers so we can just deal with doubles later + union { uint64_t u64; double d; } t; + t.d = (double)s->val.f; + *d = t.u64; + } + else + // otherwise copy to stack normally + *d = (uint64_t)s->val.u32; + break; + case nsXPTType::T_DOUBLE : *d = (uint64_t)s->val.u64; break; + case nsXPTType::T_BOOL : *d = (uint64_t)s->val.b; break; + case nsXPTType::T_CHAR : *d = (uint64_t)s->val.c; break; + case nsXPTType::T_WCHAR : *d = (uint64_t)s->val.wc; break; + default: + // all the others are plain pointer types + *d = (uint64_t)s->val.p; + break; + } + } +} + +/* + * EXPORT_XPCOM_API(nsresult) + * NS_InvokeByIndex(nsISupports* that, uint32_t methodIndex, + * uint32_t paramCount, nsXPTCVariant* params) + */ +__asm__( + "#### NS_InvokeByIndex ####\n" +".text\n\t" + ".align 5\n\t" + ".globl NS_InvokeByIndex\n\t" + ".ent NS_InvokeByIndex\n" +"NS_InvokeByIndex:\n\t" + ".frame $15,32,$26,0\n\t" + ".mask 0x4008000,-32\n\t" + "ldgp $29,0($27)\n" +"$NS_InvokeByIndex..ng:\n\t" + "subq $30,32,$30\n\t" + "stq $26,0($30)\n\t" + "stq $15,8($30)\n\t" + "bis $30,$30,$15\n\t" + ".prologue 1\n\t" + + /* + * Allocate enough stack space to hold the greater of 6 or "paramCount"+1 + * parameters. (+1 for "this" pointer) Room for at least 6 parameters + * is required for storage of those passed via registers. + */ + + "bis $31,5,$2\n\t" /* count = MAX(5, "paramCount") */ + "cmplt $2,$18,$1\n\t" + "cmovne $1,$18,$2\n\t" + "s8addq $2,16,$1\n\t" /* room for count+1 params (8 bytes each) */ + "bic $1,15,$1\n\t" /* stack space is rounded up to 0 % 16 */ + "subq $30,$1,$30\n\t" + + "stq $16,0($30)\n\t" /* save "that" (as "this" pointer) */ + "stq $17,16($15)\n\t" /* save "methodIndex" */ + + "addq $30,8,$16\n\t" /* pass stack pointer */ + "bis $18,$18,$17\n\t" /* pass "paramCount" */ + "bis $19,$19,$18\n\t" /* pass "params" */ + "bsr $26,$invoke_copy_to_stack..ng\n\t" /* call invoke_copy_to_stack */ + + /* + * Copy the first 6 parameters to registers and remove from stack frame. + * Both the integer and floating point registers are set for each parameter + * except the first which is the "this" pointer. (integer only) + * The floating point registers are all set as doubles since the + * invoke_copy_to_stack function should have converted the floats. + */ + "ldq $16,0($30)\n\t" /* integer registers */ + "ldq $17,8($30)\n\t" + "ldq $18,16($30)\n\t" + "ldq $19,24($30)\n\t" + "ldq $20,32($30)\n\t" + "ldq $21,40($30)\n\t" + "ldt $f17,8($30)\n\t" /* floating point registers */ + "ldt $f18,16($30)\n\t" + "ldt $f19,24($30)\n\t" + "ldt $f20,32($30)\n\t" + "ldt $f21,40($30)\n\t" + + "addq $30,48,$30\n\t" /* remove params from stack */ + + /* + * Call the virtual function with the constructed stack frame. + */ + "bis $16,$16,$1\n\t" /* load "this" */ + "ldq $2,16($15)\n\t" /* load "methodIndex" */ + "ldq $1,0($1)\n\t" /* load vtable */ + "s8addq $2,$31,$2\n\t" /* vtable index = "methodIndex" * 8 */ + "addq $1,$2,$1\n\t" + "ldq $27,0($1)\n\t" /* load address of function */ + "jsr $26,($27),0\n\t" /* call virtual function */ + "ldgp $29,0($26)\n\t" + + "bis $15,$15,$30\n\t" + "ldq $26,0($30)\n\t" + "ldq $15,8($30)\n\t" + "addq $30,32,$30\n\t" + "ret $31,($26),1\n\t" + ".end NS_InvokeByIndex" + ); diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_arm.cpp b/xpcom/reflect/xptcall/md/unix/xptcinvoke_arm.cpp new file mode 100644 index 000000000..4cd5eb47d --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_arm.cpp @@ -0,0 +1,417 @@ +/* -*- 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/. */ + +/* Platform specific code to invoke XPCOM methods on native objects */ + +#include "xptcprivate.h" + +#include "mozilla/Compiler.h" + +#if !defined(__arm__) && !(defined(LINUX) || defined(ANDROID) || defined(XP_IOS)) +#error "This code is for Linux/iOS ARM only. Check that it works on your system, too.\nBeware that this code is highly compiler dependent." +#endif + +#if MOZ_IS_GCC +#if defined(__ARM_EABI__) && !defined(__ARM_PCS_VFP) && !defined(__ARM_PCS) +#error "Can't identify floating point calling conventions.\nPlease ensure that your toolchain defines __ARM_PCS or __ARM_PCS_VFP." +#endif +#endif + +#ifndef __ARM_PCS_VFP + +/* This function copies a 64-bits word from dw to the given pointer in + * a buffer delimited by start and end, possibly wrapping around the + * buffer boundaries, and/or properly aligning the data at 64-bits word + * boundaries (for EABI). + * start and end are both assumed to be 64-bits aligned. + * Returns a pointer to the second 32-bits word copied (to accomodate + * the invoke_copy_to_stack loop). + */ +static uint32_t * +copy_double_word(uint32_t *start, uint32_t *current, uint32_t *end, uint64_t *dw) +{ +#ifdef __ARM_EABI__ + /* Aligning the pointer for EABI */ + current = (uint32_t *)(((uint32_t)current + 7) & ~7); + /* Wrap when reaching the end of the buffer */ + if (current == end) current = start; +#else + /* On non-EABI, 64-bits values are not aligned and when we reach the end + * of the buffer, we need to write half of the data at the end, and the + * other half at the beginning. */ + if (current == end - 1) { + *current = ((uint32_t*)dw)[0]; + *start = ((uint32_t*)dw)[1]; + return start; + } +#endif + + *((uint64_t*) current) = *dw; + return current + 1; +} + +/* See stack_space comment in NS_InvokeByIndex to see why this needs not to + * be static on DEBUG builds. */ +#ifndef DEBUG +static +#endif +void +invoke_copy_to_stack(uint32_t* stk, uint32_t *end, + uint32_t paramCount, nsXPTCVariant* s) +{ + /* The stack buffer is 64-bits aligned. The end argument points to its end. + * The caller is assumed to create a stack buffer of at least four 32-bits + * words. + * We use the last three 32-bit words to store the values for r1, r2 and r3 + * for the method call, i.e. the first words for arguments passing. + */ + uint32_t *d = end - 3; + for(uint32_t i = 0; i < paramCount; i++, d++, s++) + { + /* Wrap when reaching the end of the stack buffer */ + if (d == end) d = stk; + NS_ASSERTION(d >= stk && d < end, + "invoke_copy_to_stack is copying outside its given buffer"); + if(s->IsPtrData()) + { + *((void**)d) = s->ptr; + continue; + } + // According to the ARM EABI, integral types that are smaller than a word + // are to be sign/zero-extended to a full word and treated as 4-byte values. + + switch(s->type) + { + case nsXPTType::T_I8 : *((int32_t*) d) = s->val.i8; break; + case nsXPTType::T_I16 : *((int32_t*) d) = s->val.i16; break; + case nsXPTType::T_I32 : *((int32_t*) d) = s->val.i32; break; + case nsXPTType::T_I64 : + d = copy_double_word(stk, d, end, (uint64_t *)&s->val.i64); + break; + case nsXPTType::T_U8 : *((uint32_t*)d) = s->val.u8; break; + case nsXPTType::T_U16 : *((uint32_t*)d) = s->val.u16; break; + case nsXPTType::T_U32 : *((uint32_t*)d) = s->val.u32; break; + case nsXPTType::T_U64 : + d = copy_double_word(stk, d, end, (uint64_t *)&s->val.u64); + break; + case nsXPTType::T_FLOAT : *((float*) d) = s->val.f; break; + case nsXPTType::T_DOUBLE : + d = copy_double_word(stk, d, end, (uint64_t *)&s->val.d); + break; + case nsXPTType::T_BOOL : *((int32_t*) d) = s->val.b; break; + case nsXPTType::T_CHAR : *((int32_t*) d) = s->val.c; break; + case nsXPTType::T_WCHAR : *((int32_t*) d) = s->val.wc; break; + default: + // all the others are plain pointer types + *((void**)d) = s->val.p; + break; + } + } +} + +typedef nsresult (*vtable_func)(nsISupports *, uint32_t, uint32_t, uint32_t); + +// Avoid AddressSanitizer instrumentation for the next function because it +// depends on __builtin_alloca behavior and alignment that cannot be relied on +// once the function is compiled with a version of ASan that has dynamic-alloca +// instrumentation enabled. + +MOZ_ASAN_BLACKLIST +EXPORT_XPCOM_API(nsresult) +NS_InvokeByIndex(nsISupports* that, uint32_t methodIndex, + uint32_t paramCount, nsXPTCVariant* params) +{ + +/* This is to call a given method of class that. + * The parameters are in params, the number is in paramCount. + * The routine will issue calls to count the number of words + * required for argument passing and to copy the arguments to + * the stack. + * ACPS passes the first 3 params in r1-r3 (with exceptions for 64-bits + * arguments), and the remaining goes onto the stack. + * We allocate a buffer on the stack for a "worst case" estimate of how much + * stack might be needed for EABI, i.e. twice the number of parameters. + * The end of this buffer will be used to store r1 to r3, so that the start + * of the stack is the remaining parameters. + * The magic here is to call the method with "that" and three 32-bits + * arguments corresponding to r1-r3, so that the compiler generates the + * proper function call. The stack will also contain the remaining arguments. + * + * !!! IMPORTANT !!! + * This routine makes assumptions about the vtable layout of the c++ compiler. It's implemented + * for arm-linux GNU g++ >= 2.8.1 (including egcs and gcc-2.95.[1-3])! + * + */ + + vtable_func *vtable, func; + int base_size = (paramCount > 1) ? paramCount : 2; + +/* !!! IMPORTANT !!! + * On DEBUG builds, the NS_ASSERTION used in invoke_copy_to_stack needs to use + * the stack to pass the 5th argument to NS_DebugBreak. When invoke_copy_to_stack + * is inlined, this can result, depending on the compiler and flags, in the + * stack pointer not pointing at stack_space when the method is called at the + * end of this function. More generally, any function call requiring stack + * allocation of arguments is unsafe to be inlined in this function. + */ + uint32_t *stack_space = (uint32_t *) __builtin_alloca(base_size * 8); + + invoke_copy_to_stack(stack_space, &stack_space[base_size * 2], + paramCount, params); + + vtable = *reinterpret_cast(that); + func = vtable[methodIndex]; + + return func(that, stack_space[base_size * 2 - 3], + stack_space[base_size * 2 - 2], + stack_space[base_size * 2 - 1]); +} + +#else /* __ARM_PCS_VFP */ + +/* "Procedure Call Standard for the ARM Architecture" document, sections + * "5.5 Parameter Passing" and "6.1.2 Procedure Calling" contain all the + * needed information. + * + * http://infocenter.arm.com/help/topic/com.arm.doc.ihi0042d/IHI0042D_aapcs.pdf + */ + +#if defined(__thumb__) && !defined(__thumb2__) +#error "Thumb1 is not supported" +#endif + +#ifndef __ARMEL__ +#error "Only little endian compatibility was tested" +#endif + +/* + * Allocation of integer function arguments initially to registers r1-r3 + * and then to stack. Handling of 'this' argument which goes to r0 registers + * is handled separately and does not belong to these two inline functions. + * + * The doubleword arguments are allocated to even:odd + * register pairs or get aligned at 8-byte boundary on stack. The "holes" + * which may appear as a result of this realignment remain unused. + * + * 'ireg_args' - pointer to the current position in the buffer, + * corresponding to the register arguments + * 'stack_args' - pointer to the current position in the buffer, + * corresponding to the arguments on stack + * 'end' - pointer to the end of the registers argument + * buffer (it is guaranteed to be 8-bytes aligned) + */ + +static inline void copy_word(uint32_t* &ireg_args, + uint32_t* &stack_args, + uint32_t* end, + uint32_t data) +{ + if (ireg_args < end) { + *ireg_args = data; + ireg_args++; + } else { + *stack_args = data; + stack_args++; + } +} + +static inline void copy_dword(uint32_t* &ireg_args, + uint32_t* &stack_args, + uint32_t* end, + uint64_t data) +{ + if (ireg_args + 1 < end) { + if ((uint32_t)ireg_args & 4) { + ireg_args++; + } + *(uint64_t *)ireg_args = data; + ireg_args += 2; + } else { + ireg_args = end; + if ((uint32_t)stack_args & 4) { + stack_args++; + } + *(uint64_t *)stack_args = data; + stack_args += 2; + } +} + +/* + * Allocation of floating point arguments to VFP registers (s0-s15, d0-d7). + * + * Unlike integer registers allocation, "back-filling" needs to be + * supported. For example, the third floating point argument in the + * following function is going to be allocated to s1 register, back-filling + * the "hole": + * void f(float s0, double d1, float s1) + * + * Refer to the "Procedure Call Standard for the ARM Architecture" document + * for more details. + * + * 'vfp_s_args' - pointer to the current position in the buffer with + * the next unallocated single precision register + * 'vfp_d_args' - pointer to the current position in the buffer with + * the next unallocated double precision register, + * it has the same value as 'vfp_s_args' when back-filling + * is not used + * 'end' - pointer to the end of the vfp registers argument + * buffer (it is guaranteed to be 8-bytes aligned) + * + * Mozilla bugtracker has a test program attached which be used for + * experimenting with VFP registers allocation code and testing its + * correctness: + * https://bugzilla.mozilla.org/show_bug.cgi?id=601914#c19 + */ + +static inline bool copy_vfp_single(float* &vfp_s_args, double* &vfp_d_args, + float* end, float data) +{ + if (vfp_s_args >= end) + return false; + + *vfp_s_args = data; + vfp_s_args++; + if (vfp_s_args < (float *)vfp_d_args) { + // It was the case of back-filling, now the next free single precision + // register should overlap with the next free double precision register + vfp_s_args = (float *)vfp_d_args; + } else if (vfp_s_args > (float *)vfp_d_args) { + // also update the pointer to the next free double precision register + vfp_d_args++; + } + return true; +} + +static inline bool copy_vfp_double(float* &vfp_s_args, double* &vfp_d_args, + float* end, double data) +{ + if (vfp_d_args >= (double *)end) { + // The back-filling continues only so long as no VFP CPRC has been + // allocated to a slot on the stack. Basically no VFP registers can + // be allocated after this point. + vfp_s_args = end; + return false; + } + + if (vfp_s_args == (float *)vfp_d_args) { + // also update the pointer to the next free single precision register + vfp_s_args += 2; + } + *vfp_d_args = data; + vfp_d_args++; + return true; +} + +static void +invoke_copy_to_stack(uint32_t* stk, uint32_t *end, + uint32_t paramCount, nsXPTCVariant* s) +{ + uint32_t *ireg_args = end - 3; + float *vfp_s_args = (float *)end; + double *vfp_d_args = (double *)end; + float *vfp_end = vfp_s_args + 16; + + for (uint32_t i = 0; i < paramCount; i++, s++) { + if (s->IsPtrData()) { + copy_word(ireg_args, stk, end, (uint32_t)s->ptr); + continue; + } + // According to the ARM EABI, integral types that are smaller than a word + // are to be sign/zero-extended to a full word and treated as 4-byte values + switch (s->type) + { + case nsXPTType::T_FLOAT: + if (!copy_vfp_single(vfp_s_args, vfp_d_args, vfp_end, s->val.f)) { + copy_word(end, stk, end, reinterpret_cast(s->val.f)); + } + break; + case nsXPTType::T_DOUBLE: + if (!copy_vfp_double(vfp_s_args, vfp_d_args, vfp_end, s->val.d)) { + copy_dword(end, stk, end, reinterpret_cast(s->val.d)); + } + break; + case nsXPTType::T_I8: copy_word(ireg_args, stk, end, s->val.i8); break; + case nsXPTType::T_I16: copy_word(ireg_args, stk, end, s->val.i16); break; + case nsXPTType::T_I32: copy_word(ireg_args, stk, end, s->val.i32); break; + case nsXPTType::T_I64: copy_dword(ireg_args, stk, end, s->val.i64); break; + case nsXPTType::T_U8: copy_word(ireg_args, stk, end, s->val.u8); break; + case nsXPTType::T_U16: copy_word(ireg_args, stk, end, s->val.u16); break; + case nsXPTType::T_U32: copy_word(ireg_args, stk, end, s->val.u32); break; + case nsXPTType::T_U64: copy_dword(ireg_args, stk, end, s->val.u64); break; + case nsXPTType::T_BOOL: copy_word(ireg_args, stk, end, s->val.b); break; + case nsXPTType::T_CHAR: copy_word(ireg_args, stk, end, s->val.c); break; + case nsXPTType::T_WCHAR: copy_word(ireg_args, stk, end, s->val.wc); break; + default: + // all the others are plain pointer types + copy_word(ireg_args, stk, end, reinterpret_cast(s->val.p)); + break; + } + } +} + +typedef uint32_t (*vtable_func)(nsISupports *, uint32_t, uint32_t, uint32_t); + +EXPORT_XPCOM_API(nsresult) +NS_InvokeByIndex(nsISupports* that, uint32_t methodIndex, + uint32_t paramCount, nsXPTCVariant* params) +{ + vtable_func *vtable = *reinterpret_cast(that); + vtable_func func = vtable[methodIndex]; + // 'register uint32_t result asm("r0")' could be used here, but it does not + // seem to be reliable in all cases: http://gcc.gnu.org/PR46164 + nsresult result; + asm ( + "mov r3, sp\n" + "mov %[stack_space_size], %[param_count_plus_2], lsl #3\n" + "tst r3, #4\n" /* check stack alignment */ + + "add %[stack_space_size], #(4 * 16)\n" /* space for VFP registers */ + "mov r3, %[params]\n" + + "it ne\n" + "addne %[stack_space_size], %[stack_space_size], #4\n" + "sub r0, sp, %[stack_space_size]\n" /* allocate space on stack */ + + "sub r2, %[param_count_plus_2], #2\n" + "mov sp, r0\n" + + "add r1, r0, %[param_count_plus_2], lsl #3\n" + "blx %[invoke_copy_to_stack]\n" + + "add ip, sp, %[param_count_plus_2], lsl #3\n" + "mov r0, %[that]\n" + "ldmdb ip, {r1, r2, r3}\n" + "vldm ip, {d0, d1, d2, d3, d4, d5, d6, d7}\n" + "blx %[func]\n" + + "add sp, sp, %[stack_space_size]\n" /* cleanup stack */ + "mov %[stack_space_size], r0\n" /* it's actually 'result' variable */ + : [stack_space_size] "=&r" (result) + : [func] "r" (func), + [that] "r" (that), + [params] "r" (params), + [param_count_plus_2] "r" (paramCount + 2), + [invoke_copy_to_stack] "r" (invoke_copy_to_stack) + : "cc", "memory", + // Mark all the scratch registers as clobbered because they may be + // modified by the functions, called from this inline assembly block + "r0", "r1", "r2", "r3", "ip", "lr", + "d0", "d1", "d2", "d3", "d4", "d5", "d6", "d7", + // Also unconditionally mark d16-d31 registers as clobbered even though + // they actually don't exist in vfpv2 and vfpv3-d16 variants. There is + // no way to identify VFP variant using preprocessor at the momemnt + // (see http://gcc.gnu.org/PR46128 for more details), but fortunately + // current versions of gcc do not seem to complain about these registers + // even when this code is compiled with '-mfpu=vfpv3-d16' option. + // If gcc becomes more strict in the future and/or provides a way to + // identify VFP variant, the following d16-d31 registers list needs + // to be wrapped into some #ifdef + "d16", "d17", "d18", "d19", "d20", "d21", "d22", "d23", + "d24", "d25", "d26", "d27", "d28", "d29", "d30", "d31" + ); + return result; +} + +#endif diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_arm_netbsd.cpp b/xpcom/reflect/xptcall/md/unix/xptcinvoke_arm_netbsd.cpp new file mode 100644 index 000000000..1b0c214ea --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_arm_netbsd.cpp @@ -0,0 +1,181 @@ +/* -*- 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/. */ + +/* Platform specific code to invoke XPCOM methods on native objects */ + +#include "xptcprivate.h" + +// Remember that these 'words' are 32bit DWORDS + +static uint32_t +invoke_count_words(uint32_t paramCount, nsXPTCVariant* s) +{ + uint32_t result = 0; + for(uint32_t i = 0; i < paramCount; i++, s++) + { + if(s->IsPtrData()) + { + result++; + continue; + } + switch(s->type) + { + case nsXPTType::T_I8 : + case nsXPTType::T_I16 : + case nsXPTType::T_I32 : + result++; + break; + case nsXPTType::T_I64 : + result+=2; + break; + case nsXPTType::T_U8 : + case nsXPTType::T_U16 : + case nsXPTType::T_U32 : + result++; + break; + case nsXPTType::T_U64 : + result+=2; + break; + case nsXPTType::T_FLOAT : + result++; + break; + case nsXPTType::T_DOUBLE : + result+=2; + break; + case nsXPTType::T_BOOL : + case nsXPTType::T_CHAR : + case nsXPTType::T_WCHAR : + result++; + break; + default: + // all the others are plain pointer types + result++; + break; + } + } + return result; +} + +static void +invoke_copy_to_stack(uint32_t* d, uint32_t paramCount, nsXPTCVariant* s) +{ + for(uint32_t i = 0; i < paramCount; i++, d++, s++) + { + if(s->IsPtrData()) + { + *((void**)d) = s->ptr; + continue; + } + switch(s->type) + { + case nsXPTType::T_I8 : *((int8_t*) d) = s->val.i8; break; + case nsXPTType::T_I16 : *((int16_t*) d) = s->val.i16; break; + case nsXPTType::T_I32 : *((int32_t*) d) = s->val.i32; break; + case nsXPTType::T_I64 : *((int64_t*) d) = s->val.i64; d++; break; + case nsXPTType::T_U8 : *((uint8_t*) d) = s->val.u8; break; + case nsXPTType::T_U16 : *((uint16_t*)d) = s->val.u16; break; + case nsXPTType::T_U32 : *((uint32_t*)d) = s->val.u32; break; + case nsXPTType::T_U64 : *((uint64_t*)d) = s->val.u64; d++; break; + case nsXPTType::T_FLOAT : *((float*) d) = s->val.f; break; + case nsXPTType::T_DOUBLE : *((double*) d) = s->val.d; d++; break; + case nsXPTType::T_BOOL : *((bool*) d) = s->val.b; break; + case nsXPTType::T_CHAR : *((char*) d) = s->val.c; break; + case nsXPTType::T_WCHAR : *((wchar_t*) d) = s->val.wc; break; + default: + // all the others are plain pointer types + *((void**)d) = s->val.p; + break; + } + } +} + +extern "C" +struct my_params_struct { + nsISupports* that; + uint32_t Index; + uint32_t Count; + nsXPTCVariant* params; + uint32_t fn_count; + uint32_t fn_copy; +}; + +XPTC_PUBLIC_API(nsresult) +XPTC_InvokeByIndex(nsISupports* that, uint32_t methodIndex, + uint32_t paramCount, nsXPTCVariant* params) +{ + uint32_t result; + struct my_params_struct my_params; + my_params.that = that; + my_params.Index = methodIndex; + my_params.Count = paramCount; + my_params.params = params; + my_params.fn_copy = (uint32_t) &invoke_copy_to_stack; + my_params.fn_count = (uint32_t) &invoke_count_words; + +/* This is to call a given method of class that. + * The parameters are in params, the number is in paramCount. + * The routine will issue calls to count the number of words + * required for argument passing and to copy the arguments to + * the stack. + * Since APCS passes the first 3 params in r1-r3, we need to + * load the first three words from the stack and correct the stack + * pointer (sp) in the appropriate way. This means: + * + * 1.) more than 3 arguments: load r1-r3, correct sp and remember No. + * of bytes left on the stack in r4 + * + * 2.) <= 2 args: load r1-r3 (we won't be causing a stack overflow I hope), + * restore sp as if nothing had happened and set the marker r4 to zero. + * + * Afterwards sp will be restored using the value in r4 (which is not a temporary register + * and will be preserved by the function/method called according to APCS [ARM Procedure + * Calling Standard]). + * + * !!! IMPORTANT !!! + * This routine makes assumptions about the vtable layout of the c++ compiler. It's implemented + * for arm-linux GNU g++ >= 2.8.1 (including egcs and gcc-2.95.[1-3])! + * + */ + + __asm__ __volatile__( + "ldr r1, [%1, #12] \n\t" /* prepare to call invoke_count_words */ + "ldr ip, [%1, #16] \n\t" /* r0=paramCount, r1=params */ + "ldr r0, [%1, #8] \n\t" + "mov lr, pc \n\t" /* call it... */ + "mov pc, ip \n\t" + "mov r4, r0, lsl #2 \n\t" /* This is the amount of bytes needed. */ + "sub sp, sp, r4 \n\t" /* use stack space for the args... */ + "mov r0, sp \n\t" /* prepare a pointer an the stack */ + "ldr r1, [%1, #8] \n\t" /* =paramCount */ + "ldr r2, [%1, #12] \n\t" /* =params */ + "ldr ip, [%1, #20] \n\t" /* =invoke_copy_to_stack */ + "mov lr, pc \n\t" /* copy args to the stack like the */ + "mov pc, ip \n\t" /* compiler would. */ + "ldr r0, [%1] \n\t" /* =that */ + "ldr r1, [r0, #0] \n\t" /* get that->vtable offset */ + "ldr r2, [%1, #4] \n\t" + "add r2, r1, r2, lsl #3\n\t" /* a vtable_entry(x)=8 + (8 bytes * x) */ + "add r2, r2, #8 \n\t" /* with this compilers */ + "ldr r3, [r2] \n\t" /* get virtual offset from vtable */ + "mov r3, r3, lsl #16 \n\t" + "add r0, r0, r3, asr #16\n\t" + "ldr ip, [r2, #4] \n\t" /* get method address from vtable */ + "cmp r4, #12 \n\t" /* more than 3 arguments??? */ + "ldmgtia sp!, {r1, r2, r3}\n\t" /* yes: load arguments for r1-r3 */ + "subgt r4, r4, #12 \n\t" /* and correct the stack pointer */ + "ldmleia sp, {r1, r2, r3}\n\t" /* no: load r1-r3 from stack */ + "addle sp, sp, r4 \n\t" /* and restore stack pointer */ + "movle r4, #0 \n\t" /* a mark for restoring sp */ + "mov lr, pc \n\t" /* call mathod */ + "mov pc, ip \n\t" + "add sp, sp, r4 \n\t" /* restore stack pointer */ + "mov %0, r0 \n\t" /* the result... */ + : "=r" (result) + : "r" (&my_params) + : "r0", "r1", "r2", "r3", "r4", "ip", "lr" + ); + + return result; +} diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_arm_openbsd.cpp b/xpcom/reflect/xptcall/md/unix/xptcinvoke_arm_openbsd.cpp new file mode 100644 index 000000000..4ec7dd20f --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_arm_openbsd.cpp @@ -0,0 +1,183 @@ +/* -*- 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/. */ + +/* Platform specific code to invoke XPCOM methods on native objects */ + +#include "xptcprivate.h" + +// Remember that these 'words' are 32bit DWORDS + +static uint32_t +invoke_count_words(uint32_t paramCount, nsXPTCVariant* s) +{ + uint32_t result = 0; + for(uint32_t i = 0; i < paramCount; i++, s++) + { + if(s->IsPtrData()) + { + result++; + continue; + } + switch(s->type) + { + case nsXPTType::T_I8 : + case nsXPTType::T_I16 : + case nsXPTType::T_I32 : + result++; + break; + case nsXPTType::T_I64 : + result+=2; + break; + case nsXPTType::T_U8 : + case nsXPTType::T_U16 : + case nsXPTType::T_U32 : + result++; + break; + case nsXPTType::T_U64 : + result+=2; + break; + case nsXPTType::T_FLOAT : + result++; + break; + case nsXPTType::T_DOUBLE : + result+=2; + break; + case nsXPTType::T_BOOL : + case nsXPTType::T_CHAR : + case nsXPTType::T_WCHAR : + result++; + break; + default: + // all the others are plain pointer types + result++; + break; + } + } + return result; +} + +static void +invoke_copy_to_stack(uint32_t* d, uint32_t paramCount, nsXPTCVariant* s) +{ + for(uint32_t i = 0; i < paramCount; i++, d++, s++) + { + if(s->IsPtrData()) + { + *((void**)d) = s->ptr; + continue; + } + switch(s->type) + { + case nsXPTType::T_I8 : *((int8_t*) d) = s->val.i8; break; + case nsXPTType::T_I16 : *((int16_t*) d) = s->val.i16; break; + case nsXPTType::T_I32 : *((int32_t*) d) = s->val.i32; break; + case nsXPTType::T_I64 : *((int64_t*) d) = s->val.i64; d++; break; + case nsXPTType::T_U8 : *((uint8_t*) d) = s->val.u8; break; + case nsXPTType::T_U16 : *((uint16_t*)d) = s->val.u16; break; + case nsXPTType::T_U32 : *((uint32_t*)d) = s->val.u32; break; + case nsXPTType::T_U64 : *((uint64_t*)d) = s->val.u64; d++; break; + case nsXPTType::T_FLOAT : *((float*) d) = s->val.f; break; + case nsXPTType::T_DOUBLE : *((double*) d) = s->val.d; d++; break; + case nsXPTType::T_BOOL : *((bool*) d) = s->val.b; break; + case nsXPTType::T_CHAR : *((char*) d) = s->val.c; break; + case nsXPTType::T_WCHAR : *((wchar_t*) d) = s->val.wc; break; + default: + // all the others are plain pointer types + *((void**)d) = s->val.p; + break; + } + } +} + +extern "C" { + struct my_params_struct { + nsISupports* that; + uint32_t Index; + uint32_t Count; + nsXPTCVariant* params; + uint32_t fn_count; + uint32_t fn_copy; + }; +} + +EXPORT_XPCOM_API(nsresult) +NS_InvokeByIndex(nsISupports* that, uint32_t methodIndex, + uint32_t paramCount, nsXPTCVariant* params) +{ + uint32_t result; + struct my_params_struct my_params; + my_params.that = that; + my_params.Index = methodIndex; + my_params.Count = paramCount; + my_params.params = params; + my_params.fn_copy = (uint32_t) &invoke_copy_to_stack; + my_params.fn_count = (uint32_t) &invoke_count_words; + +/* This is to call a given method of class that. + * The parameters are in params, the number is in paramCount. + * The routine will issue calls to count the number of words + * required for argument passing and to copy the arguments to + * the stack. + * Since APCS passes the first 3 params in r1-r3, we need to + * load the first three words from the stack and correct the stack + * pointer (sp) in the appropriate way. This means: + * + * 1.) more than 3 arguments: load r1-r3, correct sp and remember No. + * of bytes left on the stack in r4 + * + * 2.) <= 2 args: load r1-r3 (we won't be causing a stack overflow I hope), + * restore sp as if nothing had happened and set the marker r4 to zero. + * + * Afterwards sp will be restored using the value in r4 (which is not a temporary register + * and will be preserved by the function/method called according to APCS [ARM Procedure + * Calling Standard]). + * + * !!! IMPORTANT !!! + * This routine makes assumptions about the vtable layout of the c++ compiler. It's implemented + * for arm-linux GNU g++ >= 2.8.1 (including egcs and gcc-2.95.[1-3])! + * + */ + +#ifdef __GNUC__ + __asm__ __volatile__( + "ldr r1, [%1, #12] \n\t" /* prepare to call invoke_count_words */ + "ldr ip, [%1, #16] \n\t" /* r0=paramCount, r1=params */ + "ldr r0, [%1, #8] \n\t" + "mov lr, pc \n\t" /* call it... */ + "mov pc, ip \n\t" + "mov r4, r0, lsl #2 \n\t" /* This is the amount of bytes needed. */ + "sub sp, sp, r4 \n\t" /* use stack space for the args... */ + "mov r0, sp \n\t" /* prepare a pointer an the stack */ + "ldr r1, [%1, #8] \n\t" /* =paramCount */ + "ldr r2, [%1, #12] \n\t" /* =params */ + "ldr ip, [%1, #20] \n\t" /* =invoke_copy_to_stack */ + "mov lr, pc \n\t" /* copy args to the stack like the */ + "mov pc, ip \n\t" /* compiler would. */ + "ldr r0, [%1] \n\t" /* =that */ + "ldr r1, [r0, #0] \n\t" /* get that->vtable offset */ + "ldr r2, [%1, #4] \n\t" + "mov r2, r2, lsl #2 \n\t" /* a vtable_entry(x)=8 + (4 bytes * x) */ + "ldr ip, [r1, r2] \n\t" /* get method adress from vtable */ + "cmp r4, #12 \n\t" /* more than 3 arguments??? */ + "ldmgtia sp!, {r1, r2, r3}\n\t" /* yes: load arguments for r1-r3 */ + "subgt r4, r4, #12 \n\t" /* and correct the stack pointer */ + "ldmleia sp, {r1, r2, r3}\n\t" /* no: load r1-r3 from stack */ + "addle sp, sp, r4 \n\t" /* and restore stack pointer */ + "movle r4, #0 \n\t" /* a mark for restoring sp */ + "ldr r0, [%1, #0] \n\t" /* get (self) */ + "mov lr, pc \n\t" /* call mathod */ + "mov pc, ip \n\t" + "add sp, sp, r4 \n\t" /* restore stack pointer */ + "mov %0, r0 \n\t" /* the result... */ + : "=r" (result) + : "r" (&my_params), "m" (my_params) + : "r0", "r1", "r2", "r3", "r4", "ip", "lr", "sp" + ); +#else +#error "Unsupported compiler. Use g++ >= 2.8 for OpenBSD/arm." +#endif /* G++ >= 2.8 */ + + return result; +} diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_aarch64.s b/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_aarch64.s new file mode 100644 index 000000000..efe691a68 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_aarch64.s @@ -0,0 +1,67 @@ +/* -*- 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/. */ + + .section ".text" + .globl _NS_InvokeByIndex + .type _NS_InvokeByIndex,@function + +/* + * _NS_InvokeByIndex(nsISupports* that, uint32_t methodIndex, + * uint32_t paramCount, nsXPTCVariant* params) + */ + +_NS_InvokeByIndex: + # set up frame + stp x29, x30, [sp,#-32]! + mov x29, sp + stp x19, x20, [sp,#16] + + # save methodIndex across function calls + mov w20, w1 + + # end of stack area passed to invoke_copy_to_stack + mov x1, sp + + # assume 8 bytes of stack for each argument with 16-byte alignment + add w19, w2, #1 + and w19, w19, #0xfffffffe + sub sp, sp, w19, uxth #3 + + # temporary place to store args passed in r0-r7,v0-v7 + sub sp, sp, #128 + + # save 'that' on stack + str x0, [sp] + + # start of stack area passed to invoke_copy_to_stack + mov x0, sp + bl invoke_copy_to_stack + + # load arguments passed in r0-r7 + ldp x6, x7, [sp, #48] + ldp x4, x5, [sp, #32] + ldp x2, x3, [sp, #16] + ldp x0, x1, [sp],#64 + + # load arguments passed in v0-v7 + ldp d6, d7, [sp, #48] + ldp d4, d5, [sp, #32] + ldp d2, d3, [sp, #16] + ldp d0, d1, [sp],#64 + + # call the method + ldr x16, [x0] + add x16, x16, w20, uxth #3 + ldr x16, [x16] + blr x16 + + add sp, sp, w19, uxth #3 + ldp x19, x20, [sp,#16] + ldp x29, x30, [sp],#32 + ret + + .size _NS_InvokeByIndex, . - _NS_InvokeByIndex + + diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_ipf32.s b/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_ipf32.s new file mode 100644 index 000000000..794c4f5c1 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_ipf32.s @@ -0,0 +1,145 @@ + +// Select C numeric constant + .radix C +// for 64 bit mode, use .psr abi64 + .psr abi32 +// big endian + .psr msb +// Section has executable code + .section .text, "ax","progbits" +// procedure named 'NS_InvokeByIndex' + .proc NS_InvokeByIndex +// manual bundling + .explicit + +// extern "C" uint32_t +// invoke_copy_to_stack(uint64_t* d, +// const uint32_t paramCount, nsXPTCVariant* s) + .global invoke_copy_to_stack +// .exclass invoke_copy_to_stack, @fullyvisible + .type invoke_copy_to_stack,@function + +// .exclass NS_InvokeByIndex, @fullyvisible + .type NS_InvokeByIndex,@function + +// NS_InvokeByIndex(nsISupports* that, uint32_t methodIndex, +// uint32_t paramCount, nsXPTCVariant* params); +NS_InvokeByIndex:: + .prologue + .save ar.pfs, r37 +// allocate 4 input args, 6 local args, and 8 output args + alloc r37 = ar.pfs, 4, 6, 8, 0 // M + nop.i 0 ;; // I + +// unwind table already knows gp, no need to specify anything + add r39 = 0, gp // A + .save rp, r36 + mov r36 = rp // I + .vframe r38 + add r38 = 0, sp ;; // A + +// We first calculate the amount of extra memory stack space required +// for the arguments, and register storage. +// We then call invoke_copy_to_stack() to write the argument contents +// to the specified memory regions. +// We then copy the integer arguments to integer registers, and floating +// arguments to float registers. +// Lastly we load the virtual table jump pointer, and call the target +// subroutine. + +// in0 : that +// in1 : methodIndex +// in2 : paramCount +// in3 : params + +// stack frame size is 16 + (8 * even(paramCount)) + 64 + 64 +// 16 byte frame header +// 8 * even(paramCount) memory argument area +// 64 bytes integer register area +// 64 bytes float register area +// This scheme makes sure stack fram size is a multiple of 16 + + .body + add r10 = 8, r0 // A +// r41 points to float register area + add r41 = -64, sp // A +// r40 points to int register area + add r40 = -128, sp ;; // A + + add out1 = 0, r40 // A + add out2 = 0, r41 // A + tbit.z p14,p15 = in2,0 ;; // I + +// compute 8 * even(paramCount) +(p14) shladd r11 = in2, 3, r0 ;; // A +(p15) shladd r11 = in2, 3, r10 ;; // A + sub out0 = r40, r11 ;; // A + +// advance the stack frame + add sp = -16, out0 // A + add out3 = 0, in2 // A + add out4 = 0, in3 // A + +// branch to invoke_copy_to_stack + br.call.sptk.few rp = invoke_copy_to_stack ;; // B + +// restore gp + add gp = 0, r39 // A + add out0 = 0, in0 // A + +// load the integer and float registers + ld8 out1 = [r40], 8 // M + ldfd f8 = [r41], 8 ;; // M + + ld8 out2 = [r40], 8 // M + ldfd f9 = [r41], 8 ;; // M + + ld8 out3 = [r40], 8 // M + ldfd f10 = [r41], 8 ;; // M + + ld8 out4 = [r40], 8 // M + ldfd f11 = [r41], 8 ;; // M + + ld8 out5 = [r40], 8 // M + ldfd f12 = [r41], 8 ;; // M +// 16 * methodIndex + shladd r11 = in1, 4, r0 // A + + ld8 out6 = [r40], 8 // M + ldfd f13 = [r41], 8 ;; // M + + ld8 out7 = [r40], 8 // M + ldfd f14 = [r41], 8 // M + addp4 r8 = 0, in0 ;; // A + +// look up virtual base table and dispatch to target subroutine +// This section assumes 32 bit pointer mode, and virtual base table +// layout from the ABI http://www.codesourcery.com/cxx-abi/abi.html + +// load base table + ld4 r8 = [r8] ;; // M + addp4 r8 = r11, r8 ;; // A + + // first entry is jump pointer, second entry is gp + addp4 r9 = 8, r8 ;; // A +// load jump pointer + ld8 r8 = [r8] + +// load gp + ld8 gp = [r9] ;; // M + mov b6 = r8 ;; // I + +// branch to target virtual function + br.call.sptk.few rp = b6 ;; // B + +// epilog + mov ar.pfs = r37 // I + mov rp = r36 ;; // I + + add sp = 0, r38 // A + add gp = 0, r39 // A + br.ret.sptk.few rp ;; // B + + .endp + + diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_ipf64.s b/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_ipf64.s new file mode 100644 index 000000000..140c44117 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_ipf64.s @@ -0,0 +1,146 @@ + +// Select C numeric constant + .radix C +// for 64 bit mode, use .psr abi64 + .psr abi64 +// little endian + .psr lsb +// Section has executable code + .section .text, "ax","progbits" +// procedure named 'NS_InvokeByIndex' + .proc NS_InvokeByIndex +// manual bundling + .explicit + +// extern "C" uint32_t +// invoke_copy_to_stack(uint64_t* d, +// const uint32_t paramCount, nsXPTCVariant* s) + .global invoke_copy_to_stack +// .exclass invoke_copy_to_stack, @fullyvisible + .type invoke_copy_to_stack,@function + +// .exclass NS_InvokeByIndex, @fullyvisible + .type NS_InvokeByIndex,@function + +// XPTC_InvokeByIndex(nsISupports* that, uint32_t methodIndex, +// uint32_t paramCount, nsXPTCVariant* params); +NS_InvokeByIndex:: + .prologue + .save ar.pfs, r37 +// allocate 4 input args, 6 local args, and 8 output args + alloc r37 = ar.pfs, 4, 6, 8, 0 // M + nop.i 0 ;; // I + +// unwind table already knows gp, no need to specify anything + add r39 = 0, gp // A + .save rp, r36 + mov r36 = rp // I + .vframe r38 + add r38 = 0, sp ;; // A + +// We first calculate the amount of extra memory stack space required +// for the arguments, and register storage. +// We then call invoke_copy_to_stack() to write the argument contents +// to the specified memory regions. +// We then copy the integer arguments to integer registers, and floating +// arguments to float registers. +// Lastly we load the virtual table jump pointer, and call the target +// subroutine. + +// in0 : that +// in1 : methodIndex +// in2 : paramCount +// in3 : params + +// stack frame size is 16 + (8 * even(paramCount)) + 64 + 64 +// 16 byte frame header +// 8 * even(paramCount) memory argument area +// 64 bytes integer register area +// 64 bytes float register area +// This scheme makes sure stack fram size is a multiple of 16 + + .body + add r10 = 8, r0 // A +// r41 points to float register area + add r41 = -64, sp // A +// r40 points to int register area + add r40 = -128, sp ;; // A + + add out1 = 0, r40 // A + add out2 = 0, r41 // A + tbit.z p14,p15 = in2,0 ;; // I + +// compute 8 * even(paramCount) +(p14) shladd r11 = in2, 3, r0 ;; // A +(p15) shladd r11 = in2, 3, r10 ;; // A + sub out0 = r40, r11 ;; // A + +// advance the stack frame + add sp = -16, out0 // A + add out3 = 0, in2 // A + add out4 = 0, in3 // A + +// branch to invoke_copy_to_stack + br.call.sptk.few rp = invoke_copy_to_stack ;; // B + +// restore gp + add gp = 0, r39 // A + add out0 = 0, in0 // A + +// load the integer and float registers + ld8 out1 = [r40], 8 // M + ldfd f8 = [r41], 8 ;; // M + + ld8 out2 = [r40], 8 // M + ldfd f9 = [r41], 8 ;; // M + + ld8 out3 = [r40], 8 // M + ldfd f10 = [r41], 8 ;; // M + + ld8 out4 = [r40], 8 // M + ldfd f11 = [r41], 8 ;; // M + + ld8 out5 = [r40], 8 // M + ldfd f12 = [r41], 8 ;; // M +// 16 * methodIndex + shladd r11 = in1, 4, r0 // A + + ld8 out6 = [r40], 8 // M + ldfd f13 = [r41], 8 ;; // M + + ld8 out7 = [r40], 8 // M + ldfd f14 = [r41], 8 // M + add r8 = 0, in0 ;; // A + +// look up virtual base table and dispatch to target subroutine +// This section assumes 64 bit pointer mode, and virtual base table +// layout from the ABI http://www.codesourcery.com/cxx-abi/abi.html + +// load base table + ld8 r8 = [r8] ;; // M + add r8 = r11, r8 ;; // A + + // first entry is jump pointer, second entry is gp + add r9 = 8, r8 ;; // A +// load jump pointer + ld8 r8 = [r8] + +// load gp + ld8 gp = [r9] ;; // M + mov b6 = r8 ;; // I + +// branch to target virtual function + br.call.sptk.few rp = b6 ;; // B + +// epilog + mov ar.pfs = r37 // I + mov rp = r36 ;; // I + + add sp = 0, r38 // A + add gp = 0, r39 // A + br.ret.sptk.few rp ;; // B + + .endp + +/* Magic indicating no need for an executable stack */ +.section .note.GNU-stack, "", @progbits ; .previous diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_mips.S b/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_mips.S new file mode 100644 index 000000000..32ff3b356 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_mips.S @@ -0,0 +1,134 @@ +/* -*- Mode: asm; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * Version: MPL 1.1 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 is for MIPS using the O32 ABI. */ + +#ifdef ANDROID +#include +#include +#include +#else +#include +#include +#endif + +# NARGSAVE is the argument space in the callers frame, including extra +# 'shadowed' space for the argument registers. The minimum of 4 +# argument slots is sometimes predefined in the header files. +#ifndef NARGSAVE +#define NARGSAVE 4 +#endif + +#define LOCALSZ 3 /* gp, fp, ra */ +#define FRAMESZ ((((NARGSAVE+LOCALSZ)*SZREG)+ALSZ)&ALMASK) + +#define RAOFF (FRAMESZ - (1*SZREG)) +#define FPOFF (FRAMESZ - (2*SZREG)) +#define GPOFF (FRAMESZ - (3*SZREG)) + +#define A0OFF (FRAMESZ + (0*SZREG)) +#define A1OFF (FRAMESZ + (1*SZREG)) +#define A2OFF (FRAMESZ + (2*SZREG)) +#define A3OFF (FRAMESZ + (3*SZREG)) + + .text + +# +# _NS_InvokeByIndex(that, methodIndex, paramCount, params) +# a0 a1 a2 a3 + + .globl _NS_InvokeByIndex + .align 2 + .type _NS_InvokeByIndex,@function + .ent _NS_InvokeByIndex,0 + .frame fp, FRAMESZ, ra +_NS_InvokeByIndex: + SETUP_GP + subu sp, FRAMESZ + + # specify the save register mask for gp, fp, ra, a3 - a0 + .mask 0xD00000F0, RAOFF-FRAMESZ + + sw ra, RAOFF(sp) + sw fp, FPOFF(sp) + + # we can't use .cprestore in a variable stack frame + sw gp, GPOFF(sp) + + sw a0, A0OFF(sp) + sw a1, A1OFF(sp) + sw a2, A2OFF(sp) + sw a3, A3OFF(sp) + + # save bottom of fixed frame + move fp, sp + + # extern "C" uint32 + # invoke_count_words(uint32_t paramCount, nsXPTCVariant* s); + la t9, invoke_count_words + move a0, a2 + move a1, a3 + jalr t9 + lw gp, GPOFF(fp) + + # allocate variable stack, with a size of: + # wordsize (of 4 bytes) * result (already aligned to dword) + # but a minimum of 16 byte + sll v0, 2 + slt t0, v0, 16 + beqz t0, 1f + li v0, 16 +1: subu sp, v0 + + # let a0 point to the bottom of the variable stack, allocate + # another fixed stack for: + # extern "C" void + # invoke_copy_to_stack(uint32_t* d, uint32_t paramCount, + # nsXPTCVariant* s); + la t9, invoke_copy_to_stack + move a0, sp + lw a1, A2OFF(fp) + lw a2, A3OFF(fp) + subu sp, 16 + jalr t9 + lw gp, GPOFF(fp) + + # back to the variable stack frame + addu sp, 16 + + # calculate the function we need to jump to, which must then be + # stored in t9 + lw a0, A0OFF(fp) # a0 = set "that" to be "this" + lw t0, A1OFF(fp) # a1 = methodIndex + lw t9, 0(a0) + # t0 = methodIndex << PTRLOG + sll t0, t0, PTRLOG + addu t9, t0 + lw t9, (t9) + + # Set a1-a3 to what invoke_copy_to_stack told us. a0 is already + # the "this" pointer. We don't have to care about floating + # point arguments, the non-FP "this" pointer as first argument + # means they'll never be used. + lw a1, 1*SZREG(sp) + lw a2, 2*SZREG(sp) + lw a3, 3*SZREG(sp) + + jalr t9 + # Micro-optimization: There's no gp usage below this point, so + # we don't reload. + # lw gp, GPOFF(fp) + + # leave variable stack frame + move sp, fp + + lw ra, RAOFF(sp) + lw fp, FPOFF(sp) + + addiu sp, FRAMESZ + j ra +END(_NS_InvokeByIndex) diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_mips64.S b/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_mips64.S new file mode 100644 index 000000000..eef34de7f --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_mips64.S @@ -0,0 +1,122 @@ +/* -*- 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 +#include + +.text +.globl invoke_count_words +.globl invoke_copy_to_stack + +LOCALSZ=7 # a0, a1, a2, a3, s0, ra, gp +FRAMESZ=(((NARGSAVE+LOCALSZ)*SZREG)+ALSZ)&ALMASK + +RAOFF=FRAMESZ-(1*SZREG) +A0OFF=FRAMESZ-(2*SZREG) +A1OFF=FRAMESZ-(3*SZREG) +A2OFF=FRAMESZ-(4*SZREG) +A3OFF=FRAMESZ-(5*SZREG) +S0OFF=FRAMESZ-(6*SZREG) +GPOFF=FRAMESZ-(7*SZREG) + +# +# _NS_InvokeByIndex(that, methodIndex, paramCount, params) +# a0 a1 a2 a3 + +NESTED(_NS_InvokeByIndex, FRAMESZ, ra) + PTR_SUBU sp, FRAMESZ + SETUP_GP64(GPOFF, _NS_InvokeByIndex) + + REG_S ra, RAOFF(sp) + REG_S a0, A0OFF(sp) + REG_S a1, A1OFF(sp) + REG_S a2, A2OFF(sp) + REG_S a3, A3OFF(sp) + REG_S s0, S0OFF(sp) + + # invoke_count_words(paramCount, params) + move a0, a2 + move a1, a3 + jal invoke_count_words + + # invoke_copy_to_stack(uint32_t* d, uint32_t paramCount, + # nsXPTCVariant* s, uint32_t *reg) + + REG_L a1, A2OFF(sp) # a1 - paramCount + REG_L a2, A3OFF(sp) # a2 - params + + # save sp before we copy the params to the stack + move t0, sp + + # assume full size of 16 bytes per param to be safe + sll v0, 4 # 16 bytes * num params + PTR_SUBU sp, sp, v0 # make room + move a0, sp # a0 - param stack address + + # create temporary stack space to write int and fp regs + PTR_SUBU sp, 64 # 64 = 8 regs of 8 bytes + move a3, sp + + # save the old sp and save the arg stack + PTR_SUBU sp, sp, 16 + REG_S t0, 0(sp) + REG_S a0, 8(sp) + + # copy the param into the stack areas + jal invoke_copy_to_stack + + REG_L t3, 8(sp) # get previous a0 + REG_L sp, 0(sp) # get orig sp back + + REG_L a0, A0OFF(sp) # a0 - that + REG_L a1, A1OFF(sp) # a1 - methodIndex + + # t1 = methodIndex * pow(2, PTRLOG) + # (use shift instead of mult) + sll t1, a1, PTRLOG + + # calculate the function we need to jump to, + # which must then be saved in t9 + PTR_L t9, 0(a0) + PTR_ADDU t9, t9, t1 + PTR_L t9, (t9) + + # get register save area from invoke_copy_to_stack + PTR_SUBU t1, t3, 64 + + # a1..a7 and f13..f19 should now be set to what + # invoke_copy_to_stack told us. skip a0 and f12 + # because that's the "this" pointer + + REG_L a1, 0(t1) + REG_L a2, 8(t1) + REG_L a3, 16(t1) + REG_L a4, 24(t1) + REG_L a5, 32(t1) + REG_L a6, 40(t1) + REG_L a7, 48(t1) + + l.d $f13, 0(t1) + l.d $f14, 8(t1) + l.d $f15, 16(t1) + l.d $f16, 24(t1) + l.d $f17, 32(t1) + l.d $f18, 40(t1) + l.d $f19, 48(t1) + + # save away our stack pointer and create + # the stack pointer for the function + move s0, sp + move sp, t3 + + jalr t9 + + move sp, s0 + + RESTORE_GP64 + REG_L ra, RAOFF(sp) + REG_L s0, S0OFF(sp) + PTR_ADDU sp, FRAMESZ + j ra +.end _NS_InvokeByIndex diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_pa32.s b/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_pa32.s new file mode 100644 index 000000000..6fa9a86ba --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_pa32.s @@ -0,0 +1,131 @@ + .LEVEL 1.1 +framesz .EQU 128 + +; XPTC_InvokeByIndex(nsISuppots* that, uint32_t methodIndex, +; uint32_t paramCount, nsXPTCVariant* params); + +; g++ need to compile everything with -fvtable-thunks ! + + .SPACE $TEXT$,SORT=8 + .SUBSPA $CODE$,QUAD=0,ALIGN=4,ACCESS=0x2c,CODE_ONLY,SORT=24 +XPTC_InvokeByIndex + .PROC + .CALLINFO CALLER,FRAME=72,ENTRY_GR=%r3,SAVE_RP,SAVE_SP,ARGS_SAVED,ALLOCA_FRAME + + ; frame marker takes 48 bytes, + ; register spill area takes 8 bytes, + ; local stack area takes 72 bytes result in 128 bytes total + + .ENTRY + STW %rp,-20(%sp) + STW,MA %r3,128(%sp) + + LDO -framesz(%r30),%r28 + STW %r28,-4(%r30) ; save previous sp + STW %r19,-32(%r30) + + STW %r26,-36-framesz(%r30) ; save argument registers in + STW %r25,-40-framesz(%r30) ; in PREVIOUS frame + STW %r24,-44-framesz(%r30) ; + STW %r23,-48-framesz(%r30) ; + + B,L .+8,%r2 + ADDIL L'invoke_count_bytes-$PIC_pcrel$1+4,%r2,%r1 + LDO R'invoke_count_bytes-$PIC_pcrel$2+8(%r1),%r1 +$PIC_pcrel$1 + LDSID (%r1),%r31 +$PIC_pcrel$2 + MTSP %r31,%sr0 + .CALL ARGW0=GR,ARGW1=GR,ARGW2=GR ;in=24,25,26;out=28 + BE,L 0(%sr0,%r1),%r31 + COPY %r31,%r2 + + CMPIB,>= 0,%r28, .+76 + COPY %r30,%r3 ; copy stack ptr to saved stack ptr + ADD %r30,%r28,%r30 ; extend stack frame + LDW -4(%r3),%r28 ; move frame + STW %r28,-4(%r30) + LDW -8(%r3),%r28 + STW %r28,-8(%r30) + LDW -12(%r3),%r28 + STW %r28,-12(%r30) + LDW -16(%r3),%r28 + STW %r28,-16(%r30) + LDW -20(%r3),%r28 + STW %r28,-20(%r30) + LDW -24(%r3),%r28 + STW %r28,-24(%r30) + LDW -28(%r3),%r28 + STW %r28,-28(%r30) + LDW -32(%r3),%r28 + STW %r28,-32(%r30) + + LDO -40(%r30),%r26 ; load copy address + LDW -44-framesz(%r3),%r25 ; load rest of 2 arguments + LDW -48-framesz(%r3),%r24 ; + + LDW -32(%r30),%r19 ; shared lib call destroys r19; reload + B,L .+8,%r2 + ADDIL L'invoke_copy_to_stack-$PIC_pcrel$3+4,%r2,%r1 + LDO R'invoke_copy_to_stack-$PIC_pcrel$4+8(%r1),%r1 +$PIC_pcrel$3 + LDSID (%r1),%r31 +$PIC_pcrel$4 + MTSP %r31,%sr0 + .CALL ARGW0=GR,ARGW1=GR,ARGW2=GR ;in=24,25,26 + BE,L 0(%sr0,%r1),%r31 + COPY %r31,%r2 + + LDO -48(%r30),%r20 + EXTRW,U,= %r28,31,1,%r22 + FLDD 0(%r20),%fr7 ; load double arg 1 + EXTRW,U,= %r28,30,1,%r22 + FLDW 8(%r20),%fr5L ; load float arg 1 + EXTRW,U,= %r28,29,1,%r22 + FLDW 4(%r20),%fr6L ; load float arg 2 + EXTRW,U,= %r28,28,1,%r22 + FLDW 0(%r20),%fr7L ; load float arg 3 + + LDW -36-framesz(%r3),%r26 ; load ptr to 'that' + LDW -40(%r30),%r25 ; load the rest of dispatch argument registers + LDW -44(%r30),%r24 + LDW -48(%r30),%r23 + + LDW -36-framesz(%r3),%r20 ; load vtable addr + LDW -40-framesz(%r3),%r28 ; load index + LDW 0(%r20),%r20 ; follow vtable + LDO 16(%r20),%r20 ; offset vtable by 16 bytes (g++: 8, aCC: 16) + SH2ADDL %r28,%r20,%r28 ; add 4*index to vtable entry + LDW 0(%r28),%r22 ; load vtable entry + + B,L .+8,%r2 + ADDIL L'$$dyncall_external-$PIC_pcrel$5+4,%r2,%r1 + LDO R'$$dyncall_external-$PIC_pcrel$6+8(%r1),%r1 +$PIC_pcrel$5 + LDSID (%r1),%r31 +$PIC_pcrel$6 + MTSP %r31,%sr0 + .CALL ARGW0=GR,ARGW1=GR,ARGW2=GR,ARGW3=GR,RTNVAL=GR +;in=22-26;out=28; + BE,L 0(%sr0,%r1),%r31 + COPY %r31,%r2 + + LDW -32(%r30),%r19 + COPY %r3,%r30 ; restore saved stack ptr + + LDW -148(%sp),%rp + BVE (%rp) + .EXIT + LDW,MB -128(%sp),%r3 + + .PROCEND ;in=23,24,25,26; + + .ALIGN 8 + .SPACE $TEXT$ + .SUBSPA $CODE$ + .IMPORT $$dyncall_external,MILLICODE + .IMPORT invoke_count_bytes,CODE + .IMPORT invoke_copy_to_stack,CODE + .EXPORT XPTC_InvokeByIndex,ENTRY,PRIV_LEV=3,ARGW0=GR,ARGW1=GR,ARGW2=GR,ARGW3=GR,RTNVAL=GR,LONG_RETURN + .END + diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_parisc_linux.s b/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_parisc_linux.s new file mode 100644 index 000000000..7a207addf --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_parisc_linux.s @@ -0,0 +1,108 @@ +/* -*- Mode: asm; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * Version: MPL 1.1 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + + .LEVEL 1.1 + .text + .align 4 + +framesz: + .equ 128 + +.globl NS_InvokeByIndex + .type NS_InvokeByIndex, @function + + +NS_InvokeByIndex: + .PROC + .CALLINFO FRAME=72, CALLER,SAVE_RP, SAVE_SP, ENTRY_GR=3 + .ENTRY + +; frame marker takes 48 bytes, +; register spill area takes 8 bytes, +; local stack area takes 72 bytes result in 128 bytes total + + STW %rp,-20(%sp) + STW,MA %r3,128(%sp) + + LDO -framesz(%r30),%r28 + STW %r28,-4(%r30) ; save previous sp + STW %r19,-32(%r30) + + STW %r26,-36-framesz(%r30) ; save argument registers in + STW %r25,-40-framesz(%r30) ; in PREVIOUS frame + STW %r24,-44-framesz(%r30) ; + STW %r23,-48-framesz(%r30) ; + + .CALL ARGW0=GR,ARGW1=GR,ARGW2=GR ;in=24,25,26;out=28 + BL invoke_count_bytes,%r31 + COPY %r31,%r2 + + CMPIB,>= 0,%r28, .+76 + COPY %r30,%r3 ; copy stack ptr to saved stack ptr + ADD %r30,%r28,%r30 ; extend stack frame + LDW -4(%r3),%r28 ; move frame + STW %r28,-4(%r30) + LDW -8(%r3),%r28 + STW %r28,-8(%r30) + LDW -12(%r3),%r28 + STW %r28,-12(%r30) + LDW -16(%r3),%r28 + STW %r28,-16(%r30) + LDW -20(%r3),%r28 + STW %r28,-20(%r30) + LDW -24(%r3),%r28 + STW %r28,-24(%r30) + LDW -28(%r3),%r28 + STW %r28,-28(%r30) + LDW -32(%r3),%r28 + STW %r28,-32(%r30) + + LDO -40(%r30),%r26 ; load copy address + LDW -44-framesz(%r3),%r25 ; load rest of 2 arguments + LDW -48-framesz(%r3),%r24 ; + + LDW -32(%r30),%r19 ; shared lib call destroys r19; reload + .CALL ARGW0=GR,ARGW1=GR,ARGW2=GR ;in=24,25,26 + BL invoke_copy_to_stack,%r31 + COPY %r31,%r2 + + LDO -48(%r30),%r20 + EXTRW,U,= %r28,31,1,%r22 + FLDD 0(%r20),%fr7 ; load double arg 1 + EXTRW,U,= %r28,30,1,%r22 + FLDW 8(%r20),%fr5L ; load float arg 1 + EXTRW,U,= %r28,29,1,%r22 + FLDW 4(%r20),%fr6L ; load float arg 2 + EXTRW,U,= %r28,28,1,%r22 + FLDW 0(%r20),%fr7L ; load float arg 3 + + LDW -36-framesz(%r3),%r26 ; load ptr to 'that' + LDW -40(%r30),%r25 ; load the rest of dispatch argument registers + LDW -44(%r30),%r24 + LDW -48(%r30),%r23 + + LDW -36-framesz(%r3),%r20 ; load vtable addr + LDW -40-framesz(%r3),%r28 ; load index + LDW 0(%r20),%r20 ; follow vtable + SH2ADDL %r28,%r20,%r28 ; add 4*index to vtable entry + LDW 0(%r28),%r22 ; load vtable entry + + .CALL ARGW0=GR,ARGW1=GR,ARGW2=GR,ARGW3=GR,RTNVAL=GR ;in=22-26;out=28; + BL $$dyncall,%r31 + COPY %r31,%r2 + + LDW -32(%r30),%r19 + COPY %r3,%r30 ; restore saved stack ptr + + LDW -148(%sp),%rp + LDWM -128(%sp),%r3 + BV,N (%rp) + NOP + .EXIT + .PROCEND ;in=23,24,25,26; + .SIZE NS_InvokeByIndex, .-NS_InvokeByIndex + diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_ppc64_linux.S b/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_ppc64_linux.S new file mode 100644 index 000000000..37211c885 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_ppc64_linux.S @@ -0,0 +1,167 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +.set r0,0; .set r1,1; .set r2,2; .set r3,3; .set r4,4 +.set r5,5; .set r6,6; .set r7,7; .set r8,8; .set r9,9 +.set r10,10; .set r11,11; .set r12,12; .set r13,13; .set r14,14 +.set r15,15; .set r16,16; .set r17,17; .set r18,18; .set r19,19 +.set r20,20; .set r21,21; .set r22,22; .set r23,23; .set r24,24 +.set r25,25; .set r26,26; .set r27,27; .set r28,28; .set r29,29 +.set r30,30; .set r31,31 +.set f0,0; .set f1,1; .set f2,2; .set f3,3; .set f4,4 +.set f5,5; .set f6,6; .set f7,7; .set f8,8; .set f9,9 +.set f10,10; .set f11,11; .set f12,12; .set f13,13; .set f14,14 +.set f15,15; .set f16,16; .set f17,17; .set f18,18; .set f19,19 +.set f20,20; .set f21,21; .set f22,22; .set f23,23; .set f24,24 +.set f25,25; .set f26,26; .set f27,27; .set f28,28; .set f29,29 +.set f30,30; .set f31,31 + +# The ABI defines a fixed stack frame area of 4 doublewords (ELFv2) +# or 6 doublewords (ELFv1); the last of these doublewords is used +# as TOC pointer save area. The fixed area is followed by a parameter +# save area of 8 doublewords (used for vararg routines), followed +# by space for parameters passed on the stack. +# +# We set STACK_TOC to the offset of the TOC pointer save area, and +# STACK_PARAMS to the offset of the first on-stack parameter. + +#if _CALL_ELF == 2 +#define STACK_TOC 24 +#define STACK_PARAMS 96 +#else +#define STACK_TOC 40 +#define STACK_PARAMS 112 +#endif + +# +# NS_InvokeByIndex(nsISupports* that, uint32_t methodIndex, +# uint32_t paramCount, nsXPTCVariant* params) +# + +#if _CALL_ELF == 2 + .section ".text" + .type NS_InvokeByIndex,@function + .globl NS_InvokeByIndex + .align 2 +NS_InvokeByIndex: +0: addis 2,12,(.TOC.-0b)@ha + addi 2,2,(.TOC.-0b)@l + .localentry NS_InvokeByIndex,.-NS_InvokeByIndex +#else + .section ".toc","aw" + .section ".text" + .align 2 + .globl NS_InvokeByIndex + .section ".opd","aw" + .align 3 +NS_InvokeByIndex: + .quad .NS_InvokeByIndex,.TOC.@tocbase + .previous + .type NS_InvokeByIndex,@function +.NS_InvokeByIndex: +#endif + mflr 0 + std 0,16(r1) + + std r29,-24(r1) + std r30,-16(r1) + std r31,-8(r1) + + mr r29,r3 # Save 'that' in r29 + mr r30,r4 # Save 'methodIndex' in r30 + mr r31,r1 # Save old frame + + # Allocate stack frame with space for params. Since at least the + # first 7 parameters (not including 'that') will be in registers, + # we don't actually need stack space for those. We must ensure + # that the stack remains 16-byte aligned. + # + # | (fixed area + | | 7 GP | 13 FP | 3 NV | + # | param. save) |(params)........| regs | regs | regs | + # (r1)......(+STACK_PARAMS)... (-23*8).(-16*8).(-3*8)..(r31) + + # +stack frame, -unused stack params, +regs storage, +1 for alignment + addi r7,r5,((STACK_PARAMS/8)-7+7+13+3+1) + rldicr r7,r7,3,59 # multiply by 8 and mask with ~15 + neg r7,r7 + stdux r1,r1,r7 + + + # Call invoke_copy_to_stack(uint64_t* gpregs, double* fpregs, + # uint32_t paramCount, nsXPTCVariant* s, + # uint64_t* d)) + + # r5, r6 are passed through intact (paramCount, params) + # r7 (d) has to be r1+STACK_PARAMS + # -- where parameters are passed on the stack. + # r3, r4 are above that, easier to address from r31 than from r1 + + subi r3,r31,(23*8) # r3 --> GPRS + subi r4,r31,(16*8) # r4 --> FPRS + addi r7,r1,STACK_PARAMS # r7 --> params + bl invoke_copy_to_stack + nop + + # Set up to invoke function + + ld r9,0(r29) # vtable (r29 is 'that') + mr r3,r29 # self is first arg, obviously + + sldi r30,r30,3 # Find function descriptor + add r9,r9,r30 + ld r12,0(r9) + + std r2,STACK_TOC(r1) # Save r2 (TOC pointer) + +#if _CALL_ELF == 2 + mtctr r12 +#else + ld r0,0(r12) # Actual address from fd. + mtctr 0 + ld r11,16(r12) # Environment pointer from fd. + ld r2,8(r12) # TOC pointer from fd. +#endif + + # Load FP and GP registers as required + ld r4, -(23*8)(r31) + ld r5, -(22*8)(r31) + ld r6, -(21*8)(r31) + ld r7, -(20*8)(r31) + ld r8, -(19*8)(r31) + ld r9, -(18*8)(r31) + ld r10, -(17*8)(r31) + + lfd f1, -(16*8)(r31) + lfd f2, -(15*8)(r31) + lfd f3, -(14*8)(r31) + lfd f4, -(13*8)(r31) + lfd f5, -(12*8)(r31) + lfd f6, -(11*8)(r31) + lfd f7, -(10*8)(r31) + lfd f8, -(9*8)(r31) + lfd f9, -(8*8)(r31) + lfd f10, -(7*8)(r31) + lfd f11, -(6*8)(r31) + lfd f12, -(5*8)(r31) + lfd f13, -(4*8)(r31) + + bctrl # Do it + + ld r2,STACK_TOC(r1) # Load our own TOC pointer + ld r1,0(r1) # Revert stack frame + ld 0,16(r1) # Reload lr + ld 29,-24(r1) # Restore NVGPRS + ld 30,-16(r1) + ld 31,-8(r1) + mtlr 0 + blr + +#if _CALL_ELF == 2 + .size NS_InvokeByIndex,.-NS_InvokeByIndex +#else + .size NS_InvokeByIndex,.-.NS_InvokeByIndex +#endif + + # Magic indicating no need for an executable stack + .section .note.GNU-stack, "", @progbits ; .previous diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_ppc_aix.s b/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_ppc_aix.s new file mode 100644 index 000000000..eb6b6661d --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_ppc_aix.s @@ -0,0 +1,129 @@ +# +# -*- 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/. + +.set r0,0; .set sp,1; .set RTOC,2; .set r3,3; .set r4,4 +.set r5,5; .set r6,6; .set r7,7; .set r8,8; .set r9,9 +.set r10,10; .set r11,11; .set r12,12; .set r13,13; .set r14,14 +.set r15,15; .set r16,16; .set r17,17; .set r18,18; .set r19,19 +.set r20,20; .set r21,21; .set r22,22; .set r23,23; .set r24,24 +.set r25,25; .set r26,26; .set r27,27; .set r28,28; .set r29,29 +.set r30,30; .set r31,31 +.set f0,0; .set f1,1; .set f2,2; .set f3,3; .set f4,4 +.set f5,5; .set f6,6; .set f7,7; .set f8,8; .set f9,9 +.set f10,10; .set f11,11; .set f12,12; .set f13,13; .set f14,14 +.set f15,15; .set f16,16; .set f17,17; .set f18,18; .set f19,19 +.set f20,20; .set f21,21; .set f22,22; .set f23,23; .set f24,24 +.set f25,25; .set f26,26; .set f27,27; .set f28,28; .set f29,29 +.set f30,30; .set f31,31 +.set BO_IF,12 +.set CR0_EQ,2 + + + + .rename H.10.NO_SYMBOL{PR},"" + .rename H.18.NS_InvokeByIndex{TC},"NS_InvokeByIndex" + + +# .text section + + .csect H.10.NO_SYMBOL{PR} + .globl .NS_InvokeByIndex + .globl NS_InvokeByIndex{DS} + .extern .invoke_copy_to_stack + .extern ._ptrgl{PR} + + +# +# NS_InvokeByIndex(nsISupports* that, uint32_t methodIndex, +# uint32_t paramCount, nsXPTCVariant* params) +# + +.NS_InvokeByIndex: + mflr r0 + stw r31,-4(sp) +# +# save off the incoming values in the caller's parameter area +# + stw r3,24(sp) # that + stw r4,28(sp) # methodIndex + stw r5,32(sp) # paramCount + stw r6,36(sp) # params + stw r0,8(sp) + stwu sp,-136(sp) # = 24 for linkage area, 8 * 13 for fprData area, 8 for saved registers + +# prepare args for 'invoke_copy_to_stack' call +# + lwz r4,168(sp) # paramCount + lwz r5,172(sp) # params + mr r6,sp # fprData + slwi r3,r4,3 # number of bytes of stack required + # at most 8*paramCount + addi r3,r3,28 # linkage area + mr r31,sp # save original stack top + subfc sp,r3,sp # bump the stack + addi r3,sp,28 # parameter pointer excludes linkage area size + 'this' + + bl .invoke_copy_to_stack + nop + + lfd f1,0(r31) # Restore floating point registers + lfd f2,8(r31) + lfd f3,16(r31) + lfd f4,24(r31) + lfd f5,32(r31) + lfd f6,40(r31) + lfd f7,48(r31) + lfd f8,56(r31) + lfd f9,64(r31) + lfd f10,72(r31) + lfd f11,80(r31) + lfd f12,88(r31) + lfd f13,96(r31) + + lwz r3,160(r31) # that + lwz r4,0(r3) # get vTable from 'that' + lwz r5,164(r31) # methodIndex + slwi r5,r5,3 # methodIndex * 8 + addi r5,r5,8 # step over junk at start of vTable ! + lwzx r11,r5,r4 # get function pointer + + addi r5,r5,4 # We need to manually adjust the 'that' pointer, this is CFRONT based + lwzx r5,r4,r5 # offset = r4(vtable) + r5(methodIndex offset) - 4 + add r3,r5,r3 # adjust 'that' r3 = r3 + r5 + + lwz r4,28(sp) + lwz r5,32(sp) + lwz r6,36(sp) + lwz r7,40(sp) + lwz r8,44(sp) + lwz r9,48(sp) + lwz r10,52(sp) + + bl ._ptrgl{PR} + nop + + mr sp,r31 + lwz r0,144(sp) + addi sp,sp,136 + mtlr r0 + lwz r31,-4(sp) + blr + + +# .data section + + .toc # 0x00000038 +T.18.NS_InvokeByIndex: + .tc H.18.NS_InvokeByIndex{TC},NS_InvokeByIndex{DS} + + .csect NS_InvokeByIndex{DS} + .long .NS_InvokeByIndex # "\0\0\0\0" + .long TOC{TC0} # "\0\0\0008" + .long 0x00000000 # "\0\0\0\0" +# End csect NS_InvokeByIndex{DS} + +# .bss section diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_ppc_aix64.s b/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_ppc_aix64.s new file mode 100644 index 000000000..722583de5 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_ppc_aix64.s @@ -0,0 +1,128 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +.set r0,0; .set sp,1; .set RTOC,2; .set r3,3; .set r4,4 +.set r5,5; .set r6,6; .set r7,7; .set r8,8; .set r9,9 +.set r10,10; .set r11,11; .set r12,12; .set r13,13; .set r14,14 +.set r15,15; .set r16,16; .set r17,17; .set r18,18; .set r19,19 +.set r20,20; .set r21,21; .set r22,22; .set r23,23; .set r24,24 +.set r25,25; .set r26,26; .set r27,27; .set r28,28; .set r29,29 +.set r30,30; .set r31,31 +.set f0,0; .set f1,1; .set f2,2; .set f3,3; .set f4,4 +.set f5,5; .set f6,6; .set f7,7; .set f8,8; .set f9,9 +.set f10,10; .set f11,11; .set f12,12; .set f13,13; .set f14,14 +.set f15,15; .set f16,16; .set f17,17; .set f18,18; .set f19,19 +.set f20,20; .set f21,21; .set f22,22; .set f23,23; .set f24,24 +.set f25,25; .set f26,26; .set f27,27; .set f28,28; .set f29,29 +.set f30,30; .set f31,31 +.set BO_IF,12 +.set CR0_EQ,2 + + .rename H.10.NO_SYMBOL{PR},"" + .rename H.18.NS_InvokeByIndex{TC},"NS_InvokeByIndex" + + +# .text section + + .csect H.10.NO_SYMBOL{PR} + .globl .NS_InvokeByIndex + .globl NS_InvokeByIndex{DS} + .extern .invoke_copy_to_stack + .extern ._ptrgl{PR} + +# +# NS_InvokeByIndex(nsISupports* that, uint32_t methodIndex, +# uint32_t paramCount, nsXPTCVariant* params) +# + +.NS_InvokeByIndex: + mflr r0 + std r31,-8(sp) +# +# save off the incoming values in the caller's parameter area +# + std r3,48(sp) # that + std r4,56(sp) # methodIndex + std r5,64(sp) # paramCount + std r6,72(sp) # params + std r0,16(sp) + stdu sp,-168(sp) # 2*24=48 for linkage area, + # 8*13=104 for fprData area + # 16 for saved registers + +# prepare args for 'invoke_copy_to_stack' call +# + ld r4,232(sp) # paramCount (168+8+56) + ld r5,240(sp) # params + mr r6,sp # fprData + sldi r3,r4,3 # number of bytes of stack required + # is at most numParams*8 + addi r3,r3,56 # linkage area (48) + this (8) + mr r31,sp # save original stack top + subfc sp,r3,sp # bump the stack + addi r3,sp,56 # parameter pointer excludes linkage area + # size + 'this' + + bl .invoke_copy_to_stack + nop + + lfd f1,0(r31) # Restore floating point registers + lfd f2,8(r31) + lfd f3,16(r31) + lfd f4,24(r31) + lfd f5,32(r31) + lfd f6,40(r31) + lfd f7,48(r31) + lfd f8,56(r31) + lfd f9,64(r31) + lfd f10,72(r31) + lfd f11,80(r31) + lfd f12,88(r31) + lfd f13,96(r31) + + ld r3,216(r31) # that (168+48) + ld r4,0(r3) # get vTable from 'that' + ld r5,224(r31) # methodIndex (168+56) + sldi r5,r5,3 # methodIndex * 8 + # No junk at the start of 64bit vtable !!! + ldx r11,r5,r4 # get function pointer (this jumps + # either to the function if no adjustment + # is needed (displacement = 0), or it + # jumps to the thunk code, which will jump + # to the function at the end) + + # No adjustment of the that pointer in 64bit mode, this is done + # by the thunk code + + ld r4,56(sp) + ld r5,64(sp) + ld r6,72(sp) + ld r7,80(sp) + ld r8,88(sp) + ld r9,96(sp) + ld r10,104(sp) + + bl ._ptrgl{PR} + nop + + mr sp,r31 + ld r0,184(sp) # 168+16 + addi sp,sp,168 + mtlr r0 + ld r31,-8(sp) + blr + +# .data section + + .toc # 0x00000038 +T.18.NS_InvokeByIndex: + .tc H.18.NS_InvokeByIndex{TC},NS_InvokeByIndex{DS} + + .csect NS_InvokeByIndex{DS} + .llong .NS_InvokeByIndex # "\0\0\0\0" + .llong TOC{TC0} # "\0\0\0008" + .llong 0x00000000 # "\0\0\0\0" +# End csect NS_InvokeByIndex{DS} + +# .bss section diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_ppc_ibmobj_aix.s b/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_ppc_ibmobj_aix.s new file mode 100644 index 000000000..414a6536f --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_ppc_ibmobj_aix.s @@ -0,0 +1,124 @@ +# +# -*- 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/. + +.set r0,0; .set sp,1; .set RTOC,2; .set r3,3; .set r4,4 +.set r5,5; .set r6,6; .set r7,7; .set r8,8; .set r9,9 +.set r10,10; .set r11,11; .set r12,12; .set r13,13; .set r14,14 +.set r15,15; .set r16,16; .set r17,17; .set r18,18; .set r19,19 +.set r20,20; .set r21,21; .set r22,22; .set r23,23; .set r24,24 +.set r25,25; .set r26,26; .set r27,27; .set r28,28; .set r29,29 +.set r30,30; .set r31,31 +.set f0,0; .set f1,1; .set f2,2; .set f3,3; .set f4,4 +.set f5,5; .set f6,6; .set f7,7; .set f8,8; .set f9,9 +.set f10,10; .set f11,11; .set f12,12; .set f13,13; .set f14,14 +.set f15,15; .set f16,16; .set f17,17; .set f18,18; .set f19,19 +.set f20,20; .set f21,21; .set f22,22; .set f23,23; .set f24,24 +.set f25,25; .set f26,26; .set f27,27; .set f28,28; .set f29,29 +.set f30,30; .set f31,31 +.set BO_IF,12 +.set CR0_EQ,2 + + + + .rename H.10.NO_SYMBOL{PR},"" + .rename H.18.NS_InvokeByIndex{TC},"NS_InvokeByIndex" + + +# .text section + + .csect H.10.NO_SYMBOL{PR} + .globl .NS_InvokeByIndex + .globl NS_InvokeByIndex{DS} + .extern .invoke_copy_to_stack + .extern ._ptrgl{PR} + + +# +# NS_InvokeByIndex(nsISupports* that, uint32_t methodIndex, +# uint32_t paramCount, nsXPTCVariant* params) +# + +.NS_InvokeByIndex: + mflr r0 + stw r31,-4(sp) +# +# save off the incoming values in the caller's parameter area +# + stw r3,24(sp) # that + stw r4,28(sp) # methodIndex + stw r5,32(sp) # paramCount + stw r6,36(sp) # params + stw r0,8(sp) + stwu sp,-136(sp) # = 24 for linkage area, 8 * 13 for fprData area, 8 for saved registers + +# prepare args for 'invoke_copy_to_stack' call +# + lwz r4,168(sp) # paramCount + lwz r5,172(sp) # params + mr r6,sp # fprData + slwi r3,r4,3 # number of bytes of stack required + # at most 8*paramCount + addi r3,r3,28 # linkage area + mr r31,sp # save original stack top + subfc sp,r3,sp # bump the stack + addi r3,sp,28 # parameter pointer excludes linkage area size + 'this' + + bl .invoke_copy_to_stack + nop + + lfd f1,0(r31) # Restore floating point registers + lfd f2,8(r31) + lfd f3,16(r31) + lfd f4,24(r31) + lfd f5,32(r31) + lfd f6,40(r31) + lfd f7,48(r31) + lfd f8,56(r31) + lfd f9,64(r31) + lfd f10,72(r31) + lfd f11,80(r31) + lfd f12,88(r31) + lfd f13,96(r31) + + lwz r3,160(r31) # that + lwz r4,0(r3) # get vTable from 'that' + lwz r5,164(r31) # methodIndex + slwi r5,r5,2 # methodIndex * 4 + lwzx r11,r5,r4 # get function pointer + + lwz r4,28(sp) + lwz r5,32(sp) + lwz r6,36(sp) + lwz r7,40(sp) + lwz r8,44(sp) + lwz r9,48(sp) + lwz r10,52(sp) + + bl ._ptrgl{PR} + nop + + mr sp,r31 + lwz r0,144(sp) + addi sp,sp,136 + mtlr r0 + lwz r31,-4(sp) + blr + + +# .data section + + .toc # 0x00000038 +T.18.NS_InvokeByIndex: + .tc H.18.NS_InvokeByIndex{TC},NS_InvokeByIndex{DS} + + .csect NS_InvokeByIndex{DS} + .long .NS_InvokeByIndex # "\0\0\0\0" + .long TOC{TC0} # "\0\0\0008" + .long 0x00000000 # "\0\0\0\0" +# End csect NS_InvokeByIndex{DS} + +# .bss section diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_ppc_linux.S b/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_ppc_linux.S new file mode 100644 index 000000000..e87382286 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_ppc_linux.S @@ -0,0 +1,98 @@ +// -*- Mode: Asm -*- +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +.set r0,0; .set sp,1; .set RTOC,2; .set r3,3; .set r4,4 +.set r5,5; .set r6,6; .set r7,7; .set r8,8; .set r9,9 +.set r10,10; .set r11,11; .set r12,12; .set r13,13; .set r14,14 +.set r15,15; .set r16,16; .set r17,17; .set r18,18; .set r19,19 +.set r20,20; .set r21,21; .set r22,22; .set r23,23; .set r24,24 +.set r25,25; .set r26,26; .set r27,27; .set r28,28; .set r29,29 +.set r30,30; .set r31,31 +.set f0,0; .set f1,1; .set f2,2; .set f3,3; .set f4,4 +.set f5,5; .set f6,6; .set f7,7; .set f8,8; .set f9,9 +.set f10,10; .set f11,11; .set f12,12; .set f13,13; .set f14,14 +.set f15,15; .set f16,16; .set f17,17; .set f18,18; .set f19,19 +.set f20,20; .set f21,21; .set f22,22; .set f23,23; .set f24,24 +.set f25,25; .set f26,26; .set f27,27; .set f28,28; .set f29,29 +.set f30,30; .set f31,31 + + .section ".text" + .align 2 + .globl NS_InvokeByIndex + .type NS_InvokeByIndex,@function + +// +// NS_InvokeByIndex(nsISupports* that, uint32_t methodIndex, +// uint32_t paramCount, nsXPTCVariant* params) +// + +NS_InvokeByIndex: + stwu sp,-32(sp) // setup standard stack frame + mflr r0 // save LR + stw r3,8(sp) // r3 <= that + stw r4,12(sp) // r4 <= methodIndex + stw r30,16(sp) + stw r31,20(sp) + + stw r0,36(sp) // store LR backchain + mr r31,sp + + rlwinm r10,r5,3,0,27 // r10 = (ParamCount * 2 * 4) & ~0x0f + addi r0,r10,96 // reserve stack for GPR and FPR register save area r0 = r10 + 96 + lwz r9,0(sp) // r9 = backchain + neg r0,r0 + stwux r9,sp,r0 // reserve stack space and save SP backchain + + addi r3,sp,8 // r3 <= args + mr r4,r5 // r4 <= paramCount + mr r5,r6 // r5 <= params + add r6,r3,r10 // r6 <= gpregs ( == args + r10 ) + mr r30,r6 // store in r30 for use later... +#ifndef __NO_FPRS__ + addi r7,r6,32 // r7 <= fpregs ( == gpregs + 32 ) +#else + li r7, 0 +#endif + + bl invoke_copy_to_stack@local // (args, paramCount, params, gpregs, fpregs) +#ifndef __NO_FPRS__ + lfd f1,32(r30) // load FP registers with method parameters + lfd f2,40(r30) + lfd f3,48(r30) + lfd f4,56(r30) + lfd f5,64(r30) + lfd f6,72(r30) + lfd f7,80(r30) + lfd f8,88(r30) +#endif + lwz r3,8(r31) // r3 <= that + lwz r4,12(r31) // r4 <= methodIndex + lwz r5,0(r3) // r5 <= vtable ( == *that ) + slwi r4,r4,2 // convert to offset ( *= 4 ) + lwzx r0,r5,r4 // r0 <= methodpointer ( == vtable + offset ) + + lwz r4,4(r30) // load GP regs with method parameters + lwz r5,8(r30) + lwz r6,12(r30) + lwz r7,16(r30) + lwz r8,20(r30) + lwz r9,24(r30) + lwz r10,28(r30) + + mtlr r0 // copy methodpointer to LR + blrl // call method + + lwz r30,16(r31) // restore r30 & r31 + lwz r31,20(r31) + + lwz r11,0(sp) // clean up the stack + lwz r0,4(r11) + mtlr r0 + mr sp,r11 + blr + +/* Magic indicating no need for an executable stack */ +.section .note.GNU-stack, "", @progbits ; .previous diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_ppc_netbsd.s b/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_ppc_netbsd.s new file mode 100644 index 000000000..33650f399 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_ppc_netbsd.s @@ -0,0 +1,95 @@ +# -*- Mode: Asm -*- +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +.set r0,0; .set sp,1; .set RTOC,2; .set r3,3; .set r4,4 +.set r5,5; .set r6,6; .set r7,7; .set r8,8; .set r9,9 +.set r10,10; .set r11,11; .set r12,12; .set r13,13; .set r14,14 +.set r15,15; .set r16,16; .set r17,17; .set r18,18; .set r19,19 +.set r20,20; .set r21,21; .set r22,22; .set r23,23; .set r24,24 +.set r25,25; .set r26,26; .set r27,27; .set r28,28; .set r29,29 +.set r30,30; .set r31,31 +.set f0,0; .set f1,1; .set f2,2; .set f3,3; .set f4,4 +.set f5,5; .set f6,6; .set f7,7; .set f8,8; .set f9,9 +.set f10,10; .set f11,11; .set f12,12; .set f13,13; .set f14,14 +.set f15,15; .set f16,16; .set f17,17; .set f18,18; .set f19,19 +.set f20,20; .set f21,21; .set f22,22; .set f23,23; .set f24,24 +.set f25,25; .set f26,26; .set f27,27; .set f28,28; .set f29,29 +.set f30,30; .set f31,31 + + .section ".text" + .align 2 + .globl XPTC_InvokeByIndex + .type XPTC_InvokeByIndex,@function + +# +# XPTC_InvokeByIndex(nsISupports* that, uint32_t methodIndex, +# uint32_t paramCount, nsXPTCVariant* params) +# + +XPTC_InvokeByIndex: + stwu sp,-32(sp) # setup standard stack frame + mflr r0 # save LR + stw r3,8(sp) # r3 <= that + stw r4,12(sp) # r4 <= methodIndex + stw r30,16(sp) + stw r31,20(sp) + + stw r0,36(sp) # store LR backchain + mr r31,sp + + rlwinm r10,r5,3,0,27 # r10 = (ParamCount * 2 * 4) & ~0x0f + addi r0,r10,96 # reserve stack for GPR and FPR register save area r0 = r10 + 96 + lwz r9,0(sp) # r9 = backchain + neg r0,r0 + stwux r9,sp,r0 # reserve stack sapce and save SP backchain + + addi r3,sp,8 # r3 <= args + mr r4,r5 # r4 <= paramCount + mr r5,r6 # r5 <= params + add r6,r3,r10 # r6 <= gpregs ( == args + r10 ) + mr r30,r6 # store in r30 for use later... + addi r7,r6,32 # r7 <= fpregs ( == gpregs + 32 ) + + bl invoke_copy_to_stack@local # (args, paramCount, params, gpregs, fpregs) + + lfd f1,32(r30) # load FP registers with method parameters + lfd f2,40(r30) + lfd f3,48(r30) + lfd f4,56(r30) + lfd f5,64(r30) + lfd f6,72(r30) + lfd f7,80(r30) + lfd f8,88(r30) + + lwz r3,8(r31) # r3 <= that + lwz r4,12(r31) # r4 <= methodIndex + lwz r5,0(r3) # r5 <= vtable ( == *that ) + slwi r4,r4,3 # convert to offset ( *= 8 ) + addi r4,r4,8 # skip first two vtable entries + add r4,r4,r5 + lhz r0,0(r4) # virtual base offset + extsh r0,r0 + add r3,r3,r0 + lwz r0,4(r4) # r0 <= methodpointer ( == vtable + offset ) + + lwz r4,4(r30) # load GP regs with method parameters + lwz r5,8(r30) + lwz r6,12(r30) + lwz r7,16(r30) + lwz r8,20(r30) + lwz r9,24(r30) + lwz r10,28(r30) + + mtlr r0 # copy methodpointer to LR + blrl # call method + + lwz r30,16(r31) # restore r30 & r31 + lwz r31,20(r31) + + lwz r11,0(sp) # clean up the stack + lwz r0,4(r11) + mtlr r0 + mr sp,r11 + blr diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_ppc_openbsd.S b/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_ppc_openbsd.S new file mode 100644 index 000000000..f02c0b220 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_ppc_openbsd.S @@ -0,0 +1,94 @@ +// -*- Mode: Asm -*- +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +.set r0,0; .set sp,1; .set RTOC,2; .set r3,3; .set r4,4 +.set r5,5; .set r6,6; .set r7,7; .set r8,8; .set r9,9 +.set r10,10; .set r11,11; .set r12,12; .set r13,13; .set r14,14 +.set r15,15; .set r16,16; .set r17,17; .set r18,18; .set r19,19 +.set r20,20; .set r21,21; .set r22,22; .set r23,23; .set r24,24 +.set r25,25; .set r26,26; .set r27,27; .set r28,28; .set r29,29 +.set r30,30; .set r31,31 +.set f0,0; .set f1,1; .set f2,2; .set f3,3; .set f4,4 +.set f5,5; .set f6,6; .set f7,7; .set f8,8; .set f9,9 +.set f10,10; .set f11,11; .set f12,12; .set f13,13; .set f14,14 +.set f15,15; .set f16,16; .set f17,17; .set f18,18; .set f19,19 +.set f20,20; .set f21,21; .set f22,22; .set f23,23; .set f24,24 +.set f25,25; .set f26,26; .set f27,27; .set f28,28; .set f29,29 +.set f30,30; .set f31,31 + + .section ".text" + .align 2 + .globl NS_InvokeByIndex + .type NS_InvokeByIndex,@function + +// +// NS_InvokeByIndex(nsISupports* that, uint32_t methodIndex, +// uint32_t paramCount, nsXPTCVariant* params) +// + +NS_InvokeByIndex: + stwu sp,-32(sp) // setup standard stack frame + mflr r0 // save LR + stw r3,8(sp) // r3 <= that + stw r4,12(sp) // r4 <= methodIndex + stw r30,16(sp) + stw r31,20(sp) + + stw r0,36(sp) // store LR backchain + mr r31,sp + + rlwinm r10,r5,3,0,27 // r10 = (ParamCount * 2 * 4) & ~0x0f + addi r0,r10,96 // reserve stack for GPR and FPR register save area r0 = r10 + 96 + lwz r9,0(sp) // r9 = backchain + neg r0,r0 + stwux r9,sp,r0 // reserve stack space and save SP backchain + + addi r3,sp,8 // r3 <= args + mr r4,r5 // r4 <= paramCount + mr r5,r6 // r5 <= params + add r6,r3,r10 // r6 <= gpregs ( == args + r10 ) + mr r30,r6 // store in r30 for use later... + addi r7,r6,32 // r7 <= fpregs ( == gpregs + 32 ) + + bl invoke_copy_to_stack@local // (args, paramCount, params, gpregs, fpregs) + + lfd f1,32(r30) // load FP registers with method parameters + lfd f2,40(r30) + lfd f3,48(r30) + lfd f4,56(r30) + lfd f5,64(r30) + lfd f6,72(r30) + lfd f7,80(r30) + lfd f8,88(r30) + + lwz r3,8(r31) // r3 <= that + lwz r4,12(r31) // r4 <= methodIndex + lwz r5,0(r3) // r5 <= vtable ( == *that ) + slwi r4,r4,2 // convert to offset ( *= 4 ) + lwzx r0,r5,r4 // r0 <= methodpointer ( == vtable + offset ) + + lwz r4,4(r30) // load GP regs with method parameters + lwz r5,8(r30) + lwz r6,12(r30) + lwz r7,16(r30) + lwz r8,20(r30) + lwz r9,24(r30) + lwz r10,28(r30) + + mtlr r0 // copy methodpointer to LR + blrl // call method + + lwz r30,16(r31) // restore r30 & r31 + lwz r31,20(r31) + + lwz r11,0(sp) // clean up the stack + lwz r0,4(r11) + mtlr r0 + mr sp,r11 + blr + +// Magic indicating no need for an executable stack +.section .note.GNU-stack, "", @progbits ; .previous diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_ppc_rhapsody.s b/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_ppc_rhapsody.s new file mode 100644 index 000000000..1dc12b2b3 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_ppc_rhapsody.s @@ -0,0 +1,142 @@ +# +# -*- Mode: Asm -*- +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +# +# ** Assumed vtable layout (obtained by disassembling with gdb): +# ** 4 bytes per vtable entry, skip 0th and 1st entries, so the mapping +# ** from index to entry is (4 * index) + 8. +# + +.text + .align 2 +# +# NS_InvokeByIndex(nsISupports* that, uint32_t methodIndex, +# uint32_t paramCount, nsXPTCVariant* params) +# + +.globl __NS_InvokeByIndex +__NS_InvokeByIndex: + mflr r0 + stw r31,-4(r1) +# +# save off the incoming values in the callers parameter area +# + stw r3,24(r1) ; that + stw r4,28(r1) ; methodIndex + stw r5,32(r1) ; paramCount + stw r6,36(r1) ; params + stw r0,8(r1) + stwu r1,-144(r1) ; 24 for linkage area, + ; 8*13 for fprData area, + ; 8 for saved registers, + ; 8 to keep stack 16-byte aligned + +# set up for and call 'invoke_count_words' to get new stack size +# + mr r3,r5 + mr r4,r6 + + stwu r1,-24(r1) + bl L_invoke_count_words$stub + lwz r1,0(r1) + +# prepare args for 'invoke_copy_to_stack' call +# + lwz r4,176(r1) ; paramCount + lwz r5,180(r1) ; params + mr r6,r1 ; fprData + slwi r3,r3,2 ; number of stack bytes required + addi r3,r3,28 ; linkage area + mr r31,r1 ; save original stack top + sub r1,r1,r3 ; bump the stack + clrrwi r1,r1,4 ; keep the stack 16-byte aligned + addi r3,r31,144 ; act like real alloca, so 0(sp) always + stw r3,0(r1) ; points back to previous stack frame + addi r3,r1,28 ; parameter pointer excludes linkage area size + 'this' + +# create "temporary" stack frame for _invoke_copy_to_stack to operate in. + stwu r1,-40(r1) + bl L_invoke_copy_to_stack$stub +# remove temporary stack frame. + lwz r1,0(r1) + + lfd f1,0(r31) + lfd f2,8(r31) + lfd f3,16(r31) + lfd f4,24(r31) + lfd f5,32(r31) + lfd f6,40(r31) + lfd f7,48(r31) + lfd f8,56(r31) + lfd f9,64(r31) + lfd f10,72(r31) + lfd f11,80(r31) + lfd f12,88(r31) + lfd f13,96(r31) + + lwz r3,168(r31) ; that + lwz r4,0(r3) ; get vTable from 'that' + lwz r5,172(r31) ; methodIndex + slwi r5,r5,2 ; methodIndex * 4 + lwzx r12,r5,r4 ; get function pointer + + lwz r4,28(r1) + lwz r5,32(r1) + lwz r6,36(r1) + lwz r7,40(r1) + lwz r8,44(r1) + lwz r9,48(r1) + lwz r10,52(r1) + + mtlr r12 + blrl + + mr r1,r31 + lwz r0,152(r1) + addi r1,r1,144 + mtlr r0 + lwz r31,-4(r1) + + blr + +.picsymbol_stub +L_invoke_count_words$stub: + .indirect_symbol _invoke_count_words + mflr r0 + bcl 20,31,L1$pb +L1$pb: + mflr r11 + addis r11,r11,ha16(L1$lz-L1$pb) + mtlr r0 + lwz r12,lo16(L1$lz-L1$pb)(r11) + mtctr r12 + addi r11,r11,lo16(L1$lz-L1$pb) + bctr +.lazy_symbol_pointer +L1$lz: + .indirect_symbol _invoke_count_words + .long dyld_stub_binding_helper + + +.picsymbol_stub +L_invoke_copy_to_stack$stub: + .indirect_symbol _invoke_copy_to_stack + mflr r0 + bcl 20,31,L2$pb +L2$pb: + mflr r11 + addis r11,r11,ha16(L2$lz-L2$pb) + mtlr r0 + lwz r12,lo16(L2$lz-L2$pb)(r11) + mtctr r12 + addi r11,r11,lo16(L2$lz-L2$pb) + bctr +.lazy_symbol_pointer +L2$lz: + .indirect_symbol _invoke_copy_to_stack + .long dyld_stub_binding_helper + diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_sparc64_openbsd.s b/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_sparc64_openbsd.s new file mode 100644 index 000000000..dfd6189f8 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_sparc64_openbsd.s @@ -0,0 +1,86 @@ +/* -*- Mode: asm; 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/. */ + +/* + Platform specific code to invoke XPCOM methods on native objects + for sparcv9 Solaris. + + See the SPARC Compliance Definition (SCD) Chapter 3 + for more information about what is going on here, including + the use of BIAS (0x7ff). + The SCD is available from http://www.sparc.com/. +*/ + + .global NS_InvokeByIndex + .type NS_InvokeByIndex, #function + +/* + NS_InvokeByIndex(nsISupports* that, uint32_t methodIndex, + uint32_t paramCount, nsXPTCVariant* params); + +*/ +NS_InvokeByIndex: + save %sp,-(128 + 64),%sp ! room for the register window and + ! struct pointer, rounded up to 0 % 64 + sll %i2,4,%l0 ! assume the worst case + ! paramCount * 2 * 8 bytes + cmp %l0, 0 ! are there any args? If not, + be .invoke ! no need to copy args to stack + nop + + sub %sp,%l0,%sp ! create the additional stack space + add %sp,0x7ff+136,%o0 ! step past the register window, the + ! struct result pointer and the 'this' slot + mov %i2,%o1 ! paramCount + call invoke_copy_to_stack + mov %i3,%o2 ! params + +! +! load arguments from stack into the outgoing registers +! BIAS is 0x7ff (2047) +! + +! load the %o1..5 64bit (extended word) output registers registers + ldx [%sp + 0x7ff + 136],%o1 ! %i1 + ldx [%sp + 0x7ff + 144],%o2 ! %i2 + ldx [%sp + 0x7ff + 152],%o3 ! %i3 + ldx [%sp + 0x7ff + 160],%o4 ! %i4 + ldx [%sp + 0x7ff + 168],%o5 ! %i5 + +! load the even number double registers starting with %f2 + ldd [%sp + 0x7ff + 136],%f2 + ldd [%sp + 0x7ff + 144],%f4 + ldd [%sp + 0x7ff + 152],%f6 + ldd [%sp + 0x7ff + 160],%f8 + ldd [%sp + 0x7ff + 168],%f10 + ldd [%sp + 0x7ff + 176],%f12 + ldd [%sp + 0x7ff + 184],%f14 + ldd [%sp + 0x7ff + 192],%f16 + ldd [%sp + 0x7ff + 200],%f18 + ldd [%sp + 0x7ff + 208],%f20 + ldd [%sp + 0x7ff + 216],%f22 + ldd [%sp + 0x7ff + 224],%f24 + ldd [%sp + 0x7ff + 232],%f26 + ldd [%sp + 0x7ff + 240],%f28 + ldd [%sp + 0x7ff + 248],%f30 + +! +! calculate the target address from the vtable +! +.invoke: + sll %i1,3,%l0 ! index *= 8 +! add %l0,16,%l0 ! there are 2 extra entries in the vTable (16bytes) + ldx [%i0],%l1 ! *that --> address of vtable + ldx [%l0 + %l1],%l0 ! that->vtable[index * 8 + 16] --> address + + jmpl %l0,%o7 ! call the routine + mov %i0,%o0 ! move 'this' pointer to out register + + mov %o0,%i0 ! propagate return value + ret + restore + + .size NS_InvokeByIndex, .-NS_InvokeByIndex diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_sparc_linux_GCC3.s b/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_sparc_linux_GCC3.s new file mode 100644 index 000000000..36196805e --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_sparc_linux_GCC3.s @@ -0,0 +1,53 @@ +/* -*- 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/. */ + +/* + * Platform specific code to invoke XPCOM methods on native objects for + * Linux/Sparc with gcc 3 ABI. + */ + .global NS_InvokeByIndex +/* + * NS_InvokeByIndex(nsISupports* that, uint32_t methodIndex, + * uint32_t paramCount, nsXPTCVariant* params); + * + */ +NS_InvokeByIndex: + save %sp,-(64 + 16),%sp ! room for the register window and + ! struct pointer, rounded up to 0 % 16 + mov %i2,%o0 ! paramCount + call invoke_count_words ! returns the required stack size in %o0 + mov %i3,%o1 ! params + + sll %o0,2,%l0 ! number of bytes + sub %sp,%l0,%sp ! create the additional stack space + + mov %sp,%o0 ! pointer for copied args + add %o0,72,%o0 ! step past the register window, the + ! struct result pointer and the 'this' slot + mov %i2,%o1 ! paramCount + call invoke_copy_to_stack + mov %i3,%o2 ! params +! +! calculate the target address from the vtable +! + ld [%i0],%l1 ! *that --> vTable + sll %i1,2,%i1 ! multiply index by 4 + add %i1,%l1,%l1 ! l1 now points to vTable entry + ld [%l1],%l0 ! target address + +.L5: ld [%sp + 88],%o5 +.L4: ld [%sp + 84],%o4 +.L3: ld [%sp + 80],%o3 +.L2: ld [%sp + 76],%o2 +.L1: ld [%sp + 72],%o1 +.L0: + jmpl %l0,%o7 ! call the routine +! always have a 'this', from the incoming 'that' + mov %i0,%o0 + + mov %o0,%i0 ! propagate return value + ret + restore diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_sparc_netbsd.s b/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_sparc_netbsd.s new file mode 100644 index 000000000..893432a5e --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_sparc_netbsd.s @@ -0,0 +1,55 @@ +/* -*- 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/. */ + +/* Platform specific code to invoke XPCOM methods on native objects */ + .global XPTC_InvokeByIndex +/* + XPTC_InvokeByIndex(nsISupports* that, uint32_t methodIndex, + uint32_t paramCount, nsXPTCVariant* params); + +*/ +XPTC_InvokeByIndex: + save %sp,-(64 + 16),%sp ! room for the register window and + ! struct pointer, rounded up to 0 % 16 + mov %i2,%o0 ! paramCount + call invoke_count_words ! returns the required stack size in %o0 + mov %i3,%o1 ! params + + sll %o0,2,%l0 ! number of bytes + sub %sp,%l0,%sp ! create the additional stack space + + mov %sp,%o0 ! pointer for copied args + add %o0,72,%o0 ! step past the register window, the + ! struct result pointer and the 'this' slot + mov %i2,%o1 ! paramCount + call invoke_copy_to_stack + mov %i3,%o2 ! params +! +! calculate the target address from the vtable +! + add %i1,1,%i1 ! vTable is zero-based, index is 1 based (?) + ld [%i0],%l1 ! *that --> vTable + sll %i1,3,%i1 + add %i1,%l1,%l1 ! vTable[index * 8], l1 now points to vTable entry + lduh [%l1],%l0 ! this adjustor + sll %l0,16,%l0 ! sign extend to 32 bits + sra %l0,16,%l0 + add %l0,%i0,%i0 ! adjust this + ld [%l1 + 4],%l0 ! target address + +.L5: ld [%sp + 88],%o5 +.L4: ld [%sp + 84],%o4 +.L3: ld [%sp + 80],%o3 +.L2: ld [%sp + 76],%o2 +.L1: ld [%sp + 72],%o1 +.L0: + jmpl %l0,%o7 ! call the routine +! always have a 'this', from the incoming 'that' + mov %i0,%o0 + + mov %o0,%i0 ! propagate return value + ret + restore diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_sparc_openbsd.s b/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_sparc_openbsd.s new file mode 100644 index 000000000..1ef695370 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_sparc_openbsd.s @@ -0,0 +1,55 @@ +/* -*- 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/. */ + +/* Platform specific code to invoke XPCOM methods on native objects */ + .global XPTC_InvokeByIndex +/* + XPTC_InvokeByIndex(nsISupports* that, uint32_t methodIndex, + uint32_t paramCount, nsXPTCVariant* params); + +*/ +XPTC_InvokeByIndex: + save %sp,-(64 + 16),%sp ! room for the register window and + ! struct pointer, rounded up to 0 % 16 + mov %i2,%o0 ! paramCount + call invoke_count_words ! returns the required stack size in %o0 + mov %i3,%o1 ! params + + sll %o0,2,%l0 ! number of bytes + sub %sp,%l0,%sp ! create the additional stack space + + mov %sp,%o0 ! pointer for copied args + add %o0,72,%o0 ! step past the register window, the + ! struct result pointer and the 'this' slot + mov %i2,%o1 ! paramCount + call invoke_copy_to_stack + mov %i3,%o2 ! params +! +! calculate the target address from the vtable +! + add %i1,1,%i1 ! vTable is zero-based, index is 1 based (?) + ld [%i0],%l1 ! *that --> vTable + sll %i1,3,%i1 + add %i1,%l1,%l1 ! vTable[index * 8], l1 now points to vTable entry + lduh [%l1],%l0 ! this adjustor + sll %l0,16,%l0 ! sign extend to 32 bits + sra %l0,16,%l0 + add %l0,%i0,%i0 ! adjust this + ld [%l1 + 4],%l0 ! target address + +.L5: ld [%sp + 88],%o5 +.L4: ld [%sp + 84],%o4 +.L3: ld [%sp + 80],%o3 +.L2: ld [%sp + 76],%o2 +.L1: ld [%sp + 72],%o1 +.L0: + jmpl %l0,%o7 ! call the routine +! always have a 'this', from the incoming 'that' + mov %i0,%o0 + + mov %o0,%i0 ! propagate return value + ret + restore diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_sparc_solaris_GCC3.s b/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_sparc_solaris_GCC3.s new file mode 100644 index 000000000..54adcd147 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_sparc_solaris_GCC3.s @@ -0,0 +1,52 @@ +/* -*- 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/. */ + +/* + * Platform specific code to invoke XPCOM methods on native objects for + * solaris/sparc with gcc 3 ABI. + */ + .global NS_InvokeByIndex +/* + * NS_InvokeByIndex(nsISupports* that, uint32_t methodIndex, + * uint32_t paramCount, nsXPTCVariant* params); + */ +NS_InvokeByIndex: + save %sp,-(64 + 32),%sp ! room for the register window and + ! struct pointer, rounded up to 0 % 32 + mov %i2,%o0 ! paramCount + call invoke_count_words ! returns the required stack size in %o0 + mov %i3,%o1 ! params + + sll %o0,2,%l0 ! number of bytes + sub %sp,%l0,%sp ! create the additional stack space + + mov %sp,%o0 ! pointer for copied args + add %o0,72,%o0 ! step past the register window, the + ! struct result pointer and the 'this' slot + mov %i2,%o1 ! paramCount + call invoke_copy_to_stack + mov %i3,%o2 ! params +! +! calculate the target address from the vtable +! + ld [%i0],%l1 ! *that --> vTable + sll %i1,2,%i1 ! multiply index by 4 + add %i1,%l1,%l1 ! l1 now points to vTable entry + ld [%l1],%l0 ! target address + +.L5: ld [%sp + 88],%o5 +.L4: ld [%sp + 84],%o4 +.L3: ld [%sp + 80],%o3 +.L2: ld [%sp + 76],%o2 +.L1: ld [%sp + 72],%o1 +.L0: + jmpl %l0,%o7 ! call the routine +! always have a 'this', from the incoming 'that' + mov %i0,%o0 + + mov %o0,%i0 ! propagate return value + ret + restore diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_sparc_solaris_SUNW.s b/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_sparc_solaris_SUNW.s new file mode 100644 index 000000000..013770392 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_sparc_solaris_SUNW.s @@ -0,0 +1,56 @@ +/* -*- 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/. */ + +/* Platform specific code to invoke XPCOM methods on native objects */ + + .global NS_InvokeByIndex + .type NS_InvokeByIndex, #function +/* + NS_InvokeByIndex(nsISupports* that, uint32_t methodIndex, + uint32_t paramCount, nsXPTCVariant* params); + +*/ +NS_InvokeByIndex: + save %sp,-(64 + 32),%sp ! room for the register window and + ! struct pointer, rounded up to 0 % 32 + sll %i2,3,%l0 ! assume the worst case + ! paramCount * 2 * 4 bytes + cmp %l0, 0 ! are there any args? If not, + be .invoke ! no need to copy args to stack + + sub %sp,%l0,%sp ! create the additional stack space + add %sp,72,%o0 ! step past the register window, the + ! struct result pointer and the 'this' slot + mov %i2,%o1 ! paramCount + call invoke_copy_to_stack + mov %i3,%o2 ! params + +! +! load arguments from stack into the outgoing registers +! + ld [%sp + 72],%o1 + ld [%sp + 76],%o2 + ld [%sp + 80],%o3 + ld [%sp + 84],%o4 + ld [%sp + 88],%o5 + +! +! calculate the target address from the vtable +! +.invoke: + sll %i1,2,%l0 ! index *= 4 + add %l0,8,%l0 ! there are 2 extra entries in the vTable + ld [%i0],%l1 ! *that --> address of vtable + ld [%l0 + %l1],%l0 ! that->vtable[index * 4 + 8] --> address + + jmpl %l0,%o7 ! call the routine + mov %i0,%o0 ! move 'this' pointer to out register + + mov %o0,%i0 ! propagate return value + ret + restore + + .size NS_InvokeByIndex, .-NS_InvokeByIndex diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_sparcv9_solaris_SUNW.s b/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_sparcv9_solaris_SUNW.s new file mode 100644 index 000000000..34861abc0 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_sparcv9_solaris_SUNW.s @@ -0,0 +1,85 @@ +/* -*- Mode: asm; 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/. */ + +/* + Platform specific code to invoke XPCOM methods on native objects + for sparcv9 Solaris. + + See the SPARC Compliance Definition (SCD) Chapter 3 + for more information about what is going on here, including + the use of BIAS (0x7ff). + The SCD is available from http://www.sparc.com/. +*/ + + .global NS_InvokeByIndex + .type NS_InvokeByIndex, #function + +/* + NS_InvokeByIndex(nsISupports* that, uint32_t methodIndex, + uint32_t paramCount, nsXPTCVariant* params); + +*/ +NS_InvokeByIndex: + save %sp,-(128 + 64),%sp ! room for the register window and + ! struct pointer, rounded up to 0 % 64 + sll %i2,4,%l0 ! assume the worst case + ! paramCount * 2 * 8 bytes + cmp %l0, 0 ! are there any args? If not, + be .invoke ! no need to copy args to stack + + sub %sp,%l0,%sp ! create the additional stack space + add %sp,0x7ff+136,%o0 ! step past the register window, the + ! struct result pointer and the 'this' slot + mov %i2,%o1 ! paramCount + call invoke_copy_to_stack + mov %i3,%o2 ! params + +! +! load arguments from stack into the outgoing registers +! BIAS is 0x7ff (2047) +! + +! load the %o1..5 64bit (extended word) output registers registers + ldx [%sp + 0x7ff + 136],%o1 ! %i1 + ldx [%sp + 0x7ff + 144],%o2 ! %i2 + ldx [%sp + 0x7ff + 152],%o3 ! %i3 + ldx [%sp + 0x7ff + 160],%o4 ! %i4 + ldx [%sp + 0x7ff + 168],%o5 ! %i5 + +! load the even number double registers starting with %d2 + ldd [%sp + 0x7ff + 136],%d2 + ldd [%sp + 0x7ff + 144],%d4 + ldd [%sp + 0x7ff + 152],%d6 + ldd [%sp + 0x7ff + 160],%d8 + ldd [%sp + 0x7ff + 168],%d10 + ldd [%sp + 0x7ff + 176],%d12 + ldd [%sp + 0x7ff + 184],%d14 + ldd [%sp + 0x7ff + 192],%d16 + ldd [%sp + 0x7ff + 200],%d18 + ldd [%sp + 0x7ff + 208],%d20 + ldd [%sp + 0x7ff + 216],%d22 + ldd [%sp + 0x7ff + 224],%d24 + ldd [%sp + 0x7ff + 232],%d26 + ldd [%sp + 0x7ff + 240],%d28 + ldd [%sp + 0x7ff + 248],%d30 + +! +! calculate the target address from the vtable +! +.invoke: + sll %i1,3,%l0 ! index *= 8 + add %l0,16,%l0 ! there are 2 extra entries in the vTable (16bytes) + ldx [%i0],%l1 ! *that --> address of vtable + ldx [%l0 + %l1],%l0 ! that->vtable[index * 8 + 16] --> address + + jmpl %l0,%o7 ! call the routine + mov %i0,%o0 ! move 'this' pointer to out register + + mov %o0,%i0 ! propagate return value + ret + restore + + .size NS_InvokeByIndex, .-NS_InvokeByIndex diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_x86_solaris_SUNW.s b/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_x86_solaris_SUNW.s new file mode 100644 index 000000000..af665a162 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_x86_solaris_SUNW.s @@ -0,0 +1,55 @@ + .globl NS_InvokeByIndex + .type NS_InvokeByIndex, @function +NS_InvokeByIndex: + push %ebp + movl %esp,%ebp + push %ebx + call .CG0.66 +.CG0.66: + pop %ebx + addl $_GLOBAL_OFFSET_TABLE_+0x1,%ebx + push 20(%ebp) + push 16(%ebp) + push 12(%ebp) + push 8(%ebp) + / INLINE: invoke_by_index + + + + pushl %ebx + pushl %esi + movl %esp, %ebx + + pushl 0x14(%ebp) + pushl 0x10(%ebp) + call invoke_count_words + mov %ebx, %esp + + sall $0x2 , %eax + subl %eax, %esp + movl %esp, %esi + + pushl %esp + pushl 0x14(%ebp) + pushl 0x10(%ebp) + call invoke_copy_to_stack + movl %esi, %esp + + movl 0x8(%ebp), %ecx + pushl %ecx + movl (%ecx), %edx + movl 0xc(%ebp), %eax + movl 0x8(%edx, %eax, 4), %edx + + call *%edx + mov %ebx, %esp + popl %esi + popl %ebx + / INLINE_END + addl $16,%esp + pop %ebx + movl %ebp,%esp + pop %ebp + ret + .size NS_InvokeByIndex, . - NS_InvokeByIndex + diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_darwin.cpp b/xpcom/reflect/xptcall/md/unix/xptcinvoke_darwin.cpp new file mode 100644 index 000000000..04407db39 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_darwin.cpp @@ -0,0 +1,16 @@ +/* -*- Mode: 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/. */ + +#if defined(__i386__) +#include "xptcinvoke_gcc_x86_unix.cpp" +#elif defined(__x86_64__) +#include "xptcinvoke_x86_64_unix.cpp" +#elif defined(__ppc__) +#include "xptcinvoke_ppc_rhapsody.cpp" +#elif defined(__arm__) +#include "xptcinvoke_arm.cpp" +#else +#error unknown cpu architecture +#endif diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_gcc_x86_unix.cpp b/xpcom/reflect/xptcall/md/unix/xptcinvoke_gcc_x86_unix.cpp new file mode 100644 index 000000000..d4a8a12fb --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_gcc_x86_unix.cpp @@ -0,0 +1,97 @@ +/* -*- 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/. */ + +/* Platform specific code to invoke XPCOM methods on native objects */ + +#include "xptcprivate.h" +#include "xptc_gcc_x86_unix.h" + +extern "C" { +static void ATTRIBUTE_USED __attribute__ ((regparm(3))) +invoke_copy_to_stack(uint32_t paramCount, nsXPTCVariant* s, uint32_t* d) +{ + for(uint32_t i = paramCount; i >0; i--, d++, s++) + { + if(s->IsPtrData()) + { + *((void**)d) = s->ptr; + continue; + } + + switch(s->type) + { + case nsXPTType::T_I64 : *((int64_t*) d) = s->val.i64; d++; break; + case nsXPTType::T_U64 : *((uint64_t*)d) = s->val.u64; d++; break; + case nsXPTType::T_DOUBLE : *((double*) d) = s->val.d; d++; break; + default : *((void**)d) = s->val.p; break; + } + } +} +} // extern "C" + +/* + EXPORT_XPCOM_API(nsresult) + NS_InvokeByIndex(nsISupports* that, uint32_t methodIndex, + uint32_t paramCount, nsXPTCVariant* params); + + Each param takes at most two 4-byte words. + It doesn't matter if we push too many words, and calculating the exact + amount takes time. + + that = ebp + 0x08 + methodIndex = ebp + 0x0c + paramCount = ebp + 0x10 + params = ebp + 0x14 + +*/ + +__asm__ ( + ".text\n\t" +/* alignment here seems unimportant here; this was 16, now it's 2 which + is what xptcstubs uses. */ + ".align 2\n\t" + ".globl " SYMBOL_UNDERSCORE "NS_InvokeByIndex\n\t" +#ifndef XP_MACOSX + ".type " SYMBOL_UNDERSCORE "NS_InvokeByIndex,@function\n" +#endif + SYMBOL_UNDERSCORE "NS_InvokeByIndex:\n\t" + "pushl %ebp\n\t" + "movl %esp, %ebp\n\t" + "movl 0x10(%ebp), %eax\n\t" + "leal 0(,%eax,8),%edx\n\t" + + /* set up call frame for method. */ + "subl %edx, %esp\n\t" /* make room for params. */ +/* Align to maximum x86 data size: 128 bits == 16 bytes == XMM register size. + * This is to avoid protection faults where SSE+ alignment of stack pointer + * is assumed and required, e.g. by GCC4's -ftree-vectorize option. + */ + "andl $0xfffffff0, %esp\n\t" /* drop(?) stack ptr to 128-bit align */ +/* $esp should be aligned to a 16-byte boundary here (note we include an + * additional 4 bytes in a later push instruction). This will ensure $ebp + * in the function called below is aligned to a 0x8 boundary. SSE instructions + * like movapd/movdqa expect memory operand to be aligned on a 16-byte + * boundary. The GCC compiler will generate the memory operand using $ebp + * with an 8-byte offset. + */ + "subl $0xc, %esp\n\t" /* lower again; push/call below will re-align */ + "movl %esp, %ecx\n\t" /* ecx = d */ + "movl 8(%ebp), %edx\n\t" /* edx = this */ + "pushl %edx\n\t" /* push this. esp % 16 == 0 */ + + "movl 0x14(%ebp), %edx\n\t" + "call " SYMBOL_UNDERSCORE "invoke_copy_to_stack\n\t" + "movl 0x08(%ebp), %ecx\n\t" /* 'that' */ + "movl (%ecx), %edx\n\t" + "movl 0x0c(%ebp), %eax\n\t" /* function index */ + "leal (%edx,%eax,4), %edx\n\t" + "call *(%edx)\n\t" + "movl %ebp, %esp\n\t" + "popl %ebp\n\t" + "ret\n" +#ifndef XP_MACOSX + ".size " SYMBOL_UNDERSCORE "NS_InvokeByIndex, . -" SYMBOL_UNDERSCORE "NS_InvokeByIndex\n\t" +#endif +); diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_ipf32.cpp b/xpcom/reflect/xptcall/md/unix/xptcinvoke_ipf32.cpp new file mode 100644 index 000000000..1500f3175 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_ipf32.cpp @@ -0,0 +1,132 @@ + +/* -*- 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 "xptcprivate.h" + +#include + +// "This code is for IA64 only" + + +/* invoke_copy_to_stack() will copy from variant array 's' to + * the stack argument area 'mloc', the integer register area 'iloc', and + * the float register area 'floc'. + * + */ +extern "C" void +invoke_copy_to_stack(uint64_t* mloc, uint64_t* iloc, uint64_t* floc, + const uint32_t paramCount, nsXPTCVariant* s) +{ + uint64_t* dest = mloc; + uint32_t len = paramCount; + nsXPTCVariant* source = s; + + uint32_t indx; + uint32_t endlen; + endlen = (len > 7) ? 7 : len; + /* handle the memory arguments */ + for (indx = 7; indx < len; ++indx) + { + if (source[indx].IsPtrData()) + { +#ifdef __LP64__ + /* 64 bit pointer mode */ + *((void**) dest) = source[indx].ptr; +#else + /* 32 bit pointer mode */ + uint32_t* adr = (uint32_t*) dest; + *(adr) = 0; + *(adr+1) = (uint32_t) source[indx].ptr; +#endif + } + else + switch (source[indx].type) + { + case nsXPTType::T_I8 : *(dest) = source[indx].val.i8; break; + case nsXPTType::T_I16 : *(dest) = source[indx].val.i16; break; + case nsXPTType::T_I32 : *(dest) = source[indx].val.i32; break; + case nsXPTType::T_I64 : *(dest) = source[indx].val.i64; break; + case nsXPTType::T_U8 : *(dest) = source[indx].val.u8; break; + case nsXPTType::T_U16 : *(dest) = source[indx].val.u16; break; + case nsXPTType::T_U32 : *(dest) = source[indx].val.u32; break; + case nsXPTType::T_U64 : *(dest) = source[indx].val.u64; break; + case nsXPTType::T_FLOAT : *(dest) = source[indx].val.u32; break; + case nsXPTType::T_DOUBLE: *(dest) = source[indx].val.u64; break; + case nsXPTType::T_BOOL : *(dest) = source[indx].val.b; break; + case nsXPTType::T_CHAR : *(dest) = source[indx].val.c; break; + case nsXPTType::T_WCHAR : *(dest) = source[indx].val.wc; break; + default: + // all the others are plain pointer types +#ifdef __LP64__ + /* 64 bit pointer mode */ + *((void**) dest) = source[indx].val.p; +#else + { + /* 32 bit pointer mode */ + uint32_t* adr = (uint32_t*) dest; + *(adr) = 0; + *(adr+1) = (uint32_t) source[indx].val.p; + } +#endif + } + ++dest; + } + /* process register arguments */ + dest = iloc; + for (indx = 0; indx < endlen; ++indx) + { + if (source[indx].IsPtrData()) + { +#ifdef __LP64__ + /* 64 bit pointer mode */ + *((void**) dest) = source[indx].ptr; +#else + /* 32 bit pointer mode */ + uint32_t* adr = (uint32_t*) dest; + *(adr) = 0; + *(adr+1) = (uint32_t) source[indx].ptr; +#endif + } + else + switch (source[indx].type) + { + case nsXPTType::T_I8 : *(dest) = source[indx].val.i8; break; + case nsXPTType::T_I16 : *(dest) = source[indx].val.i16; break; + case nsXPTType::T_I32 : *(dest) = source[indx].val.i32; break; + case nsXPTType::T_I64 : *(dest) = source[indx].val.i64; break; + case nsXPTType::T_U8 : *(dest) = source[indx].val.u8; break; + case nsXPTType::T_U16 : *(dest) = source[indx].val.u16; break; + case nsXPTType::T_U32 : *(dest) = source[indx].val.u32; break; + case nsXPTType::T_U64 : *(dest) = source[indx].val.u64; break; + case nsXPTType::T_FLOAT : + *((double*) (floc++)) = (double) source[indx].val.f; + break; + case nsXPTType::T_DOUBLE: + *((double*) (floc++)) = source[indx].val.d; + break; + case nsXPTType::T_BOOL : *(dest) = source[indx].val.b; break; + case nsXPTType::T_CHAR : *(dest) = source[indx].val.c; break; + case nsXPTType::T_WCHAR : *(dest) = source[indx].val.wc; break; + default: + // all the others are plain pointer types +#ifdef __LP64__ + /* 64 bit pointer mode */ + *((void**) dest) = source[indx].val.p; +#else + { + /* 32 bit pointer mode */ + uint32_t* adr = (uint32_t*) dest; + *(adr) = 0; + *(adr+1) = (uint32_t) source[indx].val.p; + } +#endif + } + ++dest; + } + +} + diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_ipf64.cpp b/xpcom/reflect/xptcall/md/unix/xptcinvoke_ipf64.cpp new file mode 100644 index 000000000..c2a4dd088 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_ipf64.cpp @@ -0,0 +1,100 @@ + +/* -*- 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 "xptcprivate.h" + +#include + +// "This code is for IA64 only" + + +/* invoke_copy_to_stack() will copy from variant array 's' to + * the stack argument area 'mloc', the integer register area 'iloc', and + * the float register area 'floc'. + * + */ +extern "C" void +invoke_copy_to_stack(uint64_t* mloc, uint64_t* iloc, uint64_t* floc, + const uint32_t paramCount, nsXPTCVariant* s) +{ + uint64_t* dest = mloc; + uint32_t len = paramCount; + nsXPTCVariant* source = s; + + uint32_t indx; + uint32_t endlen; + endlen = (len > 7) ? 7 : len; + /* handle the memory arguments */ + for (indx = 7; indx < len; ++indx) + { + if (source[indx].IsPtrData()) + { + /* 64 bit pointer mode */ + *((void**) dest) = source[indx].ptr; + } + else + switch (source[indx].type) + { + case nsXPTType::T_I8 : *(dest) = source[indx].val.i8; break; + case nsXPTType::T_I16 : *(dest) = source[indx].val.i16; break; + case nsXPTType::T_I32 : *(dest) = source[indx].val.i32; break; + case nsXPTType::T_I64 : *(dest) = source[indx].val.i64; break; + case nsXPTType::T_U8 : *(dest) = source[indx].val.u8; break; + case nsXPTType::T_U16 : *(dest) = source[indx].val.u16; break; + case nsXPTType::T_U32 : *(dest) = source[indx].val.u32; break; + case nsXPTType::T_U64 : *(dest) = source[indx].val.u64; break; + case nsXPTType::T_FLOAT : *(dest) = source[indx].val.u32; break; + case nsXPTType::T_DOUBLE: *(dest) = source[indx].val.u64; break; + case nsXPTType::T_BOOL : *(dest) = source[indx].val.b; break; + case nsXPTType::T_CHAR : *(dest) = source[indx].val.c; break; + case nsXPTType::T_WCHAR : *(dest) = source[indx].val.wc; break; + default: + // all the others are plain pointer types + /* 64 bit pointer mode */ + *((void**) dest) = source[indx].val.p; + } + ++dest; + } + /* process register arguments */ + dest = iloc; + for (indx = 0; indx < endlen; ++indx) + { + if (source[indx].IsPtrData()) + { + /* 64 bit pointer mode */ + *((void**) dest) = source[indx].ptr; + } + else + switch (source[indx].type) + { + case nsXPTType::T_I8 : *(dest) = source[indx].val.i8; break; + case nsXPTType::T_I16 : *(dest) = source[indx].val.i16; break; + case nsXPTType::T_I32 : *(dest) = source[indx].val.i32; break; + case nsXPTType::T_I64 : *(dest) = source[indx].val.i64; break; + case nsXPTType::T_U8 : *(dest) = source[indx].val.u8; break; + case nsXPTType::T_U16 : *(dest) = source[indx].val.u16; break; + case nsXPTType::T_U32 : *(dest) = source[indx].val.u32; break; + case nsXPTType::T_U64 : *(dest) = source[indx].val.u64; break; + case nsXPTType::T_FLOAT : + *((double*) (floc++)) = (double) source[indx].val.f; + break; + case nsXPTType::T_DOUBLE: + *((double*) (floc++)) = source[indx].val.d; + break; + case nsXPTType::T_BOOL : *(dest) = source[indx].val.b; break; + case nsXPTType::T_CHAR : *(dest) = source[indx].val.c; break; + case nsXPTType::T_WCHAR : *(dest) = source[indx].val.wc; break; + default: + // all the others are plain pointer types + /* 64 bit pointer mode */ + *((void**) dest) = source[indx].val.p; + } + ++dest; + } + +} + diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_linux_alpha.cpp b/xpcom/reflect/xptcall/md/unix/xptcinvoke_linux_alpha.cpp new file mode 100644 index 000000000..dc111e435 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_linux_alpha.cpp @@ -0,0 +1,144 @@ +/* -*- 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/. */ + +/* Platform specific code to invoke XPCOM methods on native objects */ + +#include "xptcprivate.h" + +/* Prototype specifies unmangled function name and disables unused warning */ +static void +invoke_copy_to_stack(uint64_t* d, uint32_t paramCount, nsXPTCVariant* s) +__asm__("invoke_copy_to_stack") __attribute__((used)); + +static void +invoke_copy_to_stack(uint64_t* d, uint32_t paramCount, nsXPTCVariant* s) +{ + const uint8_t NUM_ARG_REGS = 6-1; // -1 for "this" pointer + + for(uint32_t i = 0; i < paramCount; i++, d++, s++) + { + if(s->IsPtrData()) + { + *d = (uint64_t)s->ptr; + continue; + } + switch(s->type) + { + case nsXPTType::T_I8 : *d = (uint64_t)s->val.i8; break; + case nsXPTType::T_I16 : *d = (uint64_t)s->val.i16; break; + case nsXPTType::T_I32 : *d = (uint64_t)s->val.i32; break; + case nsXPTType::T_I64 : *d = (uint64_t)s->val.i64; break; + case nsXPTType::T_U8 : *d = (uint64_t)s->val.u8; break; + case nsXPTType::T_U16 : *d = (uint64_t)s->val.u16; break; + case nsXPTType::T_U32 : *d = (uint64_t)s->val.u32; break; + case nsXPTType::T_U64 : *d = (uint64_t)s->val.u64; break; + case nsXPTType::T_FLOAT : + if(i < NUM_ARG_REGS) + { + // convert floats to doubles if they are to be passed + // via registers so we can just deal with doubles later + union { uint64_t u64; double d; } t; + t.d = (double)s->val.f; + *d = t.u64; + } + else + // otherwise copy to stack normally + *d = (uint64_t)s->val.u32; + break; + case nsXPTType::T_DOUBLE : *d = (uint64_t)s->val.u64; break; + case nsXPTType::T_BOOL : *d = (uint64_t)s->val.b; break; + case nsXPTType::T_CHAR : *d = (uint64_t)s->val.c; break; + case nsXPTType::T_WCHAR : *d = (uint64_t)s->val.wc; break; + default: + // all the others are plain pointer types + *d = (uint64_t)s->val.p; + break; + } + } +} + +/* + * EXPORT_XPCOM_API(nsresult) + * NS_InvokeByIndex(nsISupports* that, uint32_t methodIndex, + * uint32_t paramCount, nsXPTCVariant* params) + */ +__asm__( + "#### NS_InvokeByIndex ####\n" +".text\n\t" + ".align 5\n\t" + ".globl NS_InvokeByIndex\n\t" + ".ent NS_InvokeByIndex\n" +"NS_InvokeByIndex:\n\t" + ".frame $15,32,$26,0\n\t" + ".mask 0x4008000,-32\n\t" + "ldgp $29,0($27)\n" +"$NS_InvokeByIndex..ng:\n\t" + "subq $30,32,$30\n\t" + "stq $26,0($30)\n\t" + "stq $15,8($30)\n\t" + "bis $30,$30,$15\n\t" + ".prologue 1\n\t" + + /* + * Allocate enough stack space to hold the greater of 6 or "paramCount"+1 + * parameters. (+1 for "this" pointer) Room for at least 6 parameters + * is required for storage of those passed via registers. + */ + + "bis $31,5,$2\n\t" /* count = MAX(5, "paramCount") */ + "cmplt $2,$18,$1\n\t" + "cmovne $1,$18,$2\n\t" + "s8addq $2,16,$1\n\t" /* room for count+1 params (8 bytes each) */ + "bic $1,15,$1\n\t" /* stack space is rounded up to 0 % 16 */ + "subq $30,$1,$30\n\t" + + "stq $16,0($30)\n\t" /* save "that" (as "this" pointer) */ + "stq $17,16($15)\n\t" /* save "methodIndex" */ + + "addq $30,8,$16\n\t" /* pass stack pointer */ + "bis $18,$18,$17\n\t" /* pass "paramCount" */ + "bis $19,$19,$18\n\t" /* pass "params" */ + "bsr $26,$invoke_copy_to_stack..ng\n\t" /* call invoke_copy_to_stack */ + + /* + * Copy the first 6 parameters to registers and remove from stack frame. + * Both the integer and floating point registers are set for each parameter + * except the first which is the "this" pointer. (integer only) + * The floating point registers are all set as doubles since the + * invoke_copy_to_stack function should have converted the floats. + */ + "ldq $16,0($30)\n\t" /* integer registers */ + "ldq $17,8($30)\n\t" + "ldq $18,16($30)\n\t" + "ldq $19,24($30)\n\t" + "ldq $20,32($30)\n\t" + "ldq $21,40($30)\n\t" + "ldt $f17,8($30)\n\t" /* floating point registers */ + "ldt $f18,16($30)\n\t" + "ldt $f19,24($30)\n\t" + "ldt $f20,32($30)\n\t" + "ldt $f21,40($30)\n\t" + + "addq $30,48,$30\n\t" /* remove params from stack */ + + /* + * Call the virtual function with the constructed stack frame. + */ + "bis $16,$16,$1\n\t" /* load "this" */ + "ldq $2,16($15)\n\t" /* load "methodIndex" */ + "ldq $1,0($1)\n\t" /* load vtable */ + "s8addq $2,$31,$2\n\t" /* vtable index = "methodIndex" * 8 */ + "addq $1,$2,$1\n\t" + "ldq $27,0($1)\n\t" /* load address of function */ + "jsr $26,($27),0\n\t" /* call virtual function */ + "ldgp $29,0($26)\n\t" + + "bis $15,$15,$30\n\t" + "ldq $26,0($30)\n\t" + "ldq $15,8($30)\n\t" + "addq $30,32,$30\n\t" + "ret $31,($26),1\n\t" + ".end NS_InvokeByIndex" + ); diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_linux_m68k.cpp b/xpcom/reflect/xptcall/md/unix/xptcinvoke_linux_m68k.cpp new file mode 100644 index 000000000..6f2934cbc --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_linux_m68k.cpp @@ -0,0 +1,130 @@ +/* -*- 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/. */ + +/* Platform specific code to invoke XPCOM methods on native objects */ + +#include "xptcprivate.h" + +// Remember that these 'words' are 32bit DWORDS + +extern "C" { + static uint32_t + invoke_count_words(uint32_t paramCount, nsXPTCVariant* s) + { + uint32_t result = 0; + for(uint32_t i = 0; i < paramCount; i++, s++) + { + if(s->IsPtrData()) + { + result++; + continue; + } + switch(s->type) + { + case nsXPTType::T_I8 : + case nsXPTType::T_I16 : + case nsXPTType::T_I32 : + result++; + break; + case nsXPTType::T_I64 : + result+=2; + break; + case nsXPTType::T_U8 : + case nsXPTType::T_U16 : + case nsXPTType::T_U32 : + result++; + break; + case nsXPTType::T_U64 : + result+=2; + break; + case nsXPTType::T_FLOAT : + result++; + break; + case nsXPTType::T_DOUBLE : + result+=2; + break; + case nsXPTType::T_BOOL : + case nsXPTType::T_CHAR : + case nsXPTType::T_WCHAR : + result++; + break; + default: + // all the others are plain pointer types + result++; + break; + } + } + return result; + } + + void + invoke_copy_to_stack(uint32_t* d, uint32_t paramCount, nsXPTCVariant* s) + { + for(uint32_t i = 0; i < paramCount; i++, d++, s++) + { + if(s->IsPtrData()) + { + *((void**)d) = s->ptr; + continue; + } + switch(s->type) + { + // 8 and 16 bit types should be promoted to 32 bits when copying + // onto the stack. + case nsXPTType::T_I8 : *((uint32_t*)d) = s->val.i8; break; + case nsXPTType::T_I16 : *((uint32_t*)d) = s->val.i16; break; + case nsXPTType::T_I32 : *((int32_t*) d) = s->val.i32; break; + case nsXPTType::T_I64 : *((int64_t*) d) = s->val.i64; d++; break; + case nsXPTType::T_U8 : *((uint32_t*)d) = s->val.u8; break; + case nsXPTType::T_U16 : *((uint32_t*)d) = s->val.u16; break; + case nsXPTType::T_U32 : *((uint32_t*)d) = s->val.u32; break; + case nsXPTType::T_U64 : *((uint64_t*)d) = s->val.u64; d++; break; + case nsXPTType::T_FLOAT : *((float*) d) = s->val.f; break; + case nsXPTType::T_DOUBLE : *((double*) d) = s->val.d; d++; break; + case nsXPTType::T_BOOL : *((uint32_t*)d) = s->val.b; break; + case nsXPTType::T_CHAR : *((uint32_t*)d) = s->val.c; break; + case nsXPTType::T_WCHAR : *((wchar_t*) d) = s->val.wc; break; + + default: + // all the others are plain pointer types + *((void**)d) = s->val.p; + break; + } + } + } +} + +EXPORT_XPCOM_API(nsresult) +NS_InvokeByIndex(nsISupports* that, uint32_t methodIndex, + uint32_t paramCount, nsXPTCVariant* params) +{ + uint32_t result, n; + + n = invoke_count_words(paramCount, params) * 4; + + __asm__ __volatile__( + "subl %5, %%sp\n\t" /* make room for params */ + "movel %4, %%sp@-\n\t" + "movel %3, %%sp@-\n\t" + "pea %%sp@(8)\n\t" + "jbsr invoke_copy_to_stack\n\t" /* copy params */ + "addw #12, %%sp\n\t" + "movel %1, %%sp@-\n\t" + "movel %1@, %%a0\n\t" + "movel %%a0@(%2:l:4), %%a0\n\t" + "jbsr %%a0@\n\t" /* safe to not cleanup sp */ + "lea %%sp@(4,%5:l), %%sp\n\t" + "movel %%d0, %0" + : "=d" (result) /* %0 */ + : "a" (that), /* %1 */ + "d" (methodIndex), /* %2 */ + "g" (paramCount), /* %3 */ + "g" (params), /* %4 */ + "d" (n) /* %5 */ + : "a0", "a1", "d0", "d1", "memory" + ); + + return result; +} diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_linux_s390.cpp b/xpcom/reflect/xptcall/md/unix/xptcinvoke_linux_s390.cpp new file mode 100644 index 000000000..573dfb00c --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_linux_s390.cpp @@ -0,0 +1,195 @@ +/* -*- 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/. */ + +/* Platform specific code to invoke XPCOM methods on native objects */ + +#include "xptcprivate.h" + + +static uint32_t +invoke_count_words(uint32_t paramCount, nsXPTCVariant* s) +{ + uint32_t overflow = 0, gpr = 1 /*this*/, fpr = 0; + for(uint32_t i = 0; i < paramCount; i++, s++) + { + if(s->IsPtrData()) + { + if (gpr < 5) gpr++; else overflow++; + continue; + } + switch(s->type) + { + case nsXPTType::T_I8 : + case nsXPTType::T_I16 : + case nsXPTType::T_I32 : + if (gpr < 5) gpr++; else overflow++; + break; + case nsXPTType::T_I64 : + if (gpr < 4) gpr+=2; else gpr=5, overflow+=2; + break; + case nsXPTType::T_U8 : + case nsXPTType::T_U16 : + case nsXPTType::T_U32 : + if (gpr < 5) gpr++; else overflow++; + break; + case nsXPTType::T_U64 : + if (gpr < 4) gpr+=2; else gpr=5, overflow+=2; + break; + case nsXPTType::T_FLOAT : + if (fpr < 2) fpr++; else overflow++; + break; + case nsXPTType::T_DOUBLE : + if (fpr < 2) fpr++; else overflow+=2; + break; + case nsXPTType::T_BOOL : + case nsXPTType::T_CHAR : + case nsXPTType::T_WCHAR : + if (gpr < 5) gpr++; else overflow++; + break; + default: + // all the others are plain pointer types + if (gpr < 5) gpr++; else overflow++; + break; + } + } + /* Round up number of overflow words to ensure stack + stays aligned to 8 bytes. */ + return (overflow + 1) & ~1; +} + +static void +invoke_copy_to_stack(uint32_t paramCount, nsXPTCVariant* s, uint32_t* d_ov, uint32_t overflow) +{ + uint32_t *d_gpr = d_ov + overflow; + uint64_t *d_fpr = (uint64_t *)(d_gpr + 4); + uint32_t gpr = 1 /*this*/, fpr = 0; + + for(uint32_t i = 0; i < paramCount; i++, s++) + { + if(s->IsPtrData()) + { + if (gpr < 5) + *((void**)d_gpr) = s->ptr, d_gpr++, gpr++; + else + *((void**)d_ov ) = s->ptr, d_ov++; + continue; + } + switch(s->type) + { + case nsXPTType::T_I8 : + if (gpr < 5) + *((int32_t*) d_gpr) = s->val.i8, d_gpr++, gpr++; + else + *((int32_t*) d_ov ) = s->val.i8, d_ov++; + break; + case nsXPTType::T_I16 : + if (gpr < 5) + *((int32_t*) d_gpr) = s->val.i16, d_gpr++, gpr++; + else + *((int32_t*) d_ov ) = s->val.i16, d_ov++; + break; + case nsXPTType::T_I32 : + if (gpr < 5) + *((int32_t*) d_gpr) = s->val.i32, d_gpr++, gpr++; + else + *((int32_t*) d_ov ) = s->val.i32, d_ov++; + break; + case nsXPTType::T_I64 : + if (gpr < 4) + *((int64_t*) d_gpr) = s->val.i64, d_gpr+=2, gpr+=2; + else + *((int64_t*) d_ov ) = s->val.i64, d_ov+=2, gpr=5; + break; + case nsXPTType::T_U8 : + if (gpr < 5) + *((uint32_t*) d_gpr) = s->val.u8, d_gpr++, gpr++; + else + *((uint32_t*) d_ov ) = s->val.u8, d_ov++; + break; + case nsXPTType::T_U16 : + if (gpr < 5) + *((uint32_t*)d_gpr) = s->val.u16, d_gpr++, gpr++; + else + *((uint32_t*)d_ov ) = s->val.u16, d_ov++; + break; + case nsXPTType::T_U32 : + if (gpr < 5) + *((uint32_t*)d_gpr) = s->val.u32, d_gpr++, gpr++; + else + *((uint32_t*)d_ov ) = s->val.u32, d_ov++; + break; + case nsXPTType::T_U64 : + if (gpr < 4) + *((uint64_t*)d_gpr) = s->val.u64, d_gpr+=2, gpr+=2; + else + *((uint64_t*)d_ov ) = s->val.u64, d_ov+=2, gpr=5; + break; + case nsXPTType::T_FLOAT : + if (fpr < 2) + *((float*) d_fpr) = s->val.f, d_fpr++, fpr++; + else + *((float*) d_ov ) = s->val.f, d_ov++; + break; + case nsXPTType::T_DOUBLE : + if (fpr < 2) + *((double*) d_fpr) = s->val.d, d_fpr++, fpr++; + else + *((double*) d_ov ) = s->val.d, d_ov+=2; + break; + case nsXPTType::T_BOOL : + if (gpr < 5) + *((uint32_t*)d_gpr) = s->val.b, d_gpr++, gpr++; + else + *((uint32_t*)d_ov ) = s->val.b, d_ov++; + break; + case nsXPTType::T_CHAR : + if (gpr < 5) + *((uint32_t*)d_gpr) = s->val.c, d_gpr++, gpr++; + else + *((uint32_t*)d_ov ) = s->val.c, d_ov++; + break; + case nsXPTType::T_WCHAR : + if (gpr < 5) + *((uint32_t*)d_gpr) = s->val.wc, d_gpr++, gpr++; + else + *((uint32_t*)d_ov ) = s->val.wc, d_ov++; + break; + default: + // all the others are plain pointer types + if (gpr < 5) + *((void**) d_gpr) = s->val.p, d_gpr++, gpr++; + else + *((void**) d_ov ) = s->val.p, d_ov++; + break; + } + } +} + +typedef nsresult (*vtable_func)(nsISupports *, uint32_t, uint32_t, uint32_t, uint32_t, double, double); + +// Avoid AddressSanitizer instrumentation for the next function because it +// depends on __builtin_alloca behavior and alignment that cannot be relied on +// once the function is compiled with a version of ASan that has dynamic-alloca +// instrumentation enabled. + +MOZ_ASAN_BLACKLIST +EXPORT_XPCOM_API(nsresult) +NS_InvokeByIndex(nsISupports* that, uint32_t methodIndex, + uint32_t paramCount, nsXPTCVariant* params) +{ + vtable_func *vtable = *reinterpret_cast(that); + vtable_func method = vtable[methodIndex]; + uint32_t overflow = invoke_count_words (paramCount, params); + uint32_t *stack_space = reinterpret_cast(__builtin_alloca((overflow + 8 /* 4 32-bits gpr + 2 64-bits fpr */) * 4)); + + invoke_copy_to_stack(paramCount, params, stack_space, overflow); + + uint32_t *d_gpr = stack_space + overflow; + double *d_fpr = reinterpret_cast(d_gpr + 4); + + return method(that, d_gpr[0], d_gpr[1], d_gpr[2], d_gpr[3], d_fpr[0], d_fpr[1]); +} + diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_linux_s390x.cpp b/xpcom/reflect/xptcall/md/unix/xptcinvoke_linux_s390x.cpp new file mode 100644 index 000000000..86478a850 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_linux_s390x.cpp @@ -0,0 +1,190 @@ +/* -*- 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/. */ + +/* Platform specific code to invoke XPCOM methods on native objects */ + +#include "xptcprivate.h" + + +static uint32_t +invoke_count_words(uint32_t paramCount, nsXPTCVariant* s) +{ + uint32_t overflow = 0, gpr = 1 /*this*/, fpr = 0; + for(uint32_t i = 0; i < paramCount; i++, s++) + { + if(s->IsPtrData()) + { + if (gpr < 5) gpr++; else overflow++; + continue; + } + switch(s->type) + { + case nsXPTType::T_I8 : + case nsXPTType::T_I16 : + case nsXPTType::T_I32 : + case nsXPTType::T_I64 : + if (gpr < 5) gpr++; else overflow++; + break; + case nsXPTType::T_U8 : + case nsXPTType::T_U16 : + case nsXPTType::T_U32 : + case nsXPTType::T_U64 : + if (gpr < 5) gpr++; else overflow++; + break; + case nsXPTType::T_FLOAT : + case nsXPTType::T_DOUBLE : + if (fpr < 4) fpr++; else overflow++; + break; + case nsXPTType::T_BOOL : + case nsXPTType::T_CHAR : + case nsXPTType::T_WCHAR : + if (gpr < 5) gpr++; else overflow++; + break; + default: + // all the others are plain pointer types + if (gpr < 5) gpr++; else overflow++; + break; + } + } + /* Round up number of overflow words to ensure stack + stays aligned to 8 bytes. */ + return (overflow + 1) & ~1; +} + +static void +invoke_copy_to_stack(uint32_t paramCount, nsXPTCVariant* s, uint64_t* d_ov, uint32_t overflow) +{ + uint64_t *d_gpr = d_ov + overflow; + uint64_t *d_fpr = (uint64_t *)(d_gpr + 4); + uint32_t gpr = 1 /*this*/, fpr = 0; + + for(uint32_t i = 0; i < paramCount; i++, s++) + { + if(s->IsPtrData()) + { + if (gpr < 5) + *((void**)d_gpr) = s->ptr, d_gpr++, gpr++; + else + *((void**)d_ov ) = s->ptr, d_ov++; + continue; + } + switch(s->type) + { + case nsXPTType::T_I8 : + if (gpr < 5) + *((int64_t*) d_gpr) = s->val.i8, d_gpr++, gpr++; + else + *((int64_t*) d_ov ) = s->val.i8, d_ov++; + break; + case nsXPTType::T_I16 : + if (gpr < 5) + *((int64_t*) d_gpr) = s->val.i16, d_gpr++, gpr++; + else + *((int64_t*) d_ov ) = s->val.i16, d_ov++; + break; + case nsXPTType::T_I32 : + if (gpr < 5) + *((int64_t*) d_gpr) = s->val.i32, d_gpr++, gpr++; + else + *((int64_t*) d_ov ) = s->val.i32, d_ov++; + break; + case nsXPTType::T_I64 : + if (gpr < 5) + *((int64_t*) d_gpr) = s->val.i64, d_gpr++, gpr++; + else + *((int64_t*) d_ov ) = s->val.i64, d_ov++; + break; + case nsXPTType::T_U8 : + if (gpr < 5) + *((uint64_t*) d_gpr) = s->val.u8, d_gpr++, gpr++; + else + *((uint64_t*) d_ov ) = s->val.u8, d_ov++; + break; + case nsXPTType::T_U16 : + if (gpr < 5) + *((uint64_t*)d_gpr) = s->val.u16, d_gpr++, gpr++; + else + *((uint64_t*)d_ov ) = s->val.u16, d_ov++; + break; + case nsXPTType::T_U32 : + if (gpr < 5) + *((uint64_t*)d_gpr) = s->val.u32, d_gpr++, gpr++; + else + *((uint64_t*)d_ov ) = s->val.u32, d_ov++; + break; + case nsXPTType::T_U64 : + if (gpr < 5) + *((uint64_t*)d_gpr) = s->val.u64, d_gpr++, gpr++; + else + *((uint64_t*)d_ov ) = s->val.u64, d_ov++; + break; + case nsXPTType::T_FLOAT : + if (fpr < 4) + *((float*) d_fpr) = s->val.f, d_fpr++, fpr++; + else + *(((float*) d_ov )+1) = s->val.f, d_ov++; + break; + case nsXPTType::T_DOUBLE : + if (fpr < 4) + *((double*) d_fpr) = s->val.d, d_fpr++, fpr++; + else + *((double*) d_ov ) = s->val.d, d_ov++; + break; + case nsXPTType::T_BOOL : + if (gpr < 5) + *((uint64_t*)d_gpr) = s->val.b, d_gpr++, gpr++; + else + *((uint64_t*)d_ov ) = s->val.b, d_ov++; + break; + case nsXPTType::T_CHAR : + if (gpr < 5) + *((uint64_t*)d_gpr) = s->val.c, d_gpr++, gpr++; + else + *((uint64_t*)d_ov ) = s->val.c, d_ov++; + break; + case nsXPTType::T_WCHAR : + if (gpr < 5) + *((uint64_t*)d_gpr) = s->val.wc, d_gpr++, gpr++; + else + *((uint64_t*)d_ov ) = s->val.wc, d_ov++; + break; + default: + // all the others are plain pointer types + if (gpr < 5) + *((void**) d_gpr) = s->val.p, d_gpr++, gpr++; + else + *((void**) d_ov ) = s->val.p, d_ov++; + break; + } + } +} + +typedef nsresult (*vtable_func)(nsISupports *, uint64_t, uint64_t, uint64_t, uint64_t, double, double, double, double); + +// Avoid AddressSanitizer instrumentation for the next function because it +// depends on __builtin_alloca behavior and alignment that cannot be relied on +// once the function is compiled with a version of ASan that has dynamic-alloca +// instrumentation enabled. + +MOZ_ASAN_BLACKLIST +EXPORT_XPCOM_API(nsresult) +NS_InvokeByIndex(nsISupports* that, uint32_t methodIndex, + uint32_t paramCount, nsXPTCVariant* params) +{ + vtable_func *vtable = *reinterpret_cast(that); + vtable_func method = vtable[methodIndex]; + uint64_t overflow = invoke_count_words (paramCount, params); + uint64_t *stack_space = reinterpret_cast(__builtin_alloca((overflow + 8 /* 4 64-bits gpr + 4 64-bits fpr */) * 8)); + uint64_t result; + + invoke_copy_to_stack(paramCount, params, stack_space, overflow); + + uint64_t *d_gpr = stack_space + overflow; + double *d_fpr = reinterpret_cast(d_gpr + 4); + + return method(that, d_gpr[0], d_gpr[1], d_gpr[2], d_gpr[3], d_fpr[0], d_fpr[1], d_fpr[2], d_fpr[3]); +} + diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_mips.cpp b/xpcom/reflect/xptcall/md/unix/xptcinvoke_mips.cpp new file mode 100644 index 000000000..f6585e7f0 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_mips.cpp @@ -0,0 +1,99 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * Version: MPL 1.1 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 is for MIPS using the O32 ABI. */ + +/* Platform specific code to invoke XPCOM methods on native objects */ + +#include "xptcprivate.h" + +#include + +extern "C" uint32_t +invoke_count_words(uint32_t paramCount, nsXPTCVariant* s) +{ + // Count a word for a0 even though it's never stored or loaded + // We do this only for alignment of register pairs. + uint32_t result = 1; + for (uint32_t i = 0; i < paramCount; i++, result++, s++) + { + if (s->IsPtrData()) + continue; + + switch(s->type) + { + case nsXPTType::T_I64 : + case nsXPTType::T_U64 : + case nsXPTType::T_DOUBLE : + if (result & 1) + result++; + result++; + break; + + default: + break; + } + } + return (result + 1) & ~(uint32_t)1; +} + +extern "C" void +invoke_copy_to_stack(uint32_t* d, uint32_t paramCount, + nsXPTCVariant* s) +{ + // Skip the unused a0 slot, which we keep only for register pair alignment. + d++; + + for (uint32_t i = 0; i < paramCount; i++, d++, s++) + { + if (s->IsPtrData()) + { + *((void**)d) = s->ptr; + continue; + } + + switch(s->type) + { + case nsXPTType::T_I8 : *d = (uint32_t) s->val.i8; break; + case nsXPTType::T_I16 : *d = (uint32_t) s->val.i16; break; + case nsXPTType::T_I32 : *d = (uint32_t) s->val.i32; break; + case nsXPTType::T_I64 : + if ((intptr_t)d & 4) d++; + *((int64_t*) d) = s->val.i64; d++; + break; + case nsXPTType::T_U8 : *d = (uint32_t) s->val.u8; break; + case nsXPTType::T_U16 : *d = (uint32_t) s->val.u16; break; + case nsXPTType::T_U32 : *d = (uint32_t) s->val.u32; break; + case nsXPTType::T_U64 : + if ((intptr_t)d & 4) d++; + *((uint64_t*) d) = s->val.u64; d++; + break; + case nsXPTType::T_FLOAT : *((float*) d) = s->val.f; break; + case nsXPTType::T_DOUBLE : + if ((intptr_t)d & 4) d++; + *((double*) d) = s->val.d; d++; + break; + case nsXPTType::T_BOOL : *d = (bool) s->val.b; break; + case nsXPTType::T_CHAR : *d = (char) s->val.c; break; + case nsXPTType::T_WCHAR : *d = (wchar_t) s->val.wc; break; + default: + *((void**)d) = s->val.p; + break; + } + } +} + +extern "C" nsresult _NS_InvokeByIndex(nsISupports* that, uint32_t methodIndex, + uint32_t paramCount, + nsXPTCVariant* params); + +EXPORT_XPCOM_API(nsresult) +NS_InvokeByIndex(nsISupports* that, uint32_t methodIndex, + uint32_t paramCount, nsXPTCVariant* params) +{ + return _NS_InvokeByIndex(that, methodIndex, paramCount, params); +} diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_mips64.cpp b/xpcom/reflect/xptcall/md/unix/xptcinvoke_mips64.cpp new file mode 100644 index 000000000..1dd54f96f --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_mips64.cpp @@ -0,0 +1,142 @@ +/* -*- 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/. */ + +/* Platform specific code to invoke XPCOM methods on native objects */ + +#include "xptcprivate.h" + +#if (_MIPS_SIM != _ABIN32) && (_MIPS_SIM != _ABI64) +#error "This code is for MIPS n32/n64 only" +#endif + +extern "C" uint32_t +invoke_count_words(uint32_t paramCount, nsXPTCVariant* s) +{ + return paramCount; +} + +extern "C" void +invoke_copy_to_stack(uint64_t* d, uint32_t paramCount, + nsXPTCVariant* s, uint64_t *regs) +{ +#define N_ARG_REGS 7 /* 8 regs minus 1 for "this" ptr */ + + for (uint32_t i = 0; i < paramCount; i++, s++) + { + if (s->IsPtrData()) { + if (i < N_ARG_REGS) + regs[i] = (uint64_t)s->ptr; + else + *d++ = (uint64_t)s->ptr; + continue; + } + switch (s->type) { + // + // signed types first + // + case nsXPTType::T_I8: + if (i < N_ARG_REGS) + ((int64_t*)regs)[i] = s->val.i8; + else + *d++ = s->val.i8; + break; + case nsXPTType::T_I16: + if (i < N_ARG_REGS) + ((int64_t*)regs)[i] = s->val.i16; + else + *d++ = s->val.i16; + break; + case nsXPTType::T_I32: + if (i < N_ARG_REGS) + ((int64_t*)regs)[i] = s->val.i32; + else + *d++ = s->val.i32; + break; + case nsXPTType::T_I64: + if (i < N_ARG_REGS) + ((int64_t*)regs)[i] = s->val.i64; + else + *d++ = s->val.i64; + break; + // + // unsigned types next + // + case nsXPTType::T_U8: + if (i < N_ARG_REGS) + regs[i] = s->val.u8; + else + *d++ = s->val.u8; + break; + case nsXPTType::T_U16: + if (i < N_ARG_REGS) + regs[i] = s->val.u16; + else + *d++ = s->val.u16; + break; + case nsXPTType::T_U32: + if (i < N_ARG_REGS) + // 32-bit values need to be sign-extended + // in register, so use the signed value. + regs[i] = s->val.i32; + else + *d++ = s->val.u32; + break; + case nsXPTType::T_U64: + if (i < N_ARG_REGS) + regs[i] = s->val.u64; + else + *d++ = s->val.u64; + break; + case nsXPTType::T_FLOAT: + if (i < N_ARG_REGS) + *(float*)®s[i] = s->val.f; + else + *(float*)d++ = s->val.f; + break; + case nsXPTType::T_DOUBLE: + if (i < N_ARG_REGS) + *(double*)®s[i] = s->val.d; + else + *(double*)d++ = s->val.d; + break; + case nsXPTType::T_BOOL: + if (i < N_ARG_REGS) + regs[i] = s->val.b; + else + *d++ = s->val.b; + break; + case nsXPTType::T_CHAR: + if (i < N_ARG_REGS) + regs[i] = s->val.c; + else + *d++ = s->val.c; + break; + case nsXPTType::T_WCHAR: + if (i < N_ARG_REGS) + regs[i] = s->val.wc; + else + *d++ = s->val.wc; + break; + default: + // all the others are plain pointer types + if (i < N_ARG_REGS) + regs[i] = (uint64_t)s->val.p; + else + *d++ = (uint64_t)s->val.p; + break; + } + } +} + +extern "C" nsresult _NS_InvokeByIndex(nsISupports* that, uint32_t methodIndex, + uint32_t paramCount, + nsXPTCVariant* params); + +EXPORT_XPCOM_API(nsresult) +NS_InvokeByIndex(nsISupports* that, uint32_t methodIndex, + uint32_t paramCount, nsXPTCVariant* params) +{ + return _NS_InvokeByIndex(that, methodIndex, paramCount, params); +} diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_netbsd_m68k.cpp b/xpcom/reflect/xptcall/md/unix/xptcinvoke_netbsd_m68k.cpp new file mode 100644 index 000000000..47e0e7666 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_netbsd_m68k.cpp @@ -0,0 +1,143 @@ +/* -*- 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/. */ + +/* Platform specific code to invoke XPCOM methods on native objects */ + +#include "xptcprivate.h" + +// Remember that these 'words' are 32bit DWORDS + +#if !defined(__NetBSD__) || !defined(__m68k__) +#error This code is for NetBSD/m68k only +#endif + +extern "C" { + static uint32_t + invoke_count_words(uint32_t paramCount, nsXPTCVariant* s) + { + uint32_t result = 0; + for(uint32_t i = 0; i < paramCount; i++, s++) + { + if(s->IsPtrData()) + { + result++; + continue; + } + switch(s->type) + { + case nsXPTType::T_I8 : + case nsXPTType::T_I16 : + case nsXPTType::T_I32 : + result++; + break; + case nsXPTType::T_I64 : + result+=2; + break; + case nsXPTType::T_U8 : + case nsXPTType::T_U16 : + case nsXPTType::T_U32 : + result++; + break; + case nsXPTType::T_U64 : + result+=2; + break; + case nsXPTType::T_FLOAT : + result++; + break; + case nsXPTType::T_DOUBLE : + result+=2; + break; + case nsXPTType::T_BOOL : + case nsXPTType::T_CHAR : + case nsXPTType::T_WCHAR : + result++; + break; + default: + // all the others are plain pointer types + result++; + break; + } + } + return result; + } + + static void + invoke_copy_to_stack(uint32_t* d, uint32_t paramCount, nsXPTCVariant* s) + { + for(uint32_t i = 0; i < paramCount; i++, d++, s++) + { + if(s->IsPtrData()) + { + *((void**)d) = s->ptr; + continue; + } + switch(s->type) + { + // 8 and 16 bit types should be promoted to 32 bits when copying + // onto the stack. + case nsXPTType::T_I8 : *((uint32_t*)d) = s->val.i8; break; + case nsXPTType::T_I16 : *((uint32_t*)d) = s->val.i16; break; + case nsXPTType::T_I32 : *((int32_t*) d) = s->val.i32; break; + case nsXPTType::T_I64 : *((int64_t*) d) = s->val.i64; d++; break; + case nsXPTType::T_U8 : *((uint32_t*)d) = s->val.u8; break; + case nsXPTType::T_U16 : *((uint32_t*)d) = s->val.u16; break; + case nsXPTType::T_U32 : *((uint32_t*)d) = s->val.u32; break; + case nsXPTType::T_U64 : *((uint64_t*)d) = s->val.u64; d++; break; + case nsXPTType::T_FLOAT : *((float*) d) = s->val.f; break; + case nsXPTType::T_DOUBLE : *((double*) d) = s->val.d; d++; break; + case nsXPTType::T_BOOL : *((uint32_t*)d) = s->val.b; break; + case nsXPTType::T_CHAR : *((uint32_t*)d) = s->val.c; break; + // wchar_t is an int (32 bits) on NetBSD + case nsXPTType::T_WCHAR : *((wchar_t*) d) = s->val.wc; break; + default: + // all the others are plain pointer types + *((void**)d) = s->val.p; + break; + } + } + } +} + +XPTC_PUBLIC_API(nsresult) +XPTC_InvokeByIndex(nsISupports* that, uint32_t methodIndex, + uint32_t paramCount, nsXPTCVariant* params) +{ + uint32_t result; + + __asm__ __volatile__( + "movl %4, sp@-\n\t" + "movl %3, sp@-\n\t" + "jbsr _invoke_count_words\n\t" /* count words */ + "addql #8, sp\n\t" + "lsll #2, d0\n\t" /* *= 4 */ + "movl sp, a2\n\t" /* save original sp */ + "subl d0, sp\n\t" /* make room for params */ + "movl sp, a0\n\t" + "movl %4, sp@-\n\t" + "movl %3, sp@-\n\t" + "movl a0, sp@-\n\t" + "jbsr _invoke_copy_to_stack\n\t" /* copy params */ + "addl #12, sp\n\t" + "movl %1, a0\n\t" + "movl a0@, a1\n\t" + "movl %2, d0\n\t" /* function index */ + "movl a0, d1\n\t" + "movw a1@(8,d0:l:8), a0\n\t" + "addl a0, d1\n\t" + "movl a1@(12,d0:l:8), a1\n\t" + "movl d1, sp@-\n\t" + "jbsr a1@\n\t" + "movl a2, sp\n\t" /* restore original sp */ + "movl d0, %0\n\t" + : "=g" (result) /* %0 */ + : "g" (that), /* %1 */ + "g" (methodIndex), /* %2 */ + "g" (paramCount), /* %3 */ + "g" (params) /* %4 */ + : "a0", "a1", "a2", "d0", "d1", "memory" + ); + + return result; +} diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_pa32.cpp b/xpcom/reflect/xptcall/md/unix/xptcinvoke_pa32.cpp new file mode 100644 index 000000000..0e6d28fbd --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_pa32.cpp @@ -0,0 +1,149 @@ + +/* -*- 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 "xptcprivate.h" + +#if _HPUX +#error "This code is for HP-PA RISC 32 bit mode only" +#endif + +#include + +typedef unsigned nsXPCVariant; + +extern "C" int32_t +invoke_count_bytes(nsISupports* that, const uint32_t methodIndex, + const uint32_t paramCount, const nsXPTCVariant* s) +{ + int32_t result = 4; /* variant records do not include self pointer */ + + /* counts the number of bytes required by the argument stack, + 64 bit integer, and double requires 8 bytes. All else requires + 4 bytes. + */ + + { + uint32_t indx; + for (indx = paramCount; indx > 0; --indx, ++s) + { + if (! s->IsPtrData()) + { + if (s->type == nsXPTType::T_I64 || s->type == nsXPTType::T_U64 || + s->type == nsXPTType::T_DOUBLE) + { + /* 64 bit integer and double aligned on 8 byte boundaries */ + result += (result & 4) + 8; + continue; + } + } + result += 4; /* all other cases use 4 bytes */ + } + } + result -= 72; /* existing stack buffer is 72 bytes */ + if (result < 0) + return 0; + { + /* round up to 64 bytes boundary */ + int32_t remainder = result & 63; + return (remainder == 0) ? result : (result + 64 - remainder); + } +} + +extern "C" uint32_t +invoke_copy_to_stack(uint32_t* d, + const uint32_t paramCount, nsXPTCVariant* s) +{ + + typedef struct + { + uint32_t hi; + uint32_t lo; + } DU; + + uint32_t* dest = d; + nsXPTCVariant* source = s; + /* we clobber param vars by copying stuff on stack, have to use local var */ + + uint32_t floatflags = 0; + /* flag indicating which floating point registers to load */ + + uint32_t regwords = 1; /* register 26 is reserved for ptr to 'that' */ + uint32_t indx; + + for (indx = paramCount; indx > 0; --indx, --dest, ++source) + { + if (source->IsPtrData()) + { + *((void**) dest) = source->ptr; + ++regwords; + continue; + } + switch (source->type) + { + case nsXPTType::T_I8 : *((int32_t*) dest) = source->val.i8; break; + case nsXPTType::T_I16 : *((int32_t*) dest) = source->val.i16; break; + case nsXPTType::T_I32 : *((int32_t*) dest) = source->val.i32; break; + case nsXPTType::T_I64 : + case nsXPTType::T_U64 : + if (regwords & 1) + { + /* align on double word boundary */ + --dest; + ++regwords; + } + *((uint32_t*) dest) = ((DU *) source)->lo; + *((uint32_t*) --dest) = ((DU *) source)->hi; + /* big endian - hi word in low addr */ + regwords += 2; + continue; + case nsXPTType::T_DOUBLE : + if (regwords & 1) + { + /* align on double word boundary */ + --dest; + ++regwords; + } + switch (regwords) /* load double precision float register */ + { + case 2: + floatflags |= 1; + } + *((uint32_t*) dest) = ((DU *) source)->lo; + *((uint32_t*) --dest) = ((DU *) source)->hi; + /* big endian - hi word in low addr */ + regwords += 2; + continue; + case nsXPTType::T_FLOAT : + switch (regwords) /* load single precision float register */ + { + case 1: + floatflags |= 2; + break; + case 2: + floatflags |= 4; + break; + case 3: + floatflags |= 8; + } + *((float*) dest) = source->val.f; + break; + case nsXPTType::T_U8 : *((uint32_t*) (dest)) = source->val.u8; break; + case nsXPTType::T_U16 : *((uint32_t*) (dest)) = source->val.u16; break; + case nsXPTType::T_U32 : *((uint32_t*) (dest)) = source->val.u32; break; + case nsXPTType::T_BOOL : *((uint32_t*) (dest)) = source->val.b; break; + case nsXPTType::T_CHAR : *((uint32_t*) (dest)) = source->val.c; break; + case nsXPTType::T_WCHAR : *((int32_t*) (dest)) = source->val.wc; break; + + default: + // all the others are plain pointer types + *((void**) dest) = source->val.p; + } + ++regwords; + } + return floatflags; +} + diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_ppc64_linux.cpp b/xpcom/reflect/xptcall/md/unix/xptcinvoke_ppc64_linux.cpp new file mode 100644 index 000000000..c93dc4621 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_ppc64_linux.cpp @@ -0,0 +1,97 @@ +/* -*- 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/. */ + +// Platform specific code to invoke XPCOM methods on native objects + +// The purpose of NS_InvokeByIndex() is to map a platform +// independent call to the platform ABI. To do that, +// NS_InvokeByIndex() has to determine the method to call via vtable +// access. The parameters for the method are read from the +// nsXPTCVariant* and prepared for the native ABI. + +// The PowerPC64 platform ABI can be found here: +// http://www.freestandards.org/spec/ELF/ppc64/ +// and in particular: +// http://www.freestandards.org/spec/ELF/ppc64/PPC-elf64abi-1.9.html#FUNC-CALL + +#include +#include "xptcprivate.h" + +// 8 integral parameters are passed in registers, not including 'that' +#define GPR_COUNT 7 + +// 8 floating point parameters are passed in registers, floats are +// promoted to doubles when passed in registers +#define FPR_COUNT 13 + +extern "C" uint32_t +invoke_count_words(uint32_t paramCount, nsXPTCVariant* s) +{ + return uint32_t(((paramCount * 2) + 3) & ~3); +} + +extern "C" void +invoke_copy_to_stack(uint64_t* gpregs, + double* fpregs, + uint32_t paramCount, + nsXPTCVariant* s, + uint64_t* d) +{ + uint64_t tempu64; + + for(uint32_t i = 0; i < paramCount; i++, s++) { + if(s->IsPtrData()) + tempu64 = (uint64_t) s->ptr; + else { + switch(s->type) { + case nsXPTType::T_FLOAT: break; + case nsXPTType::T_DOUBLE: break; + case nsXPTType::T_I8: tempu64 = s->val.i8; break; + case nsXPTType::T_I16: tempu64 = s->val.i16; break; + case nsXPTType::T_I32: tempu64 = s->val.i32; break; + case nsXPTType::T_I64: tempu64 = s->val.i64; break; + case nsXPTType::T_U8: tempu64 = s->val.u8; break; + case nsXPTType::T_U16: tempu64 = s->val.u16; break; + case nsXPTType::T_U32: tempu64 = s->val.u32; break; + case nsXPTType::T_U64: tempu64 = s->val.u64; break; + case nsXPTType::T_BOOL: tempu64 = s->val.b; break; + case nsXPTType::T_CHAR: tempu64 = s->val.c; break; + case nsXPTType::T_WCHAR: tempu64 = s->val.wc; break; + default: tempu64 = (uint64_t) s->val.p; break; + } + } + + if (!s->IsPtrData() && s->type == nsXPTType::T_DOUBLE) { + if (i < FPR_COUNT) + fpregs[i] = s->val.d; + else + *(double *)d = s->val.d; + } + else if (!s->IsPtrData() && s->type == nsXPTType::T_FLOAT) { + if (i < FPR_COUNT) { + fpregs[i] = s->val.f; // if passed in registers, floats are promoted to doubles + } else { + float *p = (float *)d; +#ifndef __LITTLE_ENDIAN__ + p++; +#endif + *p = s->val.f; + } + } + else { + if (i < GPR_COUNT) + gpregs[i] = tempu64; + else + *d = tempu64; + } + if (i >= 7) + d++; + } +} + +EXPORT_XPCOM_API(nsresult) +NS_InvokeByIndex(nsISupports* that, uint32_t methodIndex, + uint32_t paramCount, nsXPTCVariant* params); + diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_ppc_aix.cpp b/xpcom/reflect/xptcall/md/unix/xptcinvoke_ppc_aix.cpp new file mode 100644 index 000000000..ba2a5dab0 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_ppc_aix.cpp @@ -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/. */ + +/* Platform specific code to invoke XPCOM methods on native objects */ + +#include "xptcprivate.h" + +#ifndef AIX +#error "This code is for PowerPC only" +#endif + +extern "C" void +invoke_copy_to_stack(uint32_t* d, uint32_t paramCount, nsXPTCVariant* s, double *fprData) +{ +/* + We need to copy the parameters for this function to locals and use them + from there since the parameters occupy the same stack space as the stack + we're trying to populate. +*/ + uint32_t *l_d = d; + nsXPTCVariant *l_s = s; + uint32_t l_paramCount = paramCount, fpCount = 0; + double *l_fprData = fprData; + + typedef struct { + uint32_t hi; + uint32_t lo; + } DU; // have to move 64 bit entities as 32 bit halves since + // stack slots are not guaranteed 16 byte aligned + + for(uint32_t i = 0; i < l_paramCount; i++, l_d++, l_s++) + { + if(l_s->IsPtrData()) + { + *((void**)l_d) = l_s->ptr; + continue; + } + switch(l_s->type) + { + case nsXPTType::T_I8 : *((int32_t*) l_d) = l_s->val.i8; break; + case nsXPTType::T_I16 : *((int32_t*) l_d) = l_s->val.i16; break; + case nsXPTType::T_I32 : *((int32_t*) l_d) = l_s->val.i32; break; + case nsXPTType::T_I64 : + case nsXPTType::T_U64 : + *((uint32_t*) l_d++) = ((DU *)l_s)->hi; + *((uint32_t*) l_d) = ((DU *)l_s)->lo; + break; + case nsXPTType::T_DOUBLE : + *((uint32_t*) l_d++) = ((DU *)l_s)->hi; + *((uint32_t*) l_d) = ((DU *)l_s)->lo; + if(fpCount < 13) + l_fprData[fpCount++] = l_s->val.d; + break; + case nsXPTType::T_U8 : *((uint32_t*) l_d) = l_s->val.u8; break; + case nsXPTType::T_U16 : *((uint32_t*) l_d) = l_s->val.u16; break; + case nsXPTType::T_U32 : *((uint32_t*) l_d) = l_s->val.u32; break; + case nsXPTType::T_FLOAT : + *((float*) l_d) = l_s->val.f; + if(fpCount < 13) + l_fprData[fpCount++] = l_s->val.f; + break; + case nsXPTType::T_BOOL : *((uint32_t*) l_d) = l_s->val.b; break; + case nsXPTType::T_CHAR : *((uint32_t*) l_d) = l_s->val.c; break; + case nsXPTType::T_WCHAR : *((int32_t*) l_d) = l_s->val.wc; break; + default: + // all the others are plain pointer types + *((void**)l_d) = l_s->val.p; + break; + } + } +} + diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_ppc_aix64.cpp b/xpcom/reflect/xptcall/md/unix/xptcinvoke_ppc_aix64.cpp new file mode 100644 index 000000000..616c2f687 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_ppc_aix64.cpp @@ -0,0 +1,63 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* Platform specific code to invoke XPCOM methods on native objects */ + +#include "xptcprivate.h" + +#ifdef _AIX + +extern "C" void +invoke_copy_to_stack(uint64_t* d, uint32_t paramCount, nsXPTCVariant* s, double *fprData) +{ +/* + We need to copy the parameters for this function to locals and use them + from there since the parameters occupy the same stack space as the stack + we're trying to populate. +*/ + uint64_t *l_d = d; + nsXPTCVariant *l_s = s; + uint32_t l_paramCount = paramCount, fpCount = 0; + double *l_fprData = fprData; + + for(uint32_t i = 0; i < l_paramCount; i++, l_d++, l_s++) + { + if(l_s->IsPtrData()) + { + *l_d = (uint64_t)l_s->ptr; + continue; + } + switch(l_s->type) + { + case nsXPTType::T_I8: *l_d = (uint64_t)l_s->val.i8; break; + case nsXPTType::T_I16: *l_d = (uint64_t)l_s->val.i16; break; + case nsXPTType::T_I32: *l_d = (uint64_t)l_s->val.i32; break; + case nsXPTType::T_I64: *l_d = (uint64_t)l_s->val.i64; break; + case nsXPTType::T_U8: *l_d = (uint64_t)l_s->val.u8; break; + case nsXPTType::T_U16: *l_d = (uint64_t)l_s->val.u16; break; + case nsXPTType::T_U32: *l_d = (uint64_t)l_s->val.u32; break; + case nsXPTType::T_U64: *l_d = (uint64_t)l_s->val.u64; break; + case nsXPTType::T_BOOL: *l_d = (uint64_t)l_s->val.b; break; + case nsXPTType::T_CHAR: *l_d = (uint64_t)l_s->val.c; break; + case nsXPTType::T_WCHAR: *l_d = (uint64_t)l_s->val.wc; break; + + case nsXPTType::T_DOUBLE: + *((double*)l_d) = l_s->val.d; + if(fpCount < 13) + l_fprData[fpCount++] = l_s->val.d; + break; + case nsXPTType::T_FLOAT: + *((float*)l_d) = l_s->val.f; + if(fpCount < 13) + l_fprData[fpCount++] = l_s->val.f; + break; + default: + // all the others are plain pointer types + *l_d = (uint64_t)l_s->val.p; + break; + } + } +} +#endif + diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_ppc_linux.cpp b/xpcom/reflect/xptcall/md/unix/xptcinvoke_ppc_linux.cpp new file mode 100644 index 000000000..deb72db8f --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_ppc_linux.cpp @@ -0,0 +1,128 @@ +/* -*- 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/. */ + +// Platform specific code to invoke XPCOM methods on native objects + +// The purpose of NS_InvokeByIndex() is to map a platform +// indepenpent call to the platform ABI. To do that, +// NS_InvokeByIndex() has to determine the method to call via vtable +// access. The parameters for the method are read from the +// nsXPTCVariant* and prepared for th native ABI. For the Linux/PPC +// ABI this means that the first 8 integral and floating point +// parameters are passed in registers. + +#include "xptcprivate.h" + +// 8 integral parameters are passed in registers +#define GPR_COUNT 8 + +// With hardfloat support 8 floating point parameters are passed in registers, +// floats are promoted to doubles when passed in registers +// In Softfloat mode, everything is handled via gprs +#ifndef __NO_FPRS__ +#define FPR_COUNT 8 +#endif +extern "C" uint32_t +invoke_count_words(uint32_t paramCount, nsXPTCVariant* s) +{ + return uint32_t(((paramCount * 2) + 3) & ~3); +} + +extern "C" void +invoke_copy_to_stack(uint32_t* d, + uint32_t paramCount, + nsXPTCVariant* s, + uint32_t* gpregs, + double* fpregs) +{ + uint32_t gpr = 1; // skip one GP reg for 'that' +#ifndef __NO_FPRS__ + uint32_t fpr = 0; +#endif + uint32_t tempu32; + uint64_t tempu64; + + for(uint32_t i = 0; i < paramCount; i++, s++) { + if(s->IsPtrData()) { + if(s->type == nsXPTType::T_JSVAL) + tempu32 = (uint32_t) &s->ptr; + else + tempu32 = (uint32_t) s->ptr; + } + else { + switch(s->type) { + case nsXPTType::T_FLOAT: break; + case nsXPTType::T_DOUBLE: break; + case nsXPTType::T_I8: tempu32 = s->val.i8; break; + case nsXPTType::T_I16: tempu32 = s->val.i16; break; + case nsXPTType::T_I32: tempu32 = s->val.i32; break; + case nsXPTType::T_I64: tempu64 = s->val.i64; break; + case nsXPTType::T_U8: tempu32 = s->val.u8; break; + case nsXPTType::T_U16: tempu32 = s->val.u16; break; + case nsXPTType::T_U32: tempu32 = s->val.u32; break; + case nsXPTType::T_U64: tempu64 = s->val.u64; break; + case nsXPTType::T_BOOL: tempu32 = s->val.b; break; + case nsXPTType::T_CHAR: tempu32 = s->val.c; break; + case nsXPTType::T_WCHAR: tempu32 = s->val.wc; break; + default: tempu32 = (uint32_t) s->val.p; break; + } + } + + if (!s->IsPtrData() && s->type == nsXPTType::T_DOUBLE) { +#ifndef __NO_FPRS__ + if (fpr < FPR_COUNT) + fpregs[fpr++] = s->val.d; +#else + if (gpr & 1) + gpr++; + if ((gpr + 1) < GPR_COUNT) { + *((double*) &gpregs[gpr]) = s->val.d; + gpr += 2; + } +#endif + else { + if ((uint32_t) d & 4) d++; // doubles are 8-byte aligned on stack + *((double*) d) = s->val.d; + d += 2; + } + } + else if (!s->IsPtrData() && s->type == nsXPTType::T_FLOAT) { +#ifndef __NO_FPRS__ + if (fpr < FPR_COUNT) + fpregs[fpr++] = s->val.f; // if passed in registers, floats are promoted to doubles +#else + if (gpr < GPR_COUNT) + *((float*) &gpregs[gpr++]) = s->val.f; +#endif + else + *((float*) d++) = s->val.f; + } + else if (!s->IsPtrData() && (s->type == nsXPTType::T_I64 + || s->type == nsXPTType::T_U64)) { + if (gpr & 1) gpr++; // longlongs are aligned in odd/even register pairs, eg. r5/r6 + if ((gpr + 1) < GPR_COUNT) { + *((uint64_t*) &gpregs[gpr]) = tempu64; + gpr += 2; + } + else { + if ((uint32_t) d & 4) d++; // longlongs are 8-byte aligned on stack + *((uint64_t*) d) = tempu64; + d += 2; + } + } + else { + if (gpr < GPR_COUNT) + gpregs[gpr++] = tempu32; + else + *d++ = tempu32; + } + + } +} + +extern "C" +EXPORT_XPCOM_API(nsresult) +NS_InvokeByIndex(nsISupports* that, uint32_t methodIndex, + uint32_t paramCount, nsXPTCVariant* params); diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_ppc_netbsd.cpp b/xpcom/reflect/xptcall/md/unix/xptcinvoke_ppc_netbsd.cpp new file mode 100644 index 000000000..515e8c50b --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_ppc_netbsd.cpp @@ -0,0 +1,115 @@ +/* -*- 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/. */ + +// Platform specific code to invoke XPCOM methods on native objects + +// The purpose of XPTC_InvokeByIndex() is to map a platform +// indepenpent call to the platform ABI. To do that, +// XPTC_InvokeByIndex() has to determine the method to call via vtable +// access. The parameters for the method are read from the +// nsXPTCVariant* and prepared for the native ABI. For the Linux/PPC +// ABI this means that the first 8 integral and floating point +// parameters are passed in registers. + +#include "xptcprivate.h" + +// 8 integral parameters are passed in registers +#define GPR_COUNT 8 + +// 8 floating point parameters are passed in registers, floats are +// promoted to doubles when passed in registers +#define FPR_COUNT 8 + +extern "C" uint32_t +invoke_count_words(uint32_t paramCount, nsXPTCVariant* s) +{ + return uint32_t(((paramCount * 2) + 3) & ~3); +} + +extern "C" void +invoke_copy_to_stack(uint32_t* d, + uint32_t paramCount, + nsXPTCVariant* s, + uint32_t* gpregs, + double* fpregs) +{ + uint32_t gpr = 1; // skip one GP reg for 'that' + uint32_t fpr = 0; + uint32_t tempu32; + uint64_t tempu64; + + for(uint32_t i = 0; i < paramCount; i++, s++) { + if(s->IsPtrData()) { + if(s->type == nsXPTType::T_JSVAL) + tempu32 = (uint32_t) &(s->ptr); + else + tempu32 = (uint32_t) s->ptr; + } else { + switch(s->type) { + case nsXPTType::T_FLOAT: break; + case nsXPTType::T_DOUBLE: break; + case nsXPTType::T_I8: tempu32 = s->val.i8; break; + case nsXPTType::T_I16: tempu32 = s->val.i16; break; + case nsXPTType::T_I32: tempu32 = s->val.i32; break; + case nsXPTType::T_I64: tempu64 = s->val.i64; break; + case nsXPTType::T_U8: tempu32 = s->val.u8; break; + case nsXPTType::T_U16: tempu32 = s->val.u16; break; + case nsXPTType::T_U32: tempu32 = s->val.u32; break; + case nsXPTType::T_U64: tempu64 = s->val.u64; break; + case nsXPTType::T_BOOL: tempu32 = s->val.b; break; + case nsXPTType::T_CHAR: tempu32 = s->val.c; break; + case nsXPTType::T_WCHAR: tempu32 = s->val.wc; break; + default: tempu32 = (uint32_t) s->val.p; break; + } + } + + if (!s->IsPtrData() && s->type == nsXPTType::T_DOUBLE) { + if (fpr < FPR_COUNT) + fpregs[fpr++] = s->val.d; + else { + if ((uint32_t) d & 4) d++; // doubles are 8-byte aligned on stack + *((double*) d) = s->val.d; + d += 2; + if (gpr < GPR_COUNT) + gpr += 2; + } + } + else if (!s->IsPtrData() && s->type == nsXPTType::T_FLOAT) { + if (fpr < FPR_COUNT) + fpregs[fpr++] = s->val.f; // if passed in registers, floats are promoted to doubles + else { + *((float*) d) = s->val.f; + d += 1; + if (gpr < GPR_COUNT) + gpr += 1; + } + } + else if (!s->IsPtrData() && (s->type == nsXPTType::T_I64 + || s->type == nsXPTType::T_U64)) { + if ((gpr + 1) < GPR_COUNT) { + if (gpr & 1) gpr++; // longlongs are aligned in odd/even register pairs, eg. r5/r6 + *((uint64_t*) &gpregs[gpr]) = tempu64; + gpr += 2; + } + else { + if ((uint32_t) d & 4) d++; // longlongs are 8-byte aligned on stack + *((uint64_t*) d) = tempu64; + d += 2; + } + } + else { + if (gpr < GPR_COUNT) + gpregs[gpr++] = tempu32; + else + *d++ = tempu32; + } + + } +} + +extern "C" +XPTC_PUBLIC_API(nsresult) +XPTC_InvokeByIndex(nsISupports* that, uint32_t methodIndex, + uint32_t paramCount, nsXPTCVariant* params); diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_ppc_openbsd.cpp b/xpcom/reflect/xptcall/md/unix/xptcinvoke_ppc_openbsd.cpp new file mode 100644 index 000000000..5353220fe --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_ppc_openbsd.cpp @@ -0,0 +1,109 @@ +/* -*- 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/. */ + +// Platform specific code to invoke XPCOM methods on native objects + +// The purpose of NS_InvokeByIndex() is to map a platform +// indepenpent call to the platform ABI. To do that, +// NS_InvokeByIndex() has to determine the method to call via vtable +// access. The parameters for the method are read from the +// nsXPTCVariant* and prepared for th native ABI. For the Linux/PPC +// ABI this means that the first 8 integral and floating point +// parameters are passed in registers. + +#include "xptcprivate.h" + +// 8 integral parameters are passed in registers +#define GPR_COUNT 8 + +// 8 floating point parameters are passed in registers, floats are +// promoted to doubles when passed in registers +#define FPR_COUNT 8 + +extern "C" uint32_t +invoke_count_words(uint32_t paramCount, nsXPTCVariant* s) +{ + return uint32_t(((paramCount * 2) + 3) & ~3); +} + +extern "C" void +invoke_copy_to_stack(uint32_t* d, + uint32_t paramCount, + nsXPTCVariant* s, + uint32_t* gpregs, + double* fpregs) +{ + uint32_t gpr = 1; // skip one GP reg for 'that' + uint32_t fpr = 0; + uint32_t tempu32; + uint64_t tempu64; + + for(uint32_t i = 0; i < paramCount; i++, s++) { + if(s->IsPtrData()) { + if(s->type == nsXPTType::T_JSVAL) + tempu32 = (uint32_t) &(s->ptr); + else + tempu32 = (uint32_t) s->ptr; + } else { + switch(s->type) { + case nsXPTType::T_FLOAT: break; + case nsXPTType::T_DOUBLE: break; + case nsXPTType::T_I8: tempu32 = s->val.i8; break; + case nsXPTType::T_I16: tempu32 = s->val.i16; break; + case nsXPTType::T_I32: tempu32 = s->val.i32; break; + case nsXPTType::T_I64: tempu64 = s->val.i64; break; + case nsXPTType::T_U8: tempu32 = s->val.u8; break; + case nsXPTType::T_U16: tempu32 = s->val.u16; break; + case nsXPTType::T_U32: tempu32 = s->val.u32; break; + case nsXPTType::T_U64: tempu64 = s->val.u64; break; + case nsXPTType::T_BOOL: tempu32 = s->val.b; break; + case nsXPTType::T_CHAR: tempu32 = s->val.c; break; + case nsXPTType::T_WCHAR: tempu32 = s->val.wc; break; + default: tempu32 = (uint32_t) s->val.p; break; + } + } + + if (!s->IsPtrData() && s->type == nsXPTType::T_DOUBLE) { + if (fpr < FPR_COUNT) + fpregs[fpr++] = s->val.d; + else { + if ((uint32_t) d & 4) d++; // doubles are 8-byte aligned on stack + *((double*) d) = s->val.d; + d += 2; + } + } + else if (!s->IsPtrData() && s->type == nsXPTType::T_FLOAT) { + if (fpr < FPR_COUNT) + fpregs[fpr++] = s->val.f; // if passed in registers, floats are promoted to doubles + else + *((float*) d++) = s->val.f; + } + else if (!s->IsPtrData() && (s->type == nsXPTType::T_I64 + || s->type == nsXPTType::T_U64)) { + if ((gpr + 1) < GPR_COUNT) { + if (gpr & 1) gpr++; // longlongs are aligned in odd/even register pairs, eg. r5/r6 + *((uint64_t*) &gpregs[gpr]) = tempu64; + gpr += 2; + } + else { + if ((uint32_t) d & 4) d++; // longlongs are 8-byte aligned on stack + *((uint64_t*) d) = tempu64; + d += 2; + } + } + else { + if (gpr < GPR_COUNT) + gpregs[gpr++] = tempu32; + else + *d++ = tempu32; + } + + } +} + +extern "C" +EXPORT_XPCOM_API(nsresult) +NS_InvokeByIndex(nsISupports* that, uint32_t methodIndex, + uint32_t paramCount, nsXPTCVariant* params); diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_ppc_rhapsody.cpp b/xpcom/reflect/xptcall/md/unix/xptcinvoke_ppc_rhapsody.cpp new file mode 100644 index 000000000..1a40cfeea --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_ppc_rhapsody.cpp @@ -0,0 +1,113 @@ +/* -*- 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/. */ + +/* Platform specific code to invoke XPCOM methods on native objects */ + +#include "xptcprivate.h" + +extern "C" uint32_t +invoke_count_words(uint32_t paramCount, nsXPTCVariant* s) +{ + uint32_t result = 0; + /* fprintf(stderr,"invoke_count_words(%d,%p)\n",paramCount, s);*/ + + for(uint32_t i = 0; i < paramCount; i++, s++) + { + if(s->IsPtrData()) + { + result++; + continue; + } + switch(s->type) + { + case nsXPTType::T_I8 : + case nsXPTType::T_I16 : + case nsXPTType::T_I32 : + result++; + break; + case nsXPTType::T_I64 : + result+=2; + break; + case nsXPTType::T_U8 : + case nsXPTType::T_U16 : + case nsXPTType::T_U32 : + result++; + break; + case nsXPTType::T_U64 : + result+=2; + break; + case nsXPTType::T_FLOAT : + result++; + break; + case nsXPTType::T_DOUBLE : + result+=2; + break; + case nsXPTType::T_BOOL : + case nsXPTType::T_CHAR : + case nsXPTType::T_WCHAR : + result++; + break; + default: + // all the others are plain pointer types + result++; + break; + } + } + return result; +} + +extern "C" void +invoke_copy_to_stack(uint32_t* d, uint32_t paramCount, nsXPTCVariant* s, double *fprData) +{ + uint32_t fpCount = 0; + + /* fprintf(stderr,"invoke_copy_to_stack(%p, %d, %p, %p)\n", d, paramCount, s, fprData);*/ + + for(uint32_t i = 0; i < paramCount; i++, d++, s++) + { + if(s->IsPtrData()) + { + *((void**)d) = s->ptr; + continue; + } + switch(s->type) + { + case nsXPTType::T_I8 : *((int32_t*) d) = s->val.i8; break; + case nsXPTType::T_I16 : *((int32_t*) d) = s->val.i16; break; + case nsXPTType::T_I32 : *((int32_t*) d) = s->val.i32; break; + case nsXPTType::T_I64 : *((int64_t*) d) = s->val.i64; d++; break; + case nsXPTType::T_U8 : *((uint32_t*) d) = s->val.u8; break; + case nsXPTType::T_U16 : *((uint32_t*)d) = s->val.u16; break; + case nsXPTType::T_U32 : *((uint32_t*)d) = s->val.u32; break; + case nsXPTType::T_U64 : *((uint64_t*)d) = s->val.u64; d++; break; + case nsXPTType::T_FLOAT : *((float*) d) = s->val.f; + if (fpCount < 13) + fprData[fpCount++] = s->val.f; + break; + case nsXPTType::T_DOUBLE : *((double*) d) = s->val.d; d++; + if (fpCount < 13) + fprData[fpCount++] = s->val.d; + break; + case nsXPTType::T_BOOL : *((uint32_t*) d) = s->val.b; break; + case nsXPTType::T_CHAR : *((int32_t*) d) = s->val.c; break; + case nsXPTType::T_WCHAR : *((uint32_t*) d) = s->val.wc; break; + default: + // all the others are plain pointer types + *((void**)d) = s->val.p; + break; + } + } +} + +extern "C" nsresult _NS_InvokeByIndex(nsISupports* that, uint32_t methodIndex, + uint32_t paramCount, + nsXPTCVariant* params); + +EXPORT_XPCOM_API(nsresult) +NS_InvokeByIndex(nsISupports* that, uint32_t methodIndex, + uint32_t paramCount, nsXPTCVariant* params) +{ + return _NS_InvokeByIndex(that, methodIndex, paramCount, params); +} diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_sparc64_openbsd.cpp b/xpcom/reflect/xptcall/md/unix/xptcinvoke_sparc64_openbsd.cpp new file mode 100644 index 000000000..050f9414d --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_sparc64_openbsd.cpp @@ -0,0 +1,69 @@ +/* -*- 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/. */ + + +/* Platform specific code to invoke XPCOM methods on native objects */ + +#include "xptcprivate.h" + +#if !defined(__sparc) && !defined(__sparc__) +#error "This code is for Sparc only" +#endif + +extern "C" uint64_t +invoke_copy_to_stack(uint64_t* d, uint32_t paramCount, nsXPTCVariant* s) +{ + /* + We need to copy the parameters for this function to locals and use them + from there since the parameters occupy the same stack space as the stack + we're trying to populate. + */ + uint64_t *l_d = d; + nsXPTCVariant *l_s = s; + uint64_t l_paramCount = paramCount; + uint64_t regCount = 0; // return the number of registers to load from the stack + + for(uint64_t i = 0; i < l_paramCount; i++, l_d++, l_s++) + { + if (regCount < 5) regCount++; + + if (l_s->IsPtrData()) + { + *l_d = (uint64_t)l_s->ptr; + continue; + } + switch (l_s->type) + { + case nsXPTType::T_I8 : *((int64_t*)l_d) = l_s->val.i8; break; + case nsXPTType::T_I16 : *((int64_t*)l_d) = l_s->val.i16; break; + case nsXPTType::T_I32 : *((int64_t*)l_d) = l_s->val.i32; break; + case nsXPTType::T_I64 : *((int64_t*)l_d) = l_s->val.i64; break; + + case nsXPTType::T_U8 : *((uint64_t*)l_d) = l_s->val.u8; break; + case nsXPTType::T_U16 : *((uint64_t*)l_d) = l_s->val.u16; break; + case nsXPTType::T_U32 : *((uint64_t*)l_d) = l_s->val.u32; break; + case nsXPTType::T_U64 : *((uint64_t*)l_d) = l_s->val.u64; break; + + /* in the case of floats, we want to put the bits in to the + 64bit space right justified... floats in the parameter array on + sparcv9 use odd numbered registers.. %f1, %f3, so we have to skip + the space that would be occupied by %f0, %f2, etc. + */ + case nsXPTType::T_FLOAT : *(((float*)l_d) + 1) = l_s->val.f; break; + case nsXPTType::T_DOUBLE: *((double*)l_d) = l_s->val.d; break; + case nsXPTType::T_BOOL : *((int64_t*)l_d) = l_s->val.b; break; + case nsXPTType::T_CHAR : *((uint64_t*)l_d) = l_s->val.c; break; + case nsXPTType::T_WCHAR : *((int64_t*)l_d) = l_s->val.wc; break; + + default: + // all the others are plain pointer types + *((void**)l_d) = l_s->val.p; + break; + } + } + + return regCount; +} diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_sparc_netbsd.cpp b/xpcom/reflect/xptcall/md/unix/xptcinvoke_sparc_netbsd.cpp new file mode 100644 index 000000000..126ef1dad --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_sparc_netbsd.cpp @@ -0,0 +1,131 @@ +/* -*- 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/. */ + +/* Platform specific code to invoke XPCOM methods on native objects */ + +#include "xptcprivate.h" + +/* solaris defines __sparc for workshop compilers and + linux defines __sparc__ */ + +#if !defined(__sparc) && !defined(__sparc__) +#error "This code is for Sparc only" +#endif + +typedef unsigned nsXPCVariant; + +extern "C" uint32_t +invoke_count_words(uint32_t paramCount, nsXPTCVariant* s) +{ + uint32_t result = 0; + for(uint32_t i = 0; i < paramCount; i++, s++) + { + if(s->IsPtrData()) + { + result++; + continue; + } + switch(s->type) + { + case nsXPTType::T_I8 : + case nsXPTType::T_I16 : + case nsXPTType::T_I32 : + result++; + break; + case nsXPTType::T_I64 : + result+=2; + break; + case nsXPTType::T_U8 : + case nsXPTType::T_U16 : + case nsXPTType::T_U32 : + result++; + break; + case nsXPTType::T_U64 : + result+=2; + break; + case nsXPTType::T_FLOAT : + result++; + break; + case nsXPTType::T_DOUBLE : + result+=2; + break; + case nsXPTType::T_BOOL : + case nsXPTType::T_CHAR : + case nsXPTType::T_WCHAR : + result++; + break; + default: + // all the others are plain pointer types + result++; + break; + } + } + // nuts, I know there's a cooler way of doing this, but it's late + // now and it'll probably come to me in the morning. + if (result & 0x3) result += 4 - (result & 0x3); // ensure q-word alignment + return result; +} + +extern "C" uint32_t +invoke_copy_to_stack(uint32_t* d, uint32_t paramCount, nsXPTCVariant* s) +{ +/* + We need to copy the parameters for this function to locals and use them + from there since the parameters occupy the same stack space as the stack + we're trying to populate. +*/ + uint32_t *l_d = d; + nsXPTCVariant *l_s = s; + uint32_t l_paramCount = paramCount; + uint32_t regCount = 0; // return the number of registers to load from the stack + + typedef struct { + uint32_t hi; + uint32_t lo; + } DU; // have to move 64 bit entities as 32 bit halves since + // stack slots are not guaranteed 16 byte aligned + + for(uint32_t i = 0; i < l_paramCount; i++, l_d++, l_s++) + { + if (regCount < 5) regCount++; + if(l_s->IsPtrData()) + { + if(l_s->type == nsXPTType::T_JSVAL) + { + // On SPARC, we need to pass a pointer to HandleValue + *((void**)l_d) = &l_s->ptr; + } else + { + *((void**)l_d) = l_s->ptr; + } + continue; + } + switch(l_s->type) + { + case nsXPTType::T_I8 : *((int32_t*) l_d) = l_s->val.i8; break; + case nsXPTType::T_I16 : *((int32_t*) l_d) = l_s->val.i16; break; + case nsXPTType::T_I32 : *((int32_t*) l_d) = l_s->val.i32; break; + case nsXPTType::T_I64 : + case nsXPTType::T_U64 : + case nsXPTType::T_DOUBLE : *((uint32_t*) l_d++) = ((DU *)l_s)->hi; + if (regCount < 5) regCount++; + *((uint32_t*) l_d) = ((DU *)l_s)->lo; + break; + case nsXPTType::T_U8 : *((uint32_t*) l_d) = l_s->val.u8; break; + case nsXPTType::T_U16 : *((uint32_t*) l_d) = l_s->val.u16; break; + case nsXPTType::T_U32 : *((uint32_t*) l_d) = l_s->val.u32; break; + case nsXPTType::T_FLOAT : *((float*) l_d) = l_s->val.f; break; + case nsXPTType::T_BOOL : *((uint32_t*) l_d) = l_s->val.b; break; + case nsXPTType::T_CHAR : *((uint32_t*) l_d) = l_s->val.c; break; + case nsXPTType::T_WCHAR : *((int32_t*) l_d) = l_s->val.wc; break; + default: + // all the others are plain pointer types + *((void**)l_d) = l_s->val.p; + break; + } + } + return regCount; +} + diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_sparc_openbsd.cpp b/xpcom/reflect/xptcall/md/unix/xptcinvoke_sparc_openbsd.cpp new file mode 100644 index 000000000..9b2e4f858 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_sparc_openbsd.cpp @@ -0,0 +1,128 @@ +/* -*- 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/. */ + +/* Platform specific code to invoke XPCOM methods on native objects */ + +#include "xptcprivate.h" + +#if !defined(__sparc__) +#error "This code is for Sparc only" +#endif + +typedef unsigned nsXPCVariant; + +extern "C" uint32_t +invoke_count_words(uint32_t paramCount, nsXPTCVariant* s) +{ + uint32_t result = 0; + for(uint32_t i = 0; i < paramCount; i++, s++) + { + if(s->IsPtrData()) + { + result++; + continue; + } + switch(s->type) + { + case nsXPTType::T_I8 : + case nsXPTType::T_I16 : + case nsXPTType::T_I32 : + result++; + break; + case nsXPTType::T_I64 : + result+=2; + break; + case nsXPTType::T_U8 : + case nsXPTType::T_U16 : + case nsXPTType::T_U32 : + result++; + break; + case nsXPTType::T_U64 : + result+=2; + break; + case nsXPTType::T_FLOAT : + result++; + break; + case nsXPTType::T_DOUBLE : + result+=2; + break; + case nsXPTType::T_BOOL : + case nsXPTType::T_CHAR : + case nsXPTType::T_WCHAR : + result++; + break; + default: + // all the others are plain pointer types + result++; + break; + } + } + // nuts, I know there's a cooler way of doing this, but it's late + // now and it'll probably come to me in the morning. + if (result & 0x3) result += 4 - (result & 0x3); // ensure q-word alignment + return result; +} + +extern "C" uint32_t +invoke_copy_to_stack(uint32_t* d, uint32_t paramCount, nsXPTCVariant* s) +{ +/* + We need to copy the parameters for this function to locals and use them + from there since the parameters occupy the same stack space as the stack + we're trying to populate. +*/ + uint32_t *l_d = d; + nsXPTCVariant *l_s = s; + uint32_t l_paramCount = paramCount; + uint32_t regCount = 0; // return the number of registers to load from the stack + + typedef struct { + uint32_t hi; + uint32_t lo; + } DU; // have to move 64 bit entities as 32 bit halves since + // stack slots are not guaranteed 16 byte aligned + + for(uint32_t i = 0; i < l_paramCount; i++, l_d++, l_s++) + { + if (regCount < 5) regCount++; + if(l_s->IsPtrData()) + { + if(l_s->type == nsXPTType::T_JSVAL) + { + // On SPARC, we need to pass a pointer to HandleValue + *((void**)l_d) = &l_s->ptr; + } else + { + *((void**)l_d) = l_s->ptr; + } + continue; + } + switch(l_s->type) + { + case nsXPTType::T_I8 : *((int32_t*) l_d) = l_s->val.i8; break; + case nsXPTType::T_I16 : *((int32_t*) l_d) = l_s->val.i16; break; + case nsXPTType::T_I32 : *((int32_t*) l_d) = l_s->val.i32; break; + case nsXPTType::T_I64 : + case nsXPTType::T_U64 : + case nsXPTType::T_DOUBLE : *((uint32_t*) l_d++) = ((DU *)l_s)->hi; + if (regCount < 5) regCount++; + *((uint32_t*) l_d) = ((DU *)l_s)->lo; + break; + case nsXPTType::T_U8 : *((uint32_t*) l_d) = l_s->val.u8; break; + case nsXPTType::T_U16 : *((uint32_t*) l_d) = l_s->val.u16; break; + case nsXPTType::T_U32 : *((uint32_t*) l_d) = l_s->val.u32; break; + case nsXPTType::T_FLOAT : *((float*) l_d) = l_s->val.f; break; + case nsXPTType::T_BOOL : *((uint32_t*) l_d) = l_s->val.b; break; + case nsXPTType::T_CHAR : *((uint32_t*) l_d) = l_s->val.c; break; + case nsXPTType::T_WCHAR : *((int32_t*) l_d) = l_s->val.wc; break; + default: + // all the others are plain pointer types + *((void**)l_d) = l_s->val.p; + break; + } + } + return regCount; +} + diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_sparc_solaris.cpp b/xpcom/reflect/xptcall/md/unix/xptcinvoke_sparc_solaris.cpp new file mode 100644 index 000000000..126ef1dad --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_sparc_solaris.cpp @@ -0,0 +1,131 @@ +/* -*- 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/. */ + +/* Platform specific code to invoke XPCOM methods on native objects */ + +#include "xptcprivate.h" + +/* solaris defines __sparc for workshop compilers and + linux defines __sparc__ */ + +#if !defined(__sparc) && !defined(__sparc__) +#error "This code is for Sparc only" +#endif + +typedef unsigned nsXPCVariant; + +extern "C" uint32_t +invoke_count_words(uint32_t paramCount, nsXPTCVariant* s) +{ + uint32_t result = 0; + for(uint32_t i = 0; i < paramCount; i++, s++) + { + if(s->IsPtrData()) + { + result++; + continue; + } + switch(s->type) + { + case nsXPTType::T_I8 : + case nsXPTType::T_I16 : + case nsXPTType::T_I32 : + result++; + break; + case nsXPTType::T_I64 : + result+=2; + break; + case nsXPTType::T_U8 : + case nsXPTType::T_U16 : + case nsXPTType::T_U32 : + result++; + break; + case nsXPTType::T_U64 : + result+=2; + break; + case nsXPTType::T_FLOAT : + result++; + break; + case nsXPTType::T_DOUBLE : + result+=2; + break; + case nsXPTType::T_BOOL : + case nsXPTType::T_CHAR : + case nsXPTType::T_WCHAR : + result++; + break; + default: + // all the others are plain pointer types + result++; + break; + } + } + // nuts, I know there's a cooler way of doing this, but it's late + // now and it'll probably come to me in the morning. + if (result & 0x3) result += 4 - (result & 0x3); // ensure q-word alignment + return result; +} + +extern "C" uint32_t +invoke_copy_to_stack(uint32_t* d, uint32_t paramCount, nsXPTCVariant* s) +{ +/* + We need to copy the parameters for this function to locals and use them + from there since the parameters occupy the same stack space as the stack + we're trying to populate. +*/ + uint32_t *l_d = d; + nsXPTCVariant *l_s = s; + uint32_t l_paramCount = paramCount; + uint32_t regCount = 0; // return the number of registers to load from the stack + + typedef struct { + uint32_t hi; + uint32_t lo; + } DU; // have to move 64 bit entities as 32 bit halves since + // stack slots are not guaranteed 16 byte aligned + + for(uint32_t i = 0; i < l_paramCount; i++, l_d++, l_s++) + { + if (regCount < 5) regCount++; + if(l_s->IsPtrData()) + { + if(l_s->type == nsXPTType::T_JSVAL) + { + // On SPARC, we need to pass a pointer to HandleValue + *((void**)l_d) = &l_s->ptr; + } else + { + *((void**)l_d) = l_s->ptr; + } + continue; + } + switch(l_s->type) + { + case nsXPTType::T_I8 : *((int32_t*) l_d) = l_s->val.i8; break; + case nsXPTType::T_I16 : *((int32_t*) l_d) = l_s->val.i16; break; + case nsXPTType::T_I32 : *((int32_t*) l_d) = l_s->val.i32; break; + case nsXPTType::T_I64 : + case nsXPTType::T_U64 : + case nsXPTType::T_DOUBLE : *((uint32_t*) l_d++) = ((DU *)l_s)->hi; + if (regCount < 5) regCount++; + *((uint32_t*) l_d) = ((DU *)l_s)->lo; + break; + case nsXPTType::T_U8 : *((uint32_t*) l_d) = l_s->val.u8; break; + case nsXPTType::T_U16 : *((uint32_t*) l_d) = l_s->val.u16; break; + case nsXPTType::T_U32 : *((uint32_t*) l_d) = l_s->val.u32; break; + case nsXPTType::T_FLOAT : *((float*) l_d) = l_s->val.f; break; + case nsXPTType::T_BOOL : *((uint32_t*) l_d) = l_s->val.b; break; + case nsXPTType::T_CHAR : *((uint32_t*) l_d) = l_s->val.c; break; + case nsXPTType::T_WCHAR : *((int32_t*) l_d) = l_s->val.wc; break; + default: + // all the others are plain pointer types + *((void**)l_d) = l_s->val.p; + break; + } + } + return regCount; +} + diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_sparcv9_solaris.cpp b/xpcom/reflect/xptcall/md/unix/xptcinvoke_sparcv9_solaris.cpp new file mode 100644 index 000000000..0d2d6b0a8 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_sparcv9_solaris.cpp @@ -0,0 +1,73 @@ +/* -*- 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/. */ + + +/* Platform specific code to invoke XPCOM methods on native objects */ + +#include "xptcprivate.h" + +#if !defined(__sparc) && !defined(__sparc__) +#error "This code is for Sparc only" +#endif + +/* Prototype specifies unmangled function name */ +extern "C" uint64_t +invoke_copy_to_stack(uint64_t* d, uint32_t paramCount, nsXPTCVariant* s); + +extern "C" uint64_t +invoke_copy_to_stack(uint64_t* d, uint32_t paramCount, nsXPTCVariant* s) +{ + /* + We need to copy the parameters for this function to locals and use them + from there since the parameters occupy the same stack space as the stack + we're trying to populate. + */ + uint64_t *l_d = d; + nsXPTCVariant *l_s = s; + uint64_t l_paramCount = paramCount; + uint64_t regCount = 0; // return the number of registers to load from the stack + + for(uint64_t i = 0; i < l_paramCount; i++, l_d++, l_s++) + { + if (regCount < 5) regCount++; + + if (l_s->IsPtrData()) + { + *l_d = (uint64_t)l_s->ptr; + continue; + } + switch (l_s->type) + { + case nsXPTType::T_I8 : *((int64_t*)l_d) = l_s->val.i8; break; + case nsXPTType::T_I16 : *((int64_t*)l_d) = l_s->val.i16; break; + case nsXPTType::T_I32 : *((int64_t*)l_d) = l_s->val.i32; break; + case nsXPTType::T_I64 : *((int64_t*)l_d) = l_s->val.i64; break; + + case nsXPTType::T_U8 : *((uint64_t*)l_d) = l_s->val.u8; break; + case nsXPTType::T_U16 : *((uint64_t*)l_d) = l_s->val.u16; break; + case nsXPTType::T_U32 : *((uint64_t*)l_d) = l_s->val.u32; break; + case nsXPTType::T_U64 : *((uint64_t*)l_d) = l_s->val.u64; break; + + /* in the case of floats, we want to put the bits in to the + 64bit space right justified... floats in the parameter array on + sparcv9 use odd numbered registers.. %f1, %f3, so we have to skip + the space that would be occupied by %f0, %f2, etc. + */ + case nsXPTType::T_FLOAT : *(((float*)l_d) + 1) = l_s->val.f; break; + case nsXPTType::T_DOUBLE: *((double*)l_d) = l_s->val.d; break; + case nsXPTType::T_BOOL : *((uint64_t*)l_d) = l_s->val.b; break; + case nsXPTType::T_CHAR : *((uint64_t*)l_d) = l_s->val.c; break; + case nsXPTType::T_WCHAR : *((int64_t*)l_d) = l_s->val.wc; break; + + default: + // all the others are plain pointer types + *((void**)l_d) = l_s->val.p; + break; + } + } + + return regCount; +} diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_x86_64_solaris.cpp b/xpcom/reflect/xptcall/md/unix/xptcinvoke_x86_64_solaris.cpp new file mode 100644 index 000000000..0d3424366 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_x86_64_solaris.cpp @@ -0,0 +1,149 @@ +/* -*- 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/. */ + +// Platform specific code to invoke XPCOM methods on native objects + +#include "xptcprivate.h" +#include "alloca.h" + +// 6 integral parameters are passed in registers +const uint32_t GPR_COUNT = 6; + +// 8 floating point parameters are passed in SSE registers +const uint32_t FPR_COUNT = 8; + +// Remember that these 'words' are 64-bit long +static inline void +invoke_count_words(uint32_t paramCount, nsXPTCVariant * s, + uint32_t & nr_gpr, uint32_t & nr_fpr, uint32_t & nr_stack) +{ + nr_gpr = 1; // skip one GP register for 'that' + nr_fpr = 0; + nr_stack = 0; + + /* Compute number of eightbytes of class MEMORY. */ + for (uint32_t i = 0; i < paramCount; i++, s++) { + if (!s->IsPtrData() + && (s->type == nsXPTType::T_FLOAT || s->type == nsXPTType::T_DOUBLE)) { + if (nr_fpr < FPR_COUNT) + nr_fpr++; + else + nr_stack++; + } + else { + if (nr_gpr < GPR_COUNT) + nr_gpr++; + else + nr_stack++; + } + } +} + +static void +invoke_copy_to_stack(uint64_t * d, uint32_t paramCount, nsXPTCVariant * s, + uint64_t * gpregs, double * fpregs) +{ + uint32_t nr_gpr = 1; // skip one GP register for 'that' + uint32_t nr_fpr = 0; + uint64_t value; + + for (uint32_t i = 0; i < paramCount; i++, s++) { + if (s->IsPtrData()) + value = (uint64_t) s->ptr; + else { + switch (s->type) { + case nsXPTType::T_FLOAT: break; + case nsXPTType::T_DOUBLE: break; + case nsXPTType::T_I8: value = s->val.i8; break; + case nsXPTType::T_I16: value = s->val.i16; break; + case nsXPTType::T_I32: value = s->val.i32; break; + case nsXPTType::T_I64: value = s->val.i64; break; + case nsXPTType::T_U8: value = s->val.u8; break; + case nsXPTType::T_U16: value = s->val.u16; break; + case nsXPTType::T_U32: value = s->val.u32; break; + case nsXPTType::T_U64: value = s->val.u64; break; + case nsXPTType::T_BOOL: value = s->val.b; break; + case nsXPTType::T_CHAR: value = s->val.c; break; + case nsXPTType::T_WCHAR: value = s->val.wc; break; + default: value = (uint64_t) s->val.p; break; + } + } + + if (!s->IsPtrData() && s->type == nsXPTType::T_DOUBLE) { + if (nr_fpr < FPR_COUNT) + fpregs[nr_fpr++] = s->val.d; + else { + *((double *)d) = s->val.d; + d++; + } + } + else if (!s->IsPtrData() && s->type == nsXPTType::T_FLOAT) { + if (nr_fpr < FPR_COUNT) + // The value in %xmm register is already prepared to + // be retrieved as a float. Therefore, we pass the + // value verbatim, as a double without conversion. + fpregs[nr_fpr++] = s->val.d; + else { + *((float *)d) = s->val.f; + d++; + } + } + else { + if (nr_gpr < GPR_COUNT) + gpregs[nr_gpr++] = value; + else + *d++ = value; + } + } +} + +// Avoid AddressSanitizer instrumentation for the next function because it +// depends on __builtin_alloca behavior and alignment that cannot be relied on +// once the function is compiled with a version of ASan that has dynamic-alloca +// instrumentation enabled. + +MOZ_ASAN_BLACKLIST +EXPORT_XPCOM_API(nsresult) +NS_InvokeByIndex(nsISupports * that, uint32_t methodIndex, + uint32_t paramCount, nsXPTCVariant * params) +{ + uint32_t nr_gpr, nr_fpr, nr_stack; + invoke_count_words(paramCount, params, nr_gpr, nr_fpr, nr_stack); + + // Stack, if used, must be 16-bytes aligned + if (nr_stack) + nr_stack = (nr_stack + 1) & ~1; + + // Load parameters to stack, if necessary + uint64_t *stack = (uint64_t *) __builtin_alloca(nr_stack * 8); + uint64_t gpregs[GPR_COUNT]; + double fpregs[FPR_COUNT]; + invoke_copy_to_stack(stack, paramCount, params, gpregs, fpregs); + + switch (nr_fpr) { + case 8: asm("movupd %0, %xmm7" : : "xmm7" (fpregs[7])); + case 7: asm("movupd %0, %xmm6" : : "xmm6" (fpregs[6])); + case 6: asm("movupd %0, %xmm5" : : "xmm5" (fpregs[5])); + case 5: asm("movupd %0, %xmm4" : : "xmm4" (fpregs[4])); + case 4: asm("movupd %0, %xmm3" : : "xmm3" (fpregs[3])); + case 3: asm("movupd %0, %xmm2" : : "xmm2" (fpregs[2])); + case 2: asm("movupd %0, %xmm1" : : "xmm1" (fpregs[1])); + case 1: asm("movupd %0, %xmm0" : : "xmm0" (fpregs[0])); + case 0:; + } + + // Ensure that assignments to SSE registers won't be optimized away + asm("" ::: "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5", "xmm6", "xmm7"); + + // Get pointer to method + uint64_t methodAddress = *((uint64_t *)that); + methodAddress += 16 + 8 * methodIndex; + methodAddress = *((uint64_t *)methodAddress); + + typedef uint32_t (*Method)(uint64_t, uint64_t, uint64_t, uint64_t, uint64_t, uint64_t); + uint32_t result = ((Method)methodAddress)((uint64_t)that, gpregs[1], gpregs[2], gpregs[3], gpregs[4], gpregs[5]); + return result; +} diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_x86_64_unix.cpp b/xpcom/reflect/xptcall/md/unix/xptcinvoke_x86_64_unix.cpp new file mode 100644 index 000000000..08e519889 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_x86_64_unix.cpp @@ -0,0 +1,188 @@ +/* -*- 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/. */ + +// Platform specific code to invoke XPCOM methods on native objects + +#include "xptcprivate.h" + +// 6 integral parameters are passed in registers +const uint32_t GPR_COUNT = 6; + +// 8 floating point parameters are passed in SSE registers +const uint32_t FPR_COUNT = 8; + +// Remember that these 'words' are 64-bit long +static inline void +invoke_count_words(uint32_t paramCount, nsXPTCVariant * s, + uint32_t & nr_stack) +{ + uint32_t nr_gpr; + uint32_t nr_fpr; + nr_gpr = 1; // skip one GP register for 'that' + nr_fpr = 0; + nr_stack = 0; + + /* Compute number of eightbytes of class MEMORY. */ + for (uint32_t i = 0; i < paramCount; i++, s++) { + if (!s->IsPtrData() + && (s->type == nsXPTType::T_FLOAT || s->type == nsXPTType::T_DOUBLE)) { + if (nr_fpr < FPR_COUNT) + nr_fpr++; + else + nr_stack++; + } + else { + if (nr_gpr < GPR_COUNT) + nr_gpr++; + else + nr_stack++; + } + } +} + +static void +invoke_copy_to_stack(uint64_t * d, uint32_t paramCount, nsXPTCVariant * s, + uint64_t * gpregs, double * fpregs) +{ + uint32_t nr_gpr = 1u; // skip one GP register for 'that' + uint32_t nr_fpr = 0u; + uint64_t value = 0u; + + for (uint32_t i = 0; i < paramCount; i++, s++) { + if (s->IsPtrData()) + value = (uint64_t) s->ptr; + else { + switch (s->type) { + case nsXPTType::T_FLOAT: break; + case nsXPTType::T_DOUBLE: break; + case nsXPTType::T_I8: value = s->val.i8; break; + case nsXPTType::T_I16: value = s->val.i16; break; + case nsXPTType::T_I32: value = s->val.i32; break; + case nsXPTType::T_I64: value = s->val.i64; break; + case nsXPTType::T_U8: value = s->val.u8; break; + case nsXPTType::T_U16: value = s->val.u16; break; + case nsXPTType::T_U32: value = s->val.u32; break; + case nsXPTType::T_U64: value = s->val.u64; break; + case nsXPTType::T_BOOL: value = s->val.b; break; + case nsXPTType::T_CHAR: value = s->val.c; break; + case nsXPTType::T_WCHAR: value = s->val.wc; break; + default: value = (uint64_t) s->val.p; break; + } + } + + if (!s->IsPtrData() && s->type == nsXPTType::T_DOUBLE) { + if (nr_fpr < FPR_COUNT) + fpregs[nr_fpr++] = s->val.d; + else { + *((double *)d) = s->val.d; + d++; + } + } + else if (!s->IsPtrData() && s->type == nsXPTType::T_FLOAT) { + if (nr_fpr < FPR_COUNT) + // The value in %xmm register is already prepared to + // be retrieved as a float. Therefore, we pass the + // value verbatim, as a double without conversion. + fpregs[nr_fpr++] = s->val.d; + else { + *((float *)d) = s->val.f; + d++; + } + } + else { + if (nr_gpr < GPR_COUNT) + gpregs[nr_gpr++] = value; + else + *d++ = value; + } + } +} + +// Disable avx for the next function to allow compilation with +// -march=native on new machines, or similar hardcoded -march options. +// Having avx enabled appears to change the alignment behavior of alloca +// (apparently adding an extra 16 bytes) of padding/alignment (and using +// 32-byte alignment instead of 16-byte). This seems to be the best +// available workaround, given that this code, which should perhaps +// better be written in assembly, is written in C++. +#ifndef __clang__ +#pragma GCC push_options +#pragma GCC target ("no-avx") +#endif + +// Avoid AddressSanitizer instrumentation for the next function because it +// depends on __builtin_alloca behavior and alignment that cannot be relied on +// once the function is compiled with a version of ASan that has dynamic-alloca +// instrumentation enabled. + +MOZ_ASAN_BLACKLIST +EXPORT_XPCOM_API(nsresult) +NS_InvokeByIndex(nsISupports * that, uint32_t methodIndex, + uint32_t paramCount, nsXPTCVariant * params) +{ + uint32_t nr_stack; + invoke_count_words(paramCount, params, nr_stack); + + // Stack, if used, must be 16-bytes aligned + if (nr_stack) + nr_stack = (nr_stack + 1) & ~1; + + // Load parameters to stack, if necessary + uint64_t *stack = (uint64_t *) __builtin_alloca(nr_stack * 8); + uint64_t gpregs[GPR_COUNT]; + double fpregs[FPR_COUNT]; + invoke_copy_to_stack(stack, paramCount, params, gpregs, fpregs); + + // We used to have switches to make sure we would only load the registers + // that are needed for this call. That produced larger code that was + // not faster in practice. It also caused compiler warnings about the + // variables being used uninitialized. + // We now just load every every register. There could still be a warning + // from a memory analysis tools that we are loading uninitialized stack + // positions. + + // FIXME: this function depends on the above __builtin_alloca placing + // the array in the correct spot for the ABI. + + // Load FPR registers from fpregs[] + double d0, d1, d2, d3, d4, d5, d6, d7; + + d7 = fpregs[7]; + d6 = fpregs[6]; + d5 = fpregs[5]; + d4 = fpregs[4]; + d3 = fpregs[3]; + d2 = fpregs[2]; + d1 = fpregs[1]; + d0 = fpregs[0]; + + // Load GPR registers from gpregs[] + uint64_t a0, a1, a2, a3, a4, a5; + + a5 = gpregs[5]; + a4 = gpregs[4]; + a3 = gpregs[3]; + a2 = gpregs[2]; + a1 = gpregs[1]; + a0 = (uint64_t) that; + + // Get pointer to method + uint64_t methodAddress = *((uint64_t *)that); + methodAddress += 8 * methodIndex; + methodAddress = *((uint64_t *)methodAddress); + + typedef nsresult (*Method)(uint64_t, uint64_t, uint64_t, uint64_t, + uint64_t, uint64_t, double, double, double, + double, double, double, double, double); + nsresult result = ((Method)methodAddress)(a0, a1, a2, a3, a4, a5, + d0, d1, d2, d3, d4, d5, + d6, d7); + return result; +} + +#ifndef __clang__ +#pragma GCC pop_options +#endif diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_x86_solaris.cpp b/xpcom/reflect/xptcall/md/unix/xptcinvoke_x86_solaris.cpp new file mode 100644 index 000000000..f545fcc99 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_x86_solaris.cpp @@ -0,0 +1,67 @@ +/* -*- 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/. */ + +/* Platform specific code to invoke XPCOM methods on native objects */ + +#include "xptcprivate.h" + +extern "C" { + +// Remember that these 'words' are 32bit DWORDS + +uint32_t +invoke_count_words(uint32_t paramCount, nsXPTCVariant* s) +{ + uint32_t result = 0; + for(uint32_t i = 0; i < paramCount; i++, s++) + { + if(s->IsPtrData()) + { + result++; + continue; + } + result++; + switch(s->type) + { + case nsXPTType::T_I64 : + case nsXPTType::T_U64 : + case nsXPTType::T_DOUBLE : + result++; + break; + } + } + return result; +} + +void +invoke_copy_to_stack(uint32_t paramCount, nsXPTCVariant* s, uint32_t* d) +{ + for(uint32_t i = 0; i < paramCount; i++, d++, s++) + { + if(s->IsPtrData()) + { + *((void**)d) = s->ptr; + continue; + } + +/* XXX: the following line is here (rather than as the default clause in + * the following switch statement) so that the Sun native compiler + * will generate the correct assembly code on the Solaris Intel + * platform. See the comments in bug #28817 for more details. + */ + + *((void**)d) = s->val.p; + + switch(s->type) + { + case nsXPTType::T_I64 : *((int64_t*) d) = s->val.i64; d++; break; + case nsXPTType::T_U64 : *((uint64_t*)d) = s->val.u64; d++; break; + case nsXPTType::T_DOUBLE : *((double*) d) = s->val.d; d++; break; + } + } +} + +} diff --git a/xpcom/reflect/xptcall/md/unix/xptcstubs_aarch64.cpp b/xpcom/reflect/xptcall/md/unix/xptcstubs_aarch64.cpp new file mode 100644 index 000000000..571fb42a2 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcstubs_aarch64.cpp @@ -0,0 +1,219 @@ +/* -*- 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 "xptcprivate.h" +#include "xptiprivate.h" + +#ifndef __AARCH64EL__ +#error "Only little endian compatibility was tested" +#endif + +/* + * This is for AArch64 ABI + * + * When we're called, the "gp" registers are stored in gprData and + * the "fp" registers are stored in fprData. Each array has 8 regs + * but first reg in gprData is a placeholder for 'self'. + */ +extern "C" nsresult +PrepareAndDispatch(nsXPTCStubBase* self, uint32_t methodIndex, uint64_t* args, + uint64_t *gprData, double *fprData) +{ +#define PARAM_BUFFER_COUNT 16 +#define PARAM_GPR_COUNT 8 +#define PARAM_FPR_COUNT 8 + + nsXPTCMiniVariant paramBuffer[PARAM_BUFFER_COUNT]; + nsXPTCMiniVariant* dispatchParams = NULL; + const nsXPTMethodInfo* info; + nsresult result = NS_ERROR_FAILURE; + + NS_ASSERTION(self,"no self"); + + self->mEntry->GetMethodInfo(uint16_t(methodIndex), &info); + NS_ASSERTION(info,"no method info"); + + uint32_t paramCount = info->GetParamCount(); + + // setup variant array pointer + if (paramCount > PARAM_BUFFER_COUNT) { + dispatchParams = new nsXPTCMiniVariant[paramCount]; + } else { + dispatchParams = paramBuffer; + } + NS_ASSERTION(dispatchParams,"no place for params"); + + uint64_t* ap = args; + uint32_t next_gpr = 1; // skip first arg which is 'self' + uint32_t next_fpr = 0; + for (uint32_t i = 0; i < paramCount; i++) { + const nsXPTParamInfo& param = info->GetParam(i); + const nsXPTType& type = param.GetType(); + nsXPTCMiniVariant* dp = &dispatchParams[i]; + + if (param.IsOut() || !type.IsArithmetic()) { + if (next_gpr < PARAM_GPR_COUNT) { + dp->val.p = (void*)gprData[next_gpr++]; + } else { + dp->val.p = (void*)*ap++; + } + continue; + } + + switch (type) { + case nsXPTType::T_I8: + if (next_gpr < PARAM_GPR_COUNT) { + dp->val.i8 = (int8_t)gprData[next_gpr++]; + } else { + dp->val.i8 = (int8_t)*ap++; + } + break; + + case nsXPTType::T_I16: + if (next_gpr < PARAM_GPR_COUNT) { + dp->val.i16 = (int16_t)gprData[next_gpr++]; + } else { + dp->val.i16 = (int16_t)*ap++; + } + break; + + case nsXPTType::T_I32: + if (next_gpr < PARAM_GPR_COUNT) { + dp->val.i32 = (int32_t)gprData[next_gpr++]; + } else { + dp->val.i32 = (int32_t)*ap++; + } + break; + + case nsXPTType::T_I64: + if (next_gpr < PARAM_GPR_COUNT) { + dp->val.i64 = (int64_t)gprData[next_gpr++]; + } else { + dp->val.i64 = (int64_t)*ap++; + } + break; + + case nsXPTType::T_U8: + if (next_gpr < PARAM_GPR_COUNT) { + dp->val.u8 = (uint8_t)gprData[next_gpr++]; + } else { + dp->val.u8 = (uint8_t)*ap++; + } + break; + + case nsXPTType::T_U16: + if (next_gpr < PARAM_GPR_COUNT) { + dp->val.u16 = (uint16_t)gprData[next_gpr++]; + } else { + dp->val.u16 = (uint16_t)*ap++; + } + break; + + case nsXPTType::T_U32: + if (next_gpr < PARAM_GPR_COUNT) { + dp->val.u32 = (uint32_t)gprData[next_gpr++]; + } else { + dp->val.u32 = (uint32_t)*ap++; + } + break; + + case nsXPTType::T_U64: + if (next_gpr < PARAM_GPR_COUNT) { + dp->val.u64 = (uint64_t)gprData[next_gpr++]; + } else { + dp->val.u64 = (uint64_t)*ap++; + } + break; + + case nsXPTType::T_FLOAT: + if (next_fpr < PARAM_FPR_COUNT) { + memcpy(&dp->val.f, &fprData[next_fpr++], sizeof(dp->val.f)); + } else { + memcpy(&dp->val.f, ap++, sizeof(dp->val.f)); + } + break; + + case nsXPTType::T_DOUBLE: + if (next_fpr < PARAM_FPR_COUNT) { + memcpy(&dp->val.d, &fprData[next_fpr++], sizeof(dp->val.d)); + } else { + memcpy(&dp->val.d, ap++, sizeof(dp->val.d)); + } + break; + + case nsXPTType::T_BOOL: + if (next_gpr < PARAM_GPR_COUNT) { + dp->val.b = (bool)gprData[next_gpr++]; + } else { + dp->val.b = (bool)*ap++; + } + break; + + case nsXPTType::T_CHAR: + if (next_gpr < PARAM_GPR_COUNT) { + dp->val.c = (char)gprData[next_gpr++]; + } else { + dp->val.c = (char)*ap++; + } + break; + + case nsXPTType::T_WCHAR: + if (next_gpr < PARAM_GPR_COUNT) { + dp->val.wc = (wchar_t)gprData[next_gpr++]; + } else { + dp->val.wc = (wchar_t)*ap++; + } + break; + + default: + NS_ASSERTION(0, "bad type"); + break; + } + } + + result = self->mOuter->CallMethod((uint16_t)methodIndex, info, dispatchParams); + + if (dispatchParams != paramBuffer) { + delete [] dispatchParams; + } + + return result; +} + +// Load w17 with the constant 'n' and branch to SharedStub(). +# define STUB_ENTRY(n) \ + __asm__ ( \ + ".section \".text\" \n\t" \ + ".align 2\n\t" \ + ".if "#n" < 10 \n\t" \ + ".globl _ZN14nsXPTCStubBase5Stub"#n"Ev \n\t" \ + ".hidden _ZN14nsXPTCStubBase5Stub"#n"Ev \n\t" \ + ".type _ZN14nsXPTCStubBase5Stub"#n"Ev,@function \n\n" \ + "_ZN14nsXPTCStubBase5Stub"#n"Ev: \n\t" \ + ".elseif "#n" < 100 \n\t" \ + ".globl _ZN14nsXPTCStubBase6Stub"#n"Ev \n\t" \ + ".hidden _ZN14nsXPTCStubBase6Stub"#n"Ev \n\t" \ + ".type _ZN14nsXPTCStubBase6Stub"#n"Ev,@function \n\n" \ + "_ZN14nsXPTCStubBase6Stub"#n"Ev: \n\t" \ + ".elseif "#n" < 1000 \n\t" \ + ".globl _ZN14nsXPTCStubBase7Stub"#n"Ev \n\t" \ + ".hidden _ZN14nsXPTCStubBase7Stub"#n"Ev \n\t" \ + ".type _ZN14nsXPTCStubBase7Stub"#n"Ev,@function \n\n" \ + "_ZN14nsXPTCStubBase7Stub"#n"Ev: \n\t" \ + ".else \n\t" \ + ".err \"stub number "#n" >= 1000 not yet supported\"\n" \ + ".endif \n\t" \ + "mov w17,#"#n" \n\t" \ + "b SharedStub \n" \ +); + +#define SENTINEL_ENTRY(n) \ + nsresult nsXPTCStubBase::Sentinel##n() \ +{ \ + NS_ASSERTION(0,"nsXPTCStubBase::Sentinel called"); \ + return NS_ERROR_NOT_IMPLEMENTED; \ +} + +#include "xptcstubsdef.inc" diff --git a/xpcom/reflect/xptcall/md/unix/xptcstubs_alpha_openbsd.cpp b/xpcom/reflect/xptcall/md/unix/xptcstubs_alpha_openbsd.cpp new file mode 100644 index 000000000..d538767e1 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcstubs_alpha_openbsd.cpp @@ -0,0 +1,189 @@ +/* -*- 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/. */ + +/* Implement shared vtbl methods. */ + +#include "xptcprivate.h" +#include "xptiprivate.h" + +/* Prototype specifies unmangled function name and disables unused warning */ +static nsresult +PrepareAndDispatch(nsXPTCStubBase* self, uint32_t methodIndex, uint64_t* args) +__asm__("PrepareAndDispatch") ATTRIBUTE_USED; + +static nsresult +PrepareAndDispatch(nsXPTCStubBase* self, uint32_t methodIndex, uint64_t* args) +{ + const uint8_t PARAM_BUFFER_COUNT = 16; + const uint8_t NUM_ARG_REGS = 6-1; // -1 for "this" pointer + + nsXPTCMiniVariant paramBuffer[PARAM_BUFFER_COUNT]; + nsXPTCMiniVariant* dispatchParams = nullptr; + const nsXPTMethodInfo* info; + uint8_t paramCount; + uint8_t i; + nsresult result = NS_ERROR_FAILURE; + + NS_ASSERTION(self,"no self"); + + self->mEntry->GetMethodInfo(uint16_t(methodIndex), &info); + + paramCount = info->GetParamCount(); + + // setup variant array pointer + if(paramCount > PARAM_BUFFER_COUNT) + dispatchParams = new nsXPTCMiniVariant[paramCount]; + else + dispatchParams = paramBuffer; + + NS_ASSERTION(dispatchParams,"no place for params"); + if (!dispatchParams) + return NS_ERROR_OUT_OF_MEMORY; + + // args[0] to args[NUM_ARG_REGS] hold floating point register values + uint64_t* ap = args + NUM_ARG_REGS; + for(i = 0; i < paramCount; i++, ap++) + { + const nsXPTParamInfo& param = info->GetParam(i); + const nsXPTType& type = param.GetType(); + nsXPTCMiniVariant* dp = &dispatchParams[i]; + + if(param.IsOut() || !type.IsArithmetic()) + { + dp->val.p = (void*) *ap; + continue; + } + // else + switch(type) + { + case nsXPTType::T_I8 : dp->val.i8 = (int8_t) *ap; break; + case nsXPTType::T_I16 : dp->val.i16 = (int16_t) *ap; break; + case nsXPTType::T_I32 : dp->val.i32 = (int32_t) *ap; break; + case nsXPTType::T_I64 : dp->val.i64 = (int64_t) *ap; break; + case nsXPTType::T_U8 : dp->val.u8 = (uint8_t) *ap; break; + case nsXPTType::T_U16 : dp->val.u16 = (uint16_t) *ap; break; + case nsXPTType::T_U32 : dp->val.u32 = (uint32_t) *ap; break; + case nsXPTType::T_U64 : dp->val.u64 = (uint64_t) *ap; break; + case nsXPTType::T_FLOAT : + if(i < NUM_ARG_REGS) + { + // floats passed via registers are stored as doubles + // in the first NUM_ARG_REGS entries in args + dp->val.u64 = (uint64_t) args[i]; + dp->val.f = (float) dp->val.d; // convert double to float + } + else + dp->val.u32 = (uint32_t) *ap; + break; + case nsXPTType::T_DOUBLE : + // doubles passed via registers are also stored + // in the first NUM_ARG_REGS entries in args + dp->val.u64 = (i < NUM_ARG_REGS) ? args[i] : *ap; + break; + case nsXPTType::T_BOOL : dp->val.b = (bool) *ap; break; + case nsXPTType::T_CHAR : dp->val.c = (char) *ap; break; + case nsXPTType::T_WCHAR : dp->val.wc = (char16_t) *ap; break; + default: + NS_ERROR("bad type"); + break; + } + } + + result = self->mOuter->CallMethod((uint16_t)methodIndex, info, dispatchParams); + + if(dispatchParams != paramBuffer) + delete [] dispatchParams; + + return result; +} + +/* + * SharedStub() + * Collects arguments and calls PrepareAndDispatch. The "methodIndex" is + * passed to this function via $1 to preserve the argument registers. + */ +__asm__( + "#### SharedStub ####\n" +".text\n\t" + ".align 5\n\t" + ".ent SharedStub\n" +"SharedStub:\n\t" + ".frame $30,96,$26,0\n\t" + ".mask 0x4000000,-96\n\t" + "ldgp $29,0($27)\n" +"$SharedStub..ng:\n\t" + "subq $30,96,$30\n\t" + "stq $26,0($30)\n\t" + ".prologue 1\n\t" + + /* + * Store arguments passed via registers to the stack. + * Floating point registers are stored as doubles and converted + * to floats in PrepareAndDispatch if necessary. + */ + "stt $f17,16($30)\n\t" /* floating point registers */ + "stt $f18,24($30)\n\t" + "stt $f19,32($30)\n\t" + "stt $f20,40($30)\n\t" + "stt $f21,48($30)\n\t" + "stq $17,56($30)\n\t" /* integer registers */ + "stq $18,64($30)\n\t" + "stq $19,72($30)\n\t" + "stq $20,80($30)\n\t" + "stq $21,88($30)\n\t" + + /* + * Call PrepareAndDispatch function. + */ + "bis $1,$1,$17\n\t" /* pass "methodIndex" */ + "addq $30,16,$18\n\t" /* pass "args" */ + "bsr $26,$PrepareAndDispatch..ng\n\t" + + "ldq $26,0($30)\n\t" + "addq $30,96,$30\n\t" + "ret $31,($26),1\n\t" + ".end SharedStub" + ); + +/* + * nsresult nsXPTCStubBase::Stub##n() + * Sets register $1 to "methodIndex" and jumps to SharedStub. + */ +#define STUB_MANGLED_ENTRY(n, symbol) \ + "#### Stub"#n" ####" "\n\t" \ + ".text" "\n\t" \ + ".align 5" "\n\t" \ + ".globl " symbol "\n\t" \ + ".ent " symbol "\n" \ +symbol ":" "\n\t" \ + ".frame $30,0,$26,0" "\n\t" \ + "ldgp $29,0($27)" "\n" \ +"$" symbol "..ng:" "\n\t" \ + ".prologue 1" "\n\t" \ + "lda $1,"#n "\n\t" \ + "br $31,$SharedStub..ng" "\n\t" \ + ".end " symbol + +#define STUB_ENTRY(n) \ +__asm__( \ + ".if "#n" < 10" "\n\t" \ + STUB_MANGLED_ENTRY(n, "_ZN14nsXPTCStubBase5Stub"#n"Ev") "\n\t" \ + ".elseif "#n" < 100" "\n\t" \ + STUB_MANGLED_ENTRY(n, "_ZN14nsXPTCStubBase6Stub"#n"Ev") "\n\t" \ + ".elseif "#n" < 1000" "\n\t" \ + STUB_MANGLED_ENTRY(n, "_ZN14nsXPTCStubBase7Stub"#n"Ev") "\n\t" \ + ".else" "\n\t" \ + ".err \"Stub"#n" >= 1000 not yet supported.\"" "\n\t" \ + ".endif" \ + ); + +#define SENTINEL_ENTRY(n) \ +nsresult nsXPTCStubBase::Sentinel##n() \ +{ \ + NS_ERROR("nsXPTCStubBase::Sentinel called"); \ + return NS_ERROR_NOT_IMPLEMENTED; \ +} + +#include "xptcstubsdef.inc" diff --git a/xpcom/reflect/xptcall/md/unix/xptcstubs_arm.cpp b/xpcom/reflect/xptcall/md/unix/xptcstubs_arm.cpp new file mode 100644 index 000000000..ef4f8514d --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcstubs_arm.cpp @@ -0,0 +1,238 @@ +/* -*- 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/. */ + +/* Implement shared vtbl methods. */ + +#include "xptcprivate.h" +#include "xptiprivate.h" + +#if !defined(__arm__) && !(defined(LINUX) || defined(ANDROID) || defined(XP_DARWIN)) +#error "This code is for Linux/iOS ARM only. Please check if it works for you, too.\nDepends strongly on gcc behaviour." +#endif + +/* Specify explicitly a symbol for this function, don't try to guess the c++ mangled symbol. */ +static nsresult PrepareAndDispatch(nsXPTCStubBase* self, uint32_t methodIndex, uint32_t* args) asm("_PrepareAndDispatch") +ATTRIBUTE_USED; + +#ifdef __ARM_EABI__ +#define DOUBLEWORD_ALIGN(p) ((uint32_t *)((((uint32_t)(p)) + 7) & 0xfffffff8)) +#else +#define DOUBLEWORD_ALIGN(p) (p) +#endif + +// Apple's iOS toolchain is lame. +#ifdef __APPLE__ +#define GNU(str) +#define APPLE(str) str +#define UNDERSCORE "__" +#else +#define GNU(str) str +#define APPLE(str) +#define UNDERSCORE "_" +#endif + +#ifdef __thumb__ +#define THUMB_FUNC ".thumb_func\n" +#else +#define THUMB_FUNC +#endif + +static nsresult +PrepareAndDispatch(nsXPTCStubBase* self, uint32_t methodIndex, uint32_t* args) +{ +#define PARAM_BUFFER_COUNT 16 + + nsXPTCMiniVariant paramBuffer[PARAM_BUFFER_COUNT]; + nsXPTCMiniVariant* dispatchParams = nullptr; + const nsXPTMethodInfo* info; + uint8_t paramCount; + uint8_t i; + nsresult result = NS_ERROR_FAILURE; + + NS_ASSERTION(self,"no self"); + + self->mEntry->GetMethodInfo(uint16_t(methodIndex), &info); + paramCount = info->GetParamCount(); + + // setup variant array pointer + if(paramCount > PARAM_BUFFER_COUNT) + dispatchParams = new nsXPTCMiniVariant[paramCount]; + else + dispatchParams = paramBuffer; + NS_ASSERTION(dispatchParams,"no place for params"); + + uint32_t* ap = args; + for(i = 0; i < paramCount; i++, ap++) + { + const nsXPTParamInfo& param = info->GetParam(i); + const nsXPTType& type = param.GetType(); + nsXPTCMiniVariant* dp = &dispatchParams[i]; + + if(param.IsOut() || !type.IsArithmetic()) + { + dp->val.p = (void*) *ap; + continue; + } + // else + switch(type) + { + case nsXPTType::T_I8 : dp->val.i8 = *((int8_t*) ap); break; + case nsXPTType::T_I16 : dp->val.i16 = *((int16_t*) ap); break; + case nsXPTType::T_I32 : dp->val.i32 = *((int32_t*) ap); break; + case nsXPTType::T_I64 : ap = DOUBLEWORD_ALIGN(ap); + dp->val.i64 = *((int64_t*) ap); ap++; break; + case nsXPTType::T_U8 : dp->val.u8 = *((uint8_t*) ap); break; + case nsXPTType::T_U16 : dp->val.u16 = *((uint16_t*)ap); break; + case nsXPTType::T_U32 : dp->val.u32 = *((uint32_t*)ap); break; + case nsXPTType::T_U64 : ap = DOUBLEWORD_ALIGN(ap); + dp->val.u64 = *((uint64_t*)ap); ap++; break; + case nsXPTType::T_FLOAT : dp->val.f = *((float*) ap); break; + case nsXPTType::T_DOUBLE : ap = DOUBLEWORD_ALIGN(ap); + dp->val.d = *((double*) ap); ap++; break; + case nsXPTType::T_BOOL : dp->val.b = *((bool*) ap); break; + case nsXPTType::T_CHAR : dp->val.c = *((char*) ap); break; + case nsXPTType::T_WCHAR : dp->val.wc = *((wchar_t*) ap); break; + default: + NS_ERROR("bad type"); + break; + } + } + + result = self->mOuter->CallMethod((uint16_t)methodIndex, info, dispatchParams); + + if(dispatchParams != paramBuffer) + delete [] dispatchParams; + + return result; +} + +/* + * This is our shared stub. + * + * r0 = Self. + * + * The Rules: + * We pass an (undefined) number of arguments into this function. + * The first 3 C++ arguments are in r1 - r3, the rest are built + * by the calling function on the stack. + * + * We are allowed to corrupt r0 - r3, ip, and lr. + * + * Other Info: + * We pass the stub number in using `ip'. + * + * Implementation: + * - We save r1 to r3 inclusive onto the stack, which will be + * immediately below the caller saved arguments. + * - setup r2 (PrepareAndDispatch's args pointer) to point at + * the base of all these arguments + * - Save LR (for the return address) + * - Set r1 (PrepareAndDispatch's methodindex argument) from ip + * - r0 is passed through (self) + * - Call PrepareAndDispatch + * - When the call returns, we return by loading the PC off the + * stack, and undoing the stack (one instruction)! + * + */ +__asm__ ("\n" + GNU(".text\n") + APPLE(".section __TEXT,__text\n") + THUMB_FUNC + ".align 2\n" + "SharedStub:\n" + GNU(".fnstart\n") + GNU(".cfi_startproc\n") + "stmfd sp!, {r1, r2, r3}\n" + GNU(".save {r1, r2, r3}\n") + GNU(".cfi_def_cfa_offset 12\n") + GNU(".cfi_offset r3, -4\n") + GNU(".cfi_offset r2, -8\n") + GNU(".cfi_offset r1, -12\n") + "mov r2, sp\n" + "str lr, [sp, #-4]!\n" + GNU(".save {lr}\n") + GNU(".cfi_def_cfa_offset 16\n") + GNU(".cfi_offset lr, -16\n") + "mov r1, ip\n" + "bl _PrepareAndDispatch\n" + "ldr pc, [sp], #16\n" + GNU(".cfi_endproc\n") + GNU(".fnend")); + +/* + * Create sets of stubs to call the SharedStub. + * We don't touch the stack here, nor any registers, other than IP. + * IP is defined to be corruptable by a called function, so we are + * safe to use it. + * + * This will work with or without optimisation. + */ + +/* + * Note : As G++3 ABI contains the length of the functionname in the + * mangled name, it is difficult to get a generic assembler mechanism like + * in the G++ 2.95 case. + * Create names would be like : + * _ZN14nsXPTCStubBase5Stub9Ev + * _ZN14nsXPTCStubBase6Stub13Ev + * _ZN14nsXPTCStubBase7Stub144Ev + * Use the assembler directives to get the names right... + */ + +#define STUB_ENTRY(n) \ + __asm__( \ + GNU(".section \".text\"\n") \ + APPLE(".section __TEXT,__text\n") \ +" .align 2\n" \ +" .if ("#n" - 10) < 0\n" \ +" .globl " UNDERSCORE "ZN14nsXPTCStubBase5Stub"#n"Ev\n" \ + THUMB_FUNC \ + GNU(".type _ZN14nsXPTCStubBase5Stub"#n"Ev,#function\n") \ +UNDERSCORE "ZN14nsXPTCStubBase5Stub"#n"Ev:\n" \ +" .else\n" \ +" .if ("#n" - 100) < 0\n" \ +" .globl " UNDERSCORE "ZN14nsXPTCStubBase6Stub"#n"Ev\n" \ + THUMB_FUNC \ + GNU(".type _ZN14nsXPTCStubBase6Stub"#n"Ev,#function\n") \ +UNDERSCORE "ZN14nsXPTCStubBase6Stub"#n"Ev:\n" \ +" .else\n" \ +" .if ("#n" - 1000) < 0\n" \ +" .globl " UNDERSCORE "ZN14nsXPTCStubBase7Stub"#n"Ev\n" \ + THUMB_FUNC \ + GNU(".type _ZN14nsXPTCStubBase7Stub"#n"Ev,#function\n") \ +UNDERSCORE "ZN14nsXPTCStubBase7Stub"#n"Ev:\n" \ +" .else\n" \ +" .err \"stub number "#n"> 1000 not yet supported\"\n" \ +" .endif\n" \ +" .endif\n" \ +" .endif\n" \ +" mov ip, #"#n"\n" \ +" b SharedStub\n\t"); + +#if 0 +/* + * This part is left in as comment : this is how the method definition + * should look like. + */ + +#define STUB_ENTRY(n) \ +nsresult nsXPTCStubBase::Stub##n () \ +{ \ + __asm__ ( \ +" mov ip, #"#n"\n" \ +" b SharedStub\n\t"); \ + return 0; /* avoid warnings */ \ +} +#endif + + +#define SENTINEL_ENTRY(n) \ +nsresult nsXPTCStubBase::Sentinel##n() \ +{ \ + NS_ERROR("nsXPTCStubBase::Sentinel called"); \ + return NS_ERROR_NOT_IMPLEMENTED; \ +} + +#include "xptcstubsdef.inc" diff --git a/xpcom/reflect/xptcall/md/unix/xptcstubs_arm_netbsd.cpp b/xpcom/reflect/xptcall/md/unix/xptcstubs_arm_netbsd.cpp new file mode 100644 index 000000000..27c2b3d02 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcstubs_arm_netbsd.cpp @@ -0,0 +1,113 @@ +/* -*- 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/. */ + +/* Implement shared vtbl methods. */ + +#include "xptcprivate.h" + +nsresult ATTRIBUTE_USED +PrepareAndDispatch(nsXPTCStubBase* self, uint32_t methodIndex, uint32_t* args) +{ +#define PARAM_BUFFER_COUNT 16 + + nsXPTCMiniVariant paramBuffer[PARAM_BUFFER_COUNT]; + nsXPTCMiniVariant* dispatchParams = nullptr; + nsIInterfaceInfo* iface_info = nullptr; + const nsXPTMethodInfo* info; + uint8_t paramCount; + uint8_t i; + nsresult result = NS_ERROR_FAILURE; + + NS_ASSERTION(self,"no self"); + + self->GetInterfaceInfo(&iface_info); + NS_ASSERTION(iface_info,"no interface info"); + + iface_info->GetMethodInfo(uint16_t(methodIndex), &info); + NS_ASSERTION(info,"no interface info"); + + paramCount = info->GetParamCount(); + + // setup variant array pointer + if(paramCount > PARAM_BUFFER_COUNT) + dispatchParams = new nsXPTCMiniVariant[paramCount]; + else + dispatchParams = paramBuffer; + NS_ASSERTION(dispatchParams,"no place for params"); + + uint32_t* ap = args; + for(i = 0; i < paramCount; i++, ap++) + { + const nsXPTParamInfo& param = info->GetParam(i); + const nsXPTType& type = param.GetType(); + nsXPTCMiniVariant* dp = &dispatchParams[i]; + + if(param.IsOut() || !type.IsArithmetic()) + { + dp->val.p = (void*) *ap; + continue; + } + // else + switch(type) + { + case nsXPTType::T_I8 : dp->val.i8 = *((int8_t*) ap); break; + case nsXPTType::T_I16 : dp->val.i16 = *((int16_t*) ap); break; + case nsXPTType::T_I32 : dp->val.i32 = *((int32_t*) ap); break; + case nsXPTType::T_I64 : dp->val.i64 = *((int64_t*) ap); ap++; break; + case nsXPTType::T_U8 : dp->val.u8 = *((uint8_t*) ap); break; + case nsXPTType::T_U16 : dp->val.u16 = *((uint16_t*)ap); break; + case nsXPTType::T_U32 : dp->val.u32 = *((uint32_t*)ap); break; + case nsXPTType::T_U64 : dp->val.u64 = *((uint64_t*)ap); ap++; break; + case nsXPTType::T_FLOAT : dp->val.f = *((float*) ap); break; + case nsXPTType::T_DOUBLE : dp->val.d = *((double*) ap); ap++; break; + case nsXPTType::T_BOOL : dp->val.b = *((bool*) ap); break; + case nsXPTType::T_CHAR : dp->val.c = *((char*) ap); break; + case nsXPTType::T_WCHAR : dp->val.wc = *((wchar_t*) ap); break; + default: + NS_ERROR("bad type"); + break; + } + } + + result = self->CallMethod((uint16_t)methodIndex, info, dispatchParams); + + NS_RELEASE(iface_info); + + if(dispatchParams != paramBuffer) + delete [] dispatchParams; + + return result; +} + +/* + * These stubs move just move the values passed in registers onto the stack, + * so they are contiguous with values passed on the stack, and then calls + * PrepareAndDispatch() to do the dirty work. + */ + +#define STUB_ENTRY(n) \ +__asm__( \ + ".global _Stub"#n"__14nsXPTCStubBase\n\t" \ +"_Stub"#n"__14nsXPTCStubBase:\n\t" \ + "stmfd sp!, {r1, r2, r3} \n\t" \ + "mov ip, sp \n\t" \ + "stmfd sp!, {fp, ip, lr, pc} \n\t" \ + "sub fp, ip, #4 \n\t" \ + "mov r1, #"#n" \n\t" /* = methodIndex */ \ + "add r2, sp, #16 \n\t" \ + "bl _PrepareAndDispatch__FP14nsXPTCStubBaseUiPUi \n\t" \ + "ldmea fp, {fp, sp, lr} \n\t" \ + "add sp, sp, #12 \n\t" \ + "mov pc, lr \n\t" \ +); + +#define SENTINEL_ENTRY(n) \ +nsresult nsXPTCStubBase::Sentinel##n() \ +{ \ + NS_ERROR("nsXPTCStubBase::Sentinel called"); \ + return NS_ERROR_NOT_IMPLEMENTED; \ +} + +#include "xptcstubsdef.inc" diff --git a/xpcom/reflect/xptcall/md/unix/xptcstubs_arm_openbsd.cpp b/xpcom/reflect/xptcall/md/unix/xptcstubs_arm_openbsd.cpp new file mode 100644 index 000000000..f63d0c1e2 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcstubs_arm_openbsd.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/. */ + +/* Implement shared vtbl methods. */ + +#include "xptcprivate.h" +#include "xptiprivate.h" + +#ifdef __GNUC__ +/* This tells gcc3.4+ not to optimize away symbols. + * @see http://gcc.gnu.org/gcc-3.4/changes.html + */ +#define DONT_DROP_OR_WARN __attribute__((used)) +#else +/* This tells older gccs not to warn about unused vairables. + * @see http://docs.freebsd.org/info/gcc/gcc.info.Variable_Attributes.html + */ +#define DONT_DROP_OR_WARN __attribute__((unused)) +#endif + +/* Specify explicitly a symbol for this function, don't try to guess the c++ mangled symbol. */ +static nsresult PrepareAndDispatch(nsXPTCStubBase* self, uint32_t methodIndex, uint32_t* args) asm("_PrepareAndDispatch") +DONT_DROP_OR_WARN; + +static nsresult ATTRIBUTE_USED +PrepareAndDispatch(nsXPTCStubBase* self, uint32_t methodIndex, uint32_t* args) +{ +#define PARAM_BUFFER_COUNT 16 + + nsXPTCMiniVariant paramBuffer[PARAM_BUFFER_COUNT]; + nsXPTCMiniVariant* dispatchParams = nullptr; + const nsXPTMethodInfo* info; + uint8_t paramCount; + uint8_t i; + nsresult result = NS_ERROR_FAILURE; + + NS_ASSERTION(self,"no self"); + + self->mEntry->GetMethodInfo(uint16_t(methodIndex), &info); + paramCount = info->GetParamCount(); + + // setup variant array pointer + if(paramCount > PARAM_BUFFER_COUNT) + dispatchParams = new nsXPTCMiniVariant[paramCount]; + else + dispatchParams = paramBuffer; + + NS_ASSERTION(dispatchParams,"no place for params"); + if (!dispatchParams) + return NS_ERROR_OUT_OF_MEMORY; + + uint32_t* ap = args; + for(i = 0; i < paramCount; i++, ap++) + { + const nsXPTParamInfo& param = info->GetParam(i); + const nsXPTType& type = param.GetType(); + nsXPTCMiniVariant* dp = &dispatchParams[i]; + + if(param.IsOut() || !type.IsArithmetic()) + { + dp->val.p = (void*) *ap; + continue; + } + // else + switch(type) + { + case nsXPTType::T_I8 : dp->val.i8 = *((int8_t*) ap); break; + case nsXPTType::T_I16 : dp->val.i16 = *((int16_t*) ap); break; + case nsXPTType::T_I32 : dp->val.i32 = *((int32_t*) ap); break; + case nsXPTType::T_I64 : dp->val.i64 = *((int64_t*) ap); ap++; break; + case nsXPTType::T_U8 : dp->val.u8 = *((uint8_t*) ap); break; + case nsXPTType::T_U16 : dp->val.u16 = *((uint16_t*)ap); break; + case nsXPTType::T_U32 : dp->val.u32 = *((uint32_t*)ap); break; + case nsXPTType::T_U64 : dp->val.u64 = *((uint64_t*)ap); ap++; break; + case nsXPTType::T_FLOAT : dp->val.f = *((float*) ap); break; + case nsXPTType::T_DOUBLE : dp->val.d = *((double*) ap); ap++; break; + case nsXPTType::T_BOOL : dp->val.b = *((bool*) ap); break; + case nsXPTType::T_CHAR : dp->val.c = *((char*) ap); break; + case nsXPTType::T_WCHAR : dp->val.wc = *((wchar_t*) ap); break; + default: + NS_ERROR("bad type"); + break; + } + } + + result = self->mOuter->CallMethod((uint16_t)methodIndex, info, dispatchParams); + + if(dispatchParams != paramBuffer) + delete [] dispatchParams; + + return result; +} + +/* + * This is our shared stub. + * + * r0 = Self. + * + * The Rules: + * We pass an (undefined) number of arguments into this function. + * The first 3 C++ arguments are in r1 - r3, the rest are built + * by the calling function on the stack. + * + * We are allowed to corrupt r0 - r3, ip, and lr. + * + * Other Info: + * We pass the stub number in using `ip'. + * + * Implementation: + * - We save r1 to r3 inclusive onto the stack, which will be + * immediately below the caller saved arguments. + * - setup r2 (PrepareAndDispatch's args pointer) to point at + * the base of all these arguments + * - Save LR (for the return address) + * - Set r1 (PrepareAndDispatch's methodindex argument) from ip + * - r0 is passed through (self) + * - Call PrepareAndDispatch + * - When the call returns, we return by loading the PC off the + * stack, and undoing the stack (one instruction)! + * + */ +__asm__ ("\n\ + .text \n\ + .align 2 \n\ +SharedStub: \n\ + stmfd sp!, {r1, r2, r3} \n\ + mov r2, sp \n\ + str lr, [sp, #-4]! \n\ + mov r1, ip \n\ + bl _PrepareAndDispatch \n\ + ldr pc, [sp], #16"); + +/* + * Create sets of stubs to call the SharedStub. + * We don't touch the stack here, nor any registers, other than IP. + * IP is defined to be corruptable by a called function, so we are + * safe to use it. + * + * This will work with or without optimisation. + */ + +/* + * Note : As G++3 ABI contains the length of the functionname in the + * mangled name, it is difficult to get a generic assembler mechanism like + * in the G++ 2.95 case. + * Create names would be like : + * _ZN14nsXPTCStubBase5Stub9Ev + * _ZN14nsXPTCStubBase6Stub13Ev + * _ZN14nsXPTCStubBase7Stub144Ev + * Use the assembler directives to get the names right... + */ + +#define STUB_ENTRY(n) \ + __asm__( \ + ".section \".text\"\n" \ +" .align 2\n" \ +" .iflt ("#n" - 10)\n" \ +" .globl _ZN14nsXPTCStubBase5Stub"#n"Ev\n" \ +" .type _ZN14nsXPTCStubBase5Stub"#n"Ev,#function\n" \ +"_ZN14nsXPTCStubBase5Stub"#n"Ev:\n" \ +" .else\n" \ +" .iflt ("#n" - 100)\n" \ +" .globl _ZN14nsXPTCStubBase6Stub"#n"Ev\n" \ +" .type _ZN14nsXPTCStubBase6Stub"#n"Ev,#function\n" \ +"_ZN14nsXPTCStubBase6Stub"#n"Ev:\n" \ +" .else\n" \ +" .iflt ("#n" - 1000)\n" \ +" .globl _ZN14nsXPTCStubBase7Stub"#n"Ev\n" \ +" .type _ZN14nsXPTCStubBase7Stub"#n"Ev,#function\n" \ +"_ZN14nsXPTCStubBase7Stub"#n"Ev:\n" \ +" .else\n" \ +" .err \"stub number "#n"> 1000 not yet supported\"\n" \ +" .endif\n" \ +" .endif\n" \ +" .endif\n" \ +" mov ip, #"#n"\n" \ +" b SharedStub\n\t"); + +#if 0 +/* + * This part is left in as comment : this is how the method definition + * should look like. + */ + +#define STUB_ENTRY(n) \ +nsresult nsXPTCStubBase::Stub##n () \ +{ \ + __asm__ ( \ +" mov ip, #"#n"\n" \ +" b SharedStub\n\t"); \ + return 0; /* avoid warnings */ \ +} +#endif + + +#define SENTINEL_ENTRY(n) \ +nsresult nsXPTCStubBase::Sentinel##n() \ +{ \ + NS_ERROR("nsXPTCStubBase::Sentinel called"); \ + return NS_ERROR_NOT_IMPLEMENTED; \ +} + +#include "xptcstubsdef.inc" diff --git a/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_aarch64.s b/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_aarch64.s new file mode 100644 index 000000000..6603a4f25 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_aarch64.s @@ -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/. + + .set NGPREGS,8 + .set NFPREGS,8 + + .section ".text" + .globl SharedStub + .hidden SharedStub + .type SharedStub,@function +SharedStub: + stp x29, x30, [sp,#-16]! + mov x29, sp + + sub sp, sp, #8*(NGPREGS+NFPREGS) + stp x0, x1, [sp, #64+(0*8)] + stp x2, x3, [sp, #64+(2*8)] + stp x4, x5, [sp, #64+(4*8)] + stp x6, x7, [sp, #64+(6*8)] + stp d0, d1, [sp, #(0*8)] + stp d2, d3, [sp, #(2*8)] + stp d4, d5, [sp, #(4*8)] + stp d6, d7, [sp, #(6*8)] + + # methodIndex passed from stub + mov w1, w17 + + add x2, sp, #16+(8*(NGPREGS+NFPREGS)) + add x3, sp, #8*NFPREGS + add x4, sp, #0 + + bl PrepareAndDispatch + + add sp, sp, #8*(NGPREGS+NFPREGS) + ldp x29, x30, [sp],#16 + ret + + .size SharedStub, . - SharedStub diff --git a/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_ipf32.s b/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_ipf32.s new file mode 100644 index 000000000..720dd6cc7 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_ipf32.s @@ -0,0 +1,123 @@ + +// Select C numeric constant + .radix C + .psr abi32 + .psr msb +// Section has executable code + .section .text, "ax","progbits" +// procedure named 'SharedStub' + .proc SharedStub +// manual bundling + .explicit + + .global PrepareAndDispatch +// .exclass PrepareAndDispatch, @fullyvisible + .type PrepareAndDispatch,@function + +SharedStub:: +// 10 arguments, first 8 are the input arguments of previous +// function call. The 9th one is methodIndex and the 10th is the +// pointer to the remaining input arguments. The last two arguments +// are passed in memory. + .prologue + .save ar.pfs , r41 +// allocate 8 input args, 4 local args, and 5 output args + alloc r41 = ar.pfs, 8, 4, 5, 0 // M + .save rp, r40 + mov r40 = rp // I + addp4 out4 = 28, sp ;; // I + + .save ar.unat, r42 + mov r42 = ar.unat // M + .fframe 144 + add sp = -144, sp // A +// unwind table already knows gp, don't need to specify anything + add r43 = 0, gp ;; // A + +// We have possible 8 integer registers and 8 float registers that could +// be arguments. We also have a stack region from the previous +// stack frame that may hold some stack arguments. +// We need to write the integer registers to a memory region, write +// the float registers to a memory region (making sure we don't step +// on NAT while touching the registers). We also mark the memory +// address of the stack arguments. +// We then call PrepareAndDispatch() specifying the three memory +// region pointers. + + + .body + add out0 = 0, in0 // A move self ptr +// 144 bytes = 16 byte stack header + 64 byte int space + 64 byte float space +// methodIndex is at 144 + 16 bytes away from current sp +// (current frame + previous frame header) + ld4 out4 = [out4] // A restarg address + add r11 = 160, sp ;; // A address of methodIndex + + ld8 out1 = [r11] // M load methodIndex +// sp + 16 is the start of intargs + add out2 = 16, sp // A address of intargs +// the intargs take up 64 bytes, so sp + 16 + 64 is the start of floatargs + add out3 = 80, sp ;; // A address of floatargs + + add r11 = 0, out2 ;; // A + st8.spill [r11] = in1, 8 // M + add r10 = 0, out3 ;; // A + + st8.spill [r11] = in2, 8 ;; // M + st8.spill [r11] = in3, 8 // M + nop.i 0 ;; // I + + st8.spill [r11] = in4, 8 ;; // M + st8.spill [r11] = in5, 8 // M + nop.i 0 ;; // I + + st8.spill [r11] = in6, 8 ;; // M + st8.spill [r11] = in7 // M + fclass.nm p14,p15 = f8,@nat ;; // F + +(p14) stfd [r10] = f8, 8 // M +(p15) add r10 = 8, r10 // A + fclass.nm p12,p13 = f9,@nat ;; // F + +(p12) stfd [r10] = f9, 8 // M +(p13) add r10 = 8, r10 // A + fclass.nm p14,p15 =f10,@nat ;; // F + +(p14) stfd [r10] = f10, 8 // M +(p15) add r10 = 8, r10 // A + fclass.nm p12,p13 =f11,@nat ;; // F + +(p12) stfd [r10] = f11, 8 // M +(p13) add r10 = 8, r10 // A + fclass.nm p14,p15 =f12,@nat ;; // F + +(p14) stfd [r10] = f12, 8 // M +(p15) add r10 = 8, r10 // A + fclass.nm p12,p13 =f13,@nat ;; // F + +(p12) stfd [r10] = f13, 8 // M +(p13) add r10 = 8, r10 // A + fclass.nm p14,p15 =f14,@nat ;; // F + +(p14) stfd [r10] = f14, 8 // M +(p15) add r10 = 8, r10 // A + fclass.nm p12,p13 =f15,@nat ;; // F + +(p12) stfd [r10] = f15, 8 // M +(p13) add r10 = 8, r10 // A + +// branch to PrepareAndDispatch + br.call.dptk.few rp = PrepareAndDispatch ;; // B + +// epilog + mov ar.unat = r42 // M + mov ar.pfs = r41 // I + mov rp = r40 ;; // I + + add gp = 0, r43 // A + add sp = 144, sp // A + br.ret.dptk.few rp ;; // B + + .endp + + diff --git a/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_ipf64.s b/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_ipf64.s new file mode 100644 index 000000000..4c07836d7 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_ipf64.s @@ -0,0 +1,124 @@ + +// Select C numeric constant + .radix C + .psr abi64 + .psr lsb +// Section has executable code + .section .text, "ax","progbits" +// procedure named 'SharedStub' + .proc SharedStub +// manual bundling + .explicit + + .global PrepareAndDispatch +// .exclass PrepareAndDispatch, @fullyvisible + .type PrepareAndDispatch,@function + +SharedStub:: +// 10 arguments, first 8 are the input arguments of previous +// function call. The 9th one is methodIndex and the 10th is the +// pointer to the remaining input arguments. The last two arguments +// are passed in memory. + .prologue + .save ar.pfs , r41 +// allocate 8 input args, 4 local args, and 5 output args + alloc r41 = ar.pfs, 8, 4, 5, 0 // M + .save rp, r40 + mov r40 = rp // I + add out4 = 24, sp ;; // I + + .save ar.unat, r42 + mov r42 = ar.unat // M + .fframe 144 + add sp = -144, sp // A +// unwind table already knows gp, don't need to specify anything + add r43 = 0, gp ;; // A + +// We have possible 8 integer registers and 8 float registers that could +// be arguments. We also have a stack region from the previous +// stack frame that may hold some stack arguments. +// We need to write the integer registers to a memory region, write +// the float registers to a memory region (making sure we don't step +// on NAT while touching the registers). We also mark the memory +// address of the stack arguments. +// We then call PrepareAndDispatch() specifying the three memory +// region pointers. + + + .body + add out0 = 0, in0 // A move self ptr +// 144 bytes = 16 byte stack header + 64 byte int space + 64 byte float space +// methodIndex is at 144 + 16 bytes away from current sp +// (current frame + previous frame header) + ld8 out4 = [out4] // M restarg address + add r11 = 160, sp ;; // A address of methodIndex + + ld8 out1 = [r11] // M load methodIndex +// sp + 16 is the start of intargs + add out2 = 16, sp // A address of intargs +// the intargs take up 64 bytes, so sp + 16 + 64 is the start of floatargs + add out3 = 80, sp ;; // A address of floatargs + + add r11 = 0, out2 ;; // A + st8.spill [r11] = in1, 8 // M + add r10 = 0, out3 ;; // A + + st8.spill [r11] = in2, 8 ;; // M + st8.spill [r11] = in3, 8 // M + nop.i 0 ;; // I + + st8.spill [r11] = in4, 8 ;; // M + st8.spill [r11] = in5, 8 // M + nop.i 0 ;; // I + + st8.spill [r11] = in6, 8 ;; // M + st8.spill [r11] = in7 // M + fclass.nm p14,p15 = f8,@nat ;; // F + +(p14) stfd [r10] = f8, 8 // M +(p15) add r10 = 8, r10 // A + fclass.nm p12,p13 = f9,@nat ;; // F + +(p12) stfd [r10] = f9, 8 // M +(p13) add r10 = 8, r10 // A + fclass.nm p14,p15 =f10,@nat ;; // F + +(p14) stfd [r10] = f10, 8 // M +(p15) add r10 = 8, r10 // A + fclass.nm p12,p13 =f11,@nat ;; // F + +(p12) stfd [r10] = f11, 8 // M +(p13) add r10 = 8, r10 // A + fclass.nm p14,p15 =f12,@nat ;; // F + +(p14) stfd [r10] = f12, 8 // M +(p15) add r10 = 8, r10 // A + fclass.nm p12,p13 =f13,@nat ;; // F + +(p12) stfd [r10] = f13, 8 // M +(p13) add r10 = 8, r10 // A + fclass.nm p14,p15 =f14,@nat ;; // F + +(p14) stfd [r10] = f14, 8 // M +(p15) add r10 = 8, r10 // A + fclass.nm p12,p13 =f15,@nat ;; // F + +(p12) stfd [r10] = f15, 8 // M +(p13) add r10 = 8, r10 // A + +// branch to PrepareAndDispatch + br.call.dptk.few rp = PrepareAndDispatch ;; // B + +// epilog + mov ar.unat = r42 // M + mov ar.pfs = r41 // I + mov rp = r40 ;; // I + + add gp = 0, r43 // A + add sp = 144, sp // A + br.ret.dptk.few rp ;; // B + + .endp + +/* Magic indicating no need for an executable stack */ +.section .note.GNU-stack, "", @progbits ; .previous diff --git a/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_mips.S b/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_mips.S new file mode 100644 index 000000000..d17301634 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_mips.S @@ -0,0 +1,116 @@ +/* -*- Mode: asm; 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/. */ + +/* This code is for MIPS using the O32 ABI. */ + +#ifdef ANDROID +#include +#include +#include +#else +#include +#include +#endif + +# NARGSAVE is the argument space in the callers frame, including extra +# 'shadowed' space for the argument registers. The minimum of 4 +# argument slots is sometimes predefined in the header files. +#ifndef NARGSAVE +#define NARGSAVE 4 +#endif + +#define LOCALSZ 2 /* gp, ra */ +#define FRAMESZ ((((NARGSAVE+LOCALSZ)*SZREG)+ALSZ)&ALMASK) + +#define RAOFF (FRAMESZ - (1*SZREG)) +#define GPOFF (FRAMESZ - (2*SZREG)) + +#define A0OFF (FRAMESZ + (0*SZREG)) +#define A1OFF (FRAMESZ + (1*SZREG)) +#define A2OFF (FRAMESZ + (2*SZREG)) +#define A3OFF (FRAMESZ + (3*SZREG)) + + .text + +#define STUB_ENTRY(x) \ + .if x < 10; \ + .globl _ZN14nsXPTCStubBase5Stub ##x ##Ev; \ + .type _ZN14nsXPTCStubBase5Stub ##x ##Ev,@function; \ + .aent _ZN14nsXPTCStubBase5Stub ##x ##Ev,0; \ +_ZN14nsXPTCStubBase5Stub ##x ##Ev:; \ + SETUP_GP; \ + li t0,x; \ + b sharedstub; \ + .elseif x < 100; \ + .globl _ZN14nsXPTCStubBase6Stub ##x ##Ev; \ + .type _ZN14nsXPTCStubBase6Stub ##x ##Ev,@function; \ + .aent _ZN14nsXPTCStubBase6Stub ##x ##Ev,0; \ +_ZN14nsXPTCStubBase6Stub ##x ##Ev:; \ + SETUP_GP; \ + li t0,x; \ + b sharedstub; \ + .elseif x < 1000; \ + .globl _ZN14nsXPTCStubBase7Stub ##x ##Ev; \ + .type _ZN14nsXPTCStubBase7Stub ##x ##Ev,@function; \ + .aent _ZN14nsXPTCStubBase7Stub ##x ##Ev,0; \ +_ZN14nsXPTCStubBase7Stub ##x ##Ev:; \ + SETUP_GP; \ + li t0,x; \ + b sharedstub; \ + .else; \ + .err; \ + .endif + +# SENTINEL_ENTRY is handled in the cpp file. +#define SENTINEL_ENTRY(x) + +# +# open a dummy frame for the function entries +# + .align 2 + .type dummy,@function + .ent dummy, 0 + .frame sp, FRAMESZ, ra +dummy: + SETUP_GP + +#include "xptcstubsdef.inc" + +sharedstub: + subu sp, FRAMESZ + + # specify the save register mask for gp, ra, a0-a3 + .mask 0x900000F0, RAOFF-FRAMESZ + + sw ra, RAOFF(sp) + SAVE_GP(GPOFF) + + # Micro-optimization: a0 is already loaded, and its slot gets + # ignored by PrepareAndDispatch, so no need to save it here. + # sw a0, A0OFF(sp) + sw a1, A1OFF(sp) + sw a2, A2OFF(sp) + sw a3, A3OFF(sp) + + la t9, PrepareAndDispatch + + # t0 is methodIndex + move a1, t0 + # have a2 point to the begin of the argument space on stack + addiu a2, sp, FRAMESZ + + # PrepareAndDispatch(that, methodIndex, args) + jalr t9 + + # Micro-optimization: Using jalr explicitly has the side-effect + # of not triggering .cprestore. This is ok because we have no + # gp reference below this point. It also allows better + # instruction sscheduling. + # lw gp, GPOFF(fp) + + lw ra, RAOFF(sp) + addiu sp, FRAMESZ + j ra + END(dummy) diff --git a/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_mips.s.m4 b/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_mips.s.m4 new file mode 100644 index 000000000..33c7b1492 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_mips.s.m4 @@ -0,0 +1,75 @@ +/* -*- Mode: asm; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * Version: MPL 1.1 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 is for MIPS using the O32 ABI. */ + +#include +#include + + .text + .globl PrepareAndDispatch + +NARGSAVE=4 # extra space for the callee to use. gccism + # we can put our a0-a3 in our callers space. +LOCALSZ=2 # gp, ra +FRAMESZ=(((NARGSAVE+LOCALSZ)*SZREG)+ALSZ)&ALMASK + +define(STUB_NAME, `Stub'$1`__14nsXPTCStubBase') + +define(STUB_ENTRY, +` .globl 'STUB_NAME($1)` + .align 2 + .type 'STUB_NAME($1)`,@function + .ent 'STUB_NAME($1)`, 0 +'STUB_NAME($1)`: + .frame sp, FRAMESZ, ra + .set noreorder + .cpload t9 + .set reorder + subu sp, FRAMESZ + .cprestore 16 + li t0, '$1` + b sharedstub +.end 'STUB_NAME($1)` + +') + +define(SENTINEL_ENTRY, `') + +include(xptcstubsdef.inc) + + .globl sharedstub + .ent sharedstub +sharedstub: + + REG_S ra, 20(sp) + + REG_S a0, 24(sp) + REG_S a1, 28(sp) + REG_S a2, 32(sp) + REG_S a3, 36(sp) + + # t0 is methodIndex + move a1, t0 + + # put the start of a1, a2, a3, and stack + move a2, sp + addi a2, 24 # have a2 point to sp + 24 (where a0 is) + + # PrepareAndDispatch(that, methodIndex, args) + # a0 a1 a2 + # + jal PrepareAndDispatch + + REG_L ra, 20(sp) + REG_L a1, 28(sp) + REG_L a2, 32(sp) + + addu sp, FRAMESZ + j ra + +.end sharedstub diff --git a/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_mips64.S b/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_mips64.S new file mode 100644 index 000000000..11d851536 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_mips64.S @@ -0,0 +1,111 @@ +/* -*- 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 +#include + +LOCALSZ=16 +FRAMESZ=(((NARGSAVE+LOCALSZ)*SZREG)+ALSZ)&ALMASK + +A1OFF=FRAMESZ-(9*SZREG) +A2OFF=FRAMESZ-(8*SZREG) +A3OFF=FRAMESZ-(7*SZREG) +A4OFF=FRAMESZ-(6*SZREG) +A5OFF=FRAMESZ-(5*SZREG) +A6OFF=FRAMESZ-(4*SZREG) +A7OFF=FRAMESZ-(3*SZREG) +GPOFF=FRAMESZ-(2*SZREG) +RAOFF=FRAMESZ-(1*SZREG) + +F13OFF=FRAMESZ-(16*SZREG) +F14OFF=FRAMESZ-(15*SZREG) +F15OFF=FRAMESZ-(14*SZREG) +F16OFF=FRAMESZ-(13*SZREG) +F17OFF=FRAMESZ-(12*SZREG) +F18OFF=FRAMESZ-(11*SZREG) +F19OFF=FRAMESZ-(10*SZREG) + +#define SENTINEL_ENTRY(n) /* defined in cpp file, not here */ + +#define STUB_ENTRY(x) \ + .if x < 10; \ + MAKE_STUB(x, _ZN14nsXPTCStubBase5Stub ##x ##Ev); \ + .elseif x < 100; \ + MAKE_STUB(x, _ZN14nsXPTCStubBase6Stub ##x ##Ev); \ + .elseif x < 1000; \ + MAKE_STUB(x, _ZN14nsXPTCStubBase7Stub ##x ##Ev); \ + .else; \ + .err; \ + .endif + +#define MAKE_STUB(x, name) \ + .globl name; \ + .type name,@function; \ + .aent name,0; \ +name:; \ + PTR_SUBU sp,FRAMESZ; \ + SETUP_GP64(GPOFF, name); \ + li t0,x; \ + b sharedstub; \ + +# +# open a dummy frame for the function entries +# + .text + .align 2 + .type dummy,@function + .ent dummy, 0 +dummy: + .frame sp, FRAMESZ, ra + .mask 0x90000FF0, RAOFF-FRAMESZ + .fmask 0x000FF000, F19OFF-FRAMESZ + +#include "xptcstubsdef.inc" + +sharedstub: + + REG_S a1, A1OFF(sp) + REG_S a2, A2OFF(sp) + REG_S a3, A3OFF(sp) + REG_S a4, A4OFF(sp) + REG_S a5, A5OFF(sp) + REG_S a6, A6OFF(sp) + REG_S a7, A7OFF(sp) + REG_S ra, RAOFF(sp) + + s.d $f13, F13OFF(sp) + s.d $f14, F14OFF(sp) + s.d $f15, F15OFF(sp) + s.d $f16, F16OFF(sp) + s.d $f17, F17OFF(sp) + s.d $f18, F18OFF(sp) + s.d $f19, F19OFF(sp) + + # t0 is methodIndex + move a1, t0 + + # a2 is stack address where extra function params + # are stored that do not fit in registers + move a2, sp + PTR_ADDI a2, FRAMESZ + + # a3 is stack address of a1..a7 + move a3, sp + PTR_ADDI a3, A1OFF + + # a4 is stack address of f13..f19 + move a4, sp + PTR_ADDI a4, F13OFF + + # PrepareAndDispatch(that, methodIndex, args, gprArgs, fpArgs) + # a0 a1 a2 a3 a4 + # + jal PrepareAndDispatch + + REG_L ra, RAOFF(sp) + RESTORE_GP64 + + PTR_ADDU sp, FRAMESZ + j ra + END(dummy) diff --git a/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_pa32.s b/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_pa32.s new file mode 100644 index 000000000..9e86848fc --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_pa32.s @@ -0,0 +1,68 @@ + .LEVEL 1.1 + +curframesz .EQU 128 +; SharedStub has stack size of 128 bytes + +lastframesz .EQU 64 +; the StubN C++ function has a small stack size of 64 bytes + + .SPACE $TEXT$,SORT=8 + .SUBSPA $CODE$,QUAD=0,ALIGN=4,ACCESS=0x2c,CODE_ONLY,SORT=24 +SharedStub + .PROC + .CALLINFO CALLER,FRAME=80,SAVE_RP,ARGS_SAVED + + .ENTRY + STW %rp,-20(%sp) + LDO 128(%sp),%sp + + STW %r19,-32(%r30) + STW %r26,-36-curframesz(%r30) ; save arg0 in previous frame + + LDO -80(%r30),%r28 + FSTD,MA %fr5,8(%r28) ; save darg0 + FSTD,MA %fr7,8(%r28) ; save darg1 + FSTW,MA %fr4L,4(%r28) ; save farg0 + FSTW,MA %fr5L,4(%r28) ; save farg1 + FSTW,MA %fr6L,4(%r28) ; save farg2 + FSTW,MA %fr7L,4(%r28) ; save farg3 + + ; Former value of register 26 is already properly saved by StubN, + ; but register 25-23 are not because of the arguments mismatch + STW %r25,-40-curframesz-lastframesz(%r30) ; save r25 + STW %r24,-44-curframesz-lastframesz(%r30) ; save r24 + STW %r23,-48-curframesz-lastframesz(%r30) ; save r23 + COPY %r26,%r25 ; method index is arg1 + LDW -36-curframesz-lastframesz(%r30),%r26 ; self is arg0 + LDO -40-curframesz-lastframesz(%r30),%r24 ; normal args is arg2 + LDO -80(%r30),%r23 ; floating args is arg3 + + BL .+8,%r2 + ADDIL L'PrepareAndDispatch-$PIC_pcrel$0+4,%r2 + LDO R'PrepareAndDispatch-$PIC_pcrel$1+8(%r1),%r1 +$PIC_pcrel$0 + LDSID (%r1),%r31 +$PIC_pcrel$1 + MTSP %r31,%sr0 + .CALL ARGW0=GR,ARGW1=GR,ARGW2=GR,ARGW3=GR,RTNVAL=GR +;in=23-26;out=28; + BLE 0(%sr0,%r1) + COPY %r31,%r2 + + LDW -32(%r30),%r19 + + LDW -148(%sp),%rp + BVE (%rp) + .EXIT + LDO -128(%sp),%sp + + + .PROCEND ;in=26;out=28; + + .ALIGN 8 + .SPACE $TEXT$ + .SUBSPA $CODE$ + .IMPORT PrepareAndDispatch,CODE + .EXPORT SharedStub,ENTRY,PRIV_LEV=3,ARGW0=GR,RTNVAL=GR,LONG_RETURN + .END + diff --git a/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_parisc_linux.s b/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_parisc_linux.s new file mode 100644 index 000000000..b45d7763b --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_parisc_linux.s @@ -0,0 +1,73 @@ +/* -*- Mode: asm; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * Version: MPL 1.1 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + + .LEVEL 1.1 + .TEXT + .ALIGN 4 + +curframesz: + .EQU 128 + + +; SharedStub has stack size of 128 bytes + +lastframesz: + .EQU 64 + +; the StubN C++ function has a small stack size of 64 bytes + + +.globl SharedStub + .type SharedStub, @function + +SharedStub: + .PROC + .CALLINFO CALLER,FRAME=80,SAVE_RP + + .ENTRY + STW %rp,-20(%sp) + LDO 128(%sp),%sp + + STW %r19,-32(%r30) + STW %r26,-36-curframesz(%r30) ; save arg0 in previous frame + + LDO -80(%r30),%r28 + FSTD,MA %fr5,8(%r28) ; save darg0 + FSTD,MA %fr7,8(%r28) ; save darg1 + FSTW,MA %fr4L,4(%r28) ; save farg0 + FSTW,MA %fr5L,4(%r28) ; save farg1 + FSTW,MA %fr6L,4(%r28) ; save farg2 + FSTW,MA %fr7L,4(%r28) ; save farg3 + + ; Former value of register 26 is already properly saved by StubN, + ; but register 25-23 are not because of the argument mismatch + STW %r25,-40-curframesz-lastframesz(%r30) ; save r25 + STW %r24,-44-curframesz-lastframesz(%r30) ; save r24 + STW %r23,-48-curframesz-lastframesz(%r30) ; save r23 + COPY %r26,%r25 ; method index is arg1 + LDW -36-curframesz-lastframesz(%r30),%r26 ; self is arg0 + LDO -40-curframesz-lastframesz(%r30),%r24 ; normal args is arg2 + LDO -80(%r30),%r23 ; floating args is arg3 + + .CALL ARGW0=GR,ARGW1=GR,ARGW2=GR,ARGW3=GR,RTNVAL=GR ;in=23-26;out=28; + BL PrepareAndDispatch, %r31 + COPY %r31,%r2 + + LDW -32(%r30),%r19 + + LDW -148(%sp),%rp + LDO -128(%sp),%sp + + + BV,N (%rp) + NOP + NOP + + .EXIT + .PROCEND ;in=26;out=28; + + .SIZE SharedStub, .-SharedStub diff --git a/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_ppc64_linux.S b/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_ppc64_linux.S new file mode 100644 index 000000000..877e262b9 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_ppc64_linux.S @@ -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/. + +.set r0,0; .set r1,1; .set RTOC,2; .set r3,3; .set r4,4 +.set r5,5; .set r6,6; .set r7,7; .set r8,8; .set r9,9 +.set r10,10; .set r11,11; .set r12,12; .set r13,13; .set r14,14 +.set r15,15; .set r16,16; .set r17,17; .set r18,18; .set r19,19 +.set r20,20; .set r21,21; .set r22,22; .set r23,23; .set r24,24 +.set r25,25; .set r26,26; .set r27,27; .set r28,28; .set r29,29 +.set r30,30; .set r31,31 +.set f0,0; .set f1,1; .set f2,2; .set f3,3; .set f4,4 +.set f5,5; .set f6,6; .set f7,7; .set f8,8; .set f9,9 +.set f10,10; .set f11,11; .set f12,12; .set f13,13; .set f14,14 +.set f15,15; .set f16,16; .set f17,17; .set f18,18; .set f19,19 +.set f20,20; .set f21,21; .set f22,22; .set f23,23; .set f24,24 +.set f25,25; .set f26,26; .set f27,27; .set f28,28; .set f29,29 +.set f30,30; .set f31,31 + +#if _CALL_ELF == 2 +#define STACK_PARAMS 96 +#else +#define STACK_PARAMS 112 +#endif + +#if _CALL_ELF == 2 + .section ".text" + .type SharedStub,@function + .globl SharedStub + # Make the symbol hidden so that the branch from the stub does + # not go via a PLT. This is not only better for performance, + # but may be necessary to avoid linker errors since there is + # no place to restore the TOC register in a sibling call. + .hidden SharedStub + .align 2 +SharedStub: +0: addis 2,12,(.TOC.-0b)@ha + addi 2,2,(.TOC.-0b)@l + .localentry SharedStub,.-SharedStub +#else + .section ".text" + .align 2 + .globl SharedStub + # Make the symbol hidden so that the branch from the stub does + # not go via a PLT. This is not only better for performance, + # but may be necessary to avoid linker errors since there is + # no place to restore the TOC register in a sibling call. + .hidden SharedStub + .section ".opd","aw" + .align 3 + +SharedStub: + .quad .SharedStub,.TOC.@tocbase + .previous + .type SharedStub,@function + +.SharedStub: +#endif + mflr r0 + + std r4, -56(r1) # Save all GPRS + std r5, -48(r1) + std r6, -40(r1) + std r7, -32(r1) + std r8, -24(r1) + std r9, -16(r1) + std r10, -8(r1) + + stfd f13, -64(r1) # ... and FPRS + stfd f12, -72(r1) + stfd f11, -80(r1) + stfd f10, -88(r1) + stfd f9, -96(r1) + stfd f8, -104(r1) + stfd f7, -112(r1) + stfd f6, -120(r1) + stfd f5, -128(r1) + stfd f4, -136(r1) + stfd f3, -144(r1) + stfd f2, -152(r1) + stfd f1, -160(r1) + + subi r6,r1,56 # r6 --> gprData + subi r7,r1,160 # r7 --> fprData + addi r5,r1,STACK_PARAMS # r5 --> extra stack args + + std r0, 16(r1) + + stdu r1,-288(r1) + # r3 has the 'self' pointer + # already + + mr r4,r11 # r4 is methodIndex selector, + # passed via r11 in the + # nsNSStubBase::StubXX() call + + bl PrepareAndDispatch + nop + + ld 1,0(r1) # restore stack + ld r0,16(r1) # restore LR + mtlr r0 + blr + +#if _CALL_ELF == 2 + .size SharedStub,.-SharedStub +#else + .size SharedStub,.-.SharedStub +#endif + + # Magic indicating no need for an executable stack + .section .note.GNU-stack, "", @progbits ; .previous diff --git a/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_ppc_aix.s.m4 b/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_ppc_aix.s.m4 new file mode 100644 index 000000000..6dabf334d --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_ppc_aix.s.m4 @@ -0,0 +1,119 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +.set r0,0; .set sp,1; .set RTOC,2; .set r3,3; .set r4,4 +.set r5,5; .set r6,6; .set r7,7; .set r8,8; .set r9,9 +.set r10,10; .set r11,11; .set r12,12; .set r13,13; .set r14,14 +.set r15,15; .set r16,16; .set r17,17; .set r18,18; .set r19,19 +.set r20,20; .set r21,21; .set r22,22; .set r23,23; .set r24,24 +.set r25,25; .set r26,26; .set r27,27; .set r28,28; .set r29,29 +.set r30,30; .set r31,31 +.set f0,0; .set f1,1; .set f2,2; .set f3,3; .set f4,4 +.set f5,5; .set f6,6; .set f7,7; .set f8,8; .set f9,9 +.set f10,10; .set f11,11; .set f12,12; .set f13,13; .set f14,14 +.set f15,15; .set f16,16; .set f17,17; .set f18,18; .set f19,19 +.set f20,20; .set f21,21; .set f22,22; .set f23,23; .set f24,24 +.set f25,25; .set f26,26; .set f27,27; .set f28,28; .set f29,29 +.set f30,30; .set f31,31 + +# Define the correct name of the stub function based on the object model + +define(STUB_NAME, + ifelse(AIX_OBJMODEL, ibm, + `Stub'$1`__EI14nsXPTCStubBaseFv', + `Stub'$1`__14nsXPTCStubBaseFv')) + +define(STUB_ENTRY, ` + .rename H.10.NO_SYMBOL{PR},"" + .rename H.18.'STUB_NAME($1)`{TC},"'STUB_NAME($1)`" + .csect H.10.NO_SYMBOL{PR} + .globl .'STUB_NAME($1)` + .globl 'STUB_NAME($1)`{DS} + +.'STUB_NAME($1)`: + li r12, '$1` + b .SharedStub + nop + + + .toc +T.18.'STUB_NAME($1)`: + .tc H.18.'STUB_NAME($1)`{TC},'STUB_NAME($1)`{DS} + .csect 'STUB_NAME($1)`{DS} + .long .'STUB_NAME($1)` + .long TOC{TC0} + .long 0x00000000 +') + +define(SENTINEL_ENTRY, `') + +include(xptcstubsdef.inc) + + .rename H.10.NO_SYMBOL{PR},"" + .rename H.18.SharedStub{TC},"SharedStub" + +# .text section + .csect H.10.NO_SYMBOL{PR} + .globl .SharedStub + .globl SharedStub{DS} + .extern .PrepareAndDispatch + +.SharedStub: + mflr r0 + stw r0,8(sp) + + stwu sp,-176(sp) # room for linkage (24), fprData (104), gprData(28) + # outgoing params to PrepareAndDispatch (20) + + stw r4,44(sp) # link area (24) + PrepareAndDispatch params (20) + stw r5,48(sp) + stw r6,52(sp) + stw r7,56(sp) + stw r8,60(sp) + stw r9,64(sp) + stw r10,68(sp) + stfd f1,72(sp) + stfd f2,80(sp) + stfd f3,88(sp) + stfd f4,96(sp) + stfd f5,104(sp) + stfd f6,112(sp) + stfd f7,120(sp) + stfd f8,128(sp) + stfd f9,136(sp) + stfd f10,144(sp) + stfd f11,152(sp) + stfd f12,156(sp) + stfd f13,164(sp) + + addi r6,sp,44 # gprData + + addi r7,sp,72 # fprData + # r3 has the 'self' pointer already + mr r4,r12 # methodIndex selector (it is now LATER) + addi r5,sp,232 # pointer to callers args area, beyond r3-r10 + # mapped range + + bl .PrepareAndDispatch + nop + + + lwz r0,184(sp) + addi sp,sp,176 + mtlr r0 + blr + +# .data section + + .toc # 0x00000038 +T.18.SharedStub: + .tc H.18.SharedStub{TC},SharedStub{DS} + + .csect SharedStub{DS} + .long .SharedStub # "\0\0\0\0" + .long TOC{TC0} # "\0\0\0008" + .long 0x00000000 # "\0\0\0\0" +# End csect SharedStub{DS} + +# .bss section diff --git a/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_ppc_aix64.s.m4 b/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_ppc_aix64.s.m4 new file mode 100644 index 000000000..24d713cc9 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_ppc_aix64.s.m4 @@ -0,0 +1,97 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +.set r0,0; .set sp,1; .set RTOC,2; .set r3,3; .set r4,4 +.set r5,5; .set r6,6; .set r7,7; .set r8,8; .set r9,9 +.set r10,10; .set r11,11; .set r12,12; .set r13,13; .set r14,14 +.set r15,15; .set r16,16; .set r17,17; .set r18,18; .set r19,19 +.set r20,20; .set r21,21; .set r22,22; .set r23,23; .set r24,24 +.set r25,25; .set r26,26; .set r27,27; .set r28,28; .set r29,29 +.set r30,30; .set r31,31 +.set f0,0; .set f1,1; .set f2,2; .set f3,3; .set f4,4 +.set f5,5; .set f6,6; .set f7,7; .set f8,8; .set f9,9 +.set f10,10; .set f11,11; .set f12,12; .set f13,13; .set f14,14 +.set f15,15; .set f16,16; .set f17,17; .set f18,18; .set f19,19 +.set f20,20; .set f21,21; .set f22,22; .set f23,23; .set f24,24 +.set f25,25; .set f26,26; .set f27,27; .set f28,28; .set f29,29 +.set f30,30; .set f31,31 +# Define the correct name of the stub function based on the object model +define(STUB_NAME, + ifelse(AIX_OBJMODEL, ibm, + `Stub'$1`__EI14nsXPTCStubBaseFv', + `Stub'$1`__14nsXPTCStubBaseFv')) +define(STUB_ENTRY, ` + .rename H.10.NO_SYMBOL{PR},"" + .rename H.18.'STUB_NAME($1)`{TC},"'STUB_NAME($1)`" + .csect H.10.NO_SYMBOL{PR} + .globl .'STUB_NAME($1)` + .globl 'STUB_NAME($1)`{DS} +.'STUB_NAME($1)`: + li r12, '$1` + b .SharedStub + nop + .toc +T.18.'STUB_NAME($1)`: + .tc H.18.'STUB_NAME($1)`{TC},'STUB_NAME($1)`{DS} + .csect 'STUB_NAME($1)`{DS} + .llong .'STUB_NAME($1)` + .llong TOC{TC0} + .llong 0x00000000 +') +define(SENTINEL_ENTRY, `') +include(xptcstubsdef.inc) + .rename H.10.NO_SYMBOL{PR},"" + .rename H.18.SharedStub{TC},"SharedStub" +# .text section + .csect H.10.NO_SYMBOL{PR} + .globl .SharedStub + .globl SharedStub{DS} + .extern .PrepareAndDispatch +.SharedStub: + mflr r0 + std r0,16(sp) + stdu sp,-248(sp) # room for linkage (24*2), fprData (104), gprData(28*2) + # outgoing params to PrepareAndDispatch (40) + std r4,88(sp) # link area (48) + PrepareAndDispatch params (20) + std r5,96(sp) + std r6,104(sp) + std r7,112(sp) + std r8,120(sp) + std r9,128(sp) + std r10,136(sp) + stfd f1,144(sp) + stfd f2,152(sp) + stfd f3,160(sp) + stfd f4,168(sp) + stfd f5,176(sp) + stfd f6,184(sp) + stfd f7,192(sp) + stfd f8,200(sp) + stfd f9,208(sp) + stfd f10,216(sp) + stfd f11,224(sp) + stfd f12,232(sp) + stfd f13,240(sp) + addi r6,sp,88 # gprData + addi r7,sp,144 # fprData + # r3 has the 'self' pointer already + mr r4,r12 # methodIndex selector (it is now LATER) + addi r5,sp,360 # pointer to callers args area, beyond r3-r10 + # mapped range + bl .PrepareAndDispatch + nop + ld r0,264(sp) + addi sp,sp,248 + mtlr r0 + blr +# .data section + .toc # 0x00000038 +T.18.SharedStub: + .tc H.18.SharedStub{TC},SharedStub{DS} + .csect SharedStub{DS} + .llong .SharedStub # "\0\0\0\0" + .llong TOC{TC0} # "\0\0\0008" + .llong 0x00000000 # "\0\0\0\0" +# End csect SharedStub{DS} +# .bss section diff --git a/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_ppc_darwin.s.m4 b/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_ppc_darwin.s.m4 new file mode 100644 index 000000000..dda537850 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_ppc_darwin.s.m4 @@ -0,0 +1,114 @@ +/* -*- Mode: asm -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + + .text + .globl _SharedStub +dnl +define(STUB_MANGLED_ENTRY, +` .globl '$2` + .align 2 +'$2`: + addi r12, 0,'$1` + b _SharedStub') +dnl +define(STUB_ENTRY, +` .if '$1` < 10 +STUB_MANGLED_ENTRY('$1`, `__ZN14nsXPTCStubBase5Stub'$1`Ev') + .elseif '$1` < 100 +STUB_MANGLED_ENTRY('$1`, `__ZN14nsXPTCStubBase6Stub'$1`Ev') + .elseif '$1` < 1000 +STUB_MANGLED_ENTRY('$1`, `__ZN14nsXPTCStubBase7Stub'$1`Ev') + .else + .err "Stub'$1` >= 1000 not yet supported." + .endif +') +dnl +define(SENTINEL_ENTRY, `') +dnl +include(xptcstubsdef.inc) +dnl +// See also xptcstubs_ppc_rhapsody.cpp:PrepareAndDispatch. +_SharedStub: + // Prolog(ue) + mflr r0 // Save the link register in the caller's + stw r0, 8(r1) // stack frame + stwu r1,-176(r1) // Allocate stack space for our own frame and + // adjust stack pointer + + // Linkage area, 0(r1) to 24(r1) + // Original sp saved at 0(r1) + + // Parameter area, 20 bytes from 24(r1) to + // 44(r1) to accomodate 5 arguments passed + // to PrepareAndDispatch + + // Local variables, 132 bytes from 44(r1) + // to 176(r1), to accomodate 5 words and + // 13 doubles + + stw r4, 44(r1) // Save parameters passed in GPRs r4-r10; + stw r5, 48(r1) // a pointer to here will be passed to + stw r6, 52(r1) // PrepareAndDispatch for access to + stw r7, 56(r1) // arguments passed in registers. r3, + stw r8, 60(r1) // the self pointer, is used for the + stw r9, 64(r1) // call but isn't otherwise needed in + stw r10, 68(r1) // PrepareAndDispatch, so it is not saved. + + stfd f1, 72(r1) // Do the same for floating-point parameters + stfd f2, 80(r1) // passed in FPRs f1-f13 + stfd f3, 88(r1) + stfd f4, 96(r1) + stfd f5, 104(r1) + stfd f6, 112(r1) + stfd f7, 120(r1) + stfd f8, 128(r1) + stfd f9, 136(r1) + stfd f10, 144(r1) + stfd f11, 152(r1) + stfd f12, 160(r1) + stfd f13, 168(r1) + + // Set up parameters for call to + // PrepareAndDispatch. argument= + // 0, pointer to self, already in r3 + mr r4,r12 // 1, stub number + addi r5, r1, 204 // 2, pointer to the parameter area in our + // caller's stack, for access to + // parameters beyond those passed in + // registers. Skip past the first parameter + // (corresponding to r3) for the same reason + // as above. 176 (size of our frame) + 24 + // (size of caller's linkage) + 4 (skipped + // parameter) + addi r6, r1, 44 // 3, pointer to saved GPRs + addi r7, r1, 72 // 4, pointer to saved FPRs + + bl L_PrepareAndDispatch$stub + // Do it + nop // Leave room for linker magic + + // Epilog(ue) + lwz r0, 184(r1) // Retrieve old link register value + addi r1, r1, 176 // Restore stack pointer + mtlr r0 // Restore link register + blr // Return + +.picsymbol_stub +L_PrepareAndDispatch$stub: // Standard PIC symbol stub + .indirect_symbol _PrepareAndDispatch + mflr r0 + bcl 20,31,L1$pb +L1$pb: + mflr r11 + addis r11,r11,ha16(L1$lz-L1$pb) + mtlr r0 + lwz r12,lo16(L1$lz-L1$pb)(r11) + mtctr r12 + addi r11,r11,lo16(L1$lz-L1$pb) + bctr +.lazy_symbol_pointer +L1$lz: + .indirect_symbol _PrepareAndDispatch + .long dyld_stub_binding_helper diff --git a/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_ppc_linux.S b/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_ppc_linux.S new file mode 100644 index 000000000..72a5a9f4b --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_ppc_linux.S @@ -0,0 +1,77 @@ +// -*- Mode: Asm -*- +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +.set r0,0; .set sp,1; .set RTOC,2; .set r3,3; .set r4,4 +.set r5,5; .set r6,6; .set r7,7; .set r8,8; .set r9,9 +.set r10,10; .set r11,11; .set r12,12; .set r13,13; .set r14,14 +.set r15,15; .set r16,16; .set r17,17; .set r18,18; .set r19,19 +.set r20,20; .set r21,21; .set r22,22; .set r23,23; .set r24,24 +.set r25,25; .set r26,26; .set r27,27; .set r28,28; .set r29,29 +.set r30,30; .set r31,31 +.set f0,0; .set f1,1; .set f2,2; .set f3,3; .set f4,4 +.set f5,5; .set f6,6; .set f7,7; .set f8,8; .set f9,9 +.set f10,10; .set f11,11; .set f12,12; .set f13,13; .set f14,14 +.set f15,15; .set f16,16; .set f17,17; .set f18,18; .set f19,19 +.set f20,20; .set f21,21; .set f22,22; .set f23,23; .set f24,24 +.set f25,25; .set f26,26; .set f27,27; .set f28,28; .set f29,29 +.set f30,30; .set f31,31 + + .section ".text" + .align 2 + .globl SharedStub + .type SharedStub,@function + +SharedStub: + stwu sp,-112(sp) // room for + // linkage (8), + // gprData (32), + // fprData (64), + // stack alignment(8) + mflr r0 + stw r0,116(sp) // save LR backchain + + stw r4,12(sp) // save GP registers + stw r5,16(sp) // (n.b. that we don't save r3 + stw r6,20(sp) // because PrepareAndDispatch() is savvy) + stw r7,24(sp) + stw r8,28(sp) + stw r9,32(sp) + stw r10,36(sp) +#ifndef __NO_FPRS__ + stfd f1,40(sp) // save FP registers + stfd f2,48(sp) + stfd f3,56(sp) + stfd f4,64(sp) + stfd f5,72(sp) + stfd f6,80(sp) + stfd f7,88(sp) + stfd f8,96(sp) +#endif + + // r3 has the 'self' pointer already + + mr r4,r11 // r4 <= methodIndex selector, passed + // via r11 in the nsXPTCStubBase::StubXX() call + + addi r5,sp,120 // r5 <= pointer to callers args area, + // beyond r3-r10/f1-f8 mapped range + + addi r6,sp,8 // r6 <= gprData +#ifndef __NO_FPRS__ + addi r7,sp,40 // r7 <= fprData +#else + li r7, 0 // r7 should be unused +#endif + + bl PrepareAndDispatch@local // Go! + + lwz r0,116(sp) // restore LR + mtlr r0 + la sp,112(sp) // clean up the stack + blr + +/* Magic indicating no need for an executable stack */ +.section .note.GNU-stack, "", @progbits ; .previous diff --git a/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_ppc_netbsd.s b/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_ppc_netbsd.s new file mode 100644 index 000000000..8d43165ba --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_ppc_netbsd.s @@ -0,0 +1,70 @@ +# -*- Mode: Asm -*- +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +.set r0,0; .set sp,1; .set RTOC,2; .set r3,3; .set r4,4 +.set r5,5; .set r6,6; .set r7,7; .set r8,8; .set r9,9 +.set r10,10; .set r11,11; .set r12,12; .set r13,13; .set r14,14 +.set r15,15; .set r16,16; .set r17,17; .set r18,18; .set r19,19 +.set r20,20; .set r21,21; .set r22,22; .set r23,23; .set r24,24 +.set r25,25; .set r26,26; .set r27,27; .set r28,28; .set r29,29 +.set r30,30; .set r31,31 +.set f0,0; .set f1,1; .set f2,2; .set f3,3; .set f4,4 +.set f5,5; .set f6,6; .set f7,7; .set f8,8; .set f9,9 +.set f10,10; .set f11,11; .set f12,12; .set f13,13; .set f14,14 +.set f15,15; .set f16,16; .set f17,17; .set f18,18; .set f19,19 +.set f20,20; .set f21,21; .set f22,22; .set f23,23; .set f24,24 +.set f25,25; .set f26,26; .set f27,27; .set f28,28; .set f29,29 +.set f30,30; .set f31,31 + + .section ".text" + .align 2 + .globl SharedStub + .type SharedStub,@function + +SharedStub: + stwu sp,-112(sp) # room for + # linkage (8), + # gprData (32), + # fprData (64), + # stack alignment(8) + mflr r0 + stw r0,116(sp) # save LR backchain + + stw r4,12(sp) # save GP registers + stw r5,16(sp) # (n.b. that we don't save r3 + stw r6,20(sp) # because PrepareAndDispatch() is savvy) + stw r7,24(sp) + stw r8,28(sp) + stw r9,32(sp) + stw r10,36(sp) + + stfd f1,40(sp) # save FP registers + stfd f2,48(sp) + stfd f3,56(sp) + stfd f4,64(sp) + stfd f5,72(sp) + stfd f6,80(sp) + stfd f7,88(sp) + stfd f8,96(sp) + + # r3 has the 'self' pointer already + + mr r4,r11 # r4 <= methodIndex selector, passed + # via r11 in the nsXPTCStubBase::StubXX() call + + addi r5,sp,120 # r5 <= pointer to callers args area, + # beyond r3-r10/f1-f8 mapped range + + addi r6,sp,8 # r6 <= gprData + addi r7,sp,40 # r7 <= fprData + + bl PrepareAndDispatch@local # Go! + + lwz r0,116(sp) # restore LR + mtlr r0 + la sp,112(sp) # clean up the stack + blr + diff --git a/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_ppc_openbsd.S b/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_ppc_openbsd.S new file mode 100644 index 000000000..dcca08205 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_ppc_openbsd.S @@ -0,0 +1,72 @@ +// -*- Mode: Asm -*- +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +.set r0,0; .set sp,1; .set RTOC,2; .set r3,3; .set r4,4 +.set r5,5; .set r6,6; .set r7,7; .set r8,8; .set r9,9 +.set r10,10; .set r11,11; .set r12,12; .set r13,13; .set r14,14 +.set r15,15; .set r16,16; .set r17,17; .set r18,18; .set r19,19 +.set r20,20; .set r21,21; .set r22,22; .set r23,23; .set r24,24 +.set r25,25; .set r26,26; .set r27,27; .set r28,28; .set r29,29 +.set r30,30; .set r31,31 +.set f0,0; .set f1,1; .set f2,2; .set f3,3; .set f4,4 +.set f5,5; .set f6,6; .set f7,7; .set f8,8; .set f9,9 +.set f10,10; .set f11,11; .set f12,12; .set f13,13; .set f14,14 +.set f15,15; .set f16,16; .set f17,17; .set f18,18; .set f19,19 +.set f20,20; .set f21,21; .set f22,22; .set f23,23; .set f24,24 +.set f25,25; .set f26,26; .set f27,27; .set f28,28; .set f29,29 +.set f30,30; .set f31,31 + + .section ".text" + .align 2 + .globl SharedStub + .type SharedStub,@function + +SharedStub: + stwu sp,-112(sp) // room for + // linkage (8), + // gprData (32), + // fprData (64), + // stack alignment(8) + mflr r0 + stw r0,116(sp) // save LR backchain + + stw r4,12(sp) // save GP registers + stw r5,16(sp) // (n.b. that we don't save r3 + stw r6,20(sp) // because PrepareAndDispatch() is savvy) + stw r7,24(sp) + stw r8,28(sp) + stw r9,32(sp) + stw r10,36(sp) + + stfd f1,40(sp) // save FP registers + stfd f2,48(sp) + stfd f3,56(sp) + stfd f4,64(sp) + stfd f5,72(sp) + stfd f6,80(sp) + stfd f7,88(sp) + stfd f8,96(sp) + + // r3 has the 'self' pointer already + + mr r4,r11 // r4 <= methodIndex selector, passed + // via r11 in the nsXPTCStubBase::StubXX() call + + addi r5,sp,120 // r5 <= pointer to callers args area, + // beyond r3-r10/f1-f8 mapped range + + addi r6,sp,8 // r6 <= gprData + addi r7,sp,40 // r7 <= fprData + + bl PrepareAndDispatch@local // Go! + + lwz r0,116(sp) // restore LR + mtlr r0 + la sp,112(sp) // clean up the stack + blr + +// Magic indicating no need for an executable stack +.section .note.GNU-stack, "", @progbits ; .previous diff --git a/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_sparc64_openbsd.s b/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_sparc64_openbsd.s new file mode 100644 index 000000000..ab97a890c --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_sparc64_openbsd.s @@ -0,0 +1,50 @@ +/* -*- Mode: asm; 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/. */ + + .global SharedStub + +/* + in the frame for the function that called SharedStub are the + rest of the parameters we need + +*/ + +SharedStub: +! we don't create a new frame yet, but work within the frame of the calling +! function to give ourselves the other parameters we want + + mov %o0, %o1 ! shuffle the index up to 2nd place + mov %i0, %o0 ! the original 'this' + add %fp, 0x7ff + 136, %o2 ! previous stack top adjusted to the first argument slot (beyond 'this') + +! save off the original incoming parameters that arrived in +! registers, the ABI guarantees the space for us to do this + stx %i1, [%fp + 0x7ff + 136] + stx %i2, [%fp + 0x7ff + 144] + stx %i3, [%fp + 0x7ff + 152] + stx %i4, [%fp + 0x7ff + 160] + stx %i5, [%fp + 0x7ff + 168] +! now we can build our own stack frame + save %sp,-(128 + 64),%sp ! room for the register window and + ! struct pointer, rounded up to 0 % 64 +! our function now appears to have been called +! as SharedStub(nsISupports* that, uint32_t index, uint32_t* args) +! so we can just copy these through + + mov %i0, %o0 + mov %i1, %o1 + mov %i2, %o2 + call PrepareAndDispatch + nop + mov %o0,%i0 ! propagate return value + b .LL1 + nop +.LL1: + ret + restore + + .size SharedStub, .-SharedStub + .type SharedStub, #function diff --git a/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_sparc_netbsd.s b/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_sparc_netbsd.s new file mode 100644 index 000000000..9b448d7c7 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_sparc_netbsd.s @@ -0,0 +1,49 @@ +/* -*- 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/. */ + + .global SharedStub + +/* + in the frame for the function that called SharedStub are the + rest of the parameters we need + +*/ + +SharedStub: +! we don't create a new frame yet, but work within the frame of the calling +! function to give ourselves the other parameters we want + + mov %o0, %o1 ! shuffle the index up to 2nd place + mov %i0, %o0 ! the original 'this' + add %fp, 72, %o2 ! previous stack top adjusted to the first argument slot (beyond 'this') +! save off the original incoming parameters that arrived in +! registers, the ABI guarantees the space for us to do this + st %i1, [%fp + 72] + st %i2, [%fp + 76] + st %i3, [%fp + 80] + st %i4, [%fp + 84] + st %i5, [%fp + 88] +! now we can build our own stack frame + save %sp,-(64 + 32),%sp ! room for the register window and + ! struct pointer, rounded up to 0 % 32 +! our function now appears to have been called +! as SharedStub(nsISupports* that, uint32_t index, uint32_t* args) +! so we can just copy these through + + mov %i0, %o0 + mov %i1, %o1 + mov %i2, %o2 + call PrepareAndDispatch + nop + mov %o0,%i0 ! propagate return value + b .LL1 + nop +.LL1: + ret + restore + + .size SharedStub, .-SharedStub + .type SharedStub, #function diff --git a/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_sparc_openbsd.s b/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_sparc_openbsd.s new file mode 100644 index 000000000..871556d4c --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_sparc_openbsd.s @@ -0,0 +1,49 @@ +/* -*- 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/. */ + + .global SharedStub + +/* + in the frame for the function that called SharedStub are the + rest of the parameters we need + +*/ + +SharedStub: +! we don't create a new frame yet, but work within the frame of the calling +! function to give ourselves the other parameters we want + + mov %o0, %o1 ! shuffle the index up to 2nd place + mov %i0, %o0 ! the original 'this' + add %fp, 72, %o2 ! previous stack top adjusted to the first argument slot (beyond 'this') +! save off the original incoming parameters that arrived in +! registers, the ABI guarantees the space for us to do this + st %i1, [%fp + 72] + st %i2, [%fp + 76] + st %i3, [%fp + 80] + st %i4, [%fp + 84] + st %i5, [%fp + 88] +! now we can build our own stack frame + save %sp,-(64 + 32),%sp ! room for the register window and + ! struct pointer, rounded up to 0 % 32 +! our function now appears to have been called +! as SharedStub(nsISupports* that, uint32_t index, uint32_t* args) +! so we can just copy these through + + mov %i0, %o0 + mov %i1, %o1 + mov %i2, %o2 + call PrepareAndDispatch + nop + mov %o0,%i0 ! propagate return value + b .LL1 + nop +.LL1: + ret + restore + + .size SharedStub, .-SharedStub + .type SharedStub, #function diff --git a/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_sparc_solaris.s b/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_sparc_solaris.s new file mode 100644 index 000000000..9b448d7c7 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_sparc_solaris.s @@ -0,0 +1,49 @@ +/* -*- 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/. */ + + .global SharedStub + +/* + in the frame for the function that called SharedStub are the + rest of the parameters we need + +*/ + +SharedStub: +! we don't create a new frame yet, but work within the frame of the calling +! function to give ourselves the other parameters we want + + mov %o0, %o1 ! shuffle the index up to 2nd place + mov %i0, %o0 ! the original 'this' + add %fp, 72, %o2 ! previous stack top adjusted to the first argument slot (beyond 'this') +! save off the original incoming parameters that arrived in +! registers, the ABI guarantees the space for us to do this + st %i1, [%fp + 72] + st %i2, [%fp + 76] + st %i3, [%fp + 80] + st %i4, [%fp + 84] + st %i5, [%fp + 88] +! now we can build our own stack frame + save %sp,-(64 + 32),%sp ! room for the register window and + ! struct pointer, rounded up to 0 % 32 +! our function now appears to have been called +! as SharedStub(nsISupports* that, uint32_t index, uint32_t* args) +! so we can just copy these through + + mov %i0, %o0 + mov %i1, %o1 + mov %i2, %o2 + call PrepareAndDispatch + nop + mov %o0,%i0 ! propagate return value + b .LL1 + nop +.LL1: + ret + restore + + .size SharedStub, .-SharedStub + .type SharedStub, #function diff --git a/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_sparcv9_solaris.s b/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_sparcv9_solaris.s new file mode 100644 index 000000000..ab97a890c --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_sparcv9_solaris.s @@ -0,0 +1,50 @@ +/* -*- Mode: asm; 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/. */ + + .global SharedStub + +/* + in the frame for the function that called SharedStub are the + rest of the parameters we need + +*/ + +SharedStub: +! we don't create a new frame yet, but work within the frame of the calling +! function to give ourselves the other parameters we want + + mov %o0, %o1 ! shuffle the index up to 2nd place + mov %i0, %o0 ! the original 'this' + add %fp, 0x7ff + 136, %o2 ! previous stack top adjusted to the first argument slot (beyond 'this') + +! save off the original incoming parameters that arrived in +! registers, the ABI guarantees the space for us to do this + stx %i1, [%fp + 0x7ff + 136] + stx %i2, [%fp + 0x7ff + 144] + stx %i3, [%fp + 0x7ff + 152] + stx %i4, [%fp + 0x7ff + 160] + stx %i5, [%fp + 0x7ff + 168] +! now we can build our own stack frame + save %sp,-(128 + 64),%sp ! room for the register window and + ! struct pointer, rounded up to 0 % 64 +! our function now appears to have been called +! as SharedStub(nsISupports* that, uint32_t index, uint32_t* args) +! so we can just copy these through + + mov %i0, %o0 + mov %i1, %o1 + mov %i2, %o2 + call PrepareAndDispatch + nop + mov %o0,%i0 ! propagate return value + b .LL1 + nop +.LL1: + ret + restore + + .size SharedStub, .-SharedStub + .type SharedStub, #function diff --git a/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_x86_64_solaris_SUNW.s b/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_x86_64_solaris_SUNW.s new file mode 100644 index 000000000..aa6d84434 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_x86_64_solaris_SUNW.s @@ -0,0 +1,63 @@ +#define STUB_ENTRY1(nn) \ + .globl __1cOnsXPTCStubBaseFStub/**/nn/**/6M_I_; \ + .hidden __1cOnsXPTCStubBaseFStub/**/nn/**/6M_I_; \ + .type __1cOnsXPTCStubBaseFStub/**/nn/**/6M_I_, @function; \ +__1cOnsXPTCStubBaseFStub/**/nn/**/6M_I_: \ + movl $/**/nn/**/, %eax; \ + jmp SharedStub; \ + .size __1cOnsXPTCStubBaseFStub/**/nn/**/6M_I_, . - __1cOnsXPTCStubBaseFStub/**/nn/**/6M_I_ \ + +#define STUB_ENTRY2(nn) \ + .globl __1cOnsXPTCStubBaseGStub/**/nn/**/6M_I_; \ + .hidden __1cOnsXPTCStubBaseGStub/**/nn/**/6M_I_; \ + .type __1cOnsXPTCStubBaseGStub/**/nn/**/6M_I_, @function; \ +__1cOnsXPTCStubBaseGStub/**/nn/**/6M_I_: \ + movl $/**/nn/**/, %eax; \ + jmp SharedStub; \ + .size __1cOnsXPTCStubBaseGStub/**/nn/**/6M_I_, . - __1cOnsXPTCStubBaseGStub/**/nn/**/6M_I_ \ + +#define STUB_ENTRY3(nn) \ + .globl __1cOnsXPTCStubBaseHStub/**/nn/**/6M_I_; \ + .hidden __1cOnsXPTCStubBaseHStub/**/nn/**/6M_I_; \ + .type __1cOnsXPTCStubBaseHStub/**/nn/**/6M_I_, @function; \ +__1cOnsXPTCStubBaseHStub/**/nn/**/6M_I_: \ + movl $/**/nn/**/, %eax; \ + jmp SharedStub; \ + .size __1cOnsXPTCStubBaseHStub/**/nn/**/6M_I_, . - __1cOnsXPTCStubBaseHStub/**/nn/**/6M_I_ \ + +// static nsresult SharedStub(uint32_t methodIndex) + .type SharedStub, @function; + SharedStub: + // make room for gpregs (48), fpregs (64) + pushq %rbp; + movq %rsp,%rbp; + subq $112,%rsp; + // save GP registers + movq %rdi,-112(%rbp); + movq %rsi,-104(%rbp); + movq %rdx, -96(%rbp); + movq %rcx, -88(%rbp); + movq %r8 , -80(%rbp); + movq %r9 , -72(%rbp); + leaq -112(%rbp),%rcx; + // save FP registers + movsd %xmm0,-64(%rbp); + movsd %xmm1,-56(%rbp); + movsd %xmm2,-48(%rbp); + movsd %xmm3,-40(%rbp); + movsd %xmm4,-32(%rbp); + movsd %xmm5,-24(%rbp); + movsd %xmm6,-16(%rbp); + movsd %xmm7, -8(%rbp); + leaq -64(%rbp),%r8; + // rdi has the 'self' pointer already + movl %eax,%esi; + leaq 16(%rbp),%rdx; + call PrepareAndDispatch@plt; + leave; + ret; + .size SharedStub, . - SharedStub + +#define SENTINEL_ENTRY(nn) + +#include "xptcstubsdef_asm.solx86" diff --git a/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_x86_solaris_SUNW.s b/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_x86_solaris_SUNW.s new file mode 100644 index 000000000..76bdcf925 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcstubs_asm_x86_solaris_SUNW.s @@ -0,0 +1,78 @@ +#define STUB_ENTRY1(nn) \ + .globl __1cOnsXPTCStubBaseFStub/**/nn/**/6M_I_; \ + .type __1cOnsXPTCStubBaseFStub/**/nn/**/6M_I_, @function; \ +__1cOnsXPTCStubBaseFStub/**/nn/**/6M_I_: \ + push %ebp; \ + movl %esp,%ebp; \ + andl $-16,%esp; \ + push %ebx; \ + call .CG4./**/nn/**/; \ +.CG4./**/nn/**/: \ + pop %ebx; \ + addl $_GLOBAL_OFFSET_TABLE_+0x1,%ebx; \ + leal 0xc(%ebp), %ecx; \ + pushl %ecx; \ + pushl $/**/nn/**/; \ + movl 0x8(%ebp), %ecx; \ + pushl %ecx; \ + call __1cSPrepareAndDispatch6FpnOnsXPTCStubBase_IpI_I_; \ + addl $0xc , %esp; \ + pop %ebx; \ + movl %ebp,%esp; \ + pop %ebp; \ + ret ; \ + .size __1cOnsXPTCStubBaseFStub/**/nn/**/6M_I_, . - __1cOnsXPTCStubBaseFStub/**/nn/**/6M_I_ \ + +#define STUB_ENTRY2(nn) \ + .globl __1cOnsXPTCStubBaseGStub/**/nn/**/6M_I_; \ + .type __1cOnsXPTCStubBaseGStub/**/nn/**/6M_I_, @function; \ +__1cOnsXPTCStubBaseGStub/**/nn/**/6M_I_: \ + push %ebp; \ + movl %esp,%ebp; \ + andl $-16,%esp; \ + push %ebx; \ + call .CG4./**/nn/**/; \ +.CG4./**/nn/**/: \ + pop %ebx; \ + addl $_GLOBAL_OFFSET_TABLE_+0x1,%ebx; \ + leal 0xc(%ebp), %ecx; \ + pushl %ecx; \ + pushl $/**/nn/**/; \ + movl 0x8(%ebp), %ecx; \ + pushl %ecx; \ + call __1cSPrepareAndDispatch6FpnOnsXPTCStubBase_IpI_I_; \ + addl $0xc , %esp; \ + pop %ebx; \ + movl %ebp,%esp; \ + pop %ebp; \ + ret ; \ + .size __1cOnsXPTCStubBaseGStub/**/nn/**/6M_I_, . - __1cOnsXPTCStubBaseGStub/**/nn/**/6M_I_ \ + +#define STUB_ENTRY3(nn) \ + .globl __1cOnsXPTCStubBaseHStub/**/nn/**/6M_I_; \ + .type __1cOnsXPTCStubBaseHStub/**/nn/**/6M_I_, @function; \ +__1cOnsXPTCStubBaseHStub/**/nn/**/6M_I_: \ + push %ebp; \ + movl %esp,%ebp; \ + andl $-16,%esp; \ + push %ebx; \ + call .CG4./**/nn/**/; \ +.CG4./**/nn/**/: \ + pop %ebx; \ + addl $_GLOBAL_OFFSET_TABLE_+0x1,%ebx; \ + leal 0xc(%ebp), %ecx; \ + pushl %ecx; \ + pushl $/**/nn/**/; \ + movl 0x8(%ebp), %ecx; \ + pushl %ecx; \ + call __1cSPrepareAndDispatch6FpnOnsXPTCStubBase_IpI_I_; \ + addl $0xc , %esp; \ + pop %ebx; \ + movl %ebp,%esp; \ + pop %ebp; \ + ret ; \ + .size __1cOnsXPTCStubBaseHStub/**/nn/**/6M_I_, . - __1cOnsXPTCStubBaseHStub/**/nn/**/6M_I_ \ + +#define SENTINEL_ENTRY(nn) + +#include "xptcstubsdef_asm.solx86" diff --git a/xpcom/reflect/xptcall/md/unix/xptcstubs_darwin.cpp b/xpcom/reflect/xptcall/md/unix/xptcstubs_darwin.cpp new file mode 100644 index 000000000..49edfca44 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcstubs_darwin.cpp @@ -0,0 +1,16 @@ +/* -*- Mode: 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/. */ + +#if defined(__i386__) +#include "xptcstubs_gcc_x86_unix.cpp" +#elif defined(__x86_64__) +#include "xptcstubs_x86_64_darwin.cpp" +#elif defined(__ppc__) +#include "xptcstubs_ppc_rhapsody.cpp" +#elif defined(__arm__) +#include "xptcstubs_arm.cpp" +#else +#error unknown cpu architecture +#endif diff --git a/xpcom/reflect/xptcall/md/unix/xptcstubs_gcc_x86_unix.cpp b/xpcom/reflect/xptcall/md/unix/xptcstubs_gcc_x86_unix.cpp new file mode 100644 index 000000000..6811a26ad --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcstubs_gcc_x86_unix.cpp @@ -0,0 +1,139 @@ +/* -*- 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/. */ + +/* Implement shared vtbl methods. */ + +#include "xptcprivate.h" +#include "xptiprivate.h" +#include "xptc_gcc_x86_unix.h" + +extern "C" { +static nsresult ATTRIBUTE_USED +__attribute__ ((regparm (3))) +PrepareAndDispatch(uint32_t methodIndex, nsXPTCStubBase* self, uint32_t* args) +{ +#define PARAM_BUFFER_COUNT 16 + + nsXPTCMiniVariant paramBuffer[PARAM_BUFFER_COUNT]; + nsXPTCMiniVariant* dispatchParams = nullptr; + const nsXPTMethodInfo* info; + uint8_t paramCount; + uint8_t i; + nsresult result = NS_ERROR_FAILURE; + + NS_ASSERTION(self,"no self"); + + self->mEntry->GetMethodInfo(uint16_t(methodIndex), &info); + paramCount = info->GetParamCount(); + + // setup variant array pointer + if(paramCount > PARAM_BUFFER_COUNT) + dispatchParams = new nsXPTCMiniVariant[paramCount]; + else + dispatchParams = paramBuffer; + NS_ASSERTION(dispatchParams,"no place for params"); + + uint32_t* ap = args; + for(i = 0; i < paramCount; i++, ap++) + { + const nsXPTParamInfo& param = info->GetParam(i); + const nsXPTType& type = param.GetType(); + nsXPTCMiniVariant* dp = &dispatchParams[i]; + + if(param.IsOut() || !type.IsArithmetic()) + { + dp->val.p = (void*) *ap; + continue; + } + // else + dp->val.p = (void*) *ap; + switch(type) + { + case nsXPTType::T_I64 : dp->val.i64 = *((int64_t*) ap); ap++; break; + case nsXPTType::T_U64 : dp->val.u64 = *((uint64_t*)ap); ap++; break; + case nsXPTType::T_DOUBLE : dp->val.d = *((double*) ap); ap++; break; + } + } + + result = self->mOuter->CallMethod((uint16_t)methodIndex, info, dispatchParams); + + if(dispatchParams != paramBuffer) + delete [] dispatchParams; + + return result; +} +} // extern "C" + +#if !defined(XP_MACOSX) + +#define STUB_HEADER(a, b) ".hidden " SYMBOL_UNDERSCORE "_ZN14nsXPTCStubBase" #a "Stub" #b "Ev\n\t" \ + ".type " SYMBOL_UNDERSCORE "_ZN14nsXPTCStubBase" #a "Stub" #b "Ev,@function\n" + +#define STUB_SIZE(a, b) ".size " SYMBOL_UNDERSCORE "_ZN14nsXPTCStubBase" #a "Stub" #b "Ev,.-" SYMBOL_UNDERSCORE "_ZN14nsXPTCStubBase" #a "Stub" #b "Ev\n\t" + +#else + +#define STUB_HEADER(a, b) +#define STUB_SIZE(a, b) + +#endif + +// gcc3 mangling tends to insert the length of the method name +#define STUB_ENTRY(n) \ +asm(".text\n\t" \ + ".align 2\n\t" \ + ".if " #n " < 10\n\t" \ + ".globl " SYMBOL_UNDERSCORE "_ZN14nsXPTCStubBase5Stub" #n "Ev\n\t" \ + STUB_HEADER(5, n) \ + SYMBOL_UNDERSCORE "_ZN14nsXPTCStubBase5Stub" #n "Ev:\n\t" \ + ".elseif " #n " < 100\n\t" \ + ".globl " SYMBOL_UNDERSCORE "_ZN14nsXPTCStubBase6Stub" #n "Ev\n\t" \ + STUB_HEADER(6, n) \ + SYMBOL_UNDERSCORE "_ZN14nsXPTCStubBase6Stub" #n "Ev:\n\t" \ + ".elseif " #n " < 1000\n\t" \ + ".globl " SYMBOL_UNDERSCORE "_ZN14nsXPTCStubBase7Stub" #n "Ev\n\t" \ + STUB_HEADER(7, n) \ + SYMBOL_UNDERSCORE "_ZN14nsXPTCStubBase7Stub" #n "Ev:\n\t" \ + ".else\n\t" \ + ".err \"stub number " #n " >= 1000 not yet supported\"\n\t" \ + ".endif\n\t" \ + "movl $" #n ", %eax\n\t" \ + "jmp " SYMBOL_UNDERSCORE "SharedStub\n\t" \ + ".if " #n " < 10\n\t" \ + STUB_SIZE(5, n) \ + ".elseif " #n " < 100\n\t" \ + STUB_SIZE(6, n) \ + ".else\n\t" \ + STUB_SIZE(7, n) \ + ".endif"); + +// static nsresult SharedStub(uint32_t methodIndex) __attribute__((regparm(1))) +asm(".text\n\t" + ".align 2\n\t" +#if !defined(XP_MACOSX) + ".type " SYMBOL_UNDERSCORE "SharedStub,@function\n\t" +#endif + SYMBOL_UNDERSCORE "SharedStub:\n\t" + "leal 0x08(%esp), %ecx\n\t" + "movl 0x04(%esp), %edx\n\t" + "jmp " SYMBOL_UNDERSCORE "PrepareAndDispatch\n\t" +#if !defined(XP_MACOSX) + ".size " SYMBOL_UNDERSCORE "SharedStub,.-" SYMBOL_UNDERSCORE "SharedStub" +#endif +); + +#define SENTINEL_ENTRY(n) \ +nsresult nsXPTCStubBase::Sentinel##n() \ +{ \ + NS_ERROR("nsXPTCStubBase::Sentinel called"); \ + return NS_ERROR_NOT_IMPLEMENTED; \ +} + +#include "xptcstubsdef.inc" + +void +xptc_dummy() +{ +} diff --git a/xpcom/reflect/xptcall/md/unix/xptcstubs_ipf32.cpp b/xpcom/reflect/xptcall/md/unix/xptcstubs_ipf32.cpp new file mode 100644 index 000000000..e2d37e4f5 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcstubs_ipf32.cpp @@ -0,0 +1,151 @@ + +/* -*- 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 "xptcprivate.h" +#include "xptiprivate.h" + +#include +#include + +// "This code is for IA64 only" + +/* Implement shared vtbl methods. */ + +extern "C" nsresult ATTRIBUTE_USED +PrepareAndDispatch(nsXPTCStubBase* self, uint32_t methodIndex, + uint64_t* intargs, uint64_t* floatargs, uint64_t* restargs) +{ + +#define PARAM_BUFFER_COUNT 16 + + nsXPTCMiniVariant paramBuffer[PARAM_BUFFER_COUNT]; + nsXPTCMiniVariant* dispatchParams = nullptr; + const nsXPTMethodInfo* info; + nsresult result = NS_ERROR_FAILURE; + uint64_t* iargs = intargs; + uint64_t* fargs = floatargs; + uint8_t paramCount; + uint8_t i; + + NS_ASSERTION(self,"no self"); + + self->mEntry->GetMethodInfo(uint16_t(methodIndex), &info); + NS_ASSERTION(info,"no interface info"); + + paramCount = info->GetParamCount(); + + // setup variant array pointer + if(paramCount > PARAM_BUFFER_COUNT) + dispatchParams = new nsXPTCMiniVariant[paramCount]; + else + dispatchParams = paramBuffer; + NS_ASSERTION(dispatchParams,"no place for params"); + if (! dispatchParams) + return NS_ERROR_OUT_OF_MEMORY; + + for(i = 0; i < paramCount; ++i) + { + int isfloat = 0; + const nsXPTParamInfo& param = info->GetParam(i); + const nsXPTType& type = param.GetType(); + nsXPTCMiniVariant* dp = &dispatchParams[i]; + + if(param.IsOut() || !type.IsArithmetic()) + { +#ifdef __LP64__ + /* 64 bit pointer mode */ + dp->val.p = (void*) *iargs; +#else + /* 32 bit pointer mode */ + uint32_t* adr = (uint32_t*) iargs; + dp->val.p = (void*) (*(adr+1)); +#endif + } + else + switch(type) + { + case nsXPTType::T_I8 : dp->val.i8 = *(iargs); break; + case nsXPTType::T_I16 : dp->val.i16 = *(iargs); break; + case nsXPTType::T_I32 : dp->val.i32 = *(iargs); break; + case nsXPTType::T_I64 : dp->val.i64 = *(iargs); break; + case nsXPTType::T_U8 : dp->val.u8 = *(iargs); break; + case nsXPTType::T_U16 : dp->val.u16 = *(iargs); break; + case nsXPTType::T_U32 : dp->val.u32 = *(iargs); break; + case nsXPTType::T_U64 : dp->val.u64 = *(iargs); break; + case nsXPTType::T_FLOAT : + isfloat = 1; + if (i < 7) + dp->val.f = (float) *((double*) fargs); /* register */ + else + dp->val.u32 = *(fargs); /* memory */ + break; + case nsXPTType::T_DOUBLE : + isfloat = 1; + dp->val.u64 = *(fargs); + break; + case nsXPTType::T_BOOL : dp->val.b = *(iargs); break; + case nsXPTType::T_CHAR : dp->val.c = *(iargs); break; + case nsXPTType::T_WCHAR : dp->val.wc = *(iargs); break; + default: + NS_ERROR("bad type"); + break; + } + if (i < 7) + { + /* we are parsing register arguments */ + if (i == 6) + { + // run out of register arguments, move on to memory arguments + iargs = restargs; + fargs = restargs; + } + else + { + ++iargs; // advance one integer register slot + if (isfloat) ++fargs; // advance float register slot if isfloat + } + } + else + { + /* we are parsing memory arguments */ + ++iargs; + ++fargs; + } + } + + result = self->mOuter->CallMethod((uint16_t) methodIndex, info, dispatchParams); + + if(dispatchParams != paramBuffer) + delete [] dispatchParams; + + return result; +} + +extern "C" nsresult SharedStub(uint64_t,uint64_t,uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t *); + +/* Variable a0-a7 were put there so we can have access to the 8 input + registers on Stubxyz entry */ + +#define STUB_ENTRY(n) \ +nsresult nsXPTCStubBase::Stub##n(uint64_t a1, \ +uint64_t a2,uint64_t a3,uint64_t a4,uint64_t a5,uint64_t a6,uint64_t a7, \ +uint64_t a8) \ +{ uint64_t a0 = (uint64_t) this; \ + return SharedStub(a0,a1,a2,a3,a4,a5,a6,a7,(uint64_t) n, &a8); \ +} + +#define SENTINEL_ENTRY(n) \ +nsresult nsXPTCStubBase::Sentinel##n() \ +{ \ + NS_ERROR("nsXPTCStubBase::Sentinel called"); \ + return NS_ERROR_NOT_IMPLEMENTED; \ +} + +#include "xptcstubsdef.inc" + diff --git a/xpcom/reflect/xptcall/md/unix/xptcstubs_ipf64.cpp b/xpcom/reflect/xptcall/md/unix/xptcstubs_ipf64.cpp new file mode 100644 index 000000000..fea9e8e3d --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcstubs_ipf64.cpp @@ -0,0 +1,154 @@ + +/* -*- 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 "xptcprivate.h" +#include "xptiprivate.h" + +#include +#include +#include + +// "This code is for IA64 only" + +/* Implement shared vtbl methods. */ + +extern "C" nsresult ATTRIBUTE_USED +PrepareAndDispatch(nsXPTCStubBase* self, uint32_t methodIndex, + uint64_t* intargs, uint64_t* floatargs, uint64_t* restargs) +{ + +#define PARAM_BUFFER_COUNT 16 + + nsXPTCMiniVariant paramBuffer[PARAM_BUFFER_COUNT]; + nsXPTCMiniVariant* dispatchParams = nullptr; + const nsXPTMethodInfo* info; + nsresult result = NS_ERROR_FAILURE; + uint64_t* iargs = intargs; + uint64_t* fargs = floatargs; + uint8_t paramCount; + uint8_t i; + + NS_ASSERTION(self,"no self"); + + self->mEntry->GetMethodInfo(uint16_t(methodIndex), &info); + NS_ASSERTION(info,"no method info"); + if (! info) + return NS_ERROR_UNEXPECTED; + + paramCount = info->GetParamCount(); + + // setup variant array pointer + if(paramCount > PARAM_BUFFER_COUNT) + dispatchParams = new nsXPTCMiniVariant[paramCount]; + else + dispatchParams = paramBuffer; + NS_ASSERTION(dispatchParams,"no place for params"); + if (! dispatchParams) + return NS_ERROR_OUT_OF_MEMORY; + + for(i = 0; i < paramCount; ++i) + { + int isfloat = 0; + const nsXPTParamInfo& param = info->GetParam(i); + const nsXPTType& type = param.GetType(); + nsXPTCMiniVariant* dp = &dispatchParams[i]; + + if(param.IsOut() || !type.IsArithmetic()) + { +#ifdef __LP64__ + /* 64 bit pointer mode */ + dp->val.p = (void*) *iargs; +#else + /* 32 bit pointer mode */ + uint32_t* adr = (uint32_t*) iargs; + dp->val.p = (void*) (*(adr+1)); +#endif + } + else + switch(type) + { + case nsXPTType::T_I8 : dp->val.i8 = *(iargs); break; + case nsXPTType::T_I16 : dp->val.i16 = *(iargs); break; + case nsXPTType::T_I32 : dp->val.i32 = *(iargs); break; + case nsXPTType::T_I64 : dp->val.i64 = *(iargs); break; + case nsXPTType::T_U8 : dp->val.u8 = *(iargs); break; + case nsXPTType::T_U16 : dp->val.u16 = *(iargs); break; + case nsXPTType::T_U32 : dp->val.u32 = *(iargs); break; + case nsXPTType::T_U64 : dp->val.u64 = *(iargs); break; + case nsXPTType::T_FLOAT : + isfloat = 1; + if (i < 7) + dp->val.f = (float) *((double*) fargs); /* register */ + else + dp->val.u32 = *(fargs); /* memory */ + break; + case nsXPTType::T_DOUBLE : + isfloat = 1; + dp->val.u64 = *(fargs); + break; + case nsXPTType::T_BOOL : dp->val.b = *(iargs); break; + case nsXPTType::T_CHAR : dp->val.c = *(iargs); break; + case nsXPTType::T_WCHAR : dp->val.wc = *(iargs); break; + default: + NS_ERROR("bad type"); + break; + } + if (i < 7) + { + /* we are parsing register arguments */ + if (i == 6) + { + // run out of register arguments, move on to memory arguments + iargs = restargs; + fargs = restargs; + } + else + { + ++iargs; // advance one integer register slot + if (isfloat) ++fargs; // advance float register slot if isfloat + } + } + else + { + /* we are parsing memory arguments */ + ++iargs; + ++fargs; + } + } + + result = self->mOuter->CallMethod((uint16_t) methodIndex, info, dispatchParams); + + if(dispatchParams != paramBuffer) + delete [] dispatchParams; + + return result; +} + +extern "C" nsresult SharedStub(uint64_t,uint64_t,uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t *); + +/* Variable a0-a7 were put there so we can have access to the 8 input + registers on Stubxyz entry */ + +#define STUB_ENTRY(n) \ +nsresult nsXPTCStubBase::Stub##n(uint64_t a1, \ +uint64_t a2,uint64_t a3,uint64_t a4,uint64_t a5,uint64_t a6,uint64_t a7, \ +uint64_t a8) \ +{ uint64_t a0 = (uint64_t) this; \ + return SharedStub(a0,a1,a2,a3,a4,a5,a6,a7,(uint64_t) n, &a8); \ +} + +#define SENTINEL_ENTRY(n) \ +nsresult nsXPTCStubBase::Sentinel##n() \ +{ \ + NS_ERROR("nsXPTCStubBase::Sentinel called"); \ + return NS_ERROR_NOT_IMPLEMENTED; \ +} + +#include "xptcstubsdef.inc" + diff --git a/xpcom/reflect/xptcall/md/unix/xptcstubs_linux_alpha.cpp b/xpcom/reflect/xptcall/md/unix/xptcstubs_linux_alpha.cpp new file mode 100644 index 000000000..d9c41aee7 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcstubs_linux_alpha.cpp @@ -0,0 +1,187 @@ +/* -*- 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/. */ + +/* Implement shared vtbl methods. */ + +#include "xptcprivate.h" +#include "xptiprivate.h" + +/* Prototype specifies unmangled function name and disables unused warning */ +static nsresult +PrepareAndDispatch(nsXPTCStubBase* self, uint32_t methodIndex, uint64_t* args) +__asm__("PrepareAndDispatch") ATTRIBUTE_USED; + +static nsresult +PrepareAndDispatch(nsXPTCStubBase* self, uint32_t methodIndex, uint64_t* args) +{ + const uint8_t PARAM_BUFFER_COUNT = 16; + const uint8_t NUM_ARG_REGS = 6-1; // -1 for "this" pointer + + nsXPTCMiniVariant paramBuffer[PARAM_BUFFER_COUNT]; + nsXPTCMiniVariant* dispatchParams = nullptr; + const nsXPTMethodInfo* info; + uint8_t paramCount; + uint8_t i; + nsresult result = NS_ERROR_FAILURE; + + NS_ASSERTION(self,"no self"); + + self->mEntry->GetMethodInfo(uint16_t(methodIndex), &info); + + paramCount = info->GetParamCount(); + + // setup variant array pointer + if(paramCount > PARAM_BUFFER_COUNT) + dispatchParams = new nsXPTCMiniVariant[paramCount]; + else + dispatchParams = paramBuffer; + NS_ASSERTION(dispatchParams,"no place for params"); + + // args[0] to args[NUM_ARG_REGS] hold floating point register values + uint64_t* ap = args + NUM_ARG_REGS; + for(i = 0; i < paramCount; i++, ap++) + { + const nsXPTParamInfo& param = info->GetParam(i); + const nsXPTType& type = param.GetType(); + nsXPTCMiniVariant* dp = &dispatchParams[i]; + + if(param.IsOut() || !type.IsArithmetic()) + { + dp->val.p = (void*) *ap; + continue; + } + // else + switch(type) + { + case nsXPTType::T_I8 : dp->val.i8 = (int8_t) *ap; break; + case nsXPTType::T_I16 : dp->val.i16 = (int16_t) *ap; break; + case nsXPTType::T_I32 : dp->val.i32 = (int32_t) *ap; break; + case nsXPTType::T_I64 : dp->val.i64 = (int64_t) *ap; break; + case nsXPTType::T_U8 : dp->val.u8 = (uint8_t) *ap; break; + case nsXPTType::T_U16 : dp->val.u16 = (uint16_t) *ap; break; + case nsXPTType::T_U32 : dp->val.u32 = (uint32_t) *ap; break; + case nsXPTType::T_U64 : dp->val.u64 = (uint64_t) *ap; break; + case nsXPTType::T_FLOAT : + if(i < NUM_ARG_REGS) + { + // floats passed via registers are stored as doubles + // in the first NUM_ARG_REGS entries in args + dp->val.u64 = (uint64_t) args[i]; + dp->val.f = (float) dp->val.d; // convert double to float + } + else + dp->val.u32 = (uint32_t) *ap; + break; + case nsXPTType::T_DOUBLE : + // doubles passed via registers are also stored + // in the first NUM_ARG_REGS entries in args + dp->val.u64 = (i < NUM_ARG_REGS) ? args[i] : *ap; + break; + case nsXPTType::T_BOOL : dp->val.b = (bool) *ap; break; + case nsXPTType::T_CHAR : dp->val.c = (char) *ap; break; + case nsXPTType::T_WCHAR : dp->val.wc = (char16_t) *ap; break; + default: + NS_ERROR("bad type"); + break; + } + } + + result = self->mOuter->CallMethod((uint16_t)methodIndex, info, dispatchParams); + + if(dispatchParams != paramBuffer) + delete [] dispatchParams; + + return result; +} + +/* + * SharedStub() + * Collects arguments and calls PrepareAndDispatch. The "methodIndex" is + * passed to this function via $1 to preserve the argument registers. + */ +__asm__( + "#### SharedStub ####\n" +".text\n\t" + ".align 5\n\t" + ".ent SharedStub\n" +"SharedStub:\n\t" + ".frame $30,96,$26,0\n\t" + ".mask 0x4000000,-96\n\t" + "ldgp $29,0($27)\n" +"$SharedStub..ng:\n\t" + "subq $30,96,$30\n\t" + "stq $26,0($30)\n\t" + ".prologue 1\n\t" + + /* + * Store arguments passed via registers to the stack. + * Floating point registers are stored as doubles and converted + * to floats in PrepareAndDispatch if necessary. + */ + "stt $f17,16($30)\n\t" /* floating point registers */ + "stt $f18,24($30)\n\t" + "stt $f19,32($30)\n\t" + "stt $f20,40($30)\n\t" + "stt $f21,48($30)\n\t" + "stq $17,56($30)\n\t" /* integer registers */ + "stq $18,64($30)\n\t" + "stq $19,72($30)\n\t" + "stq $20,80($30)\n\t" + "stq $21,88($30)\n\t" + + /* + * Call PrepareAndDispatch function. + */ + "bis $1,$1,$17\n\t" /* pass "methodIndex" */ + "addq $30,16,$18\n\t" /* pass "args" */ + "bsr $26,$PrepareAndDispatch..ng\n\t" + + "ldq $26,0($30)\n\t" + "addq $30,96,$30\n\t" + "ret $31,($26),1\n\t" + ".end SharedStub" + ); + +/* + * nsresult nsXPTCStubBase::Stub##n() + * Sets register $1 to "methodIndex" and jumps to SharedStub. + */ +#define STUB_MANGLED_ENTRY(n, symbol) \ + "#### Stub"#n" ####" "\n\t" \ + ".text" "\n\t" \ + ".align 5" "\n\t" \ + ".globl " symbol "\n\t" \ + ".ent " symbol "\n" \ +symbol ":" "\n\t" \ + ".frame $30,0,$26,0" "\n\t" \ + "ldgp $29,0($27)" "\n" \ +"$" symbol "..ng:" "\n\t" \ + ".prologue 1" "\n\t" \ + "lda $1,"#n "\n\t" \ + "br $31,$SharedStub..ng" "\n\t" \ + ".end " symbol + +#define STUB_ENTRY(n) \ +__asm__( \ + ".if "#n" < 10" "\n\t" \ + STUB_MANGLED_ENTRY(n, "_ZN14nsXPTCStubBase5Stub"#n"Ev") "\n\t" \ + ".elseif "#n" < 100" "\n\t" \ + STUB_MANGLED_ENTRY(n, "_ZN14nsXPTCStubBase6Stub"#n"Ev") "\n\t" \ + ".elseif "#n" < 1000" "\n\t" \ + STUB_MANGLED_ENTRY(n, "_ZN14nsXPTCStubBase7Stub"#n"Ev") "\n\t" \ + ".else" "\n\t" \ + ".err \"Stub"#n" >= 1000 not yet supported.\"" "\n\t" \ + ".endif" \ + ); + + +#define SENTINEL_ENTRY(n) \ +nsresult nsXPTCStubBase::Sentinel##n() \ +{ \ + NS_ERROR("nsXPTCStubBase::Sentinel called"); \ + return NS_ERROR_NOT_IMPLEMENTED; \ +} + +#include "xptcstubsdef.inc" diff --git a/xpcom/reflect/xptcall/md/unix/xptcstubs_linux_m68k.cpp b/xpcom/reflect/xptcall/md/unix/xptcstubs_linux_m68k.cpp new file mode 100644 index 000000000..3720a5c37 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcstubs_linux_m68k.cpp @@ -0,0 +1,98 @@ +/* -*- 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/. */ + +/* Implement shared vtbl methods. */ + +#include "xptcprivate.h" +#include "xptiprivate.h" + +extern "C" { + nsresult ATTRIBUTE_USED + PrepareAndDispatch(nsXPTCStubBase* self, uint32_t methodIndex, uint32_t* args) + { +#define PARAM_BUFFER_COUNT 16 + + nsXPTCMiniVariant paramBuffer[PARAM_BUFFER_COUNT]; + nsXPTCMiniVariant* dispatchParams = nullptr; + const nsXPTMethodInfo* info; + uint8_t paramCount; + uint8_t i; + nsresult result = NS_ERROR_FAILURE; + + NS_ASSERTION(self,"no self"); + + self->mEntry->GetMethodInfo(uint16_t(methodIndex), &info); + NS_ASSERTION(info,"no method info"); + + paramCount = info->GetParamCount(); + + // setup variant array pointer + if(paramCount > PARAM_BUFFER_COUNT) + dispatchParams = new nsXPTCMiniVariant[paramCount]; + else + dispatchParams = paramBuffer; + NS_ASSERTION(dispatchParams,"no place for params"); + + uint32_t* ap = args; + for(i = 0; i < paramCount; i++, ap++) + { + const nsXPTParamInfo& param = info->GetParam(i); + const nsXPTType& type = param.GetType(); + nsXPTCMiniVariant* dp = &dispatchParams[i]; + + if(param.IsOut() || !type.IsArithmetic()) + { + dp->val.p = (void*) *ap; + continue; + } + + switch(type) + { + // the 8 and 16 bit types will have been promoted to 32 bits before + // being pushed onto the stack. Since the 68k is big endian, we + // need to skip over the leading high order bytes. + case nsXPTType::T_I8 : dp->val.i8 = *(((int8_t*) ap) + 3); break; + case nsXPTType::T_I16 : dp->val.i16 = *(((int16_t*) ap) + 1); break; + case nsXPTType::T_I32 : dp->val.i32 = *((int32_t*) ap); break; + case nsXPTType::T_I64 : dp->val.i64 = *((int64_t*) ap); ap++; break; + case nsXPTType::T_U8 : dp->val.u8 = *(((uint8_t*) ap) + 3); break; + case nsXPTType::T_U16 : dp->val.u16 = *(((uint16_t*)ap) + 1); break; + case nsXPTType::T_U32 : dp->val.u32 = *((uint32_t*)ap); break; + case nsXPTType::T_U64 : dp->val.u64 = *((uint64_t*)ap); ap++; break; + case nsXPTType::T_FLOAT : dp->val.f = *((float*) ap); break; + case nsXPTType::T_DOUBLE : dp->val.d = *((double*) ap); ap++; break; + case nsXPTType::T_BOOL : dp->val.b = *((uint32_t* ap); break; + case nsXPTType::T_CHAR : dp->val.c = *(((char*) ap) + 3); break; + case nsXPTType::T_WCHAR : dp->val.wc = *((wchar_t*) ap); break; + default: + NS_ERROR("bad type"); + break; + } + } + + result = self->mOuter->CallMethod((uint16_t)methodIndex, info, dispatchParams); + + if(dispatchParams != paramBuffer) + delete [] dispatchParams; + + return result; + } +} + +#define STUB_ENTRY(n) \ +nsresult nsXPTCStubBase::Stub##n() \ +{ \ + void *frame = __builtin_frame_address(0); \ + return PrepareAndDispatch(this, n, (uint32_t*)frame + 3); \ +} + +#define SENTINEL_ENTRY(n) \ +nsresult nsXPTCStubBase::Sentinel##n() \ +{ \ + NS_ERROR("nsXPTCStubBase::Sentinel called"); \ + return NS_ERROR_NOT_IMPLEMENTED; \ +} + +#include "xptcstubsdef.inc" diff --git a/xpcom/reflect/xptcall/md/unix/xptcstubs_linux_s390.cpp b/xpcom/reflect/xptcall/md/unix/xptcstubs_linux_s390.cpp new file mode 100644 index 000000000..cf4ab1268 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcstubs_linux_s390.cpp @@ -0,0 +1,183 @@ +/* -*- 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/. */ + +/* Implement shared vtbl methods. */ + +#include "xptcprivate.h" +#include "xptiprivate.h" + +static nsresult ATTRIBUTE_USED +PrepareAndDispatch(nsXPTCStubBase* self, uint32_t methodIndex, + uint32_t* a_gpr, uint64_t *a_fpr, uint32_t *a_ov) +{ +#define PARAM_BUFFER_COUNT 16 + + nsXPTCMiniVariant paramBuffer[PARAM_BUFFER_COUNT]; + nsXPTCMiniVariant* dispatchParams = nullptr; + const nsXPTMethodInfo* info; + uint8_t paramCount; + uint8_t i; + nsresult result = NS_ERROR_FAILURE; + + NS_ASSERTION(self,"no self"); + + self->mEntry->GetMethodInfo(uint16_t(methodIndex), &info); + NS_ASSERTION(info,"no info"); + + paramCount = info->GetParamCount(); + + // setup variant array pointer + if(paramCount > PARAM_BUFFER_COUNT) + dispatchParams = new nsXPTCMiniVariant[paramCount]; + else + dispatchParams = paramBuffer; + NS_ASSERTION(dispatchParams,"no place for params"); + + uint32_t gpr = 1, fpr = 0; + + for(i = 0; i < paramCount; i++) + { + const nsXPTParamInfo& param = info->GetParam(i); + const nsXPTType& type = param.GetType(); + nsXPTCMiniVariant* dp = &dispatchParams[i]; + + if(param.IsOut() || !type.IsArithmetic()) + { + if (gpr < 5) + dp->val.p = (void*) *a_gpr++, gpr++; + else + dp->val.p = (void*) *a_ov++; + continue; + } + // else + switch(type) + { + case nsXPTType::T_I8 : + if (gpr < 5) + dp->val.i8 = *((int32_t*) a_gpr), a_gpr++, gpr++; + else + dp->val.i8 = *((int32_t*) a_ov ), a_ov++; + break; + case nsXPTType::T_I16 : + if (gpr < 5) + dp->val.i16 = *((int32_t*) a_gpr), a_gpr++, gpr++; + else + dp->val.i16 = *((int32_t*) a_ov ), a_ov++; + break; + case nsXPTType::T_I32 : + if (gpr < 5) + dp->val.i32 = *((int32_t*) a_gpr), a_gpr++, gpr++; + else + dp->val.i32 = *((int32_t*) a_ov ), a_ov++; + break; + case nsXPTType::T_I64 : + if (gpr < 4) + dp->val.i64 = *((int64_t*) a_gpr), a_gpr+=2, gpr+=2; + else + dp->val.i64 = *((int64_t*) a_ov ), a_ov+=2, gpr=5; + break; + case nsXPTType::T_U8 : + if (gpr < 5) + dp->val.u8 = *((uint32_t*)a_gpr), a_gpr++, gpr++; + else + dp->val.u8 = *((uint32_t*)a_ov ), a_ov++; + break; + case nsXPTType::T_U16 : + if (gpr < 5) + dp->val.u16 = *((uint32_t*)a_gpr), a_gpr++, gpr++; + else + dp->val.u16 = *((uint32_t*)a_ov ), a_ov++; + break; + case nsXPTType::T_U32 : + if (gpr < 5) + dp->val.u32 = *((uint32_t*)a_gpr), a_gpr++, gpr++; + else + dp->val.u32 = *((uint32_t*)a_ov ), a_ov++; + break; + case nsXPTType::T_U64 : + if (gpr < 4) + dp->val.u64 = *((uint64_t*)a_gpr), a_gpr+=2, gpr+=2; + else + dp->val.u64 = *((uint64_t*)a_ov ), a_ov+=2, gpr=5; + break; + case nsXPTType::T_FLOAT : + if (fpr < 2) + dp->val.f = *((float*) a_fpr), a_fpr++, fpr++; + else + dp->val.f = *((float*) a_ov ), a_ov++; + break; + case nsXPTType::T_DOUBLE : + if (fpr < 2) + dp->val.d = *((double*) a_fpr), a_fpr++, fpr++; + else + dp->val.d = *((double*) a_ov ), a_ov+=2; + break; + case nsXPTType::T_BOOL : + if (gpr < 5) + dp->val.b = *((uint32_t*)a_gpr), a_gpr++, gpr++; + else + dp->val.b = *((uint32_t*)a_ov ), a_ov++; + break; + case nsXPTType::T_CHAR : + if (gpr < 5) + dp->val.c = *((uint32_t*)a_gpr), a_gpr++, gpr++; + else + dp->val.c = *((uint32_t*)a_ov ), a_ov++; + break; + case nsXPTType::T_WCHAR : + if (gpr < 5) + dp->val.wc = *((uint32_t*)a_gpr), a_gpr++, gpr++; + else + dp->val.wc = *((uint32_t*)a_ov ), a_ov++; + break; + default: + NS_ERROR("bad type"); + break; + } + } + + result = self->mOuter->CallMethod((uint16_t)methodIndex, info, dispatchParams); + + if(dispatchParams != paramBuffer) + delete [] dispatchParams; + + return result; +} + +#define STUB_ENTRY(n) \ +nsresult nsXPTCStubBase::Stub##n() \ +{ \ + uint32_t a_gpr[4]; \ + uint64_t a_fpr[2]; \ + uint32_t *a_ov; \ + \ + __asm__ __volatile__ \ + ( \ + "l %0,0(15)\n\t" \ + "ahi %0,96\n\t" \ + "stm 3,6,0(%3)\n\t" \ + "std 0,%1\n\t" \ + "std 2,%2\n\t" \ + : "=&a" (a_ov), \ + "=m" (a_fpr[0]), \ + "=m" (a_fpr[1]) \ + : "a" (a_gpr) \ + : "memory", "cc", \ + "3", "4", "5", "6" \ + ); \ + \ + return PrepareAndDispatch(this, n, a_gpr, a_fpr, a_ov); \ +} + +#define SENTINEL_ENTRY(n) \ +nsresult nsXPTCStubBase::Sentinel##n() \ +{ \ + NS_ERROR("nsXPTCStubBase::Sentinel called"); \ + return NS_ERROR_NOT_IMPLEMENTED; \ +} + +#include "xptcstubsdef.inc" + diff --git a/xpcom/reflect/xptcall/md/unix/xptcstubs_linux_s390x.cpp b/xpcom/reflect/xptcall/md/unix/xptcstubs_linux_s390x.cpp new file mode 100644 index 000000000..189cceb82 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcstubs_linux_s390x.cpp @@ -0,0 +1,187 @@ +/* -*- 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/. */ + +/* Implement shared vtbl methods. */ + +#include "xptcprivate.h" +#include "xptiprivate.h" + +static nsresult ATTRIBUTE_USED +PrepareAndDispatch(nsXPTCStubBase* self, uint32_t methodIndex, + uint64_t* a_gpr, uint64_t *a_fpr, uint64_t *a_ov) +{ +#define PARAM_BUFFER_COUNT 16 + + nsXPTCMiniVariant paramBuffer[PARAM_BUFFER_COUNT]; + nsXPTCMiniVariant* dispatchParams = nullptr; + const nsXPTMethodInfo* info; + uint8_t paramCount; + uint8_t i; + nsresult result = NS_ERROR_FAILURE; + + NS_ASSERTION(self,"no self"); + + self->mEntry->GetMethodInfo(uint16_t(methodIndex), &info); + NS_ASSERTION(info,"no info"); + + paramCount = info->GetParamCount(); + + // setup variant array pointer + if(paramCount > PARAM_BUFFER_COUNT) + dispatchParams = new nsXPTCMiniVariant[paramCount]; + else + dispatchParams = paramBuffer; + NS_ASSERTION(dispatchParams,"no place for params"); + + uint32_t gpr = 1, fpr = 0; + + for(i = 0; i < paramCount; i++) + { + const nsXPTParamInfo& param = info->GetParam(i); + const nsXPTType& type = param.GetType(); + nsXPTCMiniVariant* dp = &dispatchParams[i]; + + if(param.IsOut() || !type.IsArithmetic()) + { + if (gpr < 5) + dp->val.p = (void*) *a_gpr++, gpr++; + else + dp->val.p = (void*) *a_ov++; + continue; + } + // else + switch(type) + { + case nsXPTType::T_I8 : + if (gpr < 5) + dp->val.i8 = *((int64_t*) a_gpr), a_gpr++, gpr++; + else + dp->val.i8 = *((int64_t*) a_ov ), a_ov++; + break; + case nsXPTType::T_I16 : + if (gpr < 5) + dp->val.i16 = *((int64_t*) a_gpr), a_gpr++, gpr++; + else + dp->val.i16 = *((int64_t*) a_ov ), a_ov++; + break; + case nsXPTType::T_I32 : + if (gpr < 5) + dp->val.i32 = *((int64_t*) a_gpr), a_gpr++, gpr++; + else + dp->val.i32 = *((int64_t*) a_ov ), a_ov++; + break; + case nsXPTType::T_I64 : + if (gpr < 5) + dp->val.i64 = *((int64_t*) a_gpr), a_gpr++, gpr++; + else + dp->val.i64 = *((int64_t*) a_ov ), a_ov++; + break; + case nsXPTType::T_U8 : + if (gpr < 5) + dp->val.u8 = *((uint64_t*)a_gpr), a_gpr++, gpr++; + else + dp->val.u8 = *((uint64_t*)a_ov ), a_ov++; + break; + case nsXPTType::T_U16 : + if (gpr < 5) + dp->val.u16 = *((uint64_t*)a_gpr), a_gpr++, gpr++; + else + dp->val.u16 = *((uint64_t*)a_ov ), a_ov++; + break; + case nsXPTType::T_U32 : + if (gpr < 5) + dp->val.u32 = *((uint64_t*)a_gpr), a_gpr++, gpr++; + else + dp->val.u32 = *((uint64_t*)a_ov ), a_ov++; + break; + case nsXPTType::T_U64 : + if (gpr < 5) + dp->val.u64 = *((uint64_t*)a_gpr), a_gpr++, gpr++; + else + dp->val.u64 = *((uint64_t*)a_ov ), a_ov++; + break; + case nsXPTType::T_FLOAT : + if (fpr < 4) + dp->val.f = *((float*) a_fpr), a_fpr++, fpr++; + else + dp->val.f = *(((float*) a_ov )+1), a_ov++; + break; + case nsXPTType::T_DOUBLE : + if (fpr < 4) + dp->val.d = *((double*) a_fpr), a_fpr++, fpr++; + else + dp->val.d = *((double*) a_ov ), a_ov++; + break; + case nsXPTType::T_BOOL : + if (gpr < 5) + dp->val.b = *((uint64_t*)a_gpr), a_gpr++, gpr++; + else + dp->val.b = *((uint64_t*)a_ov ), a_ov++; + break; + case nsXPTType::T_CHAR : + if (gpr < 5) + dp->val.c = *((uint64_t*)a_gpr), a_gpr++, gpr++; + else + dp->val.c = *((uint64_t*)a_ov ), a_ov++; + break; + case nsXPTType::T_WCHAR : + if (gpr < 5) + dp->val.wc = *((uint64_t*)a_gpr), a_gpr++, gpr++; + else + dp->val.wc = *((uint64_t*)a_ov ), a_ov++; + break; + default: + NS_ERROR("bad type"); + break; + } + } + + result = self->mOuter->CallMethod((uint16_t)methodIndex, info, dispatchParams); + + if(dispatchParams != paramBuffer) + delete [] dispatchParams; + + return result; +} + +#define STUB_ENTRY(n) \ +nsresult nsXPTCStubBase::Stub##n() \ +{ \ + uint64_t a_gpr[4]; \ + uint64_t a_fpr[4]; \ + uint64_t *a_ov; \ + \ + __asm__ __volatile__ \ + ( \ + "lg %0,0(15)\n\t" \ + "aghi %0,160\n\t" \ + "stmg 3,6,0(%5)\n\t"\ + "std 0,%1\n\t" \ + "std 2,%2\n\t" \ + "std 4,%3\n\t" \ + "std 6,%4\n\t" \ + : "=&a" (a_ov), \ + "=m" (a_fpr[0]), \ + "=m" (a_fpr[1]), \ + "=m" (a_fpr[2]), \ + "=m" (a_fpr[3]) \ + : "a" (a_gpr) \ + : "memory", "cc", \ + "3", "4", "5", "6" \ + ); \ + \ + return PrepareAndDispatch(this, n, a_gpr, a_fpr, a_ov); \ +} + +#define SENTINEL_ENTRY(n) \ +nsresult nsXPTCStubBase::Sentinel##n() \ +{ \ + NS_ERROR("nsXPTCStubBase::Sentinel called"); \ + return NS_ERROR_NOT_IMPLEMENTED; \ +} + +#include "xptcstubsdef.inc" + diff --git a/xpcom/reflect/xptcall/md/unix/xptcstubs_mips.cpp b/xpcom/reflect/xptcall/md/unix/xptcstubs_mips.cpp new file mode 100644 index 000000000..75f8562ce --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcstubs_mips.cpp @@ -0,0 +1,112 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * Version: MPL 1.1 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "xptcprivate.h" +#include "xptiprivate.h" + +#include + +/* + * This is for MIPS O32 ABI + * Args contains a0-3 and then the stack. + * Because a0 is 'this', we want to skip it + */ +extern "C" nsresult ATTRIBUTE_USED +PrepareAndDispatch(nsXPTCStubBase* self, uint32_t methodIndex, uint32_t* args) +{ + args++; // always skip over a0 + +#define PARAM_BUFFER_COUNT 16 + + nsXPTCMiniVariant paramBuffer[PARAM_BUFFER_COUNT]; + nsXPTCMiniVariant* dispatchParams = nullptr; + const nsXPTMethodInfo* info; + uint8_t paramCount; + uint8_t i; + nsresult result = NS_ERROR_FAILURE; + + NS_ASSERTION(self,"no self"); + + self->mEntry->GetMethodInfo(uint16_t(methodIndex), &info); + NS_ASSERTION(info,"no method info"); + + paramCount = info->GetParamCount(); + + // setup variant array pointer + if(paramCount > PARAM_BUFFER_COUNT) + dispatchParams = new nsXPTCMiniVariant[paramCount]; + else + dispatchParams = paramBuffer; + NS_ASSERTION(dispatchParams,"no place for params"); + + uint32_t* ap = args; + for(i = 0; i < paramCount; i++, ap++) + { + const nsXPTParamInfo& param = info->GetParam(i); + const nsXPTType& type = param.GetType(); + nsXPTCMiniVariant* dp = &dispatchParams[i]; + + if(param.IsOut() || !type.IsArithmetic()) + { + dp->val.p = (void*) *ap; + continue; + } + + switch(type) + { + case nsXPTType::T_I64 : + if ((intptr_t)ap & 4) ap++; + dp->val.i64 = *((int64_t*) ap); ap++; + break; + case nsXPTType::T_U64 : + if ((intptr_t)ap & 4) ap++; + dp->val.u64 = *((int64_t*) ap); ap++; + break; + case nsXPTType::T_DOUBLE: + if ((intptr_t)ap & 4) ap++; + dp->val.d = *((double*) ap); ap++; + break; +#ifdef IS_LITTLE_ENDIAN + default: + dp->val.p = (void*) *ap; + break; +#else + case nsXPTType::T_I8 : dp->val.i8 = (int8_t) *ap; break; + case nsXPTType::T_I16 : dp->val.i16 = (int16_t) *ap; break; + case nsXPTType::T_I32 : dp->val.i32 = (int32_t) *ap; break; + case nsXPTType::T_U8 : dp->val.u8 = (uint8_t) *ap; break; + case nsXPTType::T_U16 : dp->val.u16 = (uint16_t) *ap; break; + case nsXPTType::T_U32 : dp->val.u32 = (uint32_t) *ap; break; + case nsXPTType::T_BOOL : dp->val.b = (bool) *ap; break; + case nsXPTType::T_CHAR : dp->val.c = (char) *ap; break; + case nsXPTType::T_WCHAR : dp->val.wc = (wchar_t) *ap; break; + case nsXPTType::T_FLOAT : dp->val.f = *(float *) ap; break; + default: + NS_ASSERTION(0, "bad type"); + break; +#endif + } + } + + result = self->mOuter->CallMethod((uint16_t)methodIndex, info, dispatchParams); + + if(dispatchParams != paramBuffer) + delete [] dispatchParams; + + return result; +} + +#define STUB_ENTRY(n) // done in the .s file + +#define SENTINEL_ENTRY(n) \ +nsresult nsXPTCStubBase::Sentinel##n() \ +{ \ + NS_ERROR("nsXPTCStubBase::Sentinel called"); \ + return NS_ERROR_NOT_IMPLEMENTED; \ +} + +#include "xptcstubsdef.inc" diff --git a/xpcom/reflect/xptcall/md/unix/xptcstubs_mips64.cpp b/xpcom/reflect/xptcall/md/unix/xptcstubs_mips64.cpp new file mode 100644 index 000000000..a4b553940 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcstubs_mips64.cpp @@ -0,0 +1,185 @@ +/* -*- 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 "xptcprivate.h" +#include "xptiprivate.h" + +#if (_MIPS_SIM != _ABIN32) && (_MIPS_SIM != _ABI64) +#error "This code is for MIPS n32/n64 only" +#endif + +/* + * This is for MIPS n32/n64 ABI + * + * When we're called, the "gp" registers are stored in gprData and + * the "fp" registers are stored in fprData. There are 8 regs + * available which correspond to the first 7 parameters of the + * function and the "this" pointer. If there are additional parms, + * they are stored on the stack at address "args". + * + */ +extern "C" nsresult ATTRIBUTE_USED +PrepareAndDispatch(nsXPTCStubBase* self, uint32_t methodIndex, uint64_t* args, + uint64_t *gprData, double *fprData) +{ +#define PARAM_BUFFER_COUNT 16 +#define PARAM_GPR_COUNT 7 +#define PARAM_FPR_COUNT 7 + + nsXPTCMiniVariant paramBuffer[PARAM_BUFFER_COUNT]; + nsXPTCMiniVariant* dispatchParams = nullptr; + const nsXPTMethodInfo* info; + uint8_t paramCount; + uint8_t i; + nsresult result = NS_ERROR_FAILURE; + + NS_ASSERTION(self,"no self"); + + self->mEntry->GetMethodInfo(uint16_t(methodIndex), &info); + NS_ASSERTION(info,"no method info"); + + paramCount = info->GetParamCount(); + + // setup variant array pointer + if(paramCount > PARAM_BUFFER_COUNT) + dispatchParams = new nsXPTCMiniVariant[paramCount]; + else + dispatchParams = paramBuffer; + NS_ASSERTION(dispatchParams,"no place for params"); + + uint64_t* ap = args; + uint32_t iCount = 0; + for(i = 0; i < paramCount; i++) + { + const nsXPTParamInfo& param = info->GetParam(i); + const nsXPTType& type = param.GetType(); + nsXPTCMiniVariant* dp = &dispatchParams[i]; + + if(param.IsOut() || !type.IsArithmetic()) + { + if (iCount < PARAM_GPR_COUNT) + dp->val.p = (void*)gprData[iCount++]; + else + dp->val.p = (void*)*ap++; + continue; + } + // else + switch(type) + { + case nsXPTType::T_I8: + if (iCount < PARAM_GPR_COUNT) + dp->val.i8 = (int8_t)gprData[iCount++]; + else + dp->val.i8 = (int8_t)*ap++; + break; + + case nsXPTType::T_I16: + if (iCount < PARAM_GPR_COUNT) + dp->val.i16 = (int16_t)gprData[iCount++]; + else + dp->val.i16 = (int16_t)*ap++; + break; + + case nsXPTType::T_I32: + if (iCount < PARAM_GPR_COUNT) + dp->val.i32 = (int32_t)gprData[iCount++]; + else + dp->val.i32 = (int32_t)*ap++; + break; + + case nsXPTType::T_I64: + if (iCount < PARAM_GPR_COUNT) + dp->val.i64 = (int64_t)gprData[iCount++]; + else + dp->val.i64 = (int64_t)*ap++; + break; + + case nsXPTType::T_U8: + if (iCount < PARAM_GPR_COUNT) + dp->val.u8 = (uint8_t)gprData[iCount++]; + else + dp->val.u8 = (uint8_t)*ap++; + break; + + case nsXPTType::T_U16: + if (iCount < PARAM_GPR_COUNT) + dp->val.u16 = (uint16_t)gprData[iCount++]; + else + dp->val.u16 = (uint16_t)*ap++; + break; + + case nsXPTType::T_U32: + if (iCount < PARAM_GPR_COUNT) + dp->val.u32 = (uint32_t)gprData[iCount++]; + else + dp->val.u32 = (uint32_t)*ap++; + break; + + case nsXPTType::T_U64: + if (iCount < PARAM_GPR_COUNT) + dp->val.u64 = (uint64_t)gprData[iCount++]; + else + dp->val.u64 = (uint64_t)*ap++; + break; + + case nsXPTType::T_FLOAT: + if (iCount < PARAM_FPR_COUNT) + dp->val.f = (double)fprData[iCount++]; + else + dp->val.f = *((double*)ap++); + break; + + case nsXPTType::T_DOUBLE: + if (iCount < PARAM_FPR_COUNT) + dp->val.d = (double)fprData[iCount++]; + else + dp->val.d = *((double*)ap++); + break; + + case nsXPTType::T_BOOL: + if (iCount < PARAM_GPR_COUNT) + dp->val.b = (bool)gprData[iCount++]; + else + dp->val.b = (bool)*ap++; + break; + + case nsXPTType::T_CHAR: + if (iCount < PARAM_GPR_COUNT) + dp->val.c = (char)gprData[iCount++]; + else + dp->val.c = (char)*ap++; + break; + + case nsXPTType::T_WCHAR: + if (iCount < PARAM_GPR_COUNT) + dp->val.wc = (wchar_t)gprData[iCount++]; + else + dp->val.wc = (wchar_t)*ap++; + break; + + default: + NS_ASSERTION(0, "bad type"); + break; + } + } + + result = self->mOuter->CallMethod((uint16_t)methodIndex, info, dispatchParams); + + if(dispatchParams != paramBuffer) + delete [] dispatchParams; + + return result; +} + +#define STUB_ENTRY(n) /* defined in the assembly file */ + +#define SENTINEL_ENTRY(n) \ +nsresult nsXPTCStubBase::Sentinel##n() \ +{ \ + NS_ASSERTION(0,"nsXPTCStubBase::Sentinel called"); \ + return NS_ERROR_NOT_IMPLEMENTED; \ +} + +#include "xptcstubsdef.inc" diff --git a/xpcom/reflect/xptcall/md/unix/xptcstubs_netbsd_m68k.cpp b/xpcom/reflect/xptcall/md/unix/xptcstubs_netbsd_m68k.cpp new file mode 100644 index 000000000..680114fd5 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcstubs_netbsd_m68k.cpp @@ -0,0 +1,115 @@ +/* -*- 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/. */ + +/* Implement shared vtbl methods. */ + +#include "xptcprivate.h" + +#if !defined(__NetBSD__) || !defined(__m68k__) +#error This code is for NetBSD/m68k only +#endif + +extern "C" { + static nsresult ATTRIBUTE_USED + PrepareAndDispatch(nsXPTCStubBase* self, uint32_t methodIndex, uint32_t* args) + { +#define PARAM_BUFFER_COUNT 16 + + nsXPTCMiniVariant paramBuffer[PARAM_BUFFER_COUNT]; + nsXPTCMiniVariant* dispatchParams = nullptr; + nsIInterfaceInfo* iface_info = nullptr; + const nsXPTMethodInfo* info; + uint8_t paramCount; + uint8_t i; + nsresult result = NS_ERROR_FAILURE; + + NS_ASSERTION(self,"no self"); + + self->GetInterfaceInfo(&iface_info); + NS_ASSERTION(iface_info,"no interface info"); + + iface_info->GetMethodInfo(uint16_t(methodIndex), &info); + NS_ASSERTION(info,"no interface info"); + + paramCount = info->GetParamCount(); + + // setup variant array pointer + if(paramCount > PARAM_BUFFER_COUNT) + dispatchParams = new nsXPTCMiniVariant[paramCount]; + else + dispatchParams = paramBuffer; + NS_ASSERTION(dispatchParams,"no place for params"); + + uint32_t* ap = args; + for(i = 0; i < paramCount; i++, ap++) + { + const nsXPTParamInfo& param = info->GetParam(i); + const nsXPTType& type = param.GetType(); + nsXPTCMiniVariant* dp = &dispatchParams[i]; + + if(param.IsOut() || !type.IsArithmetic()) + { + dp->val.p = (void*) *ap; + continue; + } + + switch(type) + { + // the 8 and 16 bit types will have been promoted to 32 bits before + // being pushed onto the stack. Since the 68k is big endian, we + // need to skip over the leading high order bytes. + case nsXPTType::T_I8 : dp->val.i8 = *(((int8_t*) ap) + 3); break; + case nsXPTType::T_I16 : dp->val.i16 = *(((int16_t*) ap) + 1); break; + case nsXPTType::T_I32 : dp->val.i32 = *((int32_t*) ap); break; + case nsXPTType::T_I64 : dp->val.i64 = *((int64_t*) ap); ap++; break; + case nsXPTType::T_U8 : dp->val.u8 = *(((uint8_t*) ap) + 3); break; + case nsXPTType::T_U16 : dp->val.u16 = *(((uint16_t*)ap) + 1); break; + case nsXPTType::T_U32 : dp->val.u32 = *((uint32_t*)ap); break; + case nsXPTType::T_U64 : dp->val.u64 = *((uint64_t*)ap); ap++; break; + case nsXPTType::T_FLOAT : dp->val.f = *((float*) ap); break; + case nsXPTType::T_DOUBLE : dp->val.d = *((double*) ap); ap++; break; + case nsXPTType::T_BOOL : dp->val.b = *(((char*) ap) + 3); break; + case nsXPTType::T_CHAR : dp->val.c = *(((char*) ap) + 3); break; + // wchar_t is an int (32 bits) on NetBSD + case nsXPTType::T_WCHAR : dp->val.wc = *((wchar_t*) ap); break; + default: + NS_ERROR("bad type"); + break; + } + } + + result = self->CallMethod((uint16_t)methodIndex, info, dispatchParams); + + NS_RELEASE(iface_info); + + if(dispatchParams != paramBuffer) + delete [] dispatchParams; + + return result; + } +} + +#define STUB_ENTRY(n) \ +__asm__( \ + ".global _Stub"#n"__14nsXPTCStubBase\n\t" \ +"_Stub"#n"__14nsXPTCStubBase:\n\t" \ + "link a6,#0 \n\t" \ + "lea a6@(12), a0 \n\t" /* pointer to args */ \ + "movl a0, sp@- \n\t" \ + "movl #"#n", sp@- \n\t" /* method index */ \ + "movl a6@(8), sp@- \n\t" /* this */ \ + "jbsr _PrepareAndDispatch \n\t" \ + "unlk a6 \n\t" \ + "rts \n\t" \ +); + +#define SENTINEL_ENTRY(n) \ +nsresult nsXPTCStubBase::Sentinel##n() \ +{ \ + NS_ERROR("nsXPTCStubBase::Sentinel called"); \ + return NS_ERROR_NOT_IMPLEMENTED; \ +} + +#include "xptcstubsdef.inc" diff --git a/xpcom/reflect/xptcall/md/unix/xptcstubs_pa32.cpp b/xpcom/reflect/xptcall/md/unix/xptcstubs_pa32.cpp new file mode 100644 index 000000000..7353709f1 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcstubs_pa32.cpp @@ -0,0 +1,143 @@ + +/* -*- 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/. */ + +/* Implement shared vtbl methods. */ + +#include "xptcprivate.h" +#include "xptiprivate.h" + +#if _HPUX +#error "This code is for HP-PA RISC 32 bit mode only" +#endif + +extern "C" nsresult ATTRIBUTE_USED +PrepareAndDispatch(nsXPTCStubBase* self, uint32_t methodIndex, + uint32_t* args, uint32_t* floatargs) +{ + + typedef struct { + uint32_t hi; + uint32_t lo; + } DU; + +#define PARAM_BUFFER_COUNT 16 + + nsXPTCMiniVariant paramBuffer[PARAM_BUFFER_COUNT]; + nsXPTCMiniVariant* dispatchParams = nullptr; + const nsXPTMethodInfo* info; + int32_t regwords = 1; /* self pointer is not in the variant records */ + nsresult result = NS_ERROR_FAILURE; + uint8_t paramCount; + uint8_t i; + + NS_ASSERTION(self,"no self"); + + self->mEntry->GetMethodInfo(uint16_t(methodIndex), &info); + NS_ASSERTION(info,"no method info"); + if (!info) + return NS_ERROR_UNEXPECTED; + + paramCount = info->GetParamCount(); + + // setup variant array pointer + if(paramCount > PARAM_BUFFER_COUNT) + dispatchParams = new nsXPTCMiniVariant[paramCount]; + else + dispatchParams = paramBuffer; + NS_ASSERTION(dispatchParams,"no place for params"); + if (!dispatchParams) + return NS_ERROR_OUT_OF_MEMORY; + + for(i = 0; i < paramCount; ++i, --args) + { + const nsXPTParamInfo& param = info->GetParam(i); + const nsXPTType& type = param.GetType(); + nsXPTCMiniVariant* dp = &dispatchParams[i]; + + if(param.IsOut() || !type.IsArithmetic()) + { + dp->val.p = (void*) *args; + ++regwords; + continue; + } + switch(type) + { + case nsXPTType::T_I8 : dp->val.i8 = *((int32_t*) args); break; + case nsXPTType::T_I16 : dp->val.i16 = *((int32_t*) args); break; + case nsXPTType::T_I32 : dp->val.i32 = *((int32_t*) args); break; + case nsXPTType::T_DOUBLE : + if (regwords & 1) + { + ++regwords; /* align on double word */ + --args; + } + if (regwords == 0 || regwords == 2) + { + dp->val.d=*((double*) (floatargs + regwords)); + --args; + } + else + { + dp->val.d = *((double*) --args); + } + regwords += 2; + continue; + case nsXPTType::T_U64 : + case nsXPTType::T_I64 : + if (regwords & 1) + { + ++regwords; /* align on double word */ + --args; + } + ((DU *)dp)->lo = *((uint32_t*) args); + ((DU *)dp)->hi = *((uint32_t*) --args); + regwords += 2; + continue; + case nsXPTType::T_FLOAT : + if (regwords >= 4) + dp->val.f = *((float*) args); + else + dp->val.f = *((float*) floatargs+4+regwords); + break; + case nsXPTType::T_U8 : dp->val.u8 = *((uint32_t*) args); break; + case nsXPTType::T_U16 : dp->val.u16 = *((uint32_t*) args); break; + case nsXPTType::T_U32 : dp->val.u32 = *((uint32_t*) args); break; + case nsXPTType::T_BOOL : dp->val.b = *((uint32_t*) args); break; + case nsXPTType::T_CHAR : dp->val.c = *((uint32_t*) args); break; + case nsXPTType::T_WCHAR : dp->val.wc = *((int32_t*) args); break; + default: + NS_ERROR("bad type"); + break; + } + ++regwords; + } + + result = self->mOuter->CallMethod((uint16_t) methodIndex, info, dispatchParams); + + if(dispatchParams != paramBuffer) + delete [] dispatchParams; + + return result; +} + +extern "C" nsresult SharedStub(int); + +#define STUB_ENTRY(n) \ +nsresult nsXPTCStubBase::Stub##n() \ +{ \ + return SharedStub(n); \ +} + +#define SENTINEL_ENTRY(n) \ +nsresult nsXPTCStubBase::Sentinel##n() \ +{ \ + NS_ERROR("nsXPTCStubBase::Sentinel called"); \ + return NS_ERROR_NOT_IMPLEMENTED; \ +} + +#include "xptcstubsdef.inc" + diff --git a/xpcom/reflect/xptcall/md/unix/xptcstubs_ppc64_linux.cpp b/xpcom/reflect/xptcall/md/unix/xptcstubs_ppc64_linux.cpp new file mode 100644 index 000000000..95e67cb29 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcstubs_ppc64_linux.cpp @@ -0,0 +1,246 @@ +/* -*- 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/. */ + +// Implement shared vtbl methods. + +#include "xptcprivate.h" +#include "xptiprivate.h" + +// The Linux/PPC64 ABI passes the first 8 integral +// parameters and the first 13 floating point parameters in registers +// (r3-r10 and f1-f13), no stack space is allocated for these by the +// caller. The rest of the parameters are passed in the caller's stack +// area. The stack pointer has to retain 16-byte alignment. + +// The PowerPC64 platform ABI can be found here: +// http://www.freestandards.org/spec/ELF/ppc64/ +// and in particular: +// http://www.freestandards.org/spec/ELF/ppc64/PPC-elf64abi-1.9.html#FUNC-CALL + +#define PARAM_BUFFER_COUNT 16 +#define GPR_COUNT 7 +#define FPR_COUNT 13 + +// PrepareAndDispatch() is called by SharedStub() and calls the actual method. +// +// - 'args[]' contains the arguments passed on stack +// - 'gprData[]' contains the arguments passed in integer registers +// - 'fprData[]' contains the arguments passed in floating point registers +// +// The parameters are mapped into an array of type 'nsXPTCMiniVariant' +// and then the method gets called. +#include +extern "C" nsresult ATTRIBUTE_USED +PrepareAndDispatch(nsXPTCStubBase* self, + uint64_t methodIndex, + uint64_t* args, + uint64_t *gprData, + double *fprData) +{ + nsXPTCMiniVariant paramBuffer[PARAM_BUFFER_COUNT]; + nsXPTCMiniVariant* dispatchParams = nullptr; + const nsXPTMethodInfo* info; + uint32_t paramCount; + uint32_t i; + nsresult result = NS_ERROR_FAILURE; + + NS_ASSERTION(self,"no self"); + + self->mEntry->GetMethodInfo(uint16_t(methodIndex), &info); + NS_ASSERTION(info,"no method info"); + if (! info) + return NS_ERROR_UNEXPECTED; + + paramCount = info->GetParamCount(); + + // setup variant array pointer + if(paramCount > PARAM_BUFFER_COUNT) + dispatchParams = new nsXPTCMiniVariant[paramCount]; + else + dispatchParams = paramBuffer; + + NS_ASSERTION(dispatchParams,"no place for params"); + if (! dispatchParams) + return NS_ERROR_OUT_OF_MEMORY; + + uint64_t* ap = args; + uint64_t tempu64; + + for(i = 0; i < paramCount; i++) { + const nsXPTParamInfo& param = info->GetParam(i); + const nsXPTType& type = param.GetType(); + nsXPTCMiniVariant* dp = &dispatchParams[i]; + + if (!param.IsOut() && type == nsXPTType::T_DOUBLE) { + if (i < FPR_COUNT) + dp->val.d = fprData[i]; + else + dp->val.d = *(double*) ap; + } else if (!param.IsOut() && type == nsXPTType::T_FLOAT) { + if (i < FPR_COUNT) + dp->val.f = (float) fprData[i]; // in registers floats are passed as doubles + else { + float *p = (float *)ap; +#ifndef __LITTLE_ENDIAN__ + p++; +#endif + dp->val.f = *p; + } + } else { /* integer type or pointer */ + if (i < GPR_COUNT) + tempu64 = gprData[i]; + else + tempu64 = *ap; + + if (param.IsOut() || !type.IsArithmetic()) + dp->val.p = (void*) tempu64; + else if (type == nsXPTType::T_I8) + dp->val.i8 = (int8_t) tempu64; + else if (type == nsXPTType::T_I16) + dp->val.i16 = (int16_t) tempu64; + else if (type == nsXPTType::T_I32) + dp->val.i32 = (int32_t) tempu64; + else if (type == nsXPTType::T_I64) + dp->val.i64 = (int64_t) tempu64; + else if (type == nsXPTType::T_U8) + dp->val.u8 = (uint8_t) tempu64; + else if (type == nsXPTType::T_U16) + dp->val.u16 = (uint16_t) tempu64; + else if (type == nsXPTType::T_U32) + dp->val.u32 = (uint32_t) tempu64; + else if (type == nsXPTType::T_U64) + dp->val.u64 = (uint64_t) tempu64; + else if (type == nsXPTType::T_BOOL) + dp->val.b = (bool) tempu64; + else if (type == nsXPTType::T_CHAR) + dp->val.c = (char) tempu64; + else if (type == nsXPTType::T_WCHAR) + dp->val.wc = (wchar_t) tempu64; + else + NS_ERROR("bad type"); + } + + if (i >= 7) + ap++; + } + + result = self->mOuter->CallMethod((uint16_t) methodIndex, info, + dispatchParams); + + if (dispatchParams != paramBuffer) + delete [] dispatchParams; + + return result; +} + +// Load r11 with the constant 'n' and branch to SharedStub(). +// +// XXX Yes, it's ugly that we're relying on gcc's name-mangling here; +// however, it's quick, dirty, and'll break when the ABI changes on +// us, which is what we want ;-). + + +// gcc-3 version +// +// As G++3 ABI contains the length of the functionname in the mangled +// name, it is difficult to get a generic assembler mechanism like +// in the G++ 2.95 case. +// Create names would be like: +// _ZN14nsXPTCStubBase5Stub1Ev +// _ZN14nsXPTCStubBase6Stub12Ev +// _ZN14nsXPTCStubBase7Stub123Ev +// _ZN14nsXPTCStubBase8Stub1234Ev +// etc. +// Use assembler directives to get the names right... + +#if _CALL_ELF == 2 +# define STUB_ENTRY(n) \ +__asm__ ( \ + ".section \".text\" \n\t" \ + ".align 2 \n\t" \ + ".if "#n" < 10 \n\t" \ + ".globl _ZN14nsXPTCStubBase5Stub"#n"Ev \n\t" \ + ".type _ZN14nsXPTCStubBase5Stub"#n"Ev,@function \n\n" \ +"_ZN14nsXPTCStubBase5Stub"#n"Ev: \n\t" \ + "0: addis 2,12,.TOC.-0b@ha \n\t" \ + "addi 2,2,.TOC.-0b@l \n\t" \ + ".localentry _ZN14nsXPTCStubBase5Stub"#n"Ev,.-_ZN14nsXPTCStubBase5Stub"#n"Ev \n\t" \ + \ + ".elseif "#n" < 100 \n\t" \ + ".globl _ZN14nsXPTCStubBase6Stub"#n"Ev \n\t" \ + ".type _ZN14nsXPTCStubBase6Stub"#n"Ev,@function \n\n" \ +"_ZN14nsXPTCStubBase6Stub"#n"Ev: \n\t" \ + "0: addis 2,12,.TOC.-0b@ha \n\t" \ + "addi 2,2,.TOC.-0b@l \n\t" \ + ".localentry _ZN14nsXPTCStubBase6Stub"#n"Ev,.-_ZN14nsXPTCStubBase6Stub"#n"Ev \n\t" \ + \ + ".elseif "#n" < 1000 \n\t" \ + ".globl _ZN14nsXPTCStubBase7Stub"#n"Ev \n\t" \ + ".type _ZN14nsXPTCStubBase7Stub"#n"Ev,@function \n\n" \ +"_ZN14nsXPTCStubBase7Stub"#n"Ev: \n\t" \ + "0: addis 2,12,.TOC.-0b@ha \n\t" \ + "addi 2,2,.TOC.-0b@l \n\t" \ + ".localentry _ZN14nsXPTCStubBase7Stub"#n"Ev,.-_ZN14nsXPTCStubBase7Stub"#n"Ev \n\t" \ + \ + ".else \n\t" \ + ".err \"stub number "#n" >= 1000 not yet supported\"\n" \ + ".endif \n\t" \ + \ + "li 11,"#n" \n\t" \ + "b SharedStub \n" \ +); +#else +# define STUB_ENTRY(n) \ +__asm__ ( \ + ".section \".toc\",\"aw\" \n\t" \ + ".section \".text\" \n\t" \ + ".align 2 \n\t" \ + ".if "#n" < 10 \n\t" \ + ".globl _ZN14nsXPTCStubBase5Stub"#n"Ev \n\t" \ + ".section \".opd\",\"aw\" \n\t" \ + ".align 3 \n\t" \ +"_ZN14nsXPTCStubBase5Stub"#n"Ev: \n\t" \ + ".quad ._ZN14nsXPTCStubBase5Stub"#n"Ev,.TOC.@tocbase \n\t" \ + ".previous \n\t" \ + ".type _ZN14nsXPTCStubBase5Stub"#n"Ev,@function \n\n" \ +"._ZN14nsXPTCStubBase5Stub"#n"Ev: \n\t" \ + \ + ".elseif "#n" < 100 \n\t" \ + ".globl _ZN14nsXPTCStubBase6Stub"#n"Ev \n\t" \ + ".section \".opd\",\"aw\" \n\t" \ + ".align 3 \n\t" \ +"_ZN14nsXPTCStubBase6Stub"#n"Ev: \n\t" \ + ".quad ._ZN14nsXPTCStubBase6Stub"#n"Ev,.TOC.@tocbase \n\t" \ + ".previous \n\t" \ + ".type _ZN14nsXPTCStubBase6Stub"#n"Ev,@function \n\n" \ +"._ZN14nsXPTCStubBase6Stub"#n"Ev: \n\t" \ + \ + ".elseif "#n" < 1000 \n\t" \ + ".globl _ZN14nsXPTCStubBase7Stub"#n"Ev \n\t" \ + ".section \".opd\",\"aw\" \n\t" \ + ".align 3 \n\t" \ +"_ZN14nsXPTCStubBase7Stub"#n"Ev: \n\t" \ + ".quad ._ZN14nsXPTCStubBase7Stub"#n"Ev,.TOC.@tocbase \n\t" \ + ".previous \n\t" \ + ".type _ZN14nsXPTCStubBase7Stub"#n"Ev,@function \n\n" \ +"._ZN14nsXPTCStubBase7Stub"#n"Ev: \n\t" \ + \ + ".else \n\t" \ + ".err \"stub number "#n" >= 1000 not yet supported\"\n" \ + ".endif \n\t" \ + \ + "li 11,"#n" \n\t" \ + "b SharedStub \n" \ +); +#endif + +#define SENTINEL_ENTRY(n) \ +nsresult nsXPTCStubBase::Sentinel##n() \ +{ \ + NS_ERROR("nsXPTCStubBase::Sentinel called"); \ + return NS_ERROR_NOT_IMPLEMENTED; \ +} + +#include "xptcstubsdef.inc" diff --git a/xpcom/reflect/xptcall/md/unix/xptcstubs_ppc_aix.cpp b/xpcom/reflect/xptcall/md/unix/xptcstubs_ppc_aix.cpp new file mode 100644 index 000000000..0463c21c5 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcstubs_ppc_aix.cpp @@ -0,0 +1,185 @@ +/* -*- 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/. */ + +/* Implement shared vtbl methods. */ + +#include "xptcprivate.h" +#include "xptiprivate.h" + +#if defined(AIX) + +/* + For PPC (AIX & MAC), the first 8 integral and the first 13 f.p. parameters + arrive in a separate chunk of data that has been loaded from the registers. + The args pointer has been set to the start of the parameters BEYOND the ones + arriving in registers +*/ +extern "C" nsresult ATTRIBUTE_USED +PrepareAndDispatch(nsXPTCStubBase* self, uint32_t methodIndex, uint32_t* args, uint32_t *gprData, double *fprData) +{ + typedef struct { + uint32_t hi; + uint32_t lo; // have to move 64 bit entities as 32 bit halves since + } DU; // stack slots are not guaranteed 16 byte aligned + +#define PARAM_BUFFER_COUNT 16 +#define PARAM_GPR_COUNT 7 + + nsXPTCMiniVariant paramBuffer[PARAM_BUFFER_COUNT]; + nsXPTCMiniVariant* dispatchParams = nullptr; + const nsXPTMethodInfo* info = nullptr; + uint8_t paramCount; + uint8_t i; + nsresult result = NS_ERROR_FAILURE; + + NS_ASSERTION(self,"no self"); + + self->mEntry->GetMethodInfo(uint16_t(methodIndex), &info); + NS_ASSERTION(info,"no method info"); + + paramCount = info->GetParamCount(); + + // setup variant array pointer + if(paramCount > PARAM_BUFFER_COUNT) + dispatchParams = new nsXPTCMiniVariant[paramCount]; + else + dispatchParams = paramBuffer; + NS_ASSERTION(dispatchParams,"no place for params"); + + uint32_t* ap = args; + uint32_t iCount = 0; + uint32_t fpCount = 0; + for(i = 0; i < paramCount; i++) + { + const nsXPTParamInfo& param = info->GetParam(i); + const nsXPTType& type = param.GetType(); + nsXPTCMiniVariant* dp = &dispatchParams[i]; + + if(param.IsOut() || !type.IsArithmetic()) + { + if (iCount < PARAM_GPR_COUNT) + dp->val.p = (void*) gprData[iCount++]; + else + dp->val.p = (void*) *ap++; + continue; + } + // else + switch(type) + { + case nsXPTType::T_I8 : if (iCount < PARAM_GPR_COUNT) + dp->val.i8 = (int8_t) gprData[iCount++]; + else + dp->val.i8 = (int8_t) *ap++; + break; + case nsXPTType::T_I16 : if (iCount < PARAM_GPR_COUNT) + dp->val.i16 = (int16_t) gprData[iCount++]; + else + dp->val.i16 = (int16_t) *ap++; + break; + case nsXPTType::T_I32 : if (iCount < PARAM_GPR_COUNT) + dp->val.i32 = (int32_t) gprData[iCount++]; + else + dp->val.i32 = (int32_t) *ap++; + break; + case nsXPTType::T_I64 : if (iCount < PARAM_GPR_COUNT) + ((DU *)dp)->hi = (int32_t) gprData[iCount++]; + else + ((DU *)dp)->hi = (int32_t) *ap++; + if (iCount < PARAM_GPR_COUNT) + ((DU *)dp)->lo = (uint32_t) gprData[iCount++]; + else + ((DU *)dp)->lo = (uint32_t) *ap++; + break; + case nsXPTType::T_U8 : if (iCount < PARAM_GPR_COUNT) + dp->val.u8 = (uint8_t) gprData[iCount++]; + else + dp->val.u8 = (uint8_t) *ap++; + break; + case nsXPTType::T_U16 : if (iCount < PARAM_GPR_COUNT) + dp->val.u16 = (uint16_t) gprData[iCount++]; + else + dp->val.u16 = (uint16_t) *ap++; + break; + case nsXPTType::T_U32 : if (iCount < PARAM_GPR_COUNT) + dp->val.u32 = (uint32_t) gprData[iCount++]; + else + dp->val.u32 = (uint32_t) *ap++; + break; + case nsXPTType::T_U64 : if (iCount < PARAM_GPR_COUNT) + ((DU *)dp)->hi = (uint32_t) gprData[iCount++]; + else + ((DU *)dp)->hi = (uint32_t) *ap++; + if (iCount < PARAM_GPR_COUNT) + ((DU *)dp)->lo = (uint32_t) gprData[iCount++]; + else + ((DU *)dp)->lo = (uint32_t) *ap++; + break; + case nsXPTType::T_FLOAT : if (fpCount < 13) { + dp->val.f = (float) fprData[fpCount++]; + if (iCount < PARAM_GPR_COUNT) + ++iCount; + else + ++ap; + } + else + dp->val.f = *((float*) ap++); + break; + case nsXPTType::T_DOUBLE : if (fpCount < 13) { + dp->val.d = (double) fprData[fpCount++]; + if (iCount < PARAM_GPR_COUNT) + ++iCount; + else + ++ap; + if (iCount < PARAM_GPR_COUNT) + ++iCount; + else + ++ap; + } + else { + dp->val.f = *((double*) ap); + ap += 2; + } + break; + case nsXPTType::T_BOOL : if (iCount < PARAM_GPR_COUNT) + dp->val.b = (bool) gprData[iCount++]; + else + dp->val.b = (bool) *ap++; + break; + case nsXPTType::T_CHAR : if (iCount < PARAM_GPR_COUNT) + dp->val.c = (char) gprData[iCount++]; + else + dp->val.c = (char) *ap++; + break; + case nsXPTType::T_WCHAR : if (iCount < PARAM_GPR_COUNT) + dp->val.wc = (wchar_t) gprData[iCount++]; + else + dp->val.wc = (wchar_t) *ap++; + break; + default: + NS_ERROR("bad type"); + break; + } + } + + result = self->mOuter->CallMethod((uint16_t)methodIndex,info,dispatchParams); + + if(dispatchParams != paramBuffer) + delete [] dispatchParams; + + return result; +} + +#define STUB_ENTRY(n) + +#define SENTINEL_ENTRY(n) \ +nsresult nsXPTCStubBase::Sentinel##n() \ +{ \ + NS_ERROR("nsXPTCStubBase::Sentinel called"); \ + return NS_ERROR_NOT_IMPLEMENTED; \ +} + +#include "xptcstubsdef.inc" + +#endif /* AIX */ diff --git a/xpcom/reflect/xptcall/md/unix/xptcstubs_ppc_aix64.cpp b/xpcom/reflect/xptcall/md/unix/xptcstubs_ppc_aix64.cpp new file mode 100644 index 000000000..08eb557ab --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcstubs_ppc_aix64.cpp @@ -0,0 +1,172 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* Implement shared vtbl methods. */ + +#include "xptcprivate.h" +#include "xptiprivate.h" + +#if defined(AIX) + +/* + For PPC (AIX & MAC), the first 8 integral and the first 13 f.p. parameters + arrive in a separate chunk of data that has been loaded from the registers. + The args pointer has been set to the start of the parameters BEYOND the ones + arriving in registers +*/ +extern "C" nsresult ATTRIBUTE_USED +PrepareAndDispatch(nsXPTCStubBase* self, uint64_t methodIndex, uint64_t* args, uint64_t *gprData, double *fprData) +{ + +#define PARAM_BUFFER_COUNT 16 +#define PARAM_GPR_COUNT 7 + + nsXPTCMiniVariant paramBuffer[PARAM_BUFFER_COUNT]; + nsXPTCMiniVariant* dispatchParams = nullptr; + const nsXPTMethodInfo* info = nullptr; + uint8_t paramCount; + uint8_t i; + nsresult result = NS_ERROR_FAILURE; + + NS_ASSERTION(self,"no self"); + + self->mEntry->GetMethodInfo(uint16_t(methodIndex), &info); + NS_ASSERTION(info,"no method info"); + + paramCount = info->GetParamCount(); + + // setup variant array pointer + if(paramCount > PARAM_BUFFER_COUNT) + dispatchParams = new nsXPTCMiniVariant[paramCount]; + else + dispatchParams = paramBuffer; + NS_ASSERTION(dispatchParams,"no place for params"); + + uint64_t* ap = args; + uint32_t iCount = 0; + uint32_t fpCount = 0; + for(i = 0; i < paramCount; i++) + { + const nsXPTParamInfo& param = info->GetParam(i); + const nsXPTType& type = param.GetType(); + nsXPTCMiniVariant* dp = &dispatchParams[i]; + + if(param.IsOut() || !type.IsArithmetic()) + { + if (iCount < PARAM_GPR_COUNT) + dp->val.p = (void*) gprData[iCount++]; + else + dp->val.p = (void*) *ap++; + continue; + } + // else + switch(type) + { + case nsXPTType::T_I8 : if (iCount < PARAM_GPR_COUNT) + dp->val.i8 = (int8_t) gprData[iCount++]; + else + dp->val.i8 = (int8_t) *ap++; + break; + case nsXPTType::T_I16 : if (iCount < PARAM_GPR_COUNT) + dp->val.i16 = (int16_t) gprData[iCount++]; + else + dp->val.i16 = (int16_t) *ap++; + break; + case nsXPTType::T_I32 : if (iCount < PARAM_GPR_COUNT) + dp->val.i32 = (int32_t) gprData[iCount++]; + else + dp->val.i32 = (int32_t) *ap++; + break; + case nsXPTType::T_I64 : if (iCount < PARAM_GPR_COUNT) + dp->val.i64 = (int64_t) gprData[iCount++]; + else + dp->val.i64 = (int64_t) *ap++; + break; + case nsXPTType::T_U8 : if (iCount < PARAM_GPR_COUNT) + dp->val.u8 = (uint8_t) gprData[iCount++]; + else + dp->val.u8 = (uint8_t) *ap++; + break; + case nsXPTType::T_U16 : if (iCount < PARAM_GPR_COUNT) + dp->val.u16 = (uint16_t) gprData[iCount++]; + else + dp->val.u16 = (uint16_t) *ap++; + break; + case nsXPTType::T_U32 : if (iCount < PARAM_GPR_COUNT) + dp->val.u32 = (uint32_t) gprData[iCount++]; + else + dp->val.u32 = (uint32_t) *ap++; + break; + case nsXPTType::T_U64 : if (iCount < PARAM_GPR_COUNT) + dp->val.u64 = (uint64_t) gprData[iCount++]; + else + dp->val.u64 = (uint64_t) *ap++; + break; + case nsXPTType::T_FLOAT : if (fpCount < 13) { + dp->val.f = (float) fprData[fpCount++]; + if (iCount < PARAM_GPR_COUNT) + ++iCount; + else + ++ap; + } + else + dp->val.f = *((float*) ap++); + break; + case nsXPTType::T_DOUBLE : if (fpCount < 13) { + dp->val.d = (double) fprData[fpCount++]; + if (iCount < PARAM_GPR_COUNT) + ++iCount; + else + ++ap; + if (iCount < PARAM_GPR_COUNT) + ++iCount; + else + ++ap; + } + else { + dp->val.f = *((double*) ap); + ap += 2; + } + break; + case nsXPTType::T_BOOL : if (iCount < PARAM_GPR_COUNT) + dp->val.b = (bool) gprData[iCount++]; + else + dp->val.b = (bool) *ap++; + break; + case nsXPTType::T_CHAR : if (iCount < PARAM_GPR_COUNT) + dp->val.c = (char) gprData[iCount++]; + else + dp->val.c = (char) *ap++; + break; + case nsXPTType::T_WCHAR : if (iCount < PARAM_GPR_COUNT) + dp->val.wc = (wchar_t) gprData[iCount++]; + else + dp->val.wc = (wchar_t) *ap++; + break; + default: + NS_ERROR("bad type"); + break; + } + } + + result = self->mOuter->CallMethod((uint16_t)methodIndex,info,dispatchParams); + + if(dispatchParams != paramBuffer) + delete [] dispatchParams; + + return result; +} + +#define STUB_ENTRY(n) + +#define SENTINEL_ENTRY(n) \ +nsresult nsXPTCStubBase::Sentinel##n() \ +{ \ + NS_ERROR("nsXPTCStubBase::Sentinel called"); \ + return NS_ERROR_NOT_IMPLEMENTED; \ +} + +#include "xptcstubsdef.inc" + +#endif /* AIX */ diff --git a/xpcom/reflect/xptcall/md/unix/xptcstubs_ppc_linux.cpp b/xpcom/reflect/xptcall/md/unix/xptcstubs_ppc_linux.cpp new file mode 100644 index 000000000..86405a8ca --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcstubs_ppc_linux.cpp @@ -0,0 +1,220 @@ +/* -*- 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/. */ + +// Implement shared vtbl methods. + +#include "xptcprivate.h" +#include "xptiprivate.h" + +// The Linux/PPC ABI (aka PPC/SYSV ABI) passes the first 8 integral +// parameters and the first 8 floating point parameters in registers +// (r3-r10 and f1-f8), no stack space is allocated for these by the +// caller. The rest of the parameters are passed in the callers stack +// area. The stack pointer has to retain 16-byte alignment, longlongs +// and doubles are aligned on 8-byte boundaries. +#ifndef __NO_FPRS__ +#define PARAM_BUFFER_COUNT 16 +#define GPR_COUNT 8 +#define FPR_COUNT 8 +#else +#define PARAM_BUFFER_COUNT 8 +#define GPR_COUNT 8 +#endif +// PrepareAndDispatch() is called by SharedStub() and calls the actual method. +// +// - 'args[]' contains the arguments passed on stack +// - 'gprData[]' contains the arguments passed in integer registers +// - 'fprData[]' contains the arguments passed in floating point registers +// +// The parameters are mapped into an array of type 'nsXPTCMiniVariant' +// and then the method gets called. + +extern "C" nsresult ATTRIBUTE_USED +PrepareAndDispatch(nsXPTCStubBase* self, + uint32_t methodIndex, + uint32_t* args, + uint32_t *gprData, + double *fprData) +{ + nsXPTCMiniVariant paramBuffer[PARAM_BUFFER_COUNT]; + nsXPTCMiniVariant* dispatchParams = nullptr; + const nsXPTMethodInfo* info = nullptr; + uint32_t paramCount; + uint32_t i; + nsresult result = NS_ERROR_FAILURE; + + NS_ASSERTION(self,"no self"); + + self->mEntry->GetMethodInfo(uint16_t(methodIndex), &info); + NS_ASSERTION(info,"no method info"); + if (! info) + return NS_ERROR_UNEXPECTED; + + paramCount = info->GetParamCount(); + + // setup variant array pointer + if(paramCount > PARAM_BUFFER_COUNT) + dispatchParams = new nsXPTCMiniVariant[paramCount]; + else + dispatchParams = paramBuffer; + + NS_ASSERTION(dispatchParams,"no place for params"); + if (! dispatchParams) + return NS_ERROR_OUT_OF_MEMORY; + + uint32_t* ap = args; + uint32_t gpr = 1; // skip one GPR register +#ifndef __NO_FPRS__ + uint32_t fpr = 0; +#endif + uint32_t tempu32; + uint64_t tempu64; + + for(i = 0; i < paramCount; i++) { + const nsXPTParamInfo& param = info->GetParam(i); + const nsXPTType& type = param.GetType(); + nsXPTCMiniVariant* dp = &dispatchParams[i]; + + if (!param.IsOut() && type == nsXPTType::T_DOUBLE) { +#ifndef __NO_FPRS__ + if (fpr < FPR_COUNT) + dp->val.d = fprData[fpr++]; +#else + if (gpr & 1) + gpr++; + if (gpr + 1 < GPR_COUNT) { + dp->val.d = *(double*) &gprData[gpr]; + gpr += 2; + } +#endif + else { + if ((uint32_t) ap & 4) ap++; // doubles are 8-byte aligned on stack + dp->val.d = *(double*) ap; + ap += 2; + } + continue; + } + else if (!param.IsOut() && type == nsXPTType::T_FLOAT) { +#ifndef __NO_FPRS__ + if (fpr < FPR_COUNT) + dp->val.f = (float) fprData[fpr++]; // in registers floats are passed as doubles +#else + if (gpr < GPR_COUNT) + dp->val.f = *(float*) &gprData[gpr++]; +#endif + else + dp->val.f = *(float*) ap++; + continue; + } + else if (!param.IsOut() && (type == nsXPTType::T_I64 + || type == nsXPTType::T_U64)) { + if (gpr & 1) gpr++; // longlongs are aligned in odd/even register pairs, eg. r5/r6 + if ((gpr + 1) < GPR_COUNT) { + tempu64 = *(uint64_t*) &gprData[gpr]; + gpr += 2; + } + else { + if ((uint32_t) ap & 4) ap++; // longlongs are 8-byte aligned on stack + tempu64 = *(uint64_t*) ap; + ap += 2; + } + } + else { + if (gpr < GPR_COUNT) + tempu32 = gprData[gpr++]; + else + tempu32 = *ap++; + } + + if(param.IsOut() || !type.IsArithmetic()) { + if (type == nsXPTType::T_JSVAL) + dp->val.p = *((void**) tempu32); + else + dp->val.p = (void*) tempu32; + continue; + } + + switch(type) { + case nsXPTType::T_I8: dp->val.i8 = (int8_t) tempu32; break; + case nsXPTType::T_I16: dp->val.i16 = (int16_t) tempu32; break; + case nsXPTType::T_I32: dp->val.i32 = (int32_t) tempu32; break; + case nsXPTType::T_I64: dp->val.i64 = (int64_t) tempu64; break; + case nsXPTType::T_U8: dp->val.u8 = (uint8_t) tempu32; break; + case nsXPTType::T_U16: dp->val.u16 = (uint16_t) tempu32; break; + case nsXPTType::T_U32: dp->val.u32 = (uint32_t) tempu32; break; + case nsXPTType::T_U64: dp->val.u64 = (uint64_t) tempu64; break; + case nsXPTType::T_BOOL: dp->val.b = (bool) tempu32; break; + case nsXPTType::T_CHAR: dp->val.c = (char) tempu32; break; + case nsXPTType::T_WCHAR: dp->val.wc = (wchar_t) tempu32; break; + + default: + NS_ERROR("bad type"); + break; + } + } + + result = self->mOuter->CallMethod((uint16_t)methodIndex, + info, + dispatchParams); + + if (dispatchParams != paramBuffer) + delete [] dispatchParams; + + return result; +} + +// Load r11 with the constant 'n' and branch to SharedStub(). +// +// XXX Yes, it's ugly that we're relying on gcc's name-mangling here; +// however, it's quick, dirty, and'll break when the ABI changes on +// us, which is what we want ;-). + +// gcc-3 version +// +// As G++3 ABI contains the length of the functionname in the mangled +// name, it is difficult to get a generic assembler mechanism like +// in the G++ 2.95 case. +// Create names would be like: +// _ZN14nsXPTCStubBase5Stub1Ev +// _ZN14nsXPTCStubBase6Stub12Ev +// _ZN14nsXPTCStubBase7Stub123Ev +// _ZN14nsXPTCStubBase8Stub1234Ev +// etc. +// Use assembler directives to get the names right... + +# define STUB_ENTRY(n) \ +__asm__ ( \ + ".align 2 \n\t" \ + ".if "#n" < 10 \n\t" \ + ".globl _ZN14nsXPTCStubBase5Stub"#n"Ev \n\t" \ + ".type _ZN14nsXPTCStubBase5Stub"#n"Ev,@function \n\n" \ +"_ZN14nsXPTCStubBase5Stub"#n"Ev: \n\t" \ + \ + ".elseif "#n" < 100 \n\t" \ + ".globl _ZN14nsXPTCStubBase6Stub"#n"Ev \n\t" \ + ".type _ZN14nsXPTCStubBase6Stub"#n"Ev,@function \n\n" \ +"_ZN14nsXPTCStubBase6Stub"#n"Ev: \n\t" \ + \ + ".elseif "#n" < 1000 \n\t" \ + ".globl _ZN14nsXPTCStubBase7Stub"#n"Ev \n\t" \ + ".type _ZN14nsXPTCStubBase7Stub"#n"Ev,@function \n\n" \ +"_ZN14nsXPTCStubBase7Stub"#n"Ev: \n\t" \ + \ + ".else \n\t" \ + ".err \"stub number "#n" >= 1000 not yet supported\"\n" \ + ".endif \n\t" \ + \ + "li 11,"#n" \n\t" \ + "b SharedStub@local \n" \ +); + +#define SENTINEL_ENTRY(n) \ +nsresult nsXPTCStubBase::Sentinel##n() \ +{ \ + NS_ERROR("nsXPTCStubBase::Sentinel called"); \ + return NS_ERROR_NOT_IMPLEMENTED; \ +} + +#include "xptcstubsdef.inc" diff --git a/xpcom/reflect/xptcall/md/unix/xptcstubs_ppc_netbsd.cpp b/xpcom/reflect/xptcall/md/unix/xptcstubs_ppc_netbsd.cpp new file mode 100644 index 000000000..ec359fc99 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcstubs_ppc_netbsd.cpp @@ -0,0 +1,185 @@ +/* -*- 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/. */ + +// Implement shared vtbl methods. + +#include "xptcprivate.h" + +// The Linux/PPC ABI (aka PPC/SYSV ABI) passes the first 8 integral +// parameters and the first 8 floating point parameters in registers +// (r3-r10 and f1-f8), no stack space is allocated for these by the +// caller. The rest of the parameters are passed in the callers stack +// area. The stack pointer has to retain 16-byte alignment, longlongs +// and doubles are aligned on 8-byte boundaries. + +#define PARAM_BUFFER_COUNT 16 +#define GPR_COUNT 8 +#define FPR_COUNT 8 + +// PrepareAndDispatch() is called by SharedStub() and calls the actual method. +// +// - 'args[]' contains the arguments passed on stack +// - 'gprData[]' contains the arguments passed in integer registers +// - 'fprData[]' contains the arguments passed in floating point registers +// +// The parameters are mapped into an array of type 'nsXPTCMiniVariant' +// and then the method gets called. + +extern "C" nsresult ATTRIBUTE_USED +PrepareAndDispatch(nsXPTCStubBase* self, + uint32_t methodIndex, + uint32_t* args, + uint32_t *gprData, + double *fprData) +{ + nsXPTCMiniVariant paramBuffer[PARAM_BUFFER_COUNT]; + nsXPTCMiniVariant* dispatchParams = nullptr; + nsIInterfaceInfo* iface_info = nullptr; + const nsXPTMethodInfo* info; + uint32_t paramCount; + uint32_t i; + nsresult result = NS_ERROR_FAILURE; + + NS_ASSERTION(self,"no self"); + + self->GetInterfaceInfo(&iface_info); + NS_ASSERTION(iface_info,"no interface info"); + if (! iface_info) + return NS_ERROR_UNEXPECTED; + + iface_info->GetMethodInfo(uint16_t(methodIndex), &info); + NS_ASSERTION(info,"no method info"); + if (! info) + return NS_ERROR_UNEXPECTED; + + paramCount = info->GetParamCount(); + + // setup variant array pointer + if(paramCount > PARAM_BUFFER_COUNT) + dispatchParams = new nsXPTCMiniVariant[paramCount]; + else + dispatchParams = paramBuffer; + + NS_ASSERTION(dispatchParams,"no place for params"); + if (! dispatchParams) + return NS_ERROR_OUT_OF_MEMORY; + + uint32_t* ap = args; + uint32_t gpr = 1; // skip one GPR register + uint32_t fpr = 0; + uint32_t tempu32; + uint64_t tempu64; + + for(i = 0; i < paramCount; i++) { + const nsXPTParamInfo& param = info->GetParam(i); + const nsXPTType& type = param.GetType(); + nsXPTCMiniVariant* dp = &dispatchParams[i]; + + if (!param.IsOut() && type == nsXPTType::T_DOUBLE) { + if (fpr < FPR_COUNT) + dp->val.d = fprData[fpr++]; + else { + if ((uint32_t) ap & 4) ap++; // doubles are 8-byte aligned on stack + dp->val.d = *(double*) ap; + ap += 2; + if (gpr < GPR_COUNT) + gpr += 2; + } + continue; + } + else if (!param.IsOut() && type == nsXPTType::T_FLOAT) { + if (fpr < FPR_COUNT) + dp->val.f = (float) fprData[fpr++]; // in registers floats are passed as doubles + else { + dp->val.f = *(float*) ap; + ap += 1; + if (gpr < GPR_COUNT) + gpr += 1; + } + continue; + } + else if (!param.IsOut() && (type == nsXPTType::T_I64 + || type == nsXPTType::T_U64)) { + if (gpr & 1) gpr++; // longlongs are aligned in odd/even register pairs, eg. r5/r6 + if ((gpr + 1) < GPR_COUNT) { + tempu64 = *(uint64_t*) &gprData[gpr]; + gpr += 2; + } + else { + if ((uint32_t) ap & 4) ap++; // longlongs are 8-byte aligned on stack + tempu64 = *(uint64_t*) ap; + ap += 2; + } + } + else { + if (gpr < GPR_COUNT) + tempu32 = gprData[gpr++]; + else + tempu32 = *ap++; + } + + if(param.IsOut() || !type.IsArithmetic()) { + if (type == nsXPTType::T_JSVAL) + dp->val.p = *((void**) tempu32); + else + dp->val.p = (void*) tempu32; + continue; + } + + switch(type) { + case nsXPTType::T_I8: dp->val.i8 = (int8_t) tempu32; break; + case nsXPTType::T_I16: dp->val.i16 = (int16_t) tempu32; break; + case nsXPTType::T_I32: dp->val.i32 = (int32_t) tempu32; break; + case nsXPTType::T_I64: dp->val.i64 = (int64_t) tempu64; break; + case nsXPTType::T_U8: dp->val.u8 = (uint8_t) tempu32; break; + case nsXPTType::T_U16: dp->val.u16 = (uint16_t) tempu32; break; + case nsXPTType::T_U32: dp->val.u32 = (uint32_t) tempu32; break; + case nsXPTType::T_U64: dp->val.u64 = (uint64_t) tempu64; break; + case nsXPTType::T_BOOL: dp->val.b = (bool) tempu32; break; + case nsXPTType::T_CHAR: dp->val.c = (char) tempu32; break; + case nsXPTType::T_WCHAR: dp->val.wc = (wchar_t) tempu32; break; + + default: + NS_ERROR("bad type"); + break; + } + } + + result = self->CallMethod((uint16_t) methodIndex, info, dispatchParams); + + NS_RELEASE(iface_info); + + if (dispatchParams != paramBuffer) + delete [] dispatchParams; + + return result; +} + +// Load r11 with the constant 'n' and branch to SharedStub(). +// +// XXX Yes, it's ugly that we're relying on gcc's name-mangling here; +// however, it's quick, dirty, and'll break when the ABI changes on +// us, which is what we want ;-). + +#define STUB_ENTRY(n) \ +__asm__ ( \ + ".section \".text\" \n\t" \ + ".align 2 \n\t" \ + ".globl Stub"#n"__14nsXPTCStubBase \n\t" \ + ".type Stub"#n"__14nsXPTCStubBase,@function \n\n" \ + \ +"Stub"#n"__14nsXPTCStubBase: \n\t" \ + "li 11,"#n" \n\t" \ + "b SharedStub@local \n" \ +); + +#define SENTINEL_ENTRY(n) \ +nsresult nsXPTCStubBase::Sentinel##n() \ +{ \ + NS_ERROR("nsXPTCStubBase::Sentinel called"); \ + return NS_ERROR_NOT_IMPLEMENTED; \ +} + +#include "xptcstubsdef.inc" diff --git a/xpcom/reflect/xptcall/md/unix/xptcstubs_ppc_openbsd.cpp b/xpcom/reflect/xptcall/md/unix/xptcstubs_ppc_openbsd.cpp new file mode 100644 index 000000000..62a693643 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcstubs_ppc_openbsd.cpp @@ -0,0 +1,202 @@ +/* -*- 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/. */ + +// Implement shared vtbl methods. + +#include "xptcprivate.h" +#include "xptiprivate.h" + +// The Linux/PPC ABI (aka PPC/SYSV ABI) passes the first 8 integral +// parameters and the first 8 floating point parameters in registers +// (r3-r10 and f1-f8), no stack space is allocated for these by the +// caller. The rest of the parameters are passed in the callers stack +// area. The stack pointer has to retain 16-byte alignment, longlongs +// and doubles are aligned on 8-byte boundaries. + +#define PARAM_BUFFER_COUNT 16 +#define GPR_COUNT 8 +#define FPR_COUNT 8 + +// PrepareAndDispatch() is called by SharedStub() and calls the actual method. +// +// - 'args[]' contains the arguments passed on stack +// - 'gprData[]' contains the arguments passed in integer registers +// - 'fprData[]' contains the arguments passed in floating point registers +// +// The parameters are mapped into an array of type 'nsXPTCMiniVariant' +// and then the method gets called. + +extern "C" nsresult ATTRIBUTE_USED +PrepareAndDispatch(nsXPTCStubBase* self, + uint32_t methodIndex, + uint32_t* args, + uint32_t *gprData, + double *fprData) +{ + nsXPTCMiniVariant paramBuffer[PARAM_BUFFER_COUNT]; + nsXPTCMiniVariant* dispatchParams = nullptr; + const nsXPTMethodInfo* info = nullptr; + uint32_t paramCount; + uint32_t i; + nsresult result = NS_ERROR_FAILURE; + + NS_ASSERTION(self,"no self"); + + self->mEntry->GetMethodInfo(uint16_t(methodIndex), &info); + NS_ASSERTION(info,"no method info"); + if (! info) + return NS_ERROR_UNEXPECTED; + + paramCount = info->GetParamCount(); + + // setup variant array pointer + if(paramCount > PARAM_BUFFER_COUNT) + dispatchParams = new nsXPTCMiniVariant[paramCount]; + else + dispatchParams = paramBuffer; + + NS_ASSERTION(dispatchParams,"no place for params"); + if (!dispatchParams) + return NS_ERROR_OUT_OF_MEMORY; + + uint32_t* ap = args; + uint32_t gpr = 1; // skip one GPR register + uint32_t fpr = 0; + uint32_t tempu32; + uint64_t tempu64; + + for(i = 0; i < paramCount; i++) { + const nsXPTParamInfo& param = info->GetParam(i); + const nsXPTType& type = param.GetType(); + nsXPTCMiniVariant* dp = &dispatchParams[i]; + + if (!param.IsOut() && type == nsXPTType::T_DOUBLE) { + if (fpr < FPR_COUNT) + dp->val.d = fprData[fpr++]; + else { + if ((uint32_t) ap & 4) ap++; // doubles are 8-byte aligned on stack + dp->val.d = *(double*) ap; + ap += 2; + } + continue; + } + else if (!param.IsOut() && type == nsXPTType::T_FLOAT) { + if (fpr < FPR_COUNT) + dp->val.f = (float) fprData[fpr++]; // in registers floats are passed as doubles + else + dp->val.f = *(float*) ap++; + continue; + } + else if (!param.IsOut() && (type == nsXPTType::T_I64 + || type == nsXPTType::T_U64)) { + if (gpr & 1) gpr++; // longlongs are aligned in odd/even register pairs, eg. r5/r6 + if ((gpr + 1) < GPR_COUNT) { + tempu64 = *(uint64_t*) &gprData[gpr]; + gpr += 2; + } + else { + if ((uint32_t) ap & 4) ap++; // longlongs are 8-byte aligned on stack + tempu64 = *(uint64_t*) ap; + ap += 2; + } + } + else { + if (gpr < GPR_COUNT) + tempu32 = gprData[gpr++]; + else + tempu32 = *ap++; + } + + if(param.IsOut() || !type.IsArithmetic()) { + if (type == nsXPTType::T_JSVAL) + dp->val.p = *((void**) tempu32); + else + dp->val.p = (void*) tempu32; + continue; + } + + switch(type) { + case nsXPTType::T_I8: dp->val.i8 = (int8_t) tempu32; break; + case nsXPTType::T_I16: dp->val.i16 = (int16_t) tempu32; break; + case nsXPTType::T_I32: dp->val.i32 = (int32_t) tempu32; break; + case nsXPTType::T_I64: dp->val.i64 = (int64_t) tempu64; break; + case nsXPTType::T_U8: dp->val.u8 = (uint8_t) tempu32; break; + case nsXPTType::T_U16: dp->val.u16 = (uint16_t) tempu32; break; + case nsXPTType::T_U32: dp->val.u32 = (uint32_t) tempu32; break; + case nsXPTType::T_U64: dp->val.u64 = (uint64_t) tempu64; break; + case nsXPTType::T_BOOL: dp->val.b = (bool) tempu32; break; + case nsXPTType::T_CHAR: dp->val.c = (char) tempu32; break; + case nsXPTType::T_WCHAR: dp->val.wc = (wchar_t) tempu32; break; + + default: + NS_ERROR("bad type"); + break; + } + } + + result = self->mOuter->CallMethod((uint16_t)methodIndex, + info, + dispatchParams); + + if (dispatchParams != paramBuffer) + delete [] dispatchParams; + + return result; +} + +// Load r11 with the constant 'n' and branch to SharedStub(). +// +// XXX Yes, it's ugly that we're relying on gcc's name-mangling here; +// however, it's quick, dirty, and'll break when the ABI changes on +// us, which is what we want ;-). + + +// gcc-3 version +// +// As G++3 ABI contains the length of the functionname in the mangled +// name, it is difficult to get a generic assembler mechanism like +// in the G++ 2.95 case. +// Create names would be like: +// _ZN14nsXPTCStubBase5Stub1Ev +// _ZN14nsXPTCStubBase6Stub12Ev +// _ZN14nsXPTCStubBase7Stub123Ev +// _ZN14nsXPTCStubBase8Stub1234Ev +// etc. +// Use assembler directives to get the names right... + +# define STUB_ENTRY(n) \ +__asm__ ( \ + ".align 2 \n\t" \ + ".if "#n" < 10 \n\t" \ + ".globl _ZN14nsXPTCStubBase5Stub"#n"Ev \n\t" \ + ".type _ZN14nsXPTCStubBase5Stub"#n"Ev,@function \n\n" \ +"_ZN14nsXPTCStubBase5Stub"#n"Ev: \n\t" \ + \ + ".elseif "#n" < 100 \n\t" \ + ".globl _ZN14nsXPTCStubBase6Stub"#n"Ev \n\t" \ + ".type _ZN14nsXPTCStubBase6Stub"#n"Ev,@function \n\n" \ +"_ZN14nsXPTCStubBase6Stub"#n"Ev: \n\t" \ + \ + ".elseif "#n" < 1000 \n\t" \ + ".globl _ZN14nsXPTCStubBase7Stub"#n"Ev \n\t" \ + ".type _ZN14nsXPTCStubBase7Stub"#n"Ev,@function \n\n" \ +"_ZN14nsXPTCStubBase7Stub"#n"Ev: \n\t" \ + \ + ".else \n\t" \ + ".err \"stub number "#n" >= 1000 not yet supported\"\n" \ + ".endif \n\t" \ + \ + "li 11,"#n" \n\t" \ + "b SharedStub@local \n" \ +); + +#define SENTINEL_ENTRY(n) \ +nsresult nsXPTCStubBase::Sentinel##n() \ +{ \ + NS_ERROR("nsXPTCStubBase::Sentinel called"); \ + return NS_ERROR_NOT_IMPLEMENTED; \ +} + +#include "xptcstubsdef.inc" diff --git a/xpcom/reflect/xptcall/md/unix/xptcstubs_ppc_rhapsody.cpp b/xpcom/reflect/xptcall/md/unix/xptcstubs_ppc_rhapsody.cpp new file mode 100644 index 000000000..4c485a275 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcstubs_ppc_rhapsody.cpp @@ -0,0 +1,165 @@ +/* -*- Mode: 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 "xptcprivate.h" +#include "xptiprivate.h" + +/* Under the Mac OS X PowerPC ABI, the first 8 integer and 13 floating point + * parameters are delivered in registers and are not on the stack, although + * stack space is allocated for them. The integer parameters are delivered + * in GPRs r3 through r10. The first 8 words of the parameter area on the + * stack shadow these registers. A word will either be in a register or on + * the stack, but not in both. Although the first floating point parameters + * are passed in floating point registers, GPR space and stack space is + * reserved for them as well. + * + * SharedStub has passed pointers to the parameter section of the stack + * and saved copies of the GPRs and FPRs used for parameter passing. We + * don't care about the first parameter (which is delivered here as the self + * pointer), so SharedStub pointed us past that. argsGPR thus points to GPR + * r4 (corresponding to the first argument after the self pointer) and + * argsStack points to the parameter section of the caller's stack frame + * reserved for the same argument. This way, it is possible to reference + * either argsGPR or argsStack with the same index. + * + * Contrary to the assumption made by the previous implementation, the + * Mac OS X PowerPC ABI doesn't impose any special alignment restrictions on + * parameter sections of stacks. Values that are 64 bits wide appear on the + * stack without any special padding. + * + * See also xptcstubs_asm_ppc_darwin.s.m4:_SharedStub. + * + * ABI reference: + * http://developer.apple.com/documentation/DeveloperTools/Conceptual/ + * MachORuntime/PowerPCConventions/chapter_3_section_1.html */ + +extern "C" nsresult ATTRIBUTE_USED +PrepareAndDispatch( + nsXPTCStubBase *self, + uint32_t methodIndex, + uint32_t *argsStack, + uint32_t *argsGPR, + double *argsFPR) { +#define PARAM_BUFFER_COUNT 16 +#define PARAM_FPR_COUNT 13 +#define PARAM_GPR_COUNT 7 + + nsXPTCMiniVariant paramBuffer[PARAM_BUFFER_COUNT]; + nsXPTCMiniVariant *dispatchParams = nullptr; + const nsXPTMethodInfo *methodInfo; + uint8_t paramCount; + uint8_t i; + nsresult result = NS_ERROR_FAILURE; + uint32_t argIndex = 0; + uint32_t fprIndex = 0; + + typedef struct { + uint32_t hi; + uint32_t lo; + } DU; + + NS_ASSERTION(self, "no self"); + + self->mEntry->GetMethodInfo(uint16_t(methodIndex), &methodInfo); + NS_ASSERTION(methodInfo, "no method info"); + + paramCount = methodInfo->GetParamCount(); + + if(paramCount > PARAM_BUFFER_COUNT) { + dispatchParams = new nsXPTCMiniVariant[paramCount]; + } + else { + dispatchParams = paramBuffer; + } + NS_ASSERTION(dispatchParams,"no place for params"); + + for(i = 0; i < paramCount; i++, argIndex++) { + const nsXPTParamInfo ¶m = methodInfo->GetParam(i); + const nsXPTType &type = param.GetType(); + nsXPTCMiniVariant *dp = &dispatchParams[i]; + uint32_t theParam; + + if(argIndex < PARAM_GPR_COUNT) + theParam = argsGPR[argIndex]; + else + theParam = argsStack[argIndex]; + + if(param.IsOut() || !type.IsArithmetic()) + dp->val.p = (void *) theParam; + else { + switch(type) { + case nsXPTType::T_I8: + dp->val.i8 = (int8_t) theParam; + break; + case nsXPTType::T_I16: + dp->val.i16 = (int16_t) theParam; + break; + case nsXPTType::T_I32: + dp->val.i32 = (int32_t) theParam; + break; + case nsXPTType::T_U8: + dp->val.u8 = (uint8_t) theParam; + break; + case nsXPTType::T_U16: + dp->val.u16 = (uint16_t) theParam; + break; + case nsXPTType::T_U32: + dp->val.u32 = (uint32_t) theParam; + break; + case nsXPTType::T_I64: + case nsXPTType::T_U64: + ((DU *)dp)->hi = (uint32_t) theParam; + if(++argIndex < PARAM_GPR_COUNT) + ((DU *)dp)->lo = (uint32_t) argsGPR[argIndex]; + else + ((DU *)dp)->lo = (uint32_t) argsStack[argIndex]; + break; + case nsXPTType::T_BOOL: + dp->val.b = (bool) theParam; + break; + case nsXPTType::T_CHAR: + dp->val.c = (char) theParam; + break; + case nsXPTType::T_WCHAR: + dp->val.wc = (wchar_t) theParam; + break; + case nsXPTType::T_FLOAT: + if(fprIndex < PARAM_FPR_COUNT) + dp->val.f = (float) argsFPR[fprIndex++]; + else + dp->val.f = *(float *) &argsStack[argIndex]; + break; + case nsXPTType::T_DOUBLE: + if(fprIndex < PARAM_FPR_COUNT) + dp->val.d = argsFPR[fprIndex++]; + else + dp->val.d = *(double *) &argsStack[argIndex]; + argIndex++; + break; + default: + NS_ERROR("bad type"); + break; + } + } + } + + result = self->mOuter-> + CallMethod((uint16_t)methodIndex, methodInfo, dispatchParams); + + if(dispatchParams != paramBuffer) + delete [] dispatchParams; + + return result; +} + +#define STUB_ENTRY(n) +#define SENTINEL_ENTRY(n) \ +nsresult nsXPTCStubBase::Sentinel##n() \ +{ \ + NS_ERROR("nsXPTCStubBase::Sentinel called"); \ + return NS_ERROR_NOT_IMPLEMENTED; \ +} + +#include "xptcstubsdef.inc" diff --git a/xpcom/reflect/xptcall/md/unix/xptcstubs_sparc64_openbsd.cpp b/xpcom/reflect/xptcall/md/unix/xptcstubs_sparc64_openbsd.cpp new file mode 100644 index 000000000..b8a09c97e --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcstubs_sparc64_openbsd.cpp @@ -0,0 +1,104 @@ +/* -*- 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/. */ + +/* Implement shared vtbl methods. */ + +#include "xptcprivate.h" +#include "xptiprivate.h" + +#if defined(sparc) || defined(__sparc__) + +extern "C" nsresult ATTRIBUTE_USED +PrepareAndDispatch(nsXPTCStubBase* self, uint64_t methodIndex, uint64_t* args) +{ + +#define PARAM_BUFFER_COUNT 16 + + nsXPTCMiniVariant paramBuffer[PARAM_BUFFER_COUNT]; + nsXPTCMiniVariant* dispatchParams = nullptr; + const nsXPTMethodInfo* info; + uint8_t paramCount; + uint8_t i; + nsresult result = NS_ERROR_FAILURE; + + NS_ASSERTION(self,"no self"); + + self->mEntry->GetMethodInfo(uint16_t(methodIndex), &info); + NS_ASSERTION(info,"no interface info"); + + paramCount = info->GetParamCount(); + + // setup variant array pointer + if(paramCount > PARAM_BUFFER_COUNT) + dispatchParams = new nsXPTCMiniVariant[paramCount]; + else + dispatchParams = paramBuffer; + + NS_ASSERTION(dispatchParams,"no place for params"); + if (!dispatchParams) + return NS_ERROR_OUT_OF_MEMORY; + + uint64_t* ap = args; + for(i = 0; i < paramCount; i++, ap++) + { + const nsXPTParamInfo& param = info->GetParam(i); + const nsXPTType& type = param.GetType(); + nsXPTCMiniVariant* dp = &dispatchParams[i]; + + if(param.IsOut() || !type.IsArithmetic()) + { + dp->val.p = (void*) *ap; + continue; + } + // else + switch(type) + { + case nsXPTType::T_BOOL : dp->val.b = *((int64_t*) ap); break; + case nsXPTType::T_CHAR : dp->val.c = *((uint64_t*) ap); break; + case nsXPTType::T_WCHAR : dp->val.wc = *((int64_t*) ap); break; + case nsXPTType::T_I8 : dp->val.i8 = *((int64_t*) ap); break; + case nsXPTType::T_I16 : dp->val.i16 = *((int64_t*) ap); break; + case nsXPTType::T_I32 : dp->val.i32 = *((int64_t*) ap); break; + case nsXPTType::T_I64 : dp->val.i64 = *((int64_t*) ap); break; + case nsXPTType::T_U8 : dp->val.u8 = *((uint64_t*) ap); break; + case nsXPTType::T_U16 : dp->val.u16 = *((uint64_t*)ap); break; + case nsXPTType::T_U32 : dp->val.u32 = *((uint64_t*)ap); break; + case nsXPTType::T_U64 : dp->val.u64 = *((uint64_t*) ap); break; + case nsXPTType::T_FLOAT : dp->val.f = ((float*) ap)[1]; break; + case nsXPTType::T_DOUBLE : dp->val.d = *((double*) ap); break; + default: + NS_ERROR("bad type"); + break; + } + } + + result = self->mOuter->CallMethod((uint16_t)methodIndex, info, dispatchParams); + + if(dispatchParams != paramBuffer) + delete [] dispatchParams; + + return result; +} + +extern "C" nsresult SharedStub(int, int*); + +#define STUB_ENTRY(n) \ +nsresult nsXPTCStubBase::Stub##n() \ +{ \ + int dummy; /* defeat tail-call optimization */ \ + return SharedStub(n, &dummy); \ +} + +#define SENTINEL_ENTRY(n) \ +nsresult nsXPTCStubBase::Sentinel##n() \ +{ \ + NS_ERROR("nsXPTCStubBase::Sentinel called"); \ + return NS_ERROR_NOT_IMPLEMENTED; \ +} + +#include "xptcstubsdef.inc" + +#endif /* sparc || __sparc__ */ diff --git a/xpcom/reflect/xptcall/md/unix/xptcstubs_sparc_netbsd.cpp b/xpcom/reflect/xptcall/md/unix/xptcstubs_sparc_netbsd.cpp new file mode 100644 index 000000000..22bf94d49 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcstubs_sparc_netbsd.cpp @@ -0,0 +1,117 @@ +/* -*- 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/. */ + +/* Implement shared vtbl methods. */ + +#include "xptcprivate.h" + +#if defined(sparc) || defined(__sparc__) + +extern "C" nsresult ATTRIBUTE_USED +PrepareAndDispatch(nsXPTCStubBase* self, uint32_t methodIndex, uint32_t* args) +{ + + typedef struct { + uint32_t hi; + uint32_t lo; + } DU; // have to move 64 bit entities as 32 bit halves since + // stack slots are not guaranteed 16 byte aligned + +#define PARAM_BUFFER_COUNT 16 + + nsXPTCMiniVariant paramBuffer[PARAM_BUFFER_COUNT]; + nsXPTCMiniVariant* dispatchParams = nullptr; + nsIInterfaceInfo* iface_info = nullptr; + const nsXPTMethodInfo* info; + uint8_t paramCount; + uint8_t i; + nsresult result = NS_ERROR_FAILURE; + + NS_ASSERTION(self,"no self"); + + self->GetInterfaceInfo(&iface_info); + NS_ASSERTION(iface_info,"no interface info"); + + iface_info->GetMethodInfo(uint16_t(methodIndex), &info); + NS_ASSERTION(info,"no interface info"); + + paramCount = info->GetParamCount(); + + // setup variant array pointer + if(paramCount > PARAM_BUFFER_COUNT) + dispatchParams = new nsXPTCMiniVariant[paramCount]; + else + dispatchParams = paramBuffer; + NS_ASSERTION(dispatchParams,"no place for params"); + + uint32_t* ap = args; + for(i = 0; i < paramCount; i++, ap++) + { + const nsXPTParamInfo& param = info->GetParam(i); + const nsXPTType& type = param.GetType(); + nsXPTCMiniVariant* dp = &dispatchParams[i]; + + if(param.IsOut() || !type.IsArithmetic()) + { + if (type == nsXPTType::T_JSVAL) + dp->val.p = *((void**) *ap); + else + dp->val.p = (void*) *ap; + continue; + } + // else + switch(type) + { + case nsXPTType::T_I8 : dp->val.i8 = *((int32_t*) ap); break; + case nsXPTType::T_I16 : dp->val.i16 = *((int32_t*) ap); break; + case nsXPTType::T_I32 : dp->val.i32 = *((int32_t*) ap); break; + case nsXPTType::T_DOUBLE : + case nsXPTType::T_U64 : + case nsXPTType::T_I64 : ((DU *)dp)->hi = ((DU *)ap)->hi; + ((DU *)dp)->lo = ((DU *)ap)->lo; + ap++; + break; + case nsXPTType::T_U8 : dp->val.u8 = *((uint32_t*)ap); break; + case nsXPTType::T_U16 : dp->val.u16 = *((uint32_t*)ap); break; + case nsXPTType::T_U32 : dp->val.u32 = *((uint32_t*)ap); break; + case nsXPTType::T_FLOAT : dp->val.f = *((float*) ap); break; + case nsXPTType::T_BOOL : dp->val.b = *((uint32_t*)ap); break; + case nsXPTType::T_CHAR : dp->val.c = *((uint32_t*)ap); break; + case nsXPTType::T_WCHAR : dp->val.wc = *((int32_t*) ap); break; + default: + NS_ERROR("bad type"); + break; + } + } + + result = self->CallMethod((uint16_t)methodIndex, info, dispatchParams); + + NS_RELEASE(iface_info); + + if(dispatchParams != paramBuffer) + delete [] dispatchParams; + + return result; +} + +extern "C" nsresult SharedStub(int, int*); + +#define STUB_ENTRY(n) \ +nsresult nsXPTCStubBase::Stub##n() \ +{ \ + int dummy; /* defeat tail-call optimization */ \ + return SharedStub(n, &dummy); \ +} + +#define SENTINEL_ENTRY(n) \ +nsresult nsXPTCStubBase::Sentinel##n() \ +{ \ + NS_ERROR("nsXPTCStubBase::Sentinel called"); \ + return NS_ERROR_NOT_IMPLEMENTED; \ +} + +#include "xptcstubsdef.inc" + +#endif /* sparc || __sparc__ */ diff --git a/xpcom/reflect/xptcall/md/unix/xptcstubs_sparc_openbsd.cpp b/xpcom/reflect/xptcall/md/unix/xptcstubs_sparc_openbsd.cpp new file mode 100644 index 000000000..55d814b85 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcstubs_sparc_openbsd.cpp @@ -0,0 +1,120 @@ +/* -*- 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/. */ + +/* Implement shared vtbl methods. */ + +#include "xptcprivate.h" + +#if defined(sparc) || defined(__sparc__) + +extern "C" nsresult ATTRIBUTE_USED +PrepareAndDispatch(nsXPTCStubBase* self, uint32_t methodIndex, uint32_t* args) +{ + + typedef struct { + uint32_t hi; + uint32_t lo; + } DU; // have to move 64 bit entities as 32 bit halves since + // stack slots are not guaranteed 16 byte aligned + +#define PARAM_BUFFER_COUNT 16 + + nsXPTCMiniVariant paramBuffer[PARAM_BUFFER_COUNT]; + nsXPTCMiniVariant* dispatchParams = nullptr; + nsIInterfaceInfo* iface_info = nullptr; + const nsXPTMethodInfo* info; + uint8_t paramCount; + uint8_t i; + nsresult result = NS_ERROR_FAILURE; + + NS_ASSERTION(self,"no self"); + + self->GetInterfaceInfo(&iface_info); + NS_ASSERTION(iface_info,"no interface info"); + + iface_info->GetMethodInfo(uint16_t(methodIndex), &info); + NS_ASSERTION(info,"no interface info"); + + paramCount = info->GetParamCount(); + + // setup variant array pointer + if(paramCount > PARAM_BUFFER_COUNT) + dispatchParams = new nsXPTCMiniVariant[paramCount]; + else + dispatchParams = paramBuffer; + + NS_ASSERTION(dispatchParams,"no place for params"); + if (!dispatchParams) + return NS_ERROR_OUT_OF_MEMORY; + + uint32_t* ap = args; + for(i = 0; i < paramCount; i++, ap++) + { + const nsXPTParamInfo& param = info->GetParam(i); + const nsXPTType& type = param.GetType(); + nsXPTCMiniVariant* dp = &dispatchParams[i]; + + if(param.IsOut() || !type.IsArithmetic()) + { + if (type == nsXPTType::T_JSVAL) + dp->val.p = *((void**) *ap); + else + dp->val.p = (void*) *ap; + continue; + } + // else + switch(type) + { + case nsXPTType::T_I8 : dp->val.i8 = *((int32_t*) ap); break; + case nsXPTType::T_I16 : dp->val.i16 = *((int32_t*) ap); break; + case nsXPTType::T_I32 : dp->val.i32 = *((int32_t*) ap); break; + case nsXPTType::T_DOUBLE : + case nsXPTType::T_U64 : + case nsXPTType::T_I64 : ((DU *)dp)->hi = ((DU *)ap)->hi; + ((DU *)dp)->lo = ((DU *)ap)->lo; + ap++; + break; + case nsXPTType::T_U8 : dp->val.u8 = *((uint32_t*)ap); break; + case nsXPTType::T_U16 : dp->val.u16 = *((uint32_t*)ap); break; + case nsXPTType::T_U32 : dp->val.u32 = *((uint32_t*)ap); break; + case nsXPTType::T_FLOAT : dp->val.f = *((float*) ap); break; + case nsXPTType::T_BOOL : dp->val.b = *((uint32_t*)ap); break; + case nsXPTType::T_CHAR : dp->val.c = *((uint32_t*)ap); break; + case nsXPTType::T_WCHAR : dp->val.wc = *((int32_t*) ap); break; + default: + NS_ERROR("bad type"); + break; + } + } + + result = self->CallMethod((uint16_t)methodIndex, info, dispatchParams); + + NS_RELEASE(iface_info); + + if(dispatchParams != paramBuffer) + delete [] dispatchParams; + + return result; +} + +extern "C" nsresult SharedStub(int, int*); + +#define STUB_ENTRY(n) \ +nsresult nsXPTCStubBase::Stub##n() \ +{ \ + int dummy; /* defeat tail-call optimization */ \ + return SharedStub(n, &dummy); \ +} + +#define SENTINEL_ENTRY(n) \ +nsresult nsXPTCStubBase::Sentinel##n() \ +{ \ + NS_ERROR("nsXPTCStubBase::Sentinel called"); \ + return NS_ERROR_NOT_IMPLEMENTED; \ +} + +#include "xptcstubsdef.inc" + +#endif /* sparc || __sparc__ */ diff --git a/xpcom/reflect/xptcall/md/unix/xptcstubs_sparc_solaris.cpp b/xpcom/reflect/xptcall/md/unix/xptcstubs_sparc_solaris.cpp new file mode 100644 index 000000000..61f3df4ff --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcstubs_sparc_solaris.cpp @@ -0,0 +1,112 @@ +/* -*- 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/. */ + +/* Implement shared vtbl methods. */ + +#include "xptcprivate.h" +#include "xptiprivate.h" + +#if defined(sparc) || defined(__sparc__) + +extern "C" nsresult ATTRIBUTE_USED +PrepareAndDispatch(nsXPTCStubBase* self, uint32_t methodIndex, uint32_t* args) +{ + + typedef struct { + uint32_t hi; + uint32_t lo; + } DU; // have to move 64 bit entities as 32 bit halves since + // stack slots are not guaranteed 16 byte aligned + +#define PARAM_BUFFER_COUNT 16 + + nsXPTCMiniVariant paramBuffer[PARAM_BUFFER_COUNT]; + nsXPTCMiniVariant* dispatchParams = nullptr; + const nsXPTMethodInfo* info; + uint8_t paramCount; + uint8_t i; + nsresult result = NS_ERROR_FAILURE; + + NS_ASSERTION(self,"no self"); + + self->mEntry->GetMethodInfo(uint16_t(methodIndex), &info); + NS_ASSERTION(info,"no interface info"); + + paramCount = info->GetParamCount(); + + // setup variant array pointer + if(paramCount > PARAM_BUFFER_COUNT) + dispatchParams = new nsXPTCMiniVariant[paramCount]; + else + dispatchParams = paramBuffer; + NS_ASSERTION(dispatchParams,"no place for params"); + + uint32_t* ap = args; + for(i = 0; i < paramCount; i++, ap++) + { + const nsXPTParamInfo& param = info->GetParam(i); + const nsXPTType& type = param.GetType(); + nsXPTCMiniVariant* dp = &dispatchParams[i]; + + if(param.IsOut() || !type.IsArithmetic()) + { + if (type == nsXPTType::T_JSVAL) + dp->val.p = *((void**) *ap); + else + dp->val.p = (void*) *ap; + continue; + } + // else + switch(type) + { + case nsXPTType::T_I8 : dp->val.i8 = *((int32_t*) ap); break; + case nsXPTType::T_I16 : dp->val.i16 = *((int32_t*) ap); break; + case nsXPTType::T_I32 : dp->val.i32 = *((int32_t*) ap); break; + case nsXPTType::T_DOUBLE : + case nsXPTType::T_U64 : + case nsXPTType::T_I64 : ((DU *)dp)->hi = ((DU *)ap)->hi; + ((DU *)dp)->lo = ((DU *)ap)->lo; + ap++; + break; + case nsXPTType::T_U8 : dp->val.u8 = *((uint32_t*)ap); break; + case nsXPTType::T_U16 : dp->val.u16 = *((uint32_t*)ap); break; + case nsXPTType::T_U32 : dp->val.u32 = *((uint32_t*)ap); break; + case nsXPTType::T_FLOAT : dp->val.f = *((float*) ap); break; + case nsXPTType::T_BOOL : dp->val.b = *((uint32_t*)ap); break; + case nsXPTType::T_CHAR : dp->val.c = *((uint32_t*)ap); break; + case nsXPTType::T_WCHAR : dp->val.wc = *((int32_t*) ap); break; + default: + NS_ERROR("bad type"); + break; + } + } + + result = self->mOuter->CallMethod((uint16_t)methodIndex, info, dispatchParams); + + if(dispatchParams != paramBuffer) + delete [] dispatchParams; + + return result; +} + +extern "C" nsresult SharedStub(int, int*); + +#define STUB_ENTRY(n) \ +nsresult nsXPTCStubBase::Stub##n() \ +{ \ + int dummy; /* defeat tail-call optimization */ \ + return SharedStub(n, &dummy); \ +} + +#define SENTINEL_ENTRY(n) \ +nsresult nsXPTCStubBase::Sentinel##n() \ +{ \ + NS_ERROR("nsXPTCStubBase::Sentinel called"); \ + return NS_ERROR_NOT_IMPLEMENTED; \ +} + +#include "xptcstubsdef.inc" + +#endif /* sparc || __sparc__ */ diff --git a/xpcom/reflect/xptcall/md/unix/xptcstubs_sparcv9_solaris.cpp b/xpcom/reflect/xptcall/md/unix/xptcstubs_sparcv9_solaris.cpp new file mode 100644 index 000000000..583ce9864 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcstubs_sparcv9_solaris.cpp @@ -0,0 +1,101 @@ +/* -*- 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/. */ + +/* Implement shared vtbl methods. */ + +#include "xptcprivate.h" +#include "xptiprivate.h" + +#if defined(sparc) || defined(__sparc__) + +extern "C" nsresult ATTRIBUTE_USED +PrepareAndDispatch(nsXPTCStubBase* self, uint64_t methodIndex, uint64_t* args) +{ + +#define PARAM_BUFFER_COUNT 16 + + nsXPTCMiniVariant paramBuffer[PARAM_BUFFER_COUNT]; + nsXPTCMiniVariant* dispatchParams = nullptr; + const nsXPTMethodInfo* info; + uint8_t paramCount; + uint8_t i; + nsresult result = NS_ERROR_FAILURE; + + NS_ASSERTION(self,"no self"); + + self->mEntry->GetMethodInfo(uint16_t(methodIndex), &info); + NS_ASSERTION(info,"no interface info"); + + paramCount = info->GetParamCount(); + + // setup variant array pointer + if(paramCount > PARAM_BUFFER_COUNT) + dispatchParams = new nsXPTCMiniVariant[paramCount]; + else + dispatchParams = paramBuffer; + NS_ASSERTION(dispatchParams,"no place for params"); + + uint64_t* ap = args; + for(i = 0; i < paramCount; i++, ap++) + { + const nsXPTParamInfo& param = info->GetParam(i); + const nsXPTType& type = param.GetType(); + nsXPTCMiniVariant* dp = &dispatchParams[i]; + + if(param.IsOut() || !type.IsArithmetic()) + { + dp->val.p = (void*) *ap; + continue; + } + // else + switch(type) + { + case nsXPTType::T_I8 : dp->val.i8 = *((int64_t*) ap); break; + case nsXPTType::T_I16 : dp->val.i16 = *((int64_t*) ap); break; + case nsXPTType::T_I32 : dp->val.i32 = *((int64_t*) ap); break; + case nsXPTType::T_DOUBLE : dp->val.d = *((double*) ap); break; + case nsXPTType::T_U64 : dp->val.u64 = *((uint64_t*)ap); break; + case nsXPTType::T_I64 : dp->val.i64 = *((int64_t*) ap); break; + case nsXPTType::T_U8 : dp->val.u8 = *((uint64_t*)ap); break; + case nsXPTType::T_U16 : dp->val.u16 = *((uint64_t*)ap); break; + case nsXPTType::T_U32 : dp->val.u32 = *((uint64_t*)ap); break; + case nsXPTType::T_FLOAT : dp->val.f = ((float*) ap)[1]; break; + case nsXPTType::T_BOOL : dp->val.b = *((uint64_t*)ap); break; + case nsXPTType::T_CHAR : dp->val.c = *((uint64_t*)ap); break; + case nsXPTType::T_WCHAR : dp->val.wc = *((int64_t*) ap); break; + default: + NS_ERROR("bad type"); + break; + } + } + + result = self->mOuter->CallMethod((uint16_t)methodIndex, info, dispatchParams); + + if(dispatchParams != paramBuffer) + delete [] dispatchParams; + + return result; +} + +extern "C" nsresult SharedStub(int, int*); + +#define STUB_ENTRY(n) \ +nsresult nsXPTCStubBase::Stub##n() \ +{ \ + int dummy; /* defeat tail-call optimization */ \ + return SharedStub(n, &dummy); \ +} + +#define SENTINEL_ENTRY(n) \ +nsresult nsXPTCStubBase::Sentinel##n() \ +{ \ + NS_ERROR("nsXPTCStubBase::Sentinel called"); \ + return NS_ERROR_NOT_IMPLEMENTED; \ +} + +#include "xptcstubsdef.inc" + +#endif /* sparc || __sparc__ */ diff --git a/xpcom/reflect/xptcall/md/unix/xptcstubs_x86_64_darwin.cpp b/xpcom/reflect/xptcall/md/unix/xptcstubs_x86_64_darwin.cpp new file mode 100644 index 000000000..ae058567f --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcstubs_x86_64_darwin.cpp @@ -0,0 +1,190 @@ +/* -*- 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/. */ + +// Implement shared vtbl methods. + +// Keep this in sync with the linux version. + +#include "xptcprivate.h" +#include "xptiprivate.h" + +// The Darwin/x86-64 ABI passes the first 6 integer parameters and the +// first 8 floating point parameters in registers (rdi, rsi, rdx, rcx, +// r8, r9 and xmm0-xmm7), no stack space is allocated for these by the +// caller. The rest of the parameters are passed in the callers stack +// area. + +const uint32_t PARAM_BUFFER_COUNT = 16; +const uint32_t GPR_COUNT = 6; +const uint32_t FPR_COUNT = 8; + +// PrepareAndDispatch() is called by SharedStub() and calls the actual method. +// +// - 'args[]' contains the arguments passed on stack +// - 'gpregs[]' contains the arguments passed in integer registers +// - 'fpregs[]' contains the arguments passed in floating point registers +// +// The parameters are mapped into an array of type 'nsXPTCMiniVariant' +// and then the method gets called. + +extern "C" nsresult ATTRIBUTE_USED +PrepareAndDispatch(nsXPTCStubBase * self, uint32_t methodIndex, + uint64_t * args, uint64_t * gpregs, double *fpregs) +{ + nsXPTCMiniVariant paramBuffer[PARAM_BUFFER_COUNT]; + nsXPTCMiniVariant* dispatchParams = nullptr; + const nsXPTMethodInfo* info; + uint32_t paramCount; + uint32_t i; + nsresult result = NS_ERROR_FAILURE; + + NS_ASSERTION(self,"no self"); + + self->mEntry->GetMethodInfo(uint16_t(methodIndex), &info); + NS_ASSERTION(info,"no method info"); + if (!info) + return NS_ERROR_UNEXPECTED; + + paramCount = info->GetParamCount(); + + // setup variant array pointer + if (paramCount > PARAM_BUFFER_COUNT) + dispatchParams = new nsXPTCMiniVariant[paramCount]; + else + dispatchParams = paramBuffer; + + NS_ASSERTION(dispatchParams,"no place for params"); + if (!dispatchParams) + return NS_ERROR_OUT_OF_MEMORY; + + uint64_t* ap = args; + uint32_t nr_gpr = 1; // skip one GPR register for 'that' + uint32_t nr_fpr = 0; + uint64_t value; + + for (i = 0; i < paramCount; i++) { + const nsXPTParamInfo& param = info->GetParam(i); + const nsXPTType& type = param.GetType(); + nsXPTCMiniVariant* dp = &dispatchParams[i]; + + if (!param.IsOut() && type == nsXPTType::T_DOUBLE) { + if (nr_fpr < FPR_COUNT) + dp->val.d = fpregs[nr_fpr++]; + else + dp->val.d = *(double*) ap++; + continue; + } + else if (!param.IsOut() && type == nsXPTType::T_FLOAT) { + if (nr_fpr < FPR_COUNT) + // The value in %xmm register is already prepared to + // be retrieved as a float. Therefore, we pass the + // value verbatim, as a double without conversion. + dp->val.d = fpregs[nr_fpr++]; + else + dp->val.f = *(float*) ap++; + continue; + } + else { + if (nr_gpr < GPR_COUNT) + value = gpregs[nr_gpr++]; + else + value = *ap++; + } + + if (param.IsOut() || !type.IsArithmetic()) { + dp->val.p = (void*) value; + continue; + } + + switch (type) { + case nsXPTType::T_I8: dp->val.i8 = (int8_t) value; break; + case nsXPTType::T_I16: dp->val.i16 = (int16_t) value; break; + case nsXPTType::T_I32: dp->val.i32 = (int32_t) value; break; + case nsXPTType::T_I64: dp->val.i64 = (int64_t) value; break; + case nsXPTType::T_U8: dp->val.u8 = (uint8_t) value; break; + case nsXPTType::T_U16: dp->val.u16 = (uint16_t) value; break; + case nsXPTType::T_U32: dp->val.u32 = (uint32_t) value; break; + case nsXPTType::T_U64: dp->val.u64 = (uint64_t) value; break; + // Cast to uint8_t first, to remove garbage on upper 56 bits. + case nsXPTType::T_BOOL: dp->val.b = (bool)(uint8_t) value; break; + case nsXPTType::T_CHAR: dp->val.c = (char) value; break; + case nsXPTType::T_WCHAR: dp->val.wc = (wchar_t) value; break; + + default: + NS_ERROR("bad type"); + break; + } + } + + result = self->mOuter->CallMethod((uint16_t) methodIndex, info, dispatchParams); + + if (dispatchParams != paramBuffer) + delete [] dispatchParams; + + return result; +} + +// Darwin/x86-64 uses gcc >= 4.2 + +#define STUB_ENTRY(n) \ +asm(".section __TEXT,__text\n\t" \ + ".align 3\n\t" \ + ".if " #n " < 10\n\t" \ + ".globl __ZN14nsXPTCStubBase5Stub" #n "Ev\n\t" \ + "__ZN14nsXPTCStubBase5Stub" #n "Ev:\n\t" \ + ".elseif " #n " < 100\n\t" \ + ".globl __ZN14nsXPTCStubBase6Stub" #n "Ev\n\t" \ + "__ZN14nsXPTCStubBase6Stub" #n "Ev:\n\t" \ + ".elseif " #n " < 1000\n\t" \ + ".globl __ZN14nsXPTCStubBase7Stub" #n "Ev\n\t" \ + "__ZN14nsXPTCStubBase7Stub" #n "Ev:\n\t" \ + ".else\n\t" \ + ".err \"stub number " #n " >= 1000 not yet supported\"\n\t" \ + ".endif\n\t" \ + "movl $" #n ", %eax\n\t" \ + "jmp SharedStub\n\t"); + +// static nsresult SharedStub(uint32_t methodIndex) +asm(".section __TEXT,__text\n\t" + ".align 3\n\t" + "SharedStub:\n\t" + // make room for gpregs (48), fpregs (64) + "pushq %rbp\n\t" + "movq %rsp,%rbp\n\t" + "subq $112,%rsp\n\t" + // save GP registers + "movq %rdi,-112(%rbp)\n\t" + "movq %rsi,-104(%rbp)\n\t" + "movq %rdx, -96(%rbp)\n\t" + "movq %rcx, -88(%rbp)\n\t" + "movq %r8 , -80(%rbp)\n\t" + "movq %r9 , -72(%rbp)\n\t" + "leaq -112(%rbp),%rcx\n\t" + // save FP registers + "movsd %xmm0,-64(%rbp)\n\t" + "movsd %xmm1,-56(%rbp)\n\t" + "movsd %xmm2,-48(%rbp)\n\t" + "movsd %xmm3,-40(%rbp)\n\t" + "movsd %xmm4,-32(%rbp)\n\t" + "movsd %xmm5,-24(%rbp)\n\t" + "movsd %xmm6,-16(%rbp)\n\t" + "movsd %xmm7, -8(%rbp)\n\t" + "leaq -64(%rbp),%r8\n\t" + // rdi has the 'self' pointer already + "movl %eax,%esi\n\t" + "leaq 16(%rbp),%rdx\n\t" + "call _PrepareAndDispatch\n\t" + "leave\n\t" + "ret\n\t"); + +#define SENTINEL_ENTRY(n) \ +nsresult nsXPTCStubBase::Sentinel##n() \ +{ \ + NS_ERROR("nsXPTCStubBase::Sentinel called"); \ + return NS_ERROR_NOT_IMPLEMENTED; \ +} + +#include "xptcstubsdef.inc" diff --git a/xpcom/reflect/xptcall/md/unix/xptcstubs_x86_64_linux.cpp b/xpcom/reflect/xptcall/md/unix/xptcstubs_x86_64_linux.cpp new file mode 100644 index 000000000..01044363b --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcstubs_x86_64_linux.cpp @@ -0,0 +1,204 @@ +/* -*- 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/. */ + +// Implement shared vtbl methods. + +// Keep this in sync with the darwin version. + +#include "xptcprivate.h" +#include "xptiprivate.h" + +// The Linux/x86-64 ABI passes the first 6 integer parameters and the +// first 8 floating point parameters in registers (rdi, rsi, rdx, rcx, +// r8, r9 and xmm0-xmm7), no stack space is allocated for these by the +// caller. The rest of the parameters are passed in the callers stack +// area. + +const uint32_t PARAM_BUFFER_COUNT = 16; +const uint32_t GPR_COUNT = 6; +const uint32_t FPR_COUNT = 8; + +// PrepareAndDispatch() is called by SharedStub() and calls the actual method. +// +// - 'args[]' contains the arguments passed on stack +// - 'gpregs[]' contains the arguments passed in integer registers +// - 'fpregs[]' contains the arguments passed in floating point registers +// +// The parameters are mapped into an array of type 'nsXPTCMiniVariant' +// and then the method gets called. + +extern "C" nsresult ATTRIBUTE_USED +PrepareAndDispatch(nsXPTCStubBase * self, uint32_t methodIndex, + uint64_t * args, uint64_t * gpregs, double *fpregs) +{ + nsXPTCMiniVariant paramBuffer[PARAM_BUFFER_COUNT]; + nsXPTCMiniVariant* dispatchParams = nullptr; + const nsXPTMethodInfo* info; + uint32_t paramCount; + uint32_t i; + nsresult result = NS_ERROR_FAILURE; + + NS_ASSERTION(self,"no self"); + + self->mEntry->GetMethodInfo(uint16_t(methodIndex), &info); + NS_ASSERTION(info,"no method info"); + if (!info) + return NS_ERROR_UNEXPECTED; + + paramCount = info->GetParamCount(); + + // setup variant array pointer + if (paramCount > PARAM_BUFFER_COUNT) + dispatchParams = new nsXPTCMiniVariant[paramCount]; + else + dispatchParams = paramBuffer; + + NS_ASSERTION(dispatchParams,"no place for params"); + if (!dispatchParams) + return NS_ERROR_OUT_OF_MEMORY; + + uint64_t* ap = args; + uint32_t nr_gpr = 1; // skip one GPR register for 'that' + uint32_t nr_fpr = 0; + uint64_t value; + + for (i = 0; i < paramCount; i++) { + const nsXPTParamInfo& param = info->GetParam(i); + const nsXPTType& type = param.GetType(); + nsXPTCMiniVariant* dp = &dispatchParams[i]; + + if (!param.IsOut() && type == nsXPTType::T_DOUBLE) { + if (nr_fpr < FPR_COUNT) + dp->val.d = fpregs[nr_fpr++]; + else + dp->val.d = *(double*) ap++; + continue; + } + else if (!param.IsOut() && type == nsXPTType::T_FLOAT) { + if (nr_fpr < FPR_COUNT) + // The value in %xmm register is already prepared to + // be retrieved as a float. Therefore, we pass the + // value verbatim, as a double without conversion. + dp->val.d = fpregs[nr_fpr++]; + else + dp->val.f = *(float*) ap++; + continue; + } + else { + if (nr_gpr < GPR_COUNT) + value = gpregs[nr_gpr++]; + else + value = *ap++; + } + + if (param.IsOut() || !type.IsArithmetic()) { + dp->val.p = (void*) value; + continue; + } + + switch (type) { + case nsXPTType::T_I8: dp->val.i8 = (int8_t) value; break; + case nsXPTType::T_I16: dp->val.i16 = (int16_t) value; break; + case nsXPTType::T_I32: dp->val.i32 = (int32_t) value; break; + case nsXPTType::T_I64: dp->val.i64 = (int64_t) value; break; + case nsXPTType::T_U8: dp->val.u8 = (uint8_t) value; break; + case nsXPTType::T_U16: dp->val.u16 = (uint16_t) value; break; + case nsXPTType::T_U32: dp->val.u32 = (uint32_t) value; break; + case nsXPTType::T_U64: dp->val.u64 = (uint64_t) value; break; + // Cast to uint8_t first, to remove garbage on upper 56 bits. + case nsXPTType::T_BOOL: dp->val.b = (bool)(uint8_t) value; break; + case nsXPTType::T_CHAR: dp->val.c = (char) value; break; + case nsXPTType::T_WCHAR: dp->val.wc = (wchar_t) value; break; + + default: + NS_ERROR("bad type"); + break; + } + } + + result = self->mOuter->CallMethod((uint16_t) methodIndex, info, dispatchParams); + + if (dispatchParams != paramBuffer) + delete [] dispatchParams; + + return result; +} + +// Linux/x86-64 uses gcc >= 3.1 +#define STUB_ENTRY(n) \ +asm(".section \".text\"\n\t" \ + ".align 2\n\t" \ + ".if " #n " < 10\n\t" \ + ".globl _ZN14nsXPTCStubBase5Stub" #n "Ev\n\t" \ + ".hidden _ZN14nsXPTCStubBase5Stub" #n "Ev\n\t" \ + ".type _ZN14nsXPTCStubBase5Stub" #n "Ev,@function\n" \ + "_ZN14nsXPTCStubBase5Stub" #n "Ev:\n\t" \ + ".elseif " #n " < 100\n\t" \ + ".globl _ZN14nsXPTCStubBase6Stub" #n "Ev\n\t" \ + ".hidden _ZN14nsXPTCStubBase6Stub" #n "Ev\n\t" \ + ".type _ZN14nsXPTCStubBase6Stub" #n "Ev,@function\n" \ + "_ZN14nsXPTCStubBase6Stub" #n "Ev:\n\t" \ + ".elseif " #n " < 1000\n\t" \ + ".globl _ZN14nsXPTCStubBase7Stub" #n "Ev\n\t" \ + ".hidden _ZN14nsXPTCStubBase7Stub" #n "Ev\n\t" \ + ".type _ZN14nsXPTCStubBase7Stub" #n "Ev,@function\n" \ + "_ZN14nsXPTCStubBase7Stub" #n "Ev:\n\t" \ + ".else\n\t" \ + ".err \"stub number " #n " >= 1000 not yet supported\"\n\t" \ + ".endif\n\t" \ + "movl $" #n ", %eax\n\t" \ + "jmp SharedStub\n\t" \ + ".if " #n " < 10\n\t" \ + ".size _ZN14nsXPTCStubBase5Stub" #n "Ev,.-_ZN14nsXPTCStubBase5Stub" #n "Ev\n\t" \ + ".elseif " #n " < 100\n\t" \ + ".size _ZN14nsXPTCStubBase6Stub" #n "Ev,.-_ZN14nsXPTCStubBase6Stub" #n "Ev\n\t" \ + ".else\n\t" \ + ".size _ZN14nsXPTCStubBase7Stub" #n "Ev,.-_ZN14nsXPTCStubBase7Stub" #n "Ev\n\t" \ + ".endif"); + +// static nsresult SharedStub(uint32_t methodIndex) +asm(".section \".text\"\n\t" + ".align 2\n\t" + ".type SharedStub,@function\n\t" + "SharedStub:\n\t" + // make room for gpregs (48), fpregs (64) + "pushq %rbp\n\t" + "movq %rsp,%rbp\n\t" + "subq $112,%rsp\n\t" + // save GP registers + "movq %rdi,-112(%rbp)\n\t" + "movq %rsi,-104(%rbp)\n\t" + "movq %rdx, -96(%rbp)\n\t" + "movq %rcx, -88(%rbp)\n\t" + "movq %r8 , -80(%rbp)\n\t" + "movq %r9 , -72(%rbp)\n\t" + "leaq -112(%rbp),%rcx\n\t" + // save FP registers + "movsd %xmm0,-64(%rbp)\n\t" + "movsd %xmm1,-56(%rbp)\n\t" + "movsd %xmm2,-48(%rbp)\n\t" + "movsd %xmm3,-40(%rbp)\n\t" + "movsd %xmm4,-32(%rbp)\n\t" + "movsd %xmm5,-24(%rbp)\n\t" + "movsd %xmm6,-16(%rbp)\n\t" + "movsd %xmm7, -8(%rbp)\n\t" + "leaq -64(%rbp),%r8\n\t" + // rdi has the 'self' pointer already + "movl %eax,%esi\n\t" + "leaq 16(%rbp),%rdx\n\t" + "call PrepareAndDispatch@plt\n\t" + "leave\n\t" + "ret\n\t" + ".size SharedStub,.-SharedStub"); + +#define SENTINEL_ENTRY(n) \ +nsresult nsXPTCStubBase::Sentinel##n() \ +{ \ + NS_ERROR("nsXPTCStubBase::Sentinel called"); \ + return NS_ERROR_NOT_IMPLEMENTED; \ +} + +#include "xptcstubsdef.inc" diff --git a/xpcom/reflect/xptcall/md/unix/xptcstubs_x86_64_solaris.cpp b/xpcom/reflect/xptcall/md/unix/xptcstubs_x86_64_solaris.cpp new file mode 100644 index 000000000..677fa9960 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcstubs_x86_64_solaris.cpp @@ -0,0 +1,139 @@ +/* -*- 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/. */ + +// Implement shared vtbl methods. + +// Keep this in sync with the darwin version. + +#include "xptcprivate.h" +#include "xptiprivate.h" + +// The Linux/x86-64 ABI passes the first 6 integer parameters and the +// first 8 floating point parameters in registers (rdi, rsi, rdx, rcx, +// r8, r9 and xmm0-xmm7), no stack space is allocated for these by the +// caller. The rest of the parameters are passed in the callers stack +// area. + +const uint32_t PARAM_BUFFER_COUNT = 16; +const uint32_t GPR_COUNT = 6; +const uint32_t FPR_COUNT = 8; + +// PrepareAndDispatch() is called by SharedStub() and calls the actual method. +// +// - 'args[]' contains the arguments passed on stack +// - 'gpregs[]' contains the arguments passed in integer registers +// - 'fpregs[]' contains the arguments passed in floating point registers +// +// The parameters are mapped into an array of type 'nsXPTCMiniVariant' +// and then the method gets called. + +extern "C" nsresult ATTRIBUTE_USED +PrepareAndDispatch(nsXPTCStubBase * self, uint32_t methodIndex, + uint64_t * args, uint64_t * gpregs, double *fpregs) +{ + nsXPTCMiniVariant paramBuffer[PARAM_BUFFER_COUNT]; + nsXPTCMiniVariant* dispatchParams = nullptr; + const nsXPTMethodInfo* info; + uint32_t paramCount; + uint32_t i; + nsresult result = NS_ERROR_FAILURE; + + NS_ASSERTION(self,"no self"); + + self->mEntry->GetMethodInfo(uint16_t(methodIndex), &info); + NS_ASSERTION(info,"no method info"); + if (!info) + return NS_ERROR_UNEXPECTED; + + paramCount = info->GetParamCount(); + + // setup variant array pointer + if (paramCount > PARAM_BUFFER_COUNT) + dispatchParams = new nsXPTCMiniVariant[paramCount]; + else + dispatchParams = paramBuffer; + + NS_ASSERTION(dispatchParams,"no place for params"); + if (!dispatchParams) + return NS_ERROR_OUT_OF_MEMORY; + + uint64_t* ap = args; + uint32_t nr_gpr = 1; // skip one GPR register for 'that' + uint32_t nr_fpr = 0; + uint64_t value; + + for (i = 0; i < paramCount; i++) { + const nsXPTParamInfo& param = info->GetParam(i); + const nsXPTType& type = param.GetType(); + nsXPTCMiniVariant* dp = &dispatchParams[i]; + + if (!param.IsOut() && type == nsXPTType::T_DOUBLE) { + if (nr_fpr < FPR_COUNT) + dp->val.d = fpregs[nr_fpr++]; + else + dp->val.d = *(double*) ap++; + continue; + } + else if (!param.IsOut() && type == nsXPTType::T_FLOAT) { + if (nr_fpr < FPR_COUNT) + // The value in %xmm register is already prepared to + // be retrieved as a float. Therefore, we pass the + // value verbatim, as a double without conversion. + dp->val.d = fpregs[nr_fpr++]; + else + dp->val.f = *(float*) ap++; + continue; + } + else { + if (nr_gpr < GPR_COUNT) + value = gpregs[nr_gpr++]; + else + value = *ap++; + } + + if (param.IsOut() || !type.IsArithmetic()) { + dp->val.p = (void*) value; + continue; + } + + switch (type) { + case nsXPTType::T_I8: dp->val.i8 = (int8_t) value; break; + case nsXPTType::T_I16: dp->val.i16 = (int16_t) value; break; + case nsXPTType::T_I32: dp->val.i32 = (int32_t) value; break; + case nsXPTType::T_I64: dp->val.i64 = (int64_t) value; break; + case nsXPTType::T_U8: dp->val.u8 = (uint8_t) value; break; + case nsXPTType::T_U16: dp->val.u16 = (uint16_t) value; break; + case nsXPTType::T_U32: dp->val.u32 = (uint32_t) value; break; + case nsXPTType::T_U64: dp->val.u64 = (uint64_t) value; break; + // Cast to uint8_t first, to remove garbage on upper 56 bits. + case nsXPTType::T_BOOL: dp->val.b = (bool)(uint8_t) value; break; + case nsXPTType::T_CHAR: dp->val.c = (char) value; break; + case nsXPTType::T_WCHAR: dp->val.wc = (wchar_t) value; break; + + default: + NS_ERROR("bad type"); + break; + } + } + + result = self->mOuter->CallMethod((uint16_t) methodIndex, info, dispatchParams); + + if (dispatchParams != paramBuffer) + delete [] dispatchParams; + + return result; +} + +#define STUB_ENTRY(n) + +#define SENTINEL_ENTRY(n) \ +nsresult nsXPTCStubBase::Sentinel##n() \ +{ \ + NS_ERROR("nsXPTCStubBase::Sentinel called"); \ + return NS_ERROR_NOT_IMPLEMENTED; \ +} + +#include "xptcstubsdef.inc" diff --git a/xpcom/reflect/xptcall/md/unix/xptcstubs_x86_solaris.cpp b/xpcom/reflect/xptcall/md/unix/xptcstubs_x86_solaris.cpp new file mode 100644 index 000000000..39eec5e54 --- /dev/null +++ b/xpcom/reflect/xptcall/md/unix/xptcstubs_x86_solaris.cpp @@ -0,0 +1,77 @@ +/* -*- 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/. */ + +/* Implement shared vtbl methods. */ + +#include "xptcprivate.h" +#include "xptiprivate.h" + +nsresult ATTRIBUTE_USED +PrepareAndDispatch(nsXPTCStubBase* self, uint32_t methodIndex, uint32_t* args) +{ +#define PARAM_BUFFER_COUNT 16 + + nsXPTCMiniVariant paramBuffer[PARAM_BUFFER_COUNT]; + nsXPTCMiniVariant* dispatchParams = nullptr; + const nsXPTMethodInfo* info; + uint8_t paramCount; + uint8_t i; + nsresult result = NS_ERROR_FAILURE; + + NS_ASSERTION(self,"no self"); + + self->mEntry->GetMethodInfo(uint16_t(methodIndex), &info); + NS_ASSERTION(info,"no interface info"); + + paramCount = info->GetParamCount(); + + // setup variant array pointer + if(paramCount > PARAM_BUFFER_COUNT) + dispatchParams = new nsXPTCMiniVariant[paramCount]; + else + dispatchParams = paramBuffer; + NS_ASSERTION(dispatchParams,"no place for params"); + + uint32_t* ap = args; + for(i = 0; i < paramCount; i++, ap++) + { + const nsXPTParamInfo& param = info->GetParam(i); + const nsXPTType& type = param.GetType(); + nsXPTCMiniVariant* dp = &dispatchParams[i]; + + if(param.IsOut() || !type.IsArithmetic()) + { + dp->val.p = (void*) *ap; + continue; + } + // else + dp->val.p = (void*) *ap; + switch(type) + { + case nsXPTType::T_I64 : dp->val.i64 = *((int64_t*) ap); ap++; break; + case nsXPTType::T_U64 : dp->val.u64 = *((uint64_t*)ap); ap++; break; + case nsXPTType::T_DOUBLE : dp->val.d = *((double*) ap); ap++; break; + } + } + + result = self->mOuter->CallMethod((uint16_t)methodIndex, info, dispatchParams); + + if(dispatchParams != paramBuffer) + delete [] dispatchParams; + + return result; +} + +#define STUB_ENTRY(n) + +#define SENTINEL_ENTRY(n) \ +nsresult nsXPTCStubBase::Sentinel##n() \ +{ \ + NS_ERROR("nsXPTCStubBase::Sentinel called"); \ + return NS_ERROR_NOT_IMPLEMENTED; \ +} + +#include "xptcstubsdef.inc" diff --git a/xpcom/reflect/xptcall/md/win32/moz.build b/xpcom/reflect/xptcall/md/win32/moz.build new file mode 100644 index 000000000..5dbe41d76 --- /dev/null +++ b/xpcom/reflect/xptcall/md/win32/moz.build @@ -0,0 +1,45 @@ +# -*- 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/. + +if CONFIG['TARGET_CPU'] == 'x86_64': + if CONFIG['GNU_CXX']: + SOURCES += [ + 'xptcinvoke_x86_64.cpp', + 'xptcstubs_x86_64_gnu.cpp', + ] + SOURCES += [ + 'xptcinvoke_asm_x86_64_gnu.s' + ] + else: + SOURCES += [ + 'xptcinvoke_x86_64.cpp', + 'xptcstubs_x86_64.cpp' + ] + SOURCES += [ + 'xptcinvoke_asm_x86_64.asm', + 'xptcstubs_asm_x86_64.asm' + ] +else: + if CONFIG['GNU_CXX']: + SOURCES += [ + 'xptcinvoke_x86_gnu.cpp', + 'xptcstubs.cpp', + ] + else: + SOURCES += [ + 'xptcinvoke.cpp', + 'xptcinvoke_asm_x86_msvc.asm', + 'xptcstubs.cpp', + ] + SOURCES['xptcinvoke.cpp'].no_pgo = True + SOURCES['xptcinvoke_asm_x86_msvc.asm'].flags += ['-safeseh'] + +FINAL_LIBRARY = 'xul' + +LOCAL_INCLUDES += [ + '../..', + '/xpcom/reflect/xptinfo', +] diff --git a/xpcom/reflect/xptcall/md/win32/xptcinvoke.cpp b/xpcom/reflect/xptcall/md/win32/xptcinvoke.cpp new file mode 100644 index 000000000..faec869f6 --- /dev/null +++ b/xpcom/reflect/xptcall/md/win32/xptcinvoke.cpp @@ -0,0 +1,45 @@ +/* -*- 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/. */ + +/* Platform specific code to invoke XPCOM methods on native objects */ + +#include "xptcprivate.h" + +#ifndef WIN32 +#error "This code is for Win32 only" +#endif + +extern "C" void __fastcall +invoke_copy_to_stack(uint32_t* d, uint32_t paramCount, nsXPTCVariant* s) +{ + for(; paramCount > 0; paramCount--, d++, s++) + { + if(s->IsPtrData()) + { + *((void**)d) = s->ptr; + continue; + } + switch(s->type) + { + case nsXPTType::T_I8 : *((int8_t*) d) = s->val.i8; break; + case nsXPTType::T_I16 : *((int16_t*) d) = s->val.i16; break; + case nsXPTType::T_I32 : *((int32_t*) d) = s->val.i32; break; + case nsXPTType::T_I64 : *((int64_t*) d) = s->val.i64; d++; break; + case nsXPTType::T_U8 : *((uint8_t*) d) = s->val.u8; break; + case nsXPTType::T_U16 : *((uint16_t*)d) = s->val.u16; break; + case nsXPTType::T_U32 : *((uint32_t*)d) = s->val.u32; break; + case nsXPTType::T_U64 : *((uint64_t*)d) = s->val.u64; d++; break; + case nsXPTType::T_FLOAT : *((float*) d) = s->val.f; break; + case nsXPTType::T_DOUBLE : *((double*) d) = s->val.d; d++; break; + case nsXPTType::T_BOOL : *((bool*) d) = s->val.b; break; + case nsXPTType::T_CHAR : *((char*) d) = s->val.c; break; + case nsXPTType::T_WCHAR : *((wchar_t*) d) = s->val.wc; break; + default: + // all the others are plain pointer types + *((void**)d) = s->val.p; + break; + } + } +} diff --git a/xpcom/reflect/xptcall/md/win32/xptcinvoke_asm_x86_64.asm b/xpcom/reflect/xptcall/md/win32/xptcinvoke_asm_x86_64.asm new file mode 100644 index 000000000..bf7c2ef0c --- /dev/null +++ b/xpcom/reflect/xptcall/md/win32/xptcinvoke_asm_x86_64.asm @@ -0,0 +1,107 @@ +; This Source Code Form is subject to the terms of the Mozilla Public +; License, v. 2.0. If a copy of the MPL was not distributed with this +; file, You can obtain one at http://mozilla.org/MPL/2.0/. + +extrn invoke_copy_to_stack:PROC + + +.CODE + +; +;XPTC__InvokebyIndex(nsISupports* that, uint32_t methodIndex, +; uint32_t paramCount, nsXPTCVariant* params) +; + +XPTC__InvokebyIndex PROC FRAME + + ; store register parameters + + mov qword ptr [rsp+32], r9 ; params + mov dword ptr [rsp+24], r8d ; paramCount + mov dword ptr [rsp+16], edx ; methodIndex + mov qword ptr [rsp+8], rcx ; that + + push rbp + .PUSHREG rbp + mov rbp, rsp ; store current RSP to RBP + .SETFRAME rbp, 0 + .ENDPROLOG + + sub rsp, 32 + + ; maybe we don't have any parameters to copy + + test r8d, r8d + jz noparams + + ; + ; Build stack for stdcall + ; + + ; 1st parameter is space for parameters + + mov eax, r8d + or eax, 1 + shl rax, 3 ; *= 8 + sub rsp, rax + mov rcx, rsp + + ; 2nd parameter is parameter count + + mov edx, r8d + + ; 3rd parameter is params + + mov r8, r9 + + sub rsp, 40 + call invoke_copy_to_stack ; rcx = d + ; edx = paramCount + ; r8 = s + add rsp, 32 + + ; Current stack is the following. + ; + ; 0h: [space (for this)] + ; 8h: [1st parameter] + ; 10h: [2nd parameter] + ; 18h: [3rd parameter] + ; 20h: [4th parameter] + ; ... + ; + ; On Win64 ABI, the first 4 parameters are passed using registers, + ; and others are on stack. + + ; 1st, 2nd and 3rd arguments are passed via registers + + mov rdx, qword ptr [rsp+8] ; 1st parameter + movsd xmm1, qword ptr [rsp+8] ; for double + + mov r8, qword ptr [rsp+16] ; 2nd parameter + movsd xmm2, qword ptr [rsp+16] ; for double + + mov r9, qword ptr [rsp+24] ; 3rd parameter + movsd xmm3, qword ptr [rsp+24] ; for double + + ; rcx register is this + + mov rcx, qword ptr [rbp+8+8] ; that + +noparams: + + ; calculate call address + + mov r11, qword ptr [rcx] + mov eax, dword ptr [rbp+16+8] ; methodIndex + + call qword ptr [r11+rax*8] ; stdcall, i.e. callee cleans up stack. + + mov rsp, rbp + pop rbp + + ret + +XPTC__InvokebyIndex ENDP + + +END diff --git a/xpcom/reflect/xptcall/md/win32/xptcinvoke_asm_x86_64_gnu.s b/xpcom/reflect/xptcall/md/win32/xptcinvoke_asm_x86_64_gnu.s new file mode 100644 index 000000000..006e0444b --- /dev/null +++ b/xpcom/reflect/xptcall/md/win32/xptcinvoke_asm_x86_64_gnu.s @@ -0,0 +1,110 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.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 invoke_copy_to_stack + + +.text +.intel_syntax noprefix + +# +#_XPTC__InvokebyIndex(nsISupports* that, uint32_t methodIndex, +# uint32_t paramCount, nsXPTCVariant* params) +# + +.globl XPTC__InvokebyIndex +.def XPTC__InvokebyIndex + .scl 3 + .type 46 +.endef +XPTC__InvokebyIndex: + + # store register parameters + + mov qword ptr [rsp+32], r9 # params + mov dword ptr [rsp+24], r8d # paramCount + mov dword ptr [rsp+16], edx # methodIndex + mov qword ptr [rsp+8], rcx # that + + push rbp + # .PUSHREG rbp + mov rbp, rsp # store current RSP to RBP + # .SETFRAME rbp, 0 + # .ENDPROLOG + + sub rsp, 32 + + # maybe we don't have any parameters to copy + + test r8d, r8d + jz noparams + + # + # Build stack for stdcall + # + + # 1st parameter is space for parameters + + mov eax, r8d + or eax, 1 + shl rax, 3 # *= 8 + sub rsp, rax + mov rcx, rsp + + # 2nd parameter is parameter count + + mov edx, r8d + + # 3rd parameter is params + + mov r8, r9 + + sub rsp, 40 + call invoke_copy_to_stack # rcx = d + # edx = paramCount + # r8 = s + add rsp, 32 + + # Current stack is the following. + # + # 0h: [space (for this)] + # 8h: [1st parameter] + # 10h: [2nd parameter] + # 18h: [3rd parameter] + # 20h: [4th parameter] + # ... + # + # On Win64 ABI, the first 4 parameters are passed using registers, + # and others are on stack. + + # 1st, 2nd and 3rd arguments are passed via registers + + mov rdx, qword ptr [rsp+8] # 1st parameter + movsd xmm1, qword ptr [rsp+8] # for double + + mov r8, qword ptr [rsp+16] # 2nd parameter + movsd xmm2, qword ptr [rsp+16] # for double + + mov r9, qword ptr [rsp+24] # 3rd parameter + movsd xmm3, qword ptr [rsp+24] # for double + + # rcx register is this + + mov rcx, qword ptr [rbp+8+8] # that + +noparams: + + # calculate call address + + mov r11, qword ptr [rcx] + mov eax, dword ptr [rbp+16+8] # methodIndex + + call qword ptr [r11+rax*8] # stdcall, i.e. callee cleans up stack. + + mov rsp, rbp + pop rbp + + ret + + diff --git a/xpcom/reflect/xptcall/md/win32/xptcinvoke_asm_x86_msvc.asm b/xpcom/reflect/xptcall/md/win32/xptcinvoke_asm_x86_msvc.asm new file mode 100644 index 000000000..f3b7a1826 --- /dev/null +++ b/xpcom/reflect/xptcall/md/win32/xptcinvoke_asm_x86_msvc.asm @@ -0,0 +1,63 @@ +; This Source Code Form is subject to the terms of the Mozilla Public +; License, v. 2.0. If a copy of the MPL was not distributed with this +; file, You can obtain one at http://mozilla.org/MPL/2.0/. + + TITLE xptcinvoke_asm_x86_msvc.asm + .686P + .model flat + +PUBLIC _NS_InvokeByIndex +EXTRN @invoke_copy_to_stack@12:PROC + +; +; extern "C" nsresult __cdecl +; NS_InvokeByIndex(nsISupports* that, uint32_t methodIndex, +; uint32_t paramCount, nsXPTCVariant* params) +; + +_TEXT SEGMENT +_NS_InvokeByIndex PROC + + ; Build frame + push ebp + mov ebp, esp + + ; Save paramCount for later + mov edx, dword ptr [ebp+16] + + ; Do we have any parameters? + test edx, edx + jz noparams + + ; Build call for copy_to_stack, which is __fastcall + + ; Allocate space for parameters. 8 is the biggest size + ; any parameter can be, so assume that all our parameters + ; are that large. + mov eax, edx + shl eax, 3 + sub esp, eax + + mov ecx, esp + push dword ptr [ebp+20] + call @invoke_copy_to_stack@12 +noparams: + ; Push the `this' parameter for the call. + mov ecx, dword ptr [ebp+8] + push ecx + + ; Load the vtable. + mov edx, dword ptr [ecx] + + ; Call the vtable index at `methodIndex'. + mov eax, dword ptr [ebp+12] + call dword ptr [edx+eax*4] + + ; Reset and return. + mov esp, ebp + pop ebp + ret +_NS_InvokeByIndex ENDP +_TEXT ENDS + +END diff --git a/xpcom/reflect/xptcall/md/win32/xptcinvoke_x86_64.cpp b/xpcom/reflect/xptcall/md/win32/xptcinvoke_x86_64.cpp new file mode 100644 index 000000000..36b454b35 --- /dev/null +++ b/xpcom/reflect/xptcall/md/win32/xptcinvoke_x86_64.cpp @@ -0,0 +1,59 @@ +/* -*- 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/. */ + +/* Platform specific code to invoke XPCOM methods on native objects */ + +#include "xptcprivate.h" + +extern "C" void +invoke_copy_to_stack(uint64_t* d, uint32_t paramCount, nsXPTCVariant* s) +{ + for(; paramCount > 0; paramCount--, d++, s++) + { + if(s->IsPtrData()) + { + *((void**)d) = s->ptr; + continue; + } + + /* + * AMD64 platform uses 8 bytes align. + */ + + switch(s->type) + { + case nsXPTType::T_I8 : *((int8_t*) d) = s->val.i8; break; + case nsXPTType::T_I16 : *((int16_t*) d) = s->val.i16; break; + case nsXPTType::T_I32 : *((int32_t*) d) = s->val.i32; break; + case nsXPTType::T_I64 : *((int64_t*) d) = s->val.i64; break; + case nsXPTType::T_U8 : *((uint8_t*) d) = s->val.u8; break; + case nsXPTType::T_U16 : *((uint16_t*)d) = s->val.u16; break; + case nsXPTType::T_U32 : *((uint32_t*)d) = s->val.u32; break; + case nsXPTType::T_U64 : *((uint64_t*)d) = s->val.u64; break; + case nsXPTType::T_FLOAT : *((float*) d) = s->val.f; break; + case nsXPTType::T_DOUBLE : *((double*) d) = s->val.d; break; + case nsXPTType::T_BOOL : *((bool*) d) = s->val.b; break; + case nsXPTType::T_CHAR : *((char*) d) = s->val.c; break; + case nsXPTType::T_WCHAR : *((wchar_t*) d) = s->val.wc; break; + default: + // all the others are plain pointer types + *((void**)d) = s->val.p; + break; + } + } +} + +extern "C" nsresult +XPTC__InvokebyIndex(nsISupports* that, uint32_t methodIndex, + uint32_t paramCount, nsXPTCVariant* params); + +extern "C" +EXPORT_XPCOM_API(nsresult) +NS_InvokeByIndex(nsISupports* that, uint32_t methodIndex, + uint32_t paramCount, nsXPTCVariant* params) +{ + return XPTC__InvokebyIndex(that, methodIndex, paramCount, params); +} + diff --git a/xpcom/reflect/xptcall/md/win32/xptcinvoke_x86_gnu.cpp b/xpcom/reflect/xptcall/md/win32/xptcinvoke_x86_gnu.cpp new file mode 100644 index 000000000..0e73e99f3 --- /dev/null +++ b/xpcom/reflect/xptcall/md/win32/xptcinvoke_x86_gnu.cpp @@ -0,0 +1,106 @@ +/* -*- 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/. */ + +/* Platform specific code to invoke XPCOM methods on native objects */ + +#include "xptcprivate.h" + +extern "C" { +void __attribute__ ((__used__)) __attribute__ ((regparm(3))) +invoke_copy_to_stack(uint32_t paramCount, nsXPTCVariant* s, uint32_t* d) +{ + for(uint32_t i = paramCount; i >0; i--, d++, s++) + { + if(s->IsPtrData()) + { + *((void**)d) = s->ptr; + continue; + } + + switch(s->type) + { + case nsXPTType::T_I8 : *((int8_t*) d) = s->val.i8; break; + case nsXPTType::T_I16 : *((int16_t*) d) = s->val.i16; break; + case nsXPTType::T_I32 : *((int32_t*) d) = s->val.i32; break; + case nsXPTType::T_I64 : *((int64_t*) d) = s->val.i64; d++; break; + case nsXPTType::T_U8 : *((uint8_t*) d) = s->val.u8; break; + case nsXPTType::T_U16 : *((uint16_t*)d) = s->val.u16; break; + case nsXPTType::T_U32 : *((uint32_t*)d) = s->val.u32; break; + case nsXPTType::T_U64 : *((uint64_t*)d) = s->val.u64; d++; break; + case nsXPTType::T_FLOAT : *((float*) d) = s->val.f; break; + case nsXPTType::T_DOUBLE : *((double*) d) = s->val.d; d++; break; + case nsXPTType::T_BOOL : *((bool*) d) = s->val.b; break; + case nsXPTType::T_CHAR : *((char*) d) = s->val.c; break; + case nsXPTType::T_WCHAR : *((wchar_t*) d) = s->val.wc; break; + default: + // all the others are plain pointer types + *((void**)d) = s->val.p; + break; + } + } +} +} // extern "C" + +/* + EXPORT_XPCOM_API(nsresult) + NS_InvokeByIndex(nsISupports* that, uint32_t methodIndex, + uint32_t paramCount, nsXPTCVariant* params); + + Each param takes at most two 4-byte words. + It doesn't matter if we push too many words, and calculating the exact + amount takes time. + + that = ebp + 0x08 + methodIndex = ebp + 0x0c + paramCount = ebp + 0x10 + params = ebp + 0x14 + +*/ + +__asm__ ( + ".text\n\t" +/* alignment here seems unimportant here; this was 16, now it's 2 which + is what xptcstubs uses. */ + ".align 2\n\t" + ".globl _NS_InvokeByIndex\n\t" + "_NS_InvokeByIndex:\n\t" + "pushl %ebp\n\t" + "movl %esp, %ebp\n\t" + "movl 0x10(%ebp), %eax\n\t" + "leal 0(,%eax,8),%edx\n\t" + + /* set up call frame for method. */ + "subl %edx, %esp\n\t" /* make room for params. */ +/* Align to maximum x86 data size: 128 bits == 16 bytes == XMM register size. + * This is to avoid protection faults where SSE+ alignment of stack pointer + * is assumed and required, e.g. by GCC4's -ftree-vectorize option. + */ + "andl $0xfffffff0, %esp\n\t" /* drop(?) stack ptr to 128-bit align */ +/* $esp should be aligned to a 16-byte boundary here (note we include an + * additional 4 bytes in a later push instruction). This will ensure $ebp + * in the function called below is aligned to a 0x8 boundary. SSE instructions + * like movapd/movdqa expect memory operand to be aligned on a 16-byte + * boundary. The GCC compiler will generate the memory operand using $ebp + * with an 8-byte offset. + */ + "subl $0xc, %esp\n\t" /* lower again; push/call below will re-align */ + "movl %esp, %ecx\n\t" /* ecx = d */ + "movl 8(%ebp), %edx\n\t" /* edx = this */ + "pushl %edx\n\t" /* push this. esp % 16 == 0 */ + + "movl 0x14(%ebp), %edx\n\t" + "call _invoke_copy_to_stack\n\t" + "movl 0x08(%ebp), %ecx\n\t" /* 'that' */ + "movl (%ecx), %edx\n\t" + "movl 0x0c(%ebp), %eax\n\t" /* function index */ + "leal (%edx,%eax,4), %edx\n\t" + "call *(%edx)\n\t" + "movl %ebp, %esp\n\t" + "popl %ebp\n\t" + "ret\n" + ".section .drectve\n\t" + ".ascii \" -export:NS_InvokeByIndex\"\n\t" + ".text\n\t" +); diff --git a/xpcom/reflect/xptcall/md/win32/xptcstubs.cpp b/xpcom/reflect/xptcall/md/win32/xptcstubs.cpp new file mode 100644 index 000000000..e525aebd7 --- /dev/null +++ b/xpcom/reflect/xptcall/md/win32/xptcstubs.cpp @@ -0,0 +1,227 @@ +/* -*- 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/. */ + +/* Implement shared vtbl methods. */ + +#include "xptcprivate.h" +#include "xptiprivate.h" + +#ifndef WIN32 +#error "This code is for Win32 only" +#endif + +extern "C" { + +#if !defined(__GNUC__) && !defined(__clang__) +static +#endif +nsresult __stdcall +PrepareAndDispatch(nsXPTCStubBase* self, uint32_t methodIndex, + uint32_t* args, uint32_t* stackBytesToPop) +{ +#define PARAM_BUFFER_COUNT 16 + + nsXPTCMiniVariant paramBuffer[PARAM_BUFFER_COUNT]; + nsXPTCMiniVariant* dispatchParams = nullptr; + const nsXPTMethodInfo* info = nullptr; + uint8_t paramCount; + uint8_t i; + nsresult result = NS_ERROR_FAILURE; + + // If anything fails before stackBytesToPop can be set then + // the failure is completely catastrophic! + + NS_ASSERTION(self,"no self"); + + self->mEntry->GetMethodInfo(uint16_t(methodIndex), &info); + NS_ASSERTION(info,"no method info"); + + paramCount = info->GetParamCount(); + + // setup variant array pointer + if(paramCount > PARAM_BUFFER_COUNT) + dispatchParams = new nsXPTCMiniVariant[paramCount]; + else + dispatchParams = paramBuffer; + NS_ASSERTION(dispatchParams,"no place for params"); + + uint32_t* ap = args; + for(i = 0; i < paramCount; i++, ap++) + { + const nsXPTParamInfo& param = info->GetParam(i); + const nsXPTType& type = param.GetType(); + nsXPTCMiniVariant* dp = &dispatchParams[i]; + + if(param.IsOut() || !type.IsArithmetic()) + { + dp->val.p = (void*) *ap; + continue; + } + // else + switch(type) + { + case nsXPTType::T_I8 : dp->val.i8 = *((int8_t*) ap); break; + case nsXPTType::T_I16 : dp->val.i16 = *((int16_t*) ap); break; + case nsXPTType::T_I32 : dp->val.i32 = *((int32_t*) ap); break; + case nsXPTType::T_I64 : dp->val.i64 = *((int64_t*) ap); ap++; break; + case nsXPTType::T_U8 : dp->val.u8 = *((uint8_t*) ap); break; + case nsXPTType::T_U16 : dp->val.u16 = *((uint16_t*)ap); break; + case nsXPTType::T_U32 : dp->val.u32 = *((uint32_t*)ap); break; + case nsXPTType::T_U64 : dp->val.u64 = *((uint64_t*)ap); ap++; break; + case nsXPTType::T_FLOAT : dp->val.f = *((float*) ap); break; + case nsXPTType::T_DOUBLE : dp->val.d = *((double*) ap); ap++; break; + case nsXPTType::T_BOOL : dp->val.b = *((bool*) ap); break; + case nsXPTType::T_CHAR : dp->val.c = *((char*) ap); break; + case nsXPTType::T_WCHAR : dp->val.wc = *((wchar_t*) ap); break; + default: + NS_ERROR("bad type"); + break; + } + } + *stackBytesToPop = ((uint32_t)ap) - ((uint32_t)args); + + result = self->mOuter->CallMethod((uint16_t)methodIndex, info, dispatchParams); + + if(dispatchParams != paramBuffer) + delete [] dispatchParams; + + return result; +} + +} // extern "C" + +// declspec(naked) is broken in gcc and clang-cl +#if !defined(__GNUC__) && !defined(__clang__) +static +__declspec(naked) +void SharedStub(void) +{ + __asm { + push ebp // set up simple stack frame + mov ebp, esp // stack has: ebp/vtbl_index/retaddr/this/args + push ecx // make room for a ptr + lea eax, [ebp-4] // pointer to stackBytesToPop + push eax + lea eax, [ebp+12] // pointer to args + push eax + push ecx // vtbl_index + mov eax, [ebp+8] // this + push eax + call PrepareAndDispatch + mov edx, [ebp+4] // return address + mov ecx, [ebp-4] // stackBytesToPop + add ecx, 8 // for 'this' and return address + mov esp, ebp + pop ebp + add esp, ecx // fix up stack pointer + jmp edx // simulate __stdcall return + } +} + +// these macros get expanded (many times) in the file #included below +#define STUB_ENTRY(n) \ +__declspec(naked) nsresult __stdcall nsXPTCStubBase::Stub##n() \ +{ __asm mov ecx, n __asm jmp SharedStub } + +#else + +asm(".text\n\t" + ".align 4\n\t" + "SharedStub:\n\t" + "push %ebp\n\t" + "mov %esp, %ebp\n\t" + "push %ecx\n\t" + "lea -4(%ebp), %eax\n\t" + "push %eax\n\t" + "lea 12(%ebp), %eax\n\t" + "push %eax\n\t" + "push %ecx\n\t" + "movl 8(%ebp), %eax\n\t" + "push %eax\n\t" + "call _PrepareAndDispatch@16\n\t" + "mov 4(%ebp), %edx\n\t" + "mov -4(%ebp), %ecx\n\t" + "add $8, %ecx\n\t" + "mov %ebp, %esp\n\t" + "pop %ebp\n\t" + "add %ecx, %esp\n\t" + "jmp *%edx" +); + +// The clang-cl specific code below is required because mingw uses the gcc name +// mangling, but clang-cl implements the MSVC name mangling. + +#ifdef __clang__ + +#define STUB_ENTRY(n) \ +asm(".text\n\t" \ + ".align 4\n\t" \ + ".globl \"?Stub" #n "@nsXPTCStubBase@@UAG?AW4nsresult@@XZ\"\n\t" \ + ".def \"?Stub" #n "@nsXPTCStubBase@@UAG?AW4nsresult@@XZ\"; \n\t" \ + ".scl 2\n\t" \ + ".type 46\n\t" \ + ".endef\n\t" \ + "\"?Stub" #n "@nsXPTCStubBase@@UAG?AW4nsresult@@XZ\":\n\t" \ + "mov $" #n ", %ecx\n\t" \ + "jmp SharedStub"); + +#else + +#define STUB_ENTRY(n) \ +asm(".text\n\t" \ + ".align 4\n\t" \ + ".if " #n " < 10\n\t" \ + ".globl __ZN14nsXPTCStubBase5Stub" #n "Ev@4\n\t" \ + ".def __ZN14nsXPTCStubBase5Stub" #n "Ev@4; \n\t" \ + ".scl 3\n\t" \ + ".type 46\n\t" \ + ".endef\n\t" \ + "__ZN14nsXPTCStubBase5Stub" #n "Ev@4:\n\t" \ + ".elseif " #n " < 100\n\t" \ + ".globl __ZN14nsXPTCStubBase6Stub" #n "Ev@4\n\t" \ + ".def __ZN14nsXPTCStubBase6Stub" #n "Ev@4\n\t" \ + ".scl 3\n\t" \ + ".type 46\n\t" \ + ".endef\n\t" \ + "__ZN14nsXPTCStubBase6Stub" #n "Ev@4:\n\t" \ + ".elseif " #n " < 1000\n\t" \ + ".globl __ZN14nsXPTCStubBase7Stub" #n "Ev@4\n\t" \ + ".def __ZN14nsXPTCStubBase7Stub" #n "Ev@4\n\t" \ + ".scl 3\n\t" \ + ".type 46\n\t" \ + ".endef\n\t" \ + "__ZN14nsXPTCStubBase7Stub" #n "Ev@4:\n\t" \ + ".else\n\t" \ + ".err \"stub number " #n " >= 1000 not yet supported\"\n\t" \ + ".endif\n\t" \ + "mov $" #n ", %ecx\n\t" \ + "jmp SharedStub"); + +#endif + +#endif /* __GNUC__ || __clang__ */ + +#define SENTINEL_ENTRY(n) \ +nsresult __stdcall nsXPTCStubBase::Sentinel##n() \ +{ \ + NS_ERROR("nsXPTCStubBase::Sentinel called"); \ + return NS_ERROR_NOT_IMPLEMENTED; \ +} + +#ifdef _MSC_VER +#pragma warning(disable : 4035) // OK to have no return value +#endif +#include "xptcstubsdef.inc" +#ifdef _MSC_VER +#pragma warning(default : 4035) // restore default +#endif + +void +#ifdef __GNUC__ +__cdecl +#endif +xptc_dummy() +{ +} diff --git a/xpcom/reflect/xptcall/md/win32/xptcstubs_asm_x86_64.asm b/xpcom/reflect/xptcall/md/win32/xptcstubs_asm_x86_64.asm new file mode 100644 index 000000000..db2cc16be --- /dev/null +++ b/xpcom/reflect/xptcall/md/win32/xptcstubs_asm_x86_64.asm @@ -0,0 +1,335 @@ +; This Source Code Form is subject to the terms of the Mozilla Public +; License, v. 2.0. If a copy of the MPL was not distributed with this +; file, You can obtain one at http://mozilla.org/MPL/2.0/. + +extrn PrepareAndDispatch:PROC + +.code + +SharedStub PROC FRAME + sub rsp, 104 + .ALLOCSTACK 104 + .ENDPROLOG + + ; rcx is this pointer. Need backup for optimized build + + mov qword ptr [rsp+88], rcx + + ; + ; fist 4 parameters (1st is "this" pointer) are passed in registers. + ; + + ; for floating value + + movsd qword ptr [rsp+64], xmm1 + movsd qword ptr [rsp+72], xmm2 + movsd qword ptr [rsp+80], xmm3 + + ; for integer value + + mov qword ptr [rsp+40], rdx + mov qword ptr [rsp+48], r8 + mov qword ptr [rsp+56], r9 + + ; + ; Call PrepareAndDispatch function + ; + + ; 5th parameter (floating parameters) of PrepareAndDispatch + + lea r9, qword ptr [rsp+64] + mov qword ptr [rsp+32], r9 + + ; 4th parameter (normal parameters) of PrepareAndDispatch + + lea r9, qword ptr [rsp+40] + + ; 3rd parameter (pointer to args on stack) + + lea r8, qword ptr [rsp+40+104] + + ; 2nd parameter (vtbl_index) + + mov rdx, r11 + + ; 1st parameter (this) (rcx) + + call PrepareAndDispatch + + ; restore rcx + + mov rcx, qword ptr [rsp+88] + + ; + ; clean up register + ; + + add rsp, 104+8 + + ; set return address + + mov rdx, qword ptr [rsp-8] + + ; simulate __stdcall return + + jmp rdx + +SharedStub ENDP + + +STUBENTRY MACRO functionname, paramcount +functionname PROC + mov r11, paramcount + jmp SharedStub +functionname ENDP +ENDM + + STUBENTRY ?Stub3@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 3 + STUBENTRY ?Stub4@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 4 + STUBENTRY ?Stub5@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 5 + STUBENTRY ?Stub6@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 6 + STUBENTRY ?Stub7@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 7 + STUBENTRY ?Stub8@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 8 + STUBENTRY ?Stub9@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 9 + STUBENTRY ?Stub10@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 10 + STUBENTRY ?Stub11@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 11 + STUBENTRY ?Stub12@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 12 + STUBENTRY ?Stub13@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 13 + STUBENTRY ?Stub14@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 14 + STUBENTRY ?Stub15@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 15 + STUBENTRY ?Stub16@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 16 + STUBENTRY ?Stub17@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 17 + STUBENTRY ?Stub18@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 18 + STUBENTRY ?Stub19@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 19 + STUBENTRY ?Stub20@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 20 + STUBENTRY ?Stub21@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 21 + STUBENTRY ?Stub22@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 22 + STUBENTRY ?Stub23@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 23 + STUBENTRY ?Stub24@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 24 + STUBENTRY ?Stub25@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 25 + STUBENTRY ?Stub26@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 26 + STUBENTRY ?Stub27@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 27 + STUBENTRY ?Stub28@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 28 + STUBENTRY ?Stub29@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 29 + STUBENTRY ?Stub30@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 30 + STUBENTRY ?Stub31@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 31 + STUBENTRY ?Stub32@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 32 + STUBENTRY ?Stub33@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 33 + STUBENTRY ?Stub34@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 34 + STUBENTRY ?Stub35@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 35 + STUBENTRY ?Stub36@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 36 + STUBENTRY ?Stub37@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 37 + STUBENTRY ?Stub38@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 38 + STUBENTRY ?Stub39@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 39 + STUBENTRY ?Stub40@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 40 + STUBENTRY ?Stub41@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 41 + STUBENTRY ?Stub42@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 42 + STUBENTRY ?Stub43@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 43 + STUBENTRY ?Stub44@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 44 + STUBENTRY ?Stub45@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 45 + STUBENTRY ?Stub46@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 46 + STUBENTRY ?Stub47@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 47 + STUBENTRY ?Stub48@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 48 + STUBENTRY ?Stub49@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 49 + STUBENTRY ?Stub50@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 50 + STUBENTRY ?Stub51@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 51 + STUBENTRY ?Stub52@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 52 + STUBENTRY ?Stub53@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 53 + STUBENTRY ?Stub54@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 54 + STUBENTRY ?Stub55@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 55 + STUBENTRY ?Stub56@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 56 + STUBENTRY ?Stub57@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 57 + STUBENTRY ?Stub58@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 58 + STUBENTRY ?Stub59@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 59 + STUBENTRY ?Stub60@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 60 + STUBENTRY ?Stub61@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 61 + STUBENTRY ?Stub62@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 62 + STUBENTRY ?Stub63@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 63 + STUBENTRY ?Stub64@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 64 + STUBENTRY ?Stub65@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 65 + STUBENTRY ?Stub66@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 66 + STUBENTRY ?Stub67@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 67 + STUBENTRY ?Stub68@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 68 + STUBENTRY ?Stub69@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 69 + STUBENTRY ?Stub70@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 70 + STUBENTRY ?Stub71@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 71 + STUBENTRY ?Stub72@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 72 + STUBENTRY ?Stub73@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 73 + STUBENTRY ?Stub74@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 74 + STUBENTRY ?Stub75@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 75 + STUBENTRY ?Stub76@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 76 + STUBENTRY ?Stub77@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 77 + STUBENTRY ?Stub78@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 78 + STUBENTRY ?Stub79@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 79 + STUBENTRY ?Stub80@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 80 + STUBENTRY ?Stub81@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 81 + STUBENTRY ?Stub82@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 82 + STUBENTRY ?Stub83@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 83 + STUBENTRY ?Stub84@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 84 + STUBENTRY ?Stub85@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 85 + STUBENTRY ?Stub86@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 86 + STUBENTRY ?Stub87@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 87 + STUBENTRY ?Stub88@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 88 + STUBENTRY ?Stub89@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 89 + STUBENTRY ?Stub90@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 90 + STUBENTRY ?Stub91@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 91 + STUBENTRY ?Stub92@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 92 + STUBENTRY ?Stub93@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 93 + STUBENTRY ?Stub94@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 94 + STUBENTRY ?Stub95@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 95 + STUBENTRY ?Stub96@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 96 + STUBENTRY ?Stub97@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 97 + STUBENTRY ?Stub98@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 98 + STUBENTRY ?Stub99@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 99 + STUBENTRY ?Stub100@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 100 + STUBENTRY ?Stub101@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 101 + STUBENTRY ?Stub102@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 102 + STUBENTRY ?Stub103@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 103 + STUBENTRY ?Stub104@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 104 + STUBENTRY ?Stub105@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 105 + STUBENTRY ?Stub106@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 106 + STUBENTRY ?Stub107@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 107 + STUBENTRY ?Stub108@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 108 + STUBENTRY ?Stub109@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 109 + STUBENTRY ?Stub110@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 110 + STUBENTRY ?Stub111@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 111 + STUBENTRY ?Stub112@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 112 + STUBENTRY ?Stub113@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 113 + STUBENTRY ?Stub114@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 114 + STUBENTRY ?Stub115@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 115 + STUBENTRY ?Stub116@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 116 + STUBENTRY ?Stub117@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 117 + STUBENTRY ?Stub118@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 118 + STUBENTRY ?Stub119@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 119 + STUBENTRY ?Stub120@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 120 + STUBENTRY ?Stub121@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 121 + STUBENTRY ?Stub122@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 122 + STUBENTRY ?Stub123@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 123 + STUBENTRY ?Stub124@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 124 + STUBENTRY ?Stub125@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 125 + STUBENTRY ?Stub126@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 126 + STUBENTRY ?Stub127@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 127 + STUBENTRY ?Stub128@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 128 + STUBENTRY ?Stub129@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 129 + STUBENTRY ?Stub130@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 130 + STUBENTRY ?Stub131@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 131 + STUBENTRY ?Stub132@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 132 + STUBENTRY ?Stub133@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 133 + STUBENTRY ?Stub134@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 134 + STUBENTRY ?Stub135@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 135 + STUBENTRY ?Stub136@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 136 + STUBENTRY ?Stub137@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 137 + STUBENTRY ?Stub138@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 138 + STUBENTRY ?Stub139@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 139 + STUBENTRY ?Stub140@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 140 + STUBENTRY ?Stub141@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 141 + STUBENTRY ?Stub142@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 142 + STUBENTRY ?Stub143@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 143 + STUBENTRY ?Stub144@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 144 + STUBENTRY ?Stub145@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 145 + STUBENTRY ?Stub146@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 146 + STUBENTRY ?Stub147@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 147 + STUBENTRY ?Stub148@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 148 + STUBENTRY ?Stub149@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 149 + STUBENTRY ?Stub150@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 150 + STUBENTRY ?Stub151@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 151 + STUBENTRY ?Stub152@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 152 + STUBENTRY ?Stub153@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 153 + STUBENTRY ?Stub154@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 154 + STUBENTRY ?Stub155@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 155 + STUBENTRY ?Stub156@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 156 + STUBENTRY ?Stub157@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 157 + STUBENTRY ?Stub158@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 158 + STUBENTRY ?Stub159@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 159 + STUBENTRY ?Stub160@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 160 + STUBENTRY ?Stub161@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 161 + STUBENTRY ?Stub162@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 162 + STUBENTRY ?Stub163@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 163 + STUBENTRY ?Stub164@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 164 + STUBENTRY ?Stub165@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 165 + STUBENTRY ?Stub166@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 166 + STUBENTRY ?Stub167@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 167 + STUBENTRY ?Stub168@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 168 + STUBENTRY ?Stub169@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 169 + STUBENTRY ?Stub170@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 170 + STUBENTRY ?Stub171@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 171 + STUBENTRY ?Stub172@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 172 + STUBENTRY ?Stub173@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 173 + STUBENTRY ?Stub174@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 174 + STUBENTRY ?Stub175@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 175 + STUBENTRY ?Stub176@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 176 + STUBENTRY ?Stub177@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 177 + STUBENTRY ?Stub178@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 178 + STUBENTRY ?Stub179@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 179 + STUBENTRY ?Stub180@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 180 + STUBENTRY ?Stub181@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 181 + STUBENTRY ?Stub182@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 182 + STUBENTRY ?Stub183@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 183 + STUBENTRY ?Stub184@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 184 + STUBENTRY ?Stub185@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 185 + STUBENTRY ?Stub186@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 186 + STUBENTRY ?Stub187@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 187 + STUBENTRY ?Stub188@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 188 + STUBENTRY ?Stub189@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 189 + STUBENTRY ?Stub190@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 190 + STUBENTRY ?Stub191@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 191 + STUBENTRY ?Stub192@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 192 + STUBENTRY ?Stub193@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 193 + STUBENTRY ?Stub194@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 194 + STUBENTRY ?Stub195@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 195 + STUBENTRY ?Stub196@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 196 + STUBENTRY ?Stub197@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 197 + STUBENTRY ?Stub198@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 198 + STUBENTRY ?Stub199@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 199 + STUBENTRY ?Stub200@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 200 + STUBENTRY ?Stub201@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 201 + STUBENTRY ?Stub202@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 202 + STUBENTRY ?Stub203@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 203 + STUBENTRY ?Stub204@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 204 + STUBENTRY ?Stub205@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 205 + STUBENTRY ?Stub206@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 206 + STUBENTRY ?Stub207@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 207 + STUBENTRY ?Stub208@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 208 + STUBENTRY ?Stub209@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 209 + STUBENTRY ?Stub210@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 210 + STUBENTRY ?Stub211@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 211 + STUBENTRY ?Stub212@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 212 + STUBENTRY ?Stub213@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 213 + STUBENTRY ?Stub214@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 214 + STUBENTRY ?Stub215@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 215 + STUBENTRY ?Stub216@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 216 + STUBENTRY ?Stub217@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 217 + STUBENTRY ?Stub218@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 218 + STUBENTRY ?Stub219@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 219 + STUBENTRY ?Stub220@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 220 + STUBENTRY ?Stub221@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 221 + STUBENTRY ?Stub222@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 222 + STUBENTRY ?Stub223@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 223 + STUBENTRY ?Stub224@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 224 + STUBENTRY ?Stub225@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 225 + STUBENTRY ?Stub226@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 226 + STUBENTRY ?Stub227@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 227 + STUBENTRY ?Stub228@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 228 + STUBENTRY ?Stub229@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 229 + STUBENTRY ?Stub230@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 230 + STUBENTRY ?Stub231@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 231 + STUBENTRY ?Stub232@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 232 + STUBENTRY ?Stub233@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 233 + STUBENTRY ?Stub234@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 234 + STUBENTRY ?Stub235@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 235 + STUBENTRY ?Stub236@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 236 + STUBENTRY ?Stub237@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 237 + STUBENTRY ?Stub238@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 238 + STUBENTRY ?Stub239@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 239 + STUBENTRY ?Stub240@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 240 + STUBENTRY ?Stub241@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 241 + STUBENTRY ?Stub242@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 242 + STUBENTRY ?Stub243@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 243 + STUBENTRY ?Stub244@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 244 + STUBENTRY ?Stub245@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 245 + STUBENTRY ?Stub246@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 246 + STUBENTRY ?Stub247@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 247 + STUBENTRY ?Stub248@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 248 + STUBENTRY ?Stub249@nsXPTCStubBase@@UEAA?AW4nsresult@@XZ, 249 + +END diff --git a/xpcom/reflect/xptcall/md/win32/xptcstubs_x86_64.cpp b/xpcom/reflect/xptcall/md/win32/xptcstubs_x86_64.cpp new file mode 100644 index 000000000..8ff479d1e --- /dev/null +++ b/xpcom/reflect/xptcall/md/win32/xptcstubs_x86_64.cpp @@ -0,0 +1,197 @@ +/* -*- 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/. */ + +/* Implement shared vtbl methods. */ + +#include "xptcprivate.h" +#include "xptiprivate.h" + +/* + * This is for Windows XP 64-Bit Edition / Server 2003 for AMD64 or later. + */ + +extern "C" nsresult +PrepareAndDispatch(nsXPTCStubBase* self, uint32_t methodIndex, uint64_t* args, + uint64_t *gprData, double *fprData) +{ +#define PARAM_BUFFER_COUNT 16 +// +// "this" pointer is first parameter, so parameter count is 3. +// +#define PARAM_GPR_COUNT 3 +#define PARAM_FPR_COUNT 3 + + nsXPTCMiniVariant paramBuffer[PARAM_BUFFER_COUNT]; + nsXPTCMiniVariant* dispatchParams = nullptr; + const nsXPTMethodInfo* info = nullptr; + uint8_t paramCount; + uint8_t i; + nsresult result = NS_ERROR_FAILURE; + + NS_ASSERTION(self,"no self"); + + self->mEntry->GetMethodInfo(uint16_t(methodIndex), &info); + NS_ASSERTION(info,"no method info"); + + paramCount = info->GetParamCount(); + + // + // setup variant array pointer + // + + if(paramCount > PARAM_BUFFER_COUNT) + dispatchParams = new nsXPTCMiniVariant[paramCount]; + else + dispatchParams = paramBuffer; + + NS_ASSERTION(dispatchParams,"no place for params"); + + uint64_t* ap = args; + uint32_t iCount = 0; + + for(i = 0; i < paramCount; i++) + { + const nsXPTParamInfo& param = info->GetParam(i); + const nsXPTType& type = param.GetType(); + nsXPTCMiniVariant* dp = &dispatchParams[i]; + + if(param.IsOut() || !type.IsArithmetic()) + { + if (iCount < PARAM_GPR_COUNT) + dp->val.p = (void*)gprData[iCount++]; + else + dp->val.p = (void*)*ap++; + + continue; + } + // else + switch(type) + { + case nsXPTType::T_I8: + if (iCount < PARAM_GPR_COUNT) + dp->val.i8 = (int8_t)gprData[iCount++]; + else + dp->val.i8 = *((int8_t*)ap++); + break; + + case nsXPTType::T_I16: + if (iCount < PARAM_GPR_COUNT) + dp->val.i16 = (int16_t)gprData[iCount++]; + else + dp->val.i16 = *((int16_t*)ap++); + break; + + case nsXPTType::T_I32: + if (iCount < PARAM_GPR_COUNT) + dp->val.i32 = (int32_t)gprData[iCount++]; + else + dp->val.i32 = *((int32_t*)ap++); + break; + + case nsXPTType::T_I64: + if (iCount < PARAM_GPR_COUNT) + dp->val.i64 = (int64_t)gprData[iCount++]; + else + dp->val.i64 = *((int64_t*)ap++); + break; + + case nsXPTType::T_U8: + if (iCount < PARAM_GPR_COUNT) + dp->val.u8 = (uint8_t)gprData[iCount++]; + else + dp->val.u8 = *((uint8_t*)ap++); + break; + + case nsXPTType::T_U16: + if (iCount < PARAM_GPR_COUNT) + dp->val.u16 = (uint16_t)gprData[iCount++]; + else + dp->val.u16 = *((uint16_t*)ap++); + break; + + case nsXPTType::T_U32: + if (iCount < PARAM_GPR_COUNT) + dp->val.u32 = (uint32_t)gprData[iCount++]; + else + dp->val.u32 = *((uint32_t*)ap++); + break; + + case nsXPTType::T_U64: + if (iCount < PARAM_GPR_COUNT) + dp->val.u64 = (uint64_t)gprData[iCount++]; + else + dp->val.u64 = *((uint64_t*)ap++); + break; + + case nsXPTType::T_FLOAT: + if (iCount < PARAM_FPR_COUNT) + // The value in xmm register is already prepared to + // be retrieved as a float. Therefore, we pass the + // value verbatim, as a double without conversion. + dp->val.d = (double)fprData[iCount++]; + else + dp->val.f = *((float*)ap++); + break; + + case nsXPTType::T_DOUBLE: + if (iCount < PARAM_FPR_COUNT) + dp->val.d = (double)fprData[iCount++]; + else + dp->val.d = *((double*)ap++); + break; + + case nsXPTType::T_BOOL: + if (iCount < PARAM_GPR_COUNT) + // We need cast to uint8_t to remove garbage on upper 56-bit + // at first. + dp->val.b = (bool)(uint8_t)gprData[iCount++]; + else + dp->val.b = *((bool*)ap++); + break; + + case nsXPTType::T_CHAR: + if (iCount < PARAM_GPR_COUNT) + dp->val.c = (char)gprData[iCount++]; + else + dp->val.c = *((char*)ap++); + break; + + case nsXPTType::T_WCHAR: + if (iCount < PARAM_GPR_COUNT) + dp->val.wc = (wchar_t)gprData[iCount++]; + else + dp->val.wc = *((wchar_t*)ap++); + break; + + default: + NS_ERROR("bad type"); + break; + } + } + + result = self->mOuter->CallMethod((uint16_t)methodIndex, info, dispatchParams); + + if(dispatchParams != paramBuffer) + delete [] dispatchParams; + + return result; +} + +#define STUB_ENTRY(n) /* defined in the assembly file */ + +#define SENTINEL_ENTRY(n) \ +nsresult nsXPTCStubBase::Sentinel##n() \ +{ \ + NS_ERROR("nsXPTCStubBase::Sentinel called"); \ + return NS_ERROR_NOT_IMPLEMENTED; \ +} + +#include "xptcstubsdef.inc" + +void +xptc_dummy() +{ +} + diff --git a/xpcom/reflect/xptcall/md/win32/xptcstubs_x86_64_gnu.cpp b/xpcom/reflect/xptcall/md/win32/xptcstubs_x86_64_gnu.cpp new file mode 100644 index 000000000..2676334d2 --- /dev/null +++ b/xpcom/reflect/xptcall/md/win32/xptcstubs_x86_64_gnu.cpp @@ -0,0 +1,297 @@ +/* -*- 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 "xptcprivate.h" +#include "xptiprivate.h" + +/* + * This is for Windows 64 bit (x86_64) using GCC syntax + * Code was copied from the MSVC version. + */ + +#if !defined(_AMD64_) || !defined(__GNUC__) +# error xptcstubs_x86_64_gnu.cpp being used unexpectedly +#endif + +extern "C" nsresult __attribute__((__used__)) +PrepareAndDispatch(nsXPTCStubBase * self, uint32_t methodIndex, + uint64_t * args, uint64_t * gprData, double *fprData) +{ +#define PARAM_BUFFER_COUNT 16 +// +// "this" pointer is first parameter, so parameter count is 3. +// +#define PARAM_GPR_COUNT 3 +#define PARAM_FPR_COUNT 3 + + nsXPTCMiniVariant paramBuffer[PARAM_BUFFER_COUNT]; + nsXPTCMiniVariant* dispatchParams = nullptr; + const nsXPTMethodInfo* info = nullptr; + uint8_t paramCount; + uint8_t i; + nsresult result = NS_ERROR_FAILURE; + + NS_ASSERTION(self, "no self"); + + self->mEntry->GetMethodInfo(uint16_t(methodIndex), &info); + NS_ASSERTION(info, "no method info"); + + paramCount = info->GetParamCount(); + + // + // setup variant array pointer + // + + if(paramCount > PARAM_BUFFER_COUNT) + dispatchParams = new nsXPTCMiniVariant[paramCount]; + else + dispatchParams = paramBuffer; + + NS_ASSERTION(dispatchParams,"no place for params"); + + uint64_t* ap = args; + uint32_t iCount = 0; + + for(i = 0; i < paramCount; i++) + { + const nsXPTParamInfo& param = info->GetParam(i); + const nsXPTType& type = param.GetType(); + nsXPTCMiniVariant* dp = &dispatchParams[i]; + + if(param.IsOut() || !type.IsArithmetic()) + { + if (iCount < PARAM_GPR_COUNT) + dp->val.p = (void*)gprData[iCount++]; + else + dp->val.p = (void*)*ap++; + + continue; + } + // else + switch(type) + { + case nsXPTType::T_I8: + if (iCount < PARAM_GPR_COUNT) + dp->val.i8 = (int8_t)gprData[iCount++]; + else + dp->val.i8 = *((int8_t*)ap++); + break; + + case nsXPTType::T_I16: + if (iCount < PARAM_GPR_COUNT) + dp->val.i16 = (int16_t)gprData[iCount++]; + else + dp->val.i16 = *((int16_t*)ap++); + break; + + case nsXPTType::T_I32: + if (iCount < PARAM_GPR_COUNT) + dp->val.i32 = (int32_t)gprData[iCount++]; + else + dp->val.i32 = *((int32_t*)ap++); + break; + + case nsXPTType::T_I64: + if (iCount < PARAM_GPR_COUNT) + dp->val.i64 = (int64_t)gprData[iCount++]; + else + dp->val.i64 = *((int64_t*)ap++); + break; + + case nsXPTType::T_U8: + if (iCount < PARAM_GPR_COUNT) + dp->val.u8 = (uint8_t)gprData[iCount++]; + else + dp->val.u8 = *((uint8_t*)ap++); + break; + + case nsXPTType::T_U16: + if (iCount < PARAM_GPR_COUNT) + dp->val.u16 = (uint16_t)gprData[iCount++]; + else + dp->val.u16 = *((uint16_t*)ap++); + break; + + case nsXPTType::T_U32: + if (iCount < PARAM_GPR_COUNT) + dp->val.u32 = (uint32_t)gprData[iCount++]; + else + dp->val.u32 = *((uint32_t*)ap++); + break; + + case nsXPTType::T_U64: + if (iCount < PARAM_GPR_COUNT) + dp->val.u64 = (uint64_t)gprData[iCount++]; + else + dp->val.u64 = *((uint64_t*)ap++); + break; + + case nsXPTType::T_FLOAT: + if (iCount < PARAM_FPR_COUNT) + dp->val.f = (float)fprData[iCount++]; + else + dp->val.f = *((float*)ap++); + break; + + case nsXPTType::T_DOUBLE: + if (iCount < PARAM_FPR_COUNT) + dp->val.d = (double)fprData[iCount++]; + else + dp->val.d = *((double*)ap++); + break; + + case nsXPTType::T_BOOL: + if (iCount < PARAM_GPR_COUNT) + dp->val.b = (bool)gprData[iCount++]; + else + dp->val.b = *((bool*)ap++); + break; + + case nsXPTType::T_CHAR: + if (iCount < PARAM_GPR_COUNT) + dp->val.c = (char)gprData[iCount++]; + else + dp->val.c = *((char*)ap++); + break; + + case nsXPTType::T_WCHAR: + if (iCount < PARAM_GPR_COUNT) + dp->val.wc = (wchar_t)gprData[iCount++]; + else + dp->val.wc = *((wchar_t*)ap++); + break; + + default: + NS_ASSERTION(0, "bad type"); + break; + } + } + + result = self->mOuter->CallMethod((uint16_t)methodIndex, info, dispatchParams); + + if(dispatchParams != paramBuffer) + delete [] dispatchParams; + + return result; +} + +__asm__ ( + ".text\n" + ".intel_syntax noprefix\n" /* switch to Intel syntax to look like the MSVC assembly */ + ".globl SharedStub\n" + ".def SharedStub ; .scl 3 ; .type 46 ; .endef \n" + "SharedStub:\n" + "sub rsp, 104\n" + + /* rcx is this pointer. Need backup for optimized build */ + + "mov qword ptr [rsp+88], rcx\n" + + /* + * fist 4 parameters (1st is "this" pointer) are passed in registers. + */ + + /* for floating value */ + + "movsd qword ptr [rsp+64], xmm1\n" + "movsd qword ptr [rsp+72], xmm2\n" + "movsd qword ptr [rsp+80], xmm3\n" + + /* for integer value */ + + "mov qword ptr [rsp+40], rdx\n" + "mov qword ptr [rsp+48], r8\n" + "mov qword ptr [rsp+56], r9\n" + + /* + * Call PrepareAndDispatch function + */ + + /* 5th parameter (floating parameters) of PrepareAndDispatch */ + + "lea r9, qword ptr [rsp+64]\n" + "mov qword ptr [rsp+32], r9\n" + + /* 4th parameter (normal parameters) of PrepareAndDispatch */ + + "lea r9, qword ptr [rsp+40]\n" + + /* 3rd parameter (pointer to args on stack) */ + + "lea r8, qword ptr [rsp+40+104]\n" + + /* 2nd parameter (vtbl_index) */ + + "mov rdx, r11\n" + + /* 1st parameter (this) (rcx) */ + + "call PrepareAndDispatch\n" + + /* restore rcx */ + + "mov rcx, qword ptr [rsp+88]\n" + + /* + * clean up register + */ + + "add rsp, 104+8\n" + + /* set return address */ + + "mov rdx, qword ptr [rsp-8]\n" + + /* simulate __stdcall return */ + + "jmp rdx\n" + + /* back to AT&T syntax */ + ".att_syntax\n" +); + +#define STUB_ENTRY(n) \ +asm(".intel_syntax noprefix\n" /* this is in intel syntax */ \ + ".text\n" \ + ".align 2\n" \ + ".if " #n " < 10\n" \ + ".globl _ZN14nsXPTCStubBase5Stub" #n "Ev@4\n" \ + ".def _ZN14nsXPTCStubBase5Stub" #n "Ev@4\n" \ + ".scl 3\n" /* private */ \ + ".type 46\n" /* function returning unsigned int */ \ + ".endef\n" \ + "_ZN14nsXPTCStubBase5Stub" #n "Ev@4:\n" \ + ".elseif " #n " < 100\n" \ + ".globl _ZN14nsXPTCStubBase6Stub" #n "Ev@4\n" \ + ".def _ZN14nsXPTCStubBase6Stub" #n "Ev@4\n" \ + ".scl 3\n" /* private */\ + ".type 46\n" /* function returning unsigned int */ \ + ".endef\n" \ + "_ZN14nsXPTCStubBase6Stub" #n "Ev@4:\n" \ + ".elseif " #n " < 1000\n" \ + ".globl _ZN14nsXPTCStubBase7Stub" #n "Ev@4\n" \ + ".def _ZN14nsXPTCStubBase7Stub" #n "Ev@4\n" \ + ".scl 3\n" /* private */ \ + ".type 46\n" /* function returning unsigned int */ \ + ".endef\n" \ + "_ZN14nsXPTCStubBase7Stub" #n "Ev@4:\n" \ + ".else\n" \ + ".err \"stub number " #n " >= 1000 not yet supported\"\n" \ + ".endif\n" \ + "mov r11, " #n "\n" \ + "jmp SharedStub\n" \ + ".att_syntax\n" /* back to AT&T syntax */ \ + ""); + +#define SENTINEL_ENTRY(n) \ +nsresult nsXPTCStubBase::Sentinel##n() \ +{ \ + NS_ASSERTION(0,"nsXPTCStubBase::Sentinel called"); \ + return NS_ERROR_NOT_IMPLEMENTED; \ +} + +#include "xptcstubsdef.inc" + diff --git a/xpcom/reflect/xptcall/moz.build b/xpcom/reflect/xptcall/moz.build new file mode 100644 index 000000000..fee7a3352 --- /dev/null +++ b/xpcom/reflect/xptcall/moz.build @@ -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/. + +DIRS += ['md'] + +SOURCES += [ + 'xptcall.cpp', +] + +EXPORTS += [ + 'xptcall.h', + 'xptcstubsdecl.inc', + 'xptcstubsdef.inc', +] + +LOCAL_INCLUDES += [ + '/xpcom/reflect/xptinfo', +] + +FINAL_LIBRARY = 'xul' diff --git a/xpcom/reflect/xptcall/porting.html b/xpcom/reflect/xptcall/porting.html new file mode 100644 index 000000000..0073c604e --- /dev/null +++ b/xpcom/reflect/xptcall/porting.html @@ -0,0 +1,216 @@ + + + + +xptcall Porting Guide + + +

xptcall Porting Guide

+ +

Overview

+ +
+ + 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). + +
+ +

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(nsIInterfaceInfo** 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 +. + +

+ +Getting these ports done is very important. Please let me know if you are interested in doing one. +I'll answer any questions as I get them. + +

+ + +Porting Status + + +

+ +
+Author: John Bandhauer <jband@netscape.com>
+Last modified: 31 May 1999 + + + diff --git a/xpcom/reflect/xptcall/status.html b/xpcom/reflect/xptcall/status.html new file mode 100644 index 000000000..65de20596 --- /dev/null +++ b/xpcom/reflect/xptcall/status.html @@ -0,0 +1,412 @@ + + + + +xptcall Porting Status + + +

xptcall Porting Status

+ +

What is this?

+ +This is a status page for the multiplatform porting of xptcall. +xptcall has a +FAQ +and a +Porting Guide. + +

+ +This is being maintained by John Bandhauer <jband@netscape.com>. +Feel free to email me with questions or to volunteer to contribute xptcall code for any platform. + +

+ +Mike Shaver <shaver@mozilla.org> +is the best contact regarding 'nix (Unix, Linux, Finux, etc.) ports of xptcall. + +

Status

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
StatusPlatformContributors and ? Possible ContributorsNotes
DoneWin32 x86Contributed code! +John Bandhauer <jband@netscape.com> + +win32
DoneLinux x86Contributed code! +John Bandhauer <jband@netscape.com>
+Contributed code! +Ulrich Drepper <drepper@cygnus.com> +
unix +
DoneFreeBSD and NetBSD x86Contributed code! +Christoph Toshok <toshok@hungry.com>,
+Contributed code! +John Bandhauer <jband@netscape.com>
unix (same as Linux 86 code)
DoneBSD/OS x86Contributed code! +Bert Driehuis <bert_driehuis@nl.compuware.com>unix (same as Linux 86 code) +Bert contributed patches that *should* do the right thing for all the unixish-x86 +versions of this code for GCC 2.7 or 2.8 vs. EGCS 1.1. He notes that the vtbl +scheme is different. He is hoping that others will help test the changes using +these two compilers on the various platforms where this same code is used. +Bert's details +
DoneMac PPCContributed code! + Roger Lawrence <rogerl@netscape.com>,
+Contributed code! +Patrick Beard <beard@netscape.com> +
mac (passing tests and checked in)
DoneSolaris SparcContributed code! +Roger Lawrence <rogerl@netscape.com>,
+Contributed code! +Chris McAfee <mcafee@netscape.com> +
unix This is checked in and working.
DoneSolaris Sparc v9 (64bit)Contributed code! +Stuart Parmenter <pavlov@netscape.com>,
+Contributed code! +Chris Seawood <cls@seawood.org> +
unix This is checked in and (pavlov claims!) working.
DoneOS/2Contributed code! +John Fairhurst <mjf35@cam.ac.uk>I never heard exactly who did what. But mozilla has been working on OS/2 +for a long time now. +
DoneOpenVMS AlphaContributed code! +Colin R. Blake <colin@theblakes.com> +Colin says this is passing all the tests on OpenVMS Alpha! +
DoneNT AlphaContributed code! +bob meader <bob@guiduck.com> +bob writes:
+Enclosed is xptcall for alpha/nt target.. +

+It is a variation of the IRIS port (only targeted for win32). +

+Notice the last 2 files (the change to mozilla\xpcom\build\makefile.win and +mozilla\xpcom\build) are needed because I was unable to figure how to do a +"declspecexport" from the assembler ASAXP ... if some knows how to do that then +those last 2 files won't be needed. +

+I have had someone look over this code at bridge.com (the entry point to +compaq/gem compiler team) and this code was given the OK. I consider it "done". +

+This code lives in the files where the name includes 'alpha' in the win32 directory.
+

DoneLinux ARMStarted +Stefan Hanske<sh990154@mail.uni-greifswald.de>
+? +Matthew Wilcox <willy@bofh.ai>
+Stefan's code is checked in and he says it is working. +
DoneLinux Sparc +Contributed code! +Anton Blanchard <anton@progsoc.uts.edu.au>, +
+Contributed code! +Roger Lawrence <rogerl@netscape.com>, +
+Maybe +Brandon Ehle <ehle.3@osu.edu> +
+Anton contributed patches to Roger's Sparc code. Anton says it works and passes the tests! +(24-Aug-1999) Brandon writes: I've finished testing XPTCALL Sparc Linux on 12 different Sparc machines and it checks out good. +
DoneLinux PPC +Contributed code! +Patrick Beard <beard@netscape.com>
+Contributed code! +Chris Waterson <waterson@netscape.com>
+Contributed code! +Franz Sirl <Franz.Sirl-kernel@lauterbach.com>
+? +Jason Y. Sproul <jsproul@condor.fddi.wesleyan.edu>
+ ? + Sean Chitwood <darkmane@w-link.net>
+waterson said: Mozilla runs on Linux/PPC +
DoneLinux Alpha +Contributed code! +Glen Nakamura <glen.nakamura@usa.net>
+Contributed code! +Dan Morril <morrildl@nycap.rr.com>
+
+Glen writes: +

+I am attaching a patch which contains my Linux Alpha xptcall code. +It passes TestXPTCInvoke and TestXPC on my machine which runs +kernel 2.2.7, glibc 2.1.1, and egcs 1.1.2. I have not tested it +with older GNU compilers such as gcc 2.8.x. From looking at the +Linux x86 code, I gather that the vtable layout is a little different +for those compilers and the code will need minor modifications +in order to work properly. +

+I am not sure how much of the code can be used for OpenVMS Alpha +and/or Digital UNIX. Currently the code is dependent on the g++ +name mangling convention and a few GNU extensions so I'm not sure +how useful it will be for the other systems. Hopefully the +comments in the code are detailed enough to help people attempting +a port. +

+

DoneSunOS x86 +Contributed code! +Arthur Jones <aljones@lbl.gov>
+? +Philip Pokorny <ppokorny@mindspring.com>
+
+The word I hear is that this is working and done +
DoneHP-UX +Contributed code! +Thomas Wang <wang@cup.hp.com>
+Contributed code! +Mike Gleeson <mgleeson1@netscape.com> +
I hear that this code is checked in and working. Though, there is some +doubt - see bug +#17997 +
DoneAIX PPCContributed code! +Jim Dunn <jdunn@netscape.com>Philip K. Warren writes:
+ +We have gone through several releases of AIX without any problems. +
DoneIrixContributed code! +Jason Heirtzler <jasonh@m7.engr.sgi.com>
+
Jason has declared this done. Jason is no longer working at SGI and will +not be maintaining this code. There is some doubt as to whether or not this is +working for everyone - see bug +#10061. +Mike Shaver <shaver@mozilla.org> +is the interim maintainer until someone more suitable can be found. +
DoneBeOS x86Contributed code! +Duncan Wilcox <duncan@be.com>
+
+unix (yet another reuse of the Linux 86 code!)
+Duncan says this is all working. He did the code for old cfront style 'this' adjustment for others to use too! +
HELP!BeOS PPC--
DoneCompaq Tru64 UNIX (Digital UNIX)Contributed code! +Steve Streeter <streeter@zk3.dec.com>
+
Code passes the tests and is checked in.
WorkingNeutrio x86Contributed code! +Jerry L. Kirk <Jerry.Kirk@Nexwarecorp.com>
+
+Patches for xptc*_unixish_x86.cpp checked in. Waiting for verification that this is really finished. +
InvestigatingSCO UW7 and OSR5 +Investigating +J. Kean Johnston <jkj@sco.com>
+Investigating +Evan Hunt <evanh@sco.com>
+
Recent (Feb-2001) email from jkj@sco.com suggests that work will be occuring soon.
WorksNetBSD/m68kContributed code! +Dave Huang <khym@bga.com>
+
+Dave's changes are in the tree. Waiting for verification that it is really finished.
Partially WorkingNetBSD/arm32Investigating +Mike Pumford <mpumford@black-star.demon.co.uk> +Mike writes:
+I have started porting to the platform based on the code for Linux ARM. The +InvokeByIndex code works correctly when used with TestXPTCInvoke. I am +currently working on making TestXPC function correctly. +

+I am doing the porting work with egcs-1.1.2 on NetBSD 1.4P (NetBSD-current +snapshot from a couple of days ago). +

DoneLinux ia64Contributed code! +HP
+Contributed code! +Ulrich Drepper <drepper@redhat.com> +
bug 40950 comment 15
HELP!All others!--
+ +

+ +Note: I've used the symbol (?) to +indicate people who have expressed an interest in possibly contributing code. +Just because these people are listed here does not mean that they have commited +themselves to do the work. If you would like to contribute then let me +know. Feel free to email these folks and offer to help or find out what's going +on. We're all in this together. + +

+ +


+Author: John Bandhauer <jband@netscape.com>
+Last modified: 3 February 2003 + + + diff --git a/xpcom/reflect/xptcall/xptcall.cpp b/xpcom/reflect/xptcall/xptcall.cpp new file mode 100644 index 000000000..1ea0ac34d --- /dev/null +++ b/xpcom/reflect/xptcall/xptcall.cpp @@ -0,0 +1,82 @@ +/* -*- 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/. */ + +/* entry point wrappers. */ + +#include "xptcprivate.h" +#include "xptiprivate.h" +#include "mozilla/XPTInterfaceInfoManager.h" +#include "nsPrintfCString.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; + + XPTInterfaceInfoManager *iim = + XPTInterfaceInfoManager::GetSingleton(); + if (NS_WARN_IF(!iim)) + return NS_ERROR_NOT_INITIALIZED; + + xptiInterfaceEntry *iie = iim->GetInterfaceEntryForIID(&aIID); + if (!iie || !iie->EnsureResolved() || iie->GetBuiltinClassFlag()) + return NS_ERROR_FAILURE; + + if (iie->GetHasNotXPCOMFlag()) { +#ifdef DEBUG + nsPrintfCString msg("XPTCall will not implement interface %s because of [notxpcom] members.", iie->GetTheName()); + NS_WARNING(msg.get()); +#endif + 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 000000000..1f2367b5d --- /dev/null +++ b/xpcom/reflect/xptcall/xptcall.h @@ -0,0 +1,193 @@ +/* -*- 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/. */ + +/* Public declarations for xptcall. */ + +#ifndef xptcall_h___ +#define xptcall_h___ + +#include "nscore.h" +#include "nsISupports.h" +#include "xpt_struct.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 + { + 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; + + // Types below here are unknown to the assembly implementations, and + // therefore _must_ be passed with indirect semantics. We put them in + // the union here for type safety, so that we can avoid void* tricks. + JS::Value j; + } val; +}; + +struct nsXPTCVariant : public nsXPTCMiniVariant +{ +// No ctors or dtors so that we can use arrays of these on the stack +// with no penalty. + + // inherits 'val' here + void* ptr; + nsXPTType type; + uint8_t flags; + + enum + { + // + // Bitflag definitions + // + + // Indicates that ptr (above, and distinct from val.p) is the value that + // should be passed on the stack. + // + // In theory, ptr could point anywhere. But in practice it always points + // to &val. So this flag is used to pass 'val' by reference, letting us + // avoid the extra allocation we would incur if we were to use val.p. + // + // Various parts of XPConnect assume that ptr==&val, so we enforce it + // explicitly with SetIndirect() and IsIndirect(). + // + // Since ptr always points to &val, the semantics of this flag are kind of + // dumb, since the ptr field is unnecessary. But changing them would + // require changing dozens of assembly files, so they're likely to stay + // the way they are. + PTR_IS_DATA = 0x1, + + // Indicates that the value we hold requires some sort of cleanup (memory + // deallocation, interface release, JS::Value unrooting, etc). The precise + // cleanup that is performed depends on the 'type' field above. + // If the value is an array, this flag specifies whether the elements + // within the array require cleanup (we always clean up the array itself, + // so this flag would be redundant for that purpose). + VAL_NEEDS_CLEANUP = 0x2 + }; + + void ClearFlags() {flags = 0;} + void SetIndirect() {ptr = &val; flags |= PTR_IS_DATA;} + void SetValNeedsCleanup() {flags |= VAL_NEEDS_CLEANUP;} + + bool IsIndirect() const {return 0 != (flags & PTR_IS_DATA);} + bool DoesValNeedCleanup() const {return 0 != (flags & VAL_NEEDS_CLEANUP);} + + // Internal use only. Use IsIndirect() instead. + bool IsPtrData() const {return 0 != (flags & PTR_IS_DATA);} + + void Init(const nsXPTCMiniVariant& mv, const nsXPTType& t, uint8_t f) + { + type = t; + flags = f; + + if(f & PTR_IS_DATA) + { + ptr = mv.val.p; + val.p = nullptr; + } + else + { + ptr = nullptr; + val.p = nullptr; // make sure 'val.p' is always initialized + switch(t.TagPart()) { + case nsXPTType::T_I8: val.i8 = mv.val.i8; break; + case nsXPTType::T_I16: val.i16 = mv.val.i16; break; + case nsXPTType::T_I32: val.i32 = mv.val.i32; break; + case nsXPTType::T_I64: val.i64 = mv.val.i64; break; + case nsXPTType::T_U8: val.u8 = mv.val.u8; break; + case nsXPTType::T_U16: val.u16 = mv.val.u16; break; + case nsXPTType::T_U32: val.u32 = mv.val.u32; break; + case nsXPTType::T_U64: val.u64 = mv.val.u64; break; + case nsXPTType::T_FLOAT: val.f = mv.val.f; break; + case nsXPTType::T_DOUBLE: val.d = mv.val.d; break; + case nsXPTType::T_BOOL: val.b = mv.val.b; break; + case nsXPTType::T_CHAR: val.c = mv.val.c; break; + case nsXPTType::T_WCHAR: val.wc = mv.val.wc; break; + case nsXPTType::T_VOID: /* fall through */ + case nsXPTType::T_IID: /* fall through */ + case nsXPTType::T_DOMSTRING: /* fall through */ + case nsXPTType::T_CHAR_STR: /* fall through */ + case nsXPTType::T_WCHAR_STR: /* fall through */ + case nsXPTType::T_INTERFACE: /* fall through */ + case nsXPTType::T_INTERFACE_IS: /* fall through */ + case nsXPTType::T_ARRAY: /* fall through */ + case nsXPTType::T_PSTRING_SIZE_IS: /* fall through */ + case nsXPTType::T_PWSTRING_SIZE_IS: /* fall through */ + case nsXPTType::T_UTF8STRING: /* fall through */ + case nsXPTType::T_CSTRING: /* fall through */ + default: val.p = mv.val.p; break; + } + } + } +}; + +class nsIXPTCProxy : public nsISupports +{ +public: + NS_IMETHOD CallMethod(uint16_t aMethodIndex, + const XPTMethodDescriptor *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); + +XPCOM_API(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 000000000..7805792e7 --- /dev/null +++ b/xpcom/reflect/xptcall/xptcprivate.h @@ -0,0 +1,67 @@ +/* -*- 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/. */ + +/* All the xptcall private declarations - only include locally. */ + +#ifndef xptcprivate_h___ +#define xptcprivate_h___ + +#include "xptcall.h" +#include "nsAutoPtr.h" +#include "mozilla/Attributes.h" + +class xptiInterfaceEntry; + +#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, xptiInterfaceEntry *aEntry) + : mOuter(aOuter), mEntry(aEntry) { MOZ_COUNT_CTOR(nsXPTCStubBase); } + + nsIXPTCProxy* mOuter; + xptiInterfaceEntry* mEntry; + + ~nsXPTCStubBase() { MOZ_COUNT_DTOR(nsXPTCStubBase); } +}; + +#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 000000000..91d35fe32 --- /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 000000000..2df455406 --- /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/ShimInterfaceInfo.cpp b/xpcom/reflect/xptinfo/ShimInterfaceInfo.cpp new file mode 100644 index 000000000..14e719f6c --- /dev/null +++ b/xpcom/reflect/xptinfo/ShimInterfaceInfo.cpp @@ -0,0 +1,700 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sw=4 et tw=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/. */ + +#include "ShimInterfaceInfo.h" + +#include "nsIBrowserBoxObject.h" +#include "nsIContainerBoxObject.h" +#include "nsIDOMAnimationEvent.h" +#include "nsIDOMAttr.h" +#include "nsIDOMBeforeUnloadEvent.h" +#include "nsIDOMCanvasRenderingContext2D.h" +#include "nsIDOMCDATASection.h" +#include "nsIDOMCharacterData.h" +#include "nsIDOMClientRect.h" +#include "nsIDOMClientRectList.h" +#include "nsIDOMClipboardEvent.h" +#include "nsIDOMCommandEvent.h" +#include "nsIDOMComment.h" +#include "nsIDOMCSSPrimitiveValue.h" +#include "nsIDOMCSSStyleDeclaration.h" +#include "nsIDOMCSSStyleSheet.h" +#include "nsIDOMCSSValue.h" +#include "nsIDOMCSSValueList.h" +#include "nsIDOMCustomEvent.h" +#include "nsIDOMDataContainerEvent.h" +#ifdef MOZ_WEBRTC +#include "nsIDOMDataChannel.h" +#endif +#include "nsIDOMDataTransfer.h" +#include "nsIDOMDOMCursor.h" +#include "nsIDOMDOMException.h" +#include "nsIDOMDOMRequest.h" +#include "nsIDOMDocument.h" +#include "nsIDOMDocumentFragment.h" +#include "nsIDOMDocumentType.h" +#include "nsIDOMDocumentXBL.h" +#include "nsIDOMDragEvent.h" +#include "nsIDOMElement.h" +#include "nsIDOMEvent.h" +#include "nsIDOMEventTarget.h" +#include "nsIDOMFileList.h" +#include "nsIDOMFocusEvent.h" +#include "nsIDOMFormData.h" +#include "nsIDOMGeoPositionError.h" +#include "nsIDOMHistory.h" +#include "nsIDOMHTMLAnchorElement.h" +#include "nsIDOMHTMLAppletElement.h" +#include "nsIDOMHTMLAreaElement.h" +#include "nsIDOMHTMLBaseElement.h" +#include "nsIDOMHTMLBodyElement.h" +#include "nsIDOMHTMLButtonElement.h" +#include "nsIDOMHTMLCanvasElement.h" +#include "nsIDOMHTMLCollection.h" +#include "nsIDOMHTMLDirectoryElement.h" +#include "nsIDOMHTMLDocument.h" +#include "nsIDOMHTMLElement.h" +#include "nsIDOMHTMLEmbedElement.h" +#include "nsIDOMHTMLFieldSetElement.h" +#include "nsIDOMHTMLFormElement.h" +#include "nsIDOMHTMLFrameElement.h" +#include "nsIDOMHTMLFrameSetElement.h" +#include "nsIDOMHTMLHRElement.h" +#include "nsIDOMHTMLHeadElement.h" +#include "nsIDOMHTMLHtmlElement.h" +#include "nsIDOMHTMLIFrameElement.h" +#include "nsIDOMHTMLImageElement.h" +#include "nsIDOMHTMLInputElement.h" +#include "nsIDOMHTMLLIElement.h" +#include "nsIDOMHTMLLabelElement.h" +#include "nsIDOMHTMLLinkElement.h" +#include "nsIDOMHTMLMapElement.h" +#include "nsIDOMHTMLMediaElement.h" +#include "nsIDOMHTMLMenuElement.h" +#include "nsIDOMHTMLMenuItemElement.h" +#include "nsIDOMHTMLMetaElement.h" +#include "nsIDOMHTMLOListElement.h" +#include "nsIDOMHTMLObjectElement.h" +#include "nsIDOMHTMLOptGroupElement.h" +#include "nsIDOMHTMLOptionElement.h" +#include "nsIDOMHTMLOptionsCollection.h" +#include "nsIDOMHTMLParagraphElement.h" +#include "nsIDOMHTMLPreElement.h" +#include "nsIDOMHTMLQuoteElement.h" +#include "nsIDOMHTMLScriptElement.h" +#include "nsIDOMHTMLSelectElement.h" +#include "nsIDOMHTMLSourceElement.h" +#include "nsIDOMHTMLStyleElement.h" +#include "nsIDOMHTMLTableCellElement.h" +#include "nsIDOMHTMLTextAreaElement.h" +#include "nsIDOMHTMLUListElement.h" +#include "nsIDOMKeyEvent.h" +#include "nsIDOMMediaList.h" +#include "nsIDOMMouseEvent.h" +#include "nsIDOMMouseScrollEvent.h" +#include "nsIDOMMutationEvent.h" +#include "nsIDOMMozNamedAttrMap.h" +#include "nsIDOMNode.h" +#include "nsIDOMNodeIterator.h" +#include "nsIDOMNotifyPaintEvent.h" +#include "nsIDOMNSEvent.h" +#include "nsIDOMOfflineResourceList.h" +#include "nsIDOMPaintRequest.h" +#include "nsIDOMParser.h" +#include "nsIDOMProcessingInstruction.h" +#include "nsIDOMRange.h" +#include "nsIDOMRect.h" +#include "nsIDOMScreen.h" +#include "nsIDOMScrollAreaEvent.h" +#include "nsIDOMSerializer.h" +#include "nsIDOMSimpleGestureEvent.h" +#include "nsIDOMStyleSheet.h" +#include "nsIDOMStyleSheetList.h" +#include "nsIDOMSVGElement.h" +#include "nsIDOMSVGLength.h" +#include "nsIDOMText.h" +#include "nsIDOMTimeEvent.h" +#include "nsIDOMTimeRanges.h" +#include "nsIDOMTransitionEvent.h" +#include "nsIDOMTreeWalker.h" +#include "nsIDOMUIEvent.h" +#include "nsIDOMValidityState.h" +#include "nsIDOMWheelEvent.h" +#include "nsIDOMXMLDocument.h" +#include "nsIDOMXPathEvaluator.h" +#include "nsIDOMXPathResult.h" +#include "nsIDOMXULCommandEvent.h" +#include "nsIDOMXULDocument.h" +#include "nsIDOMXULElement.h" +#include "nsIListBoxObject.h" +#include "nsIMenuBoxObject.h" +#include "nsIScrollBoxObject.h" +#include "nsISelection.h" +#include "nsITreeBoxObject.h" +#include "nsIXMLHttpRequest.h" + +#include "mozilla/dom/AnimationEventBinding.h" +#include "mozilla/dom/AttrBinding.h" +#include "mozilla/dom/BeforeUnloadEventBinding.h" +#include "mozilla/dom/CanvasRenderingContext2DBinding.h" +#include "mozilla/dom/CDATASectionBinding.h" +#include "mozilla/dom/CharacterDataBinding.h" +#include "mozilla/dom/DOMRectBinding.h" +#include "mozilla/dom/DOMRectListBinding.h" +#include "mozilla/dom/ClipboardEventBinding.h" +#include "mozilla/dom/CommandEventBinding.h" +#include "mozilla/dom/CommentBinding.h" +#include "mozilla/dom/ContainerBoxObjectBinding.h" +#include "mozilla/dom/CSSPrimitiveValueBinding.h" +#include "mozilla/dom/CSSStyleDeclarationBinding.h" +#include "mozilla/dom/CSSStyleSheetBinding.h" +#include "mozilla/dom/CSSValueBinding.h" +#include "mozilla/dom/CSSValueListBinding.h" +#include "mozilla/dom/CustomEventBinding.h" +#ifdef MOZ_WEBRTC +#include "mozilla/dom/DataChannelBinding.h" +#endif +#include "mozilla/dom/DataContainerEventBinding.h" +#include "mozilla/dom/DataTransferBinding.h" +#include "mozilla/dom/DOMCursorBinding.h" +#include "mozilla/dom/DOMExceptionBinding.h" +#include "mozilla/dom/DOMParserBinding.h" +#include "mozilla/dom/DOMRequestBinding.h" +#include "mozilla/dom/DocumentBinding.h" +#include "mozilla/dom/DocumentFragmentBinding.h" +#include "mozilla/dom/DocumentTypeBinding.h" +#include "mozilla/dom/DocumentBinding.h" +#include "mozilla/dom/DragEventBinding.h" +#include "mozilla/dom/ElementBinding.h" +#include "mozilla/dom/EventBinding.h" +#include "mozilla/dom/EventTargetBinding.h" +#include "mozilla/dom/FileListBinding.h" +#include "mozilla/dom/FocusEventBinding.h" +#include "mozilla/dom/FormDataBinding.h" +#include "mozilla/dom/HistoryBinding.h" +#include "mozilla/dom/HTMLAnchorElementBinding.h" +#include "mozilla/dom/HTMLAppletElementBinding.h" +#include "mozilla/dom/HTMLAreaElementBinding.h" +#include "mozilla/dom/HTMLBaseElementBinding.h" +#include "mozilla/dom/HTMLBodyElementBinding.h" +#include "mozilla/dom/HTMLButtonElementBinding.h" +#include "mozilla/dom/HTMLCanvasElementBinding.h" +#include "mozilla/dom/HTMLCollectionBinding.h" +#include "mozilla/dom/HTMLDirectoryElementBinding.h" +#include "mozilla/dom/HTMLDocumentBinding.h" +#include "mozilla/dom/HTMLElementBinding.h" +#include "mozilla/dom/HTMLEmbedElementBinding.h" +#include "mozilla/dom/HTMLFieldSetElementBinding.h" +#include "mozilla/dom/HTMLFormElementBinding.h" +#include "mozilla/dom/HTMLFrameElementBinding.h" +#include "mozilla/dom/HTMLFrameSetElementBinding.h" +#include "mozilla/dom/HTMLHRElementBinding.h" +#include "mozilla/dom/HTMLHeadElementBinding.h" +#include "mozilla/dom/HTMLHtmlElementBinding.h" +#include "mozilla/dom/HTMLIFrameElementBinding.h" +#include "mozilla/dom/HTMLImageElementBinding.h" +#include "mozilla/dom/HTMLInputElementBinding.h" +#include "mozilla/dom/HTMLLIElementBinding.h" +#include "mozilla/dom/HTMLLabelElementBinding.h" +#include "mozilla/dom/HTMLLinkElementBinding.h" +#include "mozilla/dom/HTMLMapElementBinding.h" +#include "mozilla/dom/HTMLMediaElementBinding.h" +#include "mozilla/dom/HTMLMenuElementBinding.h" +#include "mozilla/dom/HTMLMenuItemElementBinding.h" +#include "mozilla/dom/HTMLMetaElementBinding.h" +#include "mozilla/dom/HTMLOListElementBinding.h" +#include "mozilla/dom/HTMLObjectElementBinding.h" +#include "mozilla/dom/HTMLOptGroupElementBinding.h" +#include "mozilla/dom/HTMLOptionElementBinding.h" +#include "mozilla/dom/HTMLOptionsCollectionBinding.h" +#include "mozilla/dom/HTMLParagraphElementBinding.h" +#include "mozilla/dom/HTMLPreElementBinding.h" +#include "mozilla/dom/HTMLQuoteElementBinding.h" +#include "mozilla/dom/HTMLScriptElementBinding.h" +#include "mozilla/dom/HTMLSelectElementBinding.h" +#include "mozilla/dom/HTMLSourceElementBinding.h" +#include "mozilla/dom/HTMLStyleElementBinding.h" +#include "mozilla/dom/HTMLTableCellElementBinding.h" +#include "mozilla/dom/HTMLTextAreaElementBinding.h" +#include "mozilla/dom/HTMLUListElementBinding.h" +#include "mozilla/dom/KeyEventBinding.h" +#include "mozilla/dom/ListBoxObjectBinding.h" +#include "mozilla/dom/MediaListBinding.h" +#include "mozilla/dom/MessageEventBinding.h" +#include "mozilla/dom/MenuBoxObjectBinding.h" +#include "mozilla/dom/MouseEventBinding.h" +#include "mozilla/dom/MouseScrollEventBinding.h" +#include "mozilla/dom/MutationEventBinding.h" +#include "mozilla/dom/NamedNodeMapBinding.h" +#include "mozilla/dom/NodeIteratorBinding.h" +#include "mozilla/dom/NodeBinding.h" +#include "mozilla/dom/NotifyPaintEventBinding.h" +#include "mozilla/dom/EventBinding.h" +#include "mozilla/dom/OfflineResourceListBinding.h" +#include "mozilla/dom/PaintRequestBinding.h" +#include "mozilla/dom/PositionErrorBinding.h" +#include "mozilla/dom/ProcessingInstructionBinding.h" +#include "mozilla/dom/RangeBinding.h" +#include "mozilla/dom/RectBinding.h" +#include "mozilla/dom/ScreenBinding.h" +#include "mozilla/dom/ScrollBoxObjectBinding.h" +#include "mozilla/dom/SelectionBinding.h" +#include "mozilla/dom/ScrollAreaEventBinding.h" +#include "mozilla/dom/SimpleGestureEventBinding.h" +#include "mozilla/dom/StorageEventBinding.h" +#include "mozilla/dom/StyleSheetBinding.h" +#include "mozilla/dom/StyleSheetListBinding.h" +#include "mozilla/dom/SVGElementBinding.h" +#include "mozilla/dom/SVGLengthBinding.h" +#include "mozilla/dom/TextBinding.h" +#include "mozilla/dom/TimeEventBinding.h" +#include "mozilla/dom/TimeRangesBinding.h" +#include "mozilla/dom/TransitionEventBinding.h" +#include "mozilla/dom/TreeBoxObjectBinding.h" +#include "mozilla/dom/TreeWalkerBinding.h" +#include "mozilla/dom/UIEventBinding.h" +#include "mozilla/dom/ValidityStateBinding.h" +#include "mozilla/dom/WheelEventBinding.h" +#include "mozilla/dom/XMLDocumentBinding.h" +#include "mozilla/dom/XMLHttpRequestEventTargetBinding.h" +#include "mozilla/dom/XMLHttpRequestUploadBinding.h" +#include "mozilla/dom/XMLSerializerBinding.h" +#include "mozilla/dom/XPathEvaluatorBinding.h" +#include "mozilla/dom/XPathResultBinding.h" +#include "mozilla/dom/XULCommandEventBinding.h" +#include "mozilla/dom/XULDocumentBinding.h" +#include "mozilla/dom/XULElementBinding.h" + +using namespace mozilla; + +struct ComponentsInterfaceShimEntry { + constexpr + ComponentsInterfaceShimEntry(const char* aName, const nsIID& aIID, + const dom::NativePropertyHooks* aNativePropHooks) + : geckoName(aName), iid(aIID), nativePropHooks(aNativePropHooks) {} + + const char *geckoName; + const nsIID& iid; + const dom::NativePropertyHooks* nativePropHooks; +}; + +#define DEFINE_SHIM_WITH_CUSTOM_INTERFACE(geckoName, domName) \ + { #geckoName, NS_GET_IID(geckoName), \ + mozilla::dom::domName ## Binding::sNativePropertyHooks } +#define DEFINE_SHIM(name) \ + DEFINE_SHIM_WITH_CUSTOM_INTERFACE(nsIDOM ## name, name) + +/** + * These shim entries allow us to make old XPIDL interfaces implementing DOM + * APIs as non-scriptable in order to save some runtime memory on Firefox OS, + * without breaking the entries under Components.interfaces which might both + * be used by our code and add-ons. Specifically, the shim entries provide + * the following: + * + * * Components.interfaces.nsIFoo entries. These entries basically work + * almost exactly as the usual ones that you would get through the + * XPIDL machinery. Specifically, they have the right name, they reflect + * the right IID, and they will work properly when passed to QueryInterface. + * + * * Components.interfaces.nsIFoo.CONSTANT values. These entries will have + * the right name and the right value for most integer types. Note that + * support for non-numerical constants is untested and will probably not + * work out of the box. + * + * FAQ: + * * When should I add an entry to the list here? + * Only if you're making an XPIDL interfaces which has a corresponding + * WebIDL interface non-scriptable. + * * When should I remove an entry from this list? + * If you are completely removing an XPIDL interface from the code base. If + * you forget to do so, the compiler will remind you. + * * How should I add an entry to the list here? + * First, make sure that the XPIDL interface in question is non-scriptable + * and also has a corresponding WebIDL interface. Then, add two include + * entries above, one for the XPIDL interface and one for the WebIDL + * interface, and add a shim entry below. If the name of the XPIDL + * interface only has an "nsIDOM" prefix prepended to the WebIDL name, you + * can use the DEFINE_SHIM macro and pass in the name of the WebIDL + * interface. Otherwise, use DEFINE_SHIM_WITH_CUSTOM_INTERFACE. + */ + +const ComponentsInterfaceShimEntry kComponentsInterfaceShimMap[] = +{ + DEFINE_SHIM(AnimationEvent), + DEFINE_SHIM(Attr), + DEFINE_SHIM(BeforeUnloadEvent), + DEFINE_SHIM_WITH_CUSTOM_INTERFACE(nsIBrowserBoxObject, ContainerBoxObject), + DEFINE_SHIM(CanvasRenderingContext2D), + DEFINE_SHIM(CDATASection), + DEFINE_SHIM(CharacterData), + DEFINE_SHIM_WITH_CUSTOM_INTERFACE(nsIDOMClientRect, DOMRectReadOnly), + DEFINE_SHIM_WITH_CUSTOM_INTERFACE(nsIDOMClientRectList, DOMRectList), + DEFINE_SHIM(ClipboardEvent), + DEFINE_SHIM(CommandEvent), + DEFINE_SHIM(Comment), + DEFINE_SHIM_WITH_CUSTOM_INTERFACE(nsIContainerBoxObject, ContainerBoxObject), + DEFINE_SHIM(CSSPrimitiveValue), + DEFINE_SHIM(CSSStyleDeclaration), + DEFINE_SHIM(CSSStyleSheet), + DEFINE_SHIM(CSSValue), + DEFINE_SHIM(CSSValueList), + DEFINE_SHIM(CustomEvent), +#ifdef MOZ_WEBRTC + DEFINE_SHIM(DataChannel), +#endif + DEFINE_SHIM(DataContainerEvent), + DEFINE_SHIM(DataTransfer), + DEFINE_SHIM(DOMCursor), + DEFINE_SHIM(DOMException), + DEFINE_SHIM(DOMRequest), + DEFINE_SHIM(Document), + DEFINE_SHIM(DocumentFragment), + DEFINE_SHIM(DocumentType), + DEFINE_SHIM_WITH_CUSTOM_INTERFACE(nsIDOMDocumentXBL, Document), + DEFINE_SHIM(DragEvent), + DEFINE_SHIM(Element), + DEFINE_SHIM(Event), + DEFINE_SHIM(EventTarget), + DEFINE_SHIM(FileList), + DEFINE_SHIM(FocusEvent), + DEFINE_SHIM(FormData), + DEFINE_SHIM_WITH_CUSTOM_INTERFACE(nsIDOMGeoPositionError, PositionError), + DEFINE_SHIM(History), + DEFINE_SHIM(HTMLAnchorElement), + DEFINE_SHIM(HTMLAppletElement), + DEFINE_SHIM(HTMLAreaElement), + DEFINE_SHIM(HTMLBaseElement), + DEFINE_SHIM(HTMLBodyElement), + DEFINE_SHIM(HTMLButtonElement), + DEFINE_SHIM(HTMLCanvasElement), + DEFINE_SHIM(HTMLCollection), + DEFINE_SHIM(HTMLDirectoryElement), + DEFINE_SHIM(HTMLDocument), + DEFINE_SHIM(HTMLElement), + DEFINE_SHIM(HTMLEmbedElement), + DEFINE_SHIM(HTMLFieldSetElement), + DEFINE_SHIM(HTMLFormElement), + DEFINE_SHIM(HTMLFrameElement), + DEFINE_SHIM(HTMLFrameSetElement), + DEFINE_SHIM(HTMLHRElement), + DEFINE_SHIM(HTMLHeadElement), + DEFINE_SHIM(HTMLHtmlElement), + DEFINE_SHIM(HTMLIFrameElement), + DEFINE_SHIM(HTMLImageElement), + DEFINE_SHIM(HTMLInputElement), + DEFINE_SHIM(HTMLLIElement), + DEFINE_SHIM(HTMLLabelElement), + DEFINE_SHIM(HTMLLinkElement), + DEFINE_SHIM(HTMLMapElement), + DEFINE_SHIM(HTMLMediaElement), + DEFINE_SHIM(HTMLMenuElement), + DEFINE_SHIM(HTMLMenuItemElement), + DEFINE_SHIM(HTMLMetaElement), + DEFINE_SHIM(HTMLOListElement), + DEFINE_SHIM(HTMLObjectElement), + DEFINE_SHIM(HTMLOptGroupElement), + DEFINE_SHIM(HTMLOptionElement), + DEFINE_SHIM(HTMLOptionsCollection), + DEFINE_SHIM(HTMLParagraphElement), + DEFINE_SHIM(HTMLPreElement), + DEFINE_SHIM(HTMLQuoteElement), + DEFINE_SHIM(HTMLScriptElement), + DEFINE_SHIM(HTMLSelectElement), + DEFINE_SHIM(HTMLSourceElement), + DEFINE_SHIM(HTMLStyleElement), + DEFINE_SHIM(HTMLTableCellElement), + DEFINE_SHIM(HTMLTextAreaElement), + DEFINE_SHIM(HTMLUListElement), + DEFINE_SHIM(KeyEvent), + DEFINE_SHIM_WITH_CUSTOM_INTERFACE(nsIListBoxObject, ListBoxObject), + DEFINE_SHIM(MediaList), + DEFINE_SHIM_WITH_CUSTOM_INTERFACE(nsIMenuBoxObject, MenuBoxObject), + DEFINE_SHIM(MouseEvent), + DEFINE_SHIM(MouseScrollEvent), + DEFINE_SHIM(MutationEvent), + DEFINE_SHIM_WITH_CUSTOM_INTERFACE(nsIDOMMozNamedAttrMap, NamedNodeMap), + DEFINE_SHIM(NodeIterator), + DEFINE_SHIM(Node), + DEFINE_SHIM(NotifyPaintEvent), + DEFINE_SHIM_WITH_CUSTOM_INTERFACE(nsIDOMNSEvent, Event), + DEFINE_SHIM(OfflineResourceList), + DEFINE_SHIM(PaintRequest), + DEFINE_SHIM_WITH_CUSTOM_INTERFACE(nsIDOMParser, DOMParser), + DEFINE_SHIM(ProcessingInstruction), + DEFINE_SHIM(Range), + DEFINE_SHIM(Rect), + DEFINE_SHIM(Screen), + DEFINE_SHIM(ScrollAreaEvent), + DEFINE_SHIM_WITH_CUSTOM_INTERFACE(nsIScrollBoxObject, ScrollBoxObject), + DEFINE_SHIM_WITH_CUSTOM_INTERFACE(nsIDOMSerializer, XMLSerializer), + DEFINE_SHIM(SimpleGestureEvent), + DEFINE_SHIM(StyleSheet), + DEFINE_SHIM(StyleSheetList), + DEFINE_SHIM(SVGElement), + DEFINE_SHIM(SVGLength), + DEFINE_SHIM(Text), + DEFINE_SHIM(TimeEvent), + DEFINE_SHIM(TimeRanges), + DEFINE_SHIM(TransitionEvent), + DEFINE_SHIM_WITH_CUSTOM_INTERFACE(nsITreeBoxObject, TreeBoxObject), + DEFINE_SHIM(TreeWalker), + DEFINE_SHIM(UIEvent), + DEFINE_SHIM(ValidityState), + DEFINE_SHIM(WheelEvent), + DEFINE_SHIM(XMLDocument), + DEFINE_SHIM_WITH_CUSTOM_INTERFACE(nsIXMLHttpRequestEventTarget, XMLHttpRequestEventTarget), + DEFINE_SHIM_WITH_CUSTOM_INTERFACE(nsIXMLHttpRequestUpload, XMLHttpRequestUpload), + DEFINE_SHIM(XPathEvaluator), + DEFINE_SHIM(XPathResult), + DEFINE_SHIM(XULCommandEvent), + DEFINE_SHIM(XULDocument), + DEFINE_SHIM(XULElement), + DEFINE_SHIM_WITH_CUSTOM_INTERFACE(nsISelection, Selection), +}; + +#undef DEFINE_SHIM +#undef DEFINE_SHIM_WITH_CUSTOM_INTERFACE + +NS_IMPL_ISUPPORTS(ShimInterfaceInfo, nsISupports, nsIInterfaceInfo) + +already_AddRefed +ShimInterfaceInfo::MaybeConstruct(const char* aName, JSContext* cx) +{ + RefPtr info; + for (uint32_t i = 0; i < ArrayLength(kComponentsInterfaceShimMap); ++i) { + if (!strcmp(aName, kComponentsInterfaceShimMap[i].geckoName)) { + const ComponentsInterfaceShimEntry& shimEntry = + kComponentsInterfaceShimMap[i]; + info = new ShimInterfaceInfo(shimEntry.iid, + shimEntry.geckoName, + shimEntry.nativePropHooks); + break; + } + } + return info.forget(); +} + +ShimInterfaceInfo::ShimInterfaceInfo(const nsIID& aIID, + const char* aName, + const mozilla::dom::NativePropertyHooks* aNativePropHooks) + : mIID(aIID) + , mName(aName) + , mNativePropHooks(aNativePropHooks) +{ +} + +NS_IMETHODIMP +ShimInterfaceInfo::GetName(char** aName) +{ + *aName = ToNewCString(mName); + return NS_OK; +} + +NS_IMETHODIMP +ShimInterfaceInfo::GetInterfaceIID(nsIID** aIID) +{ + *aIID = static_cast (nsMemory::Clone(&mIID, sizeof(mIID))); + return NS_OK; +} + +NS_IMETHODIMP +ShimInterfaceInfo::IsScriptable(bool* aRetVal) +{ + // This class should pretend that the interface is scriptable because + // that's what nsJSIID assumes. + *aRetVal = true; + return NS_OK; +} + +NS_IMETHODIMP +ShimInterfaceInfo::IsBuiltinClass(bool* aRetVal) +{ + *aRetVal = true; + return NS_OK; +} + +NS_IMETHODIMP +ShimInterfaceInfo::IsMainProcessScriptableOnly(bool* aRetVal) +{ + *aRetVal = false; + return NS_OK; +} + +NS_IMETHODIMP +ShimInterfaceInfo::GetParent(nsIInterfaceInfo** aParent) +{ + *aParent = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +ShimInterfaceInfo::GetMethodCount(uint16_t* aCount) +{ + // Pretend we don't have any methods. + *aCount = 0; + return NS_OK; +} + +NS_IMETHODIMP +ShimInterfaceInfo::GetConstantCount(uint16_t* aCount) +{ + // We assume that we never have interfaces with more than UINT16_MAX + // constants defined on them. + uint16_t count = 0; + + // NOTE: The structure of this loop must be kept in sync with the loop + // in GetConstant. + const mozilla::dom::NativePropertyHooks* propHooks = mNativePropHooks; + do { + const mozilla::dom::NativeProperties* props[] = { + propHooks->mNativeProperties.regular, + propHooks->mNativeProperties.chromeOnly + }; + for (size_t i = 0; i < ArrayLength(props); ++i) { + auto prop = props[i]; + if (prop && prop->HasConstants()) { + for (auto cs = prop->Constants()->specs; cs->name; ++cs) { + // We have found one constant here. We explicitly do not + // bother calling isEnabled() here because it's OK to define + // potentially extra constants on these shim interfaces. + ++count; + } + } + } + } while ((propHooks = propHooks->mProtoHooks)); + *aCount = count; + return NS_OK; +} + +NS_IMETHODIMP +ShimInterfaceInfo::GetMethodInfo(uint16_t aIndex, const nsXPTMethodInfo** aInfo) +{ + MOZ_ASSERT(false, "This should never be called"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +ShimInterfaceInfo::GetMethodInfoForName(const char* aName, uint16_t* aIndex, const nsXPTMethodInfo** aInfo) +{ + MOZ_ASSERT(false, "This should never be called"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +ShimInterfaceInfo::GetConstant(uint16_t aIndex, JS::MutableHandleValue aConstant, + char** aName) +{ + // We assume that we never have interfaces with more than UINT16_MAX + // constants defined on them. + uint16_t index = 0; + + // NOTE: The structure of this loop must be kept in sync with the loop + // in GetConstantCount. + const mozilla::dom::NativePropertyHooks* propHooks = mNativePropHooks; + do { + const mozilla::dom::NativeProperties* props[] = { + propHooks->mNativeProperties.regular, + propHooks->mNativeProperties.chromeOnly + }; + for (size_t i = 0; i < ArrayLength(props); ++i) { + auto prop = props[i]; + if (prop && prop->HasConstants()) { + for (auto cs = prop->Constants()->specs; cs->name; ++cs) { + // We have found one constant here. We explicitly do not + // bother calling isEnabled() here because it's OK to define + // potentially extra constants on these shim interfaces. + if (index == aIndex) { + aConstant.set(cs->value); + *aName = ToNewCString(nsDependentCString(cs->name)); + return NS_OK; + } + ++index; + } + } + } + } while ((propHooks = propHooks->mProtoHooks)); + + // aIndex was bigger than the number of constants we have. + return NS_ERROR_INVALID_ARG; +} + +NS_IMETHODIMP +ShimInterfaceInfo::GetInfoForParam(uint16_t aIndex, const nsXPTParamInfo* aParam, nsIInterfaceInfo** aRetVal) +{ + MOZ_ASSERT(false, "This should never be called"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +ShimInterfaceInfo::GetIIDForParam(uint16_t aIndex, const nsXPTParamInfo* aParam, nsIID** aRetVal) +{ + MOZ_ASSERT(false, "This should never be called"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +ShimInterfaceInfo::GetTypeForParam(uint16_t aInex, const nsXPTParamInfo* aParam, uint16_t aDimension, nsXPTType* aRetVal) +{ + MOZ_ASSERT(false, "This should never be called"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +ShimInterfaceInfo::GetSizeIsArgNumberForParam(uint16_t aInex, const nsXPTParamInfo* aParam, uint16_t aDimension, uint8_t* aRetVal) +{ + MOZ_ASSERT(false, "This should never be called"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +ShimInterfaceInfo::GetInterfaceIsArgNumberForParam(uint16_t aInex, const nsXPTParamInfo* aParam, uint8_t* aRetVal) +{ + MOZ_ASSERT(false, "This should never be called"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +ShimInterfaceInfo::IsIID(const nsIID* aIID, bool* aRetVal) +{ + *aRetVal = mIID.Equals(*aIID); + return NS_OK; +} + +NS_IMETHODIMP +ShimInterfaceInfo::GetNameShared(const char** aName) +{ + *aName = mName.get(); + return NS_OK; +} + +NS_IMETHODIMP +ShimInterfaceInfo::GetIIDShared(const nsIID** aIID) +{ + *aIID = &mIID; + return NS_OK; +} + +NS_IMETHODIMP +ShimInterfaceInfo::IsFunction(bool* aRetVal) +{ + *aRetVal = false; + return NS_OK; +} + +NS_IMETHODIMP +ShimInterfaceInfo::HasAncestor(const nsIID* aIID, bool* aRetVal) +{ + *aRetVal = false; + return NS_OK; +} + +NS_IMETHODIMP_(nsresult) +ShimInterfaceInfo::GetIIDForParamNoAlloc(uint16_t aIndex, const nsXPTParamInfo* aInfo, nsIID* aIID) +{ + MOZ_ASSERT(false, "This should never be called"); + return NS_ERROR_NOT_IMPLEMENTED; +} diff --git a/xpcom/reflect/xptinfo/ShimInterfaceInfo.h b/xpcom/reflect/xptinfo/ShimInterfaceInfo.h new file mode 100644 index 000000000..868f503a5 --- /dev/null +++ b/xpcom/reflect/xptinfo/ShimInterfaceInfo.h @@ -0,0 +1,50 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sw=4 et tw=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/. */ + +#ifndef ShimInterfaceInfo_h +#define ShimInterfaceInfo_h + +#include "mozilla/Attributes.h" +#include "nsIInterfaceInfo.h" +#include "nsString.h" +#include "nsID.h" +#include "nsTArray.h" +#include "xptinfo.h" +#include "nsAutoPtr.h" +#include "js/RootingAPI.h" + +namespace mozilla { +namespace dom { +struct NativePropertyHooks; +} // namespace dom +} // namespace mozilla + +class ShimInterfaceInfo final : public nsIInterfaceInfo +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIINTERFACEINFO + + // Construct a ShimInterfaceInfo object if we have a shim available for aName. + // Otherwise, returns nullptr. + static already_AddRefed + MaybeConstruct(const char* aName, JSContext* cx); + +private: + ShimInterfaceInfo(const nsIID& aIID, + const char* aName, + const mozilla::dom::NativePropertyHooks* aNativePropHooks); + + ~ShimInterfaceInfo() {} + +private: + nsIID mIID; + nsAutoCString mName; + const mozilla::dom::NativePropertyHooks* mNativePropHooks; +}; + +#endif diff --git a/xpcom/reflect/xptinfo/TODO b/xpcom/reflect/xptinfo/TODO new file mode 100644 index 000000000..50215a4fb --- /dev/null +++ b/xpcom/reflect/xptinfo/TODO @@ -0,0 +1,20 @@ +/* jband - 03/24/00 - */ + +- DOCS +- improve error handling + - should some errors really be warnings? + - should autoreg support additional channel to receive warnings so that + an installer can decide whether or not to accept the consequences of + leaving the newly installed files in place? +- verification of interfaces (warnings and/or errors) + - verify that repeated interfaces are identical in all ways + - verify that interface names are always one-to-one with iids +- check for truncated xpt files and version problems + - http://bugzilla.mozilla.org/show_bug.cgi?id=33193 +- TESTS! + - e.g. verify the merge stuff really works for various inputs. + - we really need a set of .xpt and .zip files and code that does an array + of autoreg and interfaceinof use activitities to test various corners + of the system. +- better autoreg logging +- use only 32 bits for file size? diff --git a/xpcom/reflect/xptinfo/XPTInterfaceInfoManager.h b/xpcom/reflect/xptinfo/XPTInterfaceInfoManager.h new file mode 100644 index 000000000..e72153ad2 --- /dev/null +++ b/xpcom/reflect/xptinfo/XPTInterfaceInfoManager.h @@ -0,0 +1,119 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim: set ts=4 et sw=4 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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_XPTInterfaceInfoManager_h_ +#define mozilla_XPTInterfaceInfoManager_h_ + +#include "nsIInterfaceInfoManager.h" +#include "nsIMemoryReporter.h" + +#include "mozilla/MemoryReporting.h" +#include "mozilla/Mutex.h" +#include "mozilla/ReentrantMonitor.h" +#include "nsDataHashtable.h" + +template class nsCOMArray; +class nsIMemoryReporter; +struct XPTHeader; +struct XPTInterfaceDirectoryEntry; +class xptiInterfaceEntry; +class xptiInterfaceInfo; +class xptiTypelibGuts; + +namespace mozilla { + +class XPTInterfaceInfoManager final + : public nsIInterfaceInfoManager + , public nsIMemoryReporter +{ + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIINTERFACEINFOMANAGER + NS_DECL_NSIMEMORYREPORTER + +public: + // GetSingleton() is infallible + static XPTInterfaceInfoManager* GetSingleton(); + static void FreeInterfaceInfoManager(); + + void GetScriptableInterfaces(nsCOMArray& aInterfaces); + + void RegisterBuffer(char *buf, uint32_t length); + + static Mutex& GetResolveLock() + { + return GetSingleton()->mResolveLock; + } + + xptiInterfaceEntry* GetInterfaceEntryForIID(const nsIID *iid); + + size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf); + +private: + XPTInterfaceInfoManager(); + ~XPTInterfaceInfoManager(); + + void InitMemoryReporter(); + + void RegisterXPTHeader(XPTHeader* aHeader); + + // idx is the index of this interface in the XPTHeader + void VerifyAndAddEntryIfNew(XPTInterfaceDirectoryEntry* iface, + uint16_t idx, + xptiTypelibGuts* typelib); + +private: + + class xptiWorkingSet + { + public: + xptiWorkingSet(); + ~xptiWorkingSet(); + + bool IsValid() const; + + void InvalidateInterfaceInfos(); + void ClearHashTables(); + + // utility methods... + + enum {NOT_FOUND = 0xffffffff}; + + // Directory stuff... + + uint32_t GetDirectoryCount(); + nsresult GetCloneOfDirectoryAt(uint32_t i, nsIFile** dir); + nsresult GetDirectoryAt(uint32_t i, nsIFile** dir); + bool FindDirectory(nsIFile* dir, uint32_t* index); + bool FindDirectoryOfFile(nsIFile* file, uint32_t* index); + bool DirectoryAtMatchesPersistentDescriptor(uint32_t i, const char* desc); + + private: + uint32_t mFileCount; + uint32_t mMaxFileCount; + + public: + // XXX make these private with accessors + // mTableMonitor must be held across: + // * any read from or write to mIIDTable or mNameTable + // * any writing to the links between an xptiInterfaceEntry + // and its xptiInterfaceInfo (mEntry/mInfo) + mozilla::ReentrantMonitor mTableReentrantMonitor; + nsDataHashtable mIIDTable; + nsDataHashtable mNameTable; + }; + + // XXX xptiInterfaceInfo want's to poke at the working set itself + friend class ::xptiInterfaceInfo; + friend class ::xptiInterfaceEntry; + friend class ::xptiTypelibGuts; + + xptiWorkingSet mWorkingSet; + Mutex mResolveLock; +}; + +} // namespace mozilla + +#endif diff --git a/xpcom/reflect/xptinfo/moz.build b/xpcom/reflect/xptinfo/moz.build new file mode 100644 index 000000000..320949782 --- /dev/null +++ b/xpcom/reflect/xptinfo/moz.build @@ -0,0 +1,37 @@ +# -*- 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 += [ + 'ShimInterfaceInfo.cpp', + 'xptiInterfaceInfo.cpp', + 'xptiInterfaceInfoManager.cpp', + 'xptiTypelibGuts.cpp', + 'xptiWorkingSet.cpp', +] + +XPIDL_SOURCES += [ + 'nsIInterfaceInfo.idl', + 'nsIInterfaceInfoManager.idl', +] + +XPIDL_MODULE = 'xpcom_xpti' + +EXPORTS += [ + 'xptinfo.h', +] + +EXPORTS.mozilla += [ + 'XPTInterfaceInfoManager.h', +] + +LOCAL_INCLUDES += [ + '/dom/base', +] + +FINAL_LIBRARY = 'xul' + +if CONFIG['GNU_CXX']: + CXXFLAGS += ['-Wno-error=shadow'] diff --git a/xpcom/reflect/xptinfo/nsIInterfaceInfo.idl b/xpcom/reflect/xptinfo/nsIInterfaceInfo.idl new file mode 100644 index 000000000..8b902d5ee --- /dev/null +++ b/xpcom/reflect/xptinfo/nsIInterfaceInfo.idl @@ -0,0 +1,101 @@ +/* -*- 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/. */ + +/* The nsIInterfaceInfo public declaration. */ + + +#include "nsISupports.idl" + +// forward declaration of non-XPCOM types + +[ptr] native nsXPTMethodInfoPtr(nsXPTMethodInfo); +[ptr] native nsXPTParamInfoPtr(nsXPTParamInfo); + native nsXPTType(nsXPTType); + +// We bend the rules to do a [shared] nsIID (but this is never scriptable) +[ptr] native nsIIDPtrShared(nsIID); + +%{C++ +class nsXPTMethodInfo; +class nsXPTParamInfo; +class nsXPTType; +%} + +[builtinclass, uuid(3820e663-8e22-4789-b470-56bcf7083f2b)] +interface nsIInterfaceInfo : nsISupports +{ + readonly attribute string name; + readonly attribute nsIIDPtr InterfaceIID; + + boolean isScriptable(); + boolean isBuiltinClass(); + + readonly attribute nsIInterfaceInfo parent; + + /** + * These include counts for parent (and all ancestors). + */ + readonly attribute uint16_t methodCount; + readonly attribute uint16_t constantCount; + + /** + * These include methods and constants for parent (and all ancestors). + * + * These do *not* make copies ***explicit bending of XPCOM rules***. + */ + + void getMethodInfo(in uint16_t index, + [shared, retval] out nsXPTMethodInfoPtr info); + + void getMethodInfoForName(in string methodName, out uint16_t index, + [shared, retval] out nsXPTMethodInfoPtr info); + + void getConstant(in uint16_t index, + out jsval constant, + out string name); + + + /** + * Get the interface information or iid associated with a param of some + * method in this interface. + */ + + nsIInterfaceInfo getInfoForParam(in uint16_t methodIndex, + [const] in nsXPTParamInfoPtr param); + + nsIIDPtr getIIDForParam(in uint16_t methodIndex, + [const] in nsXPTParamInfoPtr param); + + + /** + * These do *not* make copies ***explicit bending of XPCOM rules***. + */ + + nsXPTType getTypeForParam(in uint16_t methodIndex, + [const] in nsXPTParamInfoPtr param, + in uint16_t dimension); + + uint8_t getSizeIsArgNumberForParam(in uint16_t methodIndex, + [const] in nsXPTParamInfoPtr param, + in uint16_t dimension); + + uint8_t getInterfaceIsArgNumberForParam(in uint16_t methodIndex, + [const] in nsXPTParamInfoPtr param); + + boolean isIID(in nsIIDPtr IID); + + void getNameShared([shared,retval] out string name); + void getIIDShared([shared,retval] out nsIIDPtrShared iid); + + boolean isFunction(); + + boolean hasAncestor(in nsIIDPtr iid); + + [notxpcom] nsresult getIIDForParamNoAlloc(in uint16_t methodIndex, + [const] in nsXPTParamInfoPtr param, + out nsIID iid); + + boolean isMainProcessScriptableOnly(); +}; diff --git a/xpcom/reflect/xptinfo/nsIInterfaceInfoManager.idl b/xpcom/reflect/xptinfo/nsIInterfaceInfoManager.idl new file mode 100644 index 000000000..bc63aed49 --- /dev/null +++ b/xpcom/reflect/xptinfo/nsIInterfaceInfoManager.idl @@ -0,0 +1,28 @@ +/* -*- 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/. */ + +/* The nsIInterfaceInfoManager public declaration. */ + + +#include "nsISupports.idl" + +interface nsIInterfaceInfo; + +[builtinclass, uuid(1d53d8d9-1d92-428f-b5cc-198b55e897d7)] +interface nsIInterfaceInfoManager : nsISupports +{ + nsIInterfaceInfo getInfoForIID(in nsIIDPtr iid); + nsIInterfaceInfo getInfoForName(in string name); +}; + +%{C++ +#define NS_INTERFACEINFOMANAGER_SERVICE_CID \ + { /* 13bef784-f8e0-4f96-85c1-09f9ef4f9a19 */ \ + 0x13bef784, 0xf8e0, 0x4f96, \ + {0x85, 0xc1, 0x09, 0xf9, 0xef, 0x4f, 0x9a, 0x19} } + +#define NS_INTERFACEINFOMANAGER_SERVICE_CONTRACTID \ + "@mozilla.org/xpti/interfaceinfomanager-service;1" +%} diff --git a/xpcom/reflect/xptinfo/xptiInterfaceInfo.cpp b/xpcom/reflect/xptinfo/xptiInterfaceInfo.cpp new file mode 100644 index 000000000..8ea80fda1 --- /dev/null +++ b/xpcom/reflect/xptinfo/xptiInterfaceInfo.cpp @@ -0,0 +1,742 @@ +/* -*- 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/. */ + +/* Implementation of xptiInterfaceEntry and xptiInterfaceInfo. */ + +#include "xptiprivate.h" +#include "mozilla/dom/ScriptSettings.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/XPTInterfaceInfoManager.h" +#include "mozilla/PodOperations.h" +#include "jsapi.h" + +using namespace mozilla; + +/* static */ xptiInterfaceEntry* +xptiInterfaceEntry::Create(const char* name, const nsID& iid, + XPTInterfaceDescriptor* aDescriptor, + xptiTypelibGuts* aTypelib) +{ + int namelen = strlen(name); + void* place = + XPT_CALLOC8(gXPTIStructArena, sizeof(xptiInterfaceEntry) + namelen); + if (!place) { + return nullptr; + } + return new (place) xptiInterfaceEntry(name, namelen, iid, aDescriptor, + aTypelib); +} + +xptiInterfaceEntry::xptiInterfaceEntry(const char* name, + size_t nameLength, + const nsID& iid, + XPTInterfaceDescriptor* aDescriptor, + xptiTypelibGuts* aTypelib) + : mIID(iid) + , mDescriptor(aDescriptor) + , mTypelib(aTypelib) + , mParent(nullptr) + , mInfo(nullptr) + , mMethodBaseIndex(0) + , mConstantBaseIndex(0) + , mFlags(0) +{ + memcpy(mName, name, nameLength); + SetResolvedState(PARTIALLY_RESOLVED); +} + +bool +xptiInterfaceEntry::Resolve() +{ + MutexAutoLock lock(XPTInterfaceInfoManager::GetResolveLock()); + return ResolveLocked(); +} + +bool +xptiInterfaceEntry::ResolveLocked() +{ + int resolvedState = GetResolveState(); + + if(resolvedState == FULLY_RESOLVED) + return true; + if(resolvedState == RESOLVE_FAILED) + return false; + + NS_ASSERTION(GetResolveState() == PARTIALLY_RESOLVED, "bad state!"); + + // Finish out resolution by finding parent and Resolving it so + // we can set the info we get from it. + + uint16_t parent_index = mDescriptor->parent_interface; + + if(parent_index) + { + xptiInterfaceEntry* parent = + mTypelib->GetEntryAt(parent_index - 1); + + if(!parent || !parent->EnsureResolvedLocked()) + { + SetResolvedState(RESOLVE_FAILED); + return false; + } + + mParent = parent; + if (parent->GetHasNotXPCOMFlag()) { + SetHasNotXPCOMFlag(); + } else { + for (uint16_t idx = 0; idx < mDescriptor->num_methods; ++idx) { + nsXPTMethodInfo* method = reinterpret_cast( + mDescriptor->method_descriptors + idx); + if (method->IsNotXPCOM()) { + SetHasNotXPCOMFlag(); + break; + } + } + } + + + mMethodBaseIndex = + parent->mMethodBaseIndex + + parent->mDescriptor->num_methods; + + mConstantBaseIndex = + parent->mConstantBaseIndex + + parent->mDescriptor->num_constants; + + } + LOG_RESOLVE(("+ complete resolve of %s\n", mName)); + + SetResolvedState(FULLY_RESOLVED); + return true; +} + +/**************************************************/ +// These non-virtual methods handle the delegated nsIInterfaceInfo methods. + +nsresult +xptiInterfaceEntry::GetName(char **name) +{ + // It is not necessary to Resolve because this info is read from manifest. + *name = (char*) nsMemory::Clone(mName, strlen(mName)+1); + return *name ? NS_OK : NS_ERROR_OUT_OF_MEMORY; +} + +nsresult +xptiInterfaceEntry::GetIID(nsIID **iid) +{ + // It is not necessary to Resolve because this info is read from manifest. + *iid = (nsIID*) nsMemory::Clone(&mIID, sizeof(nsIID)); + return *iid ? NS_OK : NS_ERROR_OUT_OF_MEMORY; +} + +nsresult +xptiInterfaceEntry::IsScriptable(bool* result) +{ + // It is not necessary to Resolve because this info is read from manifest. + *result = GetScriptableFlag(); + return NS_OK; +} + +nsresult +xptiInterfaceEntry::IsFunction(bool* result) +{ + if(!EnsureResolved()) + return NS_ERROR_UNEXPECTED; + + *result = XPT_ID_IS_FUNCTION(mDescriptor->flags); + return NS_OK; +} + +nsresult +xptiInterfaceEntry::GetMethodCount(uint16_t* count) +{ + if(!EnsureResolved()) + return NS_ERROR_UNEXPECTED; + + *count = mMethodBaseIndex + + mDescriptor->num_methods; + return NS_OK; +} + +nsresult +xptiInterfaceEntry::GetConstantCount(uint16_t* count) +{ + if(!EnsureResolved()) + return NS_ERROR_UNEXPECTED; + + if(!count) + return NS_ERROR_UNEXPECTED; + + *count = mConstantBaseIndex + + mDescriptor->num_constants; + return NS_OK; +} + +nsresult +xptiInterfaceEntry::GetMethodInfo(uint16_t index, const nsXPTMethodInfo** info) +{ + if(!EnsureResolved()) + return NS_ERROR_UNEXPECTED; + + if(index < mMethodBaseIndex) + return mParent->GetMethodInfo(index, info); + + if(index >= mMethodBaseIndex + + mDescriptor->num_methods) + { + NS_ERROR("bad param"); + *info = nullptr; + return NS_ERROR_INVALID_ARG; + } + + // else... + *info = reinterpret_cast + (&mDescriptor->method_descriptors[index - mMethodBaseIndex]); + return NS_OK; +} + +nsresult +xptiInterfaceEntry::GetMethodInfoForName(const char* methodName, uint16_t *index, + const nsXPTMethodInfo** result) +{ + if(!EnsureResolved()) + return NS_ERROR_UNEXPECTED; + + // This is a slow algorithm, but this is not expected to be called much. + for(uint16_t i = 0; i < mDescriptor->num_methods; ++i) + { + const nsXPTMethodInfo* info; + info = reinterpret_cast + (&mDescriptor-> + method_descriptors[i]); + if (PL_strcmp(methodName, info->GetName()) == 0) { + *index = i + mMethodBaseIndex; + *result = info; + return NS_OK; + } + } + + if(mParent) + return mParent->GetMethodInfoForName(methodName, index, result); + else + { + *index = 0; + *result = 0; + return NS_ERROR_INVALID_ARG; + } +} + +nsresult +xptiInterfaceEntry::GetConstant(uint16_t index, JS::MutableHandleValue constant, + char** name) +{ + if(!EnsureResolved()) + return NS_ERROR_UNEXPECTED; + + if(index < mConstantBaseIndex) + return mParent->GetConstant(index, constant, name); + + if(index >= mConstantBaseIndex + + mDescriptor->num_constants) + { + NS_PRECONDITION(0, "bad param"); + return NS_ERROR_INVALID_ARG; + } + + const auto& c = mDescriptor->const_descriptors[index - mConstantBaseIndex]; + AutoJSContext cx; + JS::Rooted v(cx); + v.setUndefined(); + + switch (c.type.prefix.flags) { + case nsXPTType::T_I8: + { + v.setInt32(c.value.i8); + break; + } + case nsXPTType::T_U8: + { + v.setInt32(c.value.ui8); + break; + } + case nsXPTType::T_I16: + { + v.setInt32(c.value.i16); + break; + } + case nsXPTType::T_U16: + { + v.setInt32(c.value.ui16); + break; + } + case nsXPTType::T_I32: + { + v = JS_NumberValue(c.value.i32); + break; + } + case nsXPTType::T_U32: + { + v = JS_NumberValue(c.value.ui32); + break; + } + default: + { +#ifdef DEBUG + NS_ERROR("Non-numeric constant found in interface."); +#endif + } + } + + constant.set(v); + *name = ToNewCString(nsDependentCString(c.name)); + + return NS_OK; +} + +// this is a private helper + +nsresult +xptiInterfaceEntry::GetInterfaceIndexForParam(uint16_t methodIndex, + const nsXPTParamInfo* param, + uint16_t* interfaceIndex) +{ + if(!EnsureResolved()) + return NS_ERROR_UNEXPECTED; + + if(methodIndex < mMethodBaseIndex) + return mParent->GetInterfaceIndexForParam(methodIndex, param, + interfaceIndex); + + if(methodIndex >= mMethodBaseIndex + + mDescriptor->num_methods) + { + NS_ERROR("bad param"); + return NS_ERROR_INVALID_ARG; + } + + const XPTTypeDescriptor *td = ¶m->type; + + while (XPT_TDP_TAG(td->prefix) == TD_ARRAY) { + td = &mDescriptor->additional_types[td->u.array.additional_type]; + } + + if(XPT_TDP_TAG(td->prefix) != TD_INTERFACE_TYPE) { + NS_ERROR("not an interface"); + return NS_ERROR_INVALID_ARG; + } + + *interfaceIndex = (td->u.iface.iface_hi8 << 8) | td->u.iface.iface_lo8; + return NS_OK; +} + +nsresult +xptiInterfaceEntry::GetEntryForParam(uint16_t methodIndex, + const nsXPTParamInfo * param, + xptiInterfaceEntry** entry) +{ + if(!EnsureResolved()) + return NS_ERROR_UNEXPECTED; + + if(methodIndex < mMethodBaseIndex) + return mParent->GetEntryForParam(methodIndex, param, entry); + + uint16_t interfaceIndex = 0; + nsresult rv = GetInterfaceIndexForParam(methodIndex, param, + &interfaceIndex); + if (NS_FAILED(rv)) { + return rv; + } + + xptiInterfaceEntry* theEntry = mTypelib->GetEntryAt(interfaceIndex - 1); + + // This can happen if a declared interface is not available at runtime. + if(!theEntry) + { + *entry = nullptr; + return NS_ERROR_FAILURE; + } + + *entry = theEntry; + return NS_OK; +} + +already_AddRefed +xptiInterfaceEntry::GetShimForParam(uint16_t methodIndex, + const nsXPTParamInfo* param) +{ + if(methodIndex < mMethodBaseIndex) { + return mParent->GetShimForParam(methodIndex, param); + } + + uint16_t interfaceIndex = 0; + nsresult rv = GetInterfaceIndexForParam(methodIndex, param, + &interfaceIndex); + if (NS_FAILED(rv)) { + return nullptr; + } + + const char* shimName = mTypelib->GetEntryNameAt(interfaceIndex - 1); + RefPtr shim = + ShimInterfaceInfo::MaybeConstruct(shimName, nullptr); + return shim.forget(); +} + +nsresult +xptiInterfaceEntry::GetInfoForParam(uint16_t methodIndex, + const nsXPTParamInfo *param, + nsIInterfaceInfo** info) +{ + xptiInterfaceEntry* entry; + nsresult rv = GetEntryForParam(methodIndex, param, &entry); + if (NS_FAILED(rv)) { + RefPtr shim = GetShimForParam(methodIndex, param); + if (!shim) { + return rv; + } + + shim.forget(info); + return NS_OK; + } + + *info = entry->InterfaceInfo().take(); + + return NS_OK; +} + +nsresult +xptiInterfaceEntry::GetIIDForParam(uint16_t methodIndex, + const nsXPTParamInfo* param, nsIID** iid) +{ + xptiInterfaceEntry* entry; + nsresult rv = GetEntryForParam(methodIndex, param, &entry); + if (NS_FAILED(rv)) { + RefPtr shim = GetShimForParam(methodIndex, param); + if (!shim) { + return rv; + } + + return shim->GetInterfaceIID(iid); + } + return entry->GetIID(iid); +} + +nsresult +xptiInterfaceEntry::GetIIDForParamNoAlloc(uint16_t methodIndex, + const nsXPTParamInfo * param, + nsIID *iid) +{ + xptiInterfaceEntry* entry; + nsresult rv = GetEntryForParam(methodIndex, param, &entry); + if (NS_FAILED(rv)) { + RefPtr shim = GetShimForParam(methodIndex, param); + if (!shim) { + return rv; + } + + const nsIID* shimIID; + DebugOnly rv2 = shim->GetIIDShared(&shimIID); + MOZ_ASSERT(NS_SUCCEEDED(rv2)); + *iid = *shimIID; + return NS_OK; + } + *iid = entry->mIID; + return NS_OK; +} + +// this is a private helper +nsresult +xptiInterfaceEntry::GetTypeInArray(const nsXPTParamInfo* param, + uint16_t dimension, + const XPTTypeDescriptor** type) +{ + NS_ASSERTION(IsFullyResolved(), "bad state"); + + const XPTTypeDescriptor *td = ¶m->type; + const XPTTypeDescriptor *additional_types = + mDescriptor->additional_types; + + for (uint16_t i = 0; i < dimension; i++) { + if(XPT_TDP_TAG(td->prefix) != TD_ARRAY) { + NS_ERROR("bad dimension"); + return NS_ERROR_INVALID_ARG; + } + td = &additional_types[td->u.array.additional_type]; + } + + *type = td; + return NS_OK; +} + +nsresult +xptiInterfaceEntry::GetTypeForParam(uint16_t methodIndex, + const nsXPTParamInfo* param, + uint16_t dimension, + nsXPTType* type) +{ + if(!EnsureResolved()) + return NS_ERROR_UNEXPECTED; + + if(methodIndex < mMethodBaseIndex) + return mParent-> + GetTypeForParam(methodIndex, param, dimension, type); + + if(methodIndex >= mMethodBaseIndex + + mDescriptor->num_methods) + { + NS_ERROR("bad index"); + return NS_ERROR_INVALID_ARG; + } + + const XPTTypeDescriptor *td; + + if(dimension) { + nsresult rv = GetTypeInArray(param, dimension, &td); + if(NS_FAILED(rv)) + return rv; + } + else + td = ¶m->type; + + *type = nsXPTType(td->prefix); + return NS_OK; +} + +nsresult +xptiInterfaceEntry::GetSizeIsArgNumberForParam(uint16_t methodIndex, + const nsXPTParamInfo* param, + uint16_t dimension, + uint8_t* argnum) +{ + if(!EnsureResolved()) + return NS_ERROR_UNEXPECTED; + + if(methodIndex < mMethodBaseIndex) + return mParent-> + GetSizeIsArgNumberForParam(methodIndex, param, dimension, argnum); + + if(methodIndex >= mMethodBaseIndex + + mDescriptor->num_methods) + { + NS_ERROR("bad index"); + return NS_ERROR_INVALID_ARG; + } + + const XPTTypeDescriptor *td; + + if(dimension) { + nsresult rv = GetTypeInArray(param, dimension, &td); + if(NS_FAILED(rv)) + return rv; + } + else + td = ¶m->type; + + // verify that this is a type that has size_is + switch (XPT_TDP_TAG(td->prefix)) { + case TD_ARRAY: + *argnum = td->u.array.argnum; + break; + case TD_PSTRING_SIZE_IS: + case TD_PWSTRING_SIZE_IS: + *argnum = td->u.pstring_is.argnum; + break; + default: + NS_ERROR("not a size_is"); + return NS_ERROR_INVALID_ARG; + } + + return NS_OK; +} + +nsresult +xptiInterfaceEntry::GetInterfaceIsArgNumberForParam(uint16_t methodIndex, + const nsXPTParamInfo* param, + uint8_t* argnum) +{ + if(!EnsureResolved()) + return NS_ERROR_UNEXPECTED; + + if(methodIndex < mMethodBaseIndex) + return mParent-> + GetInterfaceIsArgNumberForParam(methodIndex, param, argnum); + + if(methodIndex >= mMethodBaseIndex + + mDescriptor->num_methods) + { + NS_ERROR("bad index"); + return NS_ERROR_INVALID_ARG; + } + + const XPTTypeDescriptor *td = ¶m->type; + + while (XPT_TDP_TAG(td->prefix) == TD_ARRAY) { + td = &mDescriptor->additional_types[td->u.array.additional_type]; + } + + if(XPT_TDP_TAG(td->prefix) != TD_INTERFACE_IS_TYPE) { + NS_ERROR("not an iid_is"); + return NS_ERROR_INVALID_ARG; + } + + *argnum = td->u.interface_is.argnum; + return NS_OK; +} + +nsresult +xptiInterfaceEntry::IsIID(const nsIID * iid, bool *_retval) +{ + // It is not necessary to Resolve because this info is read from manifest. + *_retval = mIID.Equals(*iid); + return NS_OK; +} + +nsresult +xptiInterfaceEntry::GetNameShared(const char **name) +{ + // It is not necessary to Resolve because this info is read from manifest. + *name = mName; + return NS_OK; +} + +nsresult +xptiInterfaceEntry::GetIIDShared(const nsIID * *iid) +{ + // It is not necessary to Resolve because this info is read from manifest. + *iid = &mIID; + return NS_OK; +} + +nsresult +xptiInterfaceEntry::HasAncestor(const nsIID * iid, bool *_retval) +{ + *_retval = false; + + for(xptiInterfaceEntry* current = this; + current; + current = current->mParent) + { + if(current->mIID.Equals(*iid)) + { + *_retval = true; + break; + } + if(!current->EnsureResolved()) + return NS_ERROR_UNEXPECTED; + } + + return NS_OK; +} + +/***************************************************/ + +already_AddRefed +xptiInterfaceEntry::InterfaceInfo() +{ +#ifdef DEBUG + XPTInterfaceInfoManager::GetSingleton()->mWorkingSet.mTableReentrantMonitor. + AssertCurrentThreadIn(); +#endif + + if(!mInfo) + { + mInfo = new xptiInterfaceInfo(this); + } + + RefPtr info = mInfo; + return info.forget(); +} + +void +xptiInterfaceEntry::LockedInvalidateInterfaceInfo() +{ + if(mInfo) + { + mInfo->Invalidate(); + mInfo = nullptr; + } +} + +bool +xptiInterfaceInfo::BuildParent() +{ + mozilla::ReentrantMonitorAutoEnter monitor(XPTInterfaceInfoManager::GetSingleton()-> + mWorkingSet.mTableReentrantMonitor); + NS_ASSERTION(mEntry && + mEntry->IsFullyResolved() && + !mParent && + mEntry->Parent(), + "bad BuildParent call"); + mParent = mEntry->Parent()->InterfaceInfo(); + return true; +} + +/***************************************************************************/ + +NS_IMPL_QUERY_INTERFACE(xptiInterfaceInfo, nsIInterfaceInfo) + +xptiInterfaceInfo::xptiInterfaceInfo(xptiInterfaceEntry* entry) + : mEntry(entry) +{ +} + +xptiInterfaceInfo::~xptiInterfaceInfo() +{ + NS_ASSERTION(!mEntry, "bad state in dtor"); +} + +void +xptiInterfaceInfo::Invalidate() +{ + mParent = nullptr; + mEntry = nullptr; +} + +MozExternalRefCountType +xptiInterfaceInfo::AddRef(void) +{ + nsrefcnt cnt = ++mRefCnt; + NS_LOG_ADDREF(this, cnt, "xptiInterfaceInfo", sizeof(*this)); + return cnt; +} + +MozExternalRefCountType +xptiInterfaceInfo::Release(void) +{ + xptiInterfaceEntry* entry = mEntry; + nsrefcnt cnt = --mRefCnt; + NS_LOG_RELEASE(this, cnt, "xptiInterfaceInfo"); + if(!cnt) + { + mozilla::ReentrantMonitorAutoEnter monitor(XPTInterfaceInfoManager:: + GetSingleton()->mWorkingSet. + mTableReentrantMonitor); + + // If InterfaceInfo added and *released* a reference before we + // acquired the monitor then 'this' might already be dead. In that + // case we would not want to try to access any instance data. We + // would want to bail immediately. If 'this' is already dead then the + // entry will no longer have a pointer to 'this'. So, we can protect + // ourselves from danger without more aggressive locking. + if(entry && !entry->InterfaceInfoEquals(this)) + return 0; + + // If InterfaceInfo added a reference before we acquired the monitor + // then we want to bail out of here without destorying the object. + if(mRefCnt) + return 1; + + if(mEntry) + { + mEntry->LockedInterfaceInfoDeathNotification(); + mEntry = nullptr; + } + + delete this; + return 0; + } + return cnt; +} + +/***************************************************************************/ diff --git a/xpcom/reflect/xptinfo/xptiInterfaceInfoManager.cpp b/xpcom/reflect/xptinfo/xptiInterfaceInfoManager.cpp new file mode 100644 index 000000000..600a99b94 --- /dev/null +++ b/xpcom/reflect/xptinfo/xptiInterfaceInfoManager.cpp @@ -0,0 +1,242 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim: set ts=8 sts=4 et sw=4 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* Implementation of xptiInterfaceInfoManager. */ + +#include "mozilla/XPTInterfaceInfoManager.h" + +#include "mozilla/FileUtils.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/StaticPtr.h" + +#include "xptiprivate.h" +#include "nsDependentString.h" +#include "nsString.h" +#include "nsArrayEnumerator.h" +#include "nsDirectoryService.h" +#include "nsIMemoryReporter.h" + +using namespace mozilla; + +NS_IMPL_ISUPPORTS( + XPTInterfaceInfoManager, + nsIInterfaceInfoManager, + nsIMemoryReporter) + +static StaticRefPtr gInterfaceInfoManager; + +size_t +XPTInterfaceInfoManager::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) +{ + size_t n = aMallocSizeOf(this); + ReentrantMonitorAutoEnter monitor(mWorkingSet.mTableReentrantMonitor); + // The entries themselves are allocated out of an arena accounted + // for elsewhere, so don't measure them + n += mWorkingSet.mIIDTable.ShallowSizeOfExcludingThis(aMallocSizeOf); + n += mWorkingSet.mNameTable.ShallowSizeOfExcludingThis(aMallocSizeOf); + return n; +} + +MOZ_DEFINE_MALLOC_SIZE_OF(XPTIMallocSizeOf) + +NS_IMETHODIMP +XPTInterfaceInfoManager::CollectReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, bool aAnonymize) +{ + size_t amount = SizeOfIncludingThis(XPTIMallocSizeOf); + + // Measure gXPTIStructArena here, too. This is a bit grotty because it + // doesn't belong to the XPTIInterfaceInfoManager, but there's no + // obviously better place to measure it. + amount += XPT_SizeOfArenaIncludingThis(gXPTIStructArena, XPTIMallocSizeOf); + + MOZ_COLLECT_REPORT( + "explicit/xpti-working-set", KIND_HEAP, UNITS_BYTES, amount, + "Memory used by the XPCOM typelib system."); + + return NS_OK; +} + +// static +XPTInterfaceInfoManager* +XPTInterfaceInfoManager::GetSingleton() +{ + if (!gInterfaceInfoManager) { + gInterfaceInfoManager = new XPTInterfaceInfoManager(); + gInterfaceInfoManager->InitMemoryReporter(); + } + return gInterfaceInfoManager; +} + +void +XPTInterfaceInfoManager::FreeInterfaceInfoManager() +{ + gInterfaceInfoManager = nullptr; +} + +XPTInterfaceInfoManager::XPTInterfaceInfoManager() + : mWorkingSet(), + mResolveLock("XPTInterfaceInfoManager.mResolveLock") +{ +} + +XPTInterfaceInfoManager::~XPTInterfaceInfoManager() +{ + // We only do this on shutdown of the service. + mWorkingSet.InvalidateInterfaceInfos(); + + UnregisterWeakMemoryReporter(this); +} + +void +XPTInterfaceInfoManager::InitMemoryReporter() +{ + RegisterWeakMemoryReporter(this); +} + +void +XPTInterfaceInfoManager::RegisterBuffer(char *buf, uint32_t length) +{ + XPTState state; + XPT_InitXDRState(&state, buf, length); + + XPTCursor curs; + NotNull cursor = WrapNotNull(&curs); + if (!XPT_MakeCursor(&state, XPT_HEADER, 0, cursor)) { + return; + } + + XPTHeader *header = nullptr; + if (XPT_DoHeader(gXPTIStructArena, cursor, &header)) { + RegisterXPTHeader(header); + } +} + +void +XPTInterfaceInfoManager::RegisterXPTHeader(XPTHeader* aHeader) +{ + if (aHeader->major_version >= XPT_MAJOR_INCOMPATIBLE_VERSION) { + NS_ASSERTION(!aHeader->num_interfaces,"bad libxpt"); + LOG_AUTOREG((" file is version %d.%d Type file of version %d.0 or higher can not be read.\n", (int)header->major_version, (int)header->minor_version, (int)XPT_MAJOR_INCOMPATIBLE_VERSION)); + } + + xptiTypelibGuts* typelib = xptiTypelibGuts::Create(aHeader); + + ReentrantMonitorAutoEnter monitor(mWorkingSet.mTableReentrantMonitor); + for(uint16_t k = 0; k < aHeader->num_interfaces; k++) + VerifyAndAddEntryIfNew(aHeader->interface_directory + k, k, typelib); +} + +void +XPTInterfaceInfoManager::VerifyAndAddEntryIfNew(XPTInterfaceDirectoryEntry* iface, + uint16_t idx, + xptiTypelibGuts* typelib) +{ + if (!iface->interface_descriptor) + return; + + // 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. + if (iface->interface_descriptor->num_methods > 250 && + !(XPT_ID_IS_BUILTINCLASS(iface->interface_descriptor->flags))) { + NS_ASSERTION(0, "Too many methods to handle for the stub, cannot load"); + fprintf(stderr, "ignoring too large interface: %s\n", iface->name); + return; + } + + mWorkingSet.mTableReentrantMonitor.AssertCurrentThreadIn(); + xptiInterfaceEntry* entry = mWorkingSet.mIIDTable.Get(iface->iid); + if (entry) { + // XXX validate this info to find possible inconsistencies + LOG_AUTOREG((" ignoring repeated interface: %s\n", iface->name)); + return; + } + + // Build a new xptiInterfaceEntry object and hook it up. + + entry = xptiInterfaceEntry::Create(iface->name, + iface->iid, + iface->interface_descriptor, + typelib); + if (!entry) + return; + + //XXX We should SetHeader too as part of the validation, no? + entry->SetScriptableFlag(XPT_ID_IS_SCRIPTABLE(iface->interface_descriptor->flags)); + entry->SetBuiltinClassFlag(XPT_ID_IS_BUILTINCLASS(iface->interface_descriptor->flags)); + entry->SetMainProcessScriptableOnlyFlag( + XPT_ID_IS_MAIN_PROCESS_SCRIPTABLE_ONLY(iface->interface_descriptor->flags)); + + mWorkingSet.mIIDTable.Put(entry->IID(), entry); + mWorkingSet.mNameTable.Put(entry->GetTheName(), entry); + + typelib->SetEntryAt(idx, entry); + + LOG_AUTOREG((" added interface: %s\n", iface->name)); +} + +// this is a private helper +static nsresult +EntryToInfo(xptiInterfaceEntry* entry, nsIInterfaceInfo **_retval) +{ + if (!entry) { + *_retval = nullptr; + return NS_ERROR_FAILURE; + } + + RefPtr info = entry->InterfaceInfo(); + info.forget(_retval); + return NS_OK; +} + +xptiInterfaceEntry* +XPTInterfaceInfoManager::GetInterfaceEntryForIID(const nsIID *iid) +{ + ReentrantMonitorAutoEnter monitor(mWorkingSet.mTableReentrantMonitor); + return mWorkingSet.mIIDTable.Get(*iid); +} + +NS_IMETHODIMP +XPTInterfaceInfoManager::GetInfoForIID(const nsIID * iid, nsIInterfaceInfo **_retval) +{ + NS_ASSERTION(iid, "bad param"); + NS_ASSERTION(_retval, "bad param"); + + ReentrantMonitorAutoEnter monitor(mWorkingSet.mTableReentrantMonitor); + xptiInterfaceEntry* entry = mWorkingSet.mIIDTable.Get(*iid); + return EntryToInfo(entry, _retval); +} + +NS_IMETHODIMP +XPTInterfaceInfoManager::GetInfoForName(const char *name, nsIInterfaceInfo **_retval) +{ + NS_ASSERTION(name, "bad param"); + NS_ASSERTION(_retval, "bad param"); + + ReentrantMonitorAutoEnter monitor(mWorkingSet.mTableReentrantMonitor); + xptiInterfaceEntry* entry = mWorkingSet.mNameTable.Get(name); + return EntryToInfo(entry, _retval); +} + +void +XPTInterfaceInfoManager::GetScriptableInterfaces(nsCOMArray& aInterfaces) +{ + // I didn't want to incur the size overhead of using nsHashtable just to + // make building an enumerator easier. So, this code makes a snapshot of + // the table using an nsCOMArray and builds an enumerator for that. + // We can afford this transient cost. + + ReentrantMonitorAutoEnter monitor(mWorkingSet.mTableReentrantMonitor); + aInterfaces.SetCapacity(mWorkingSet.mNameTable.Count()); + for (auto iter = mWorkingSet.mNameTable.Iter(); !iter.Done(); iter.Next()) { + xptiInterfaceEntry* entry = iter.UserData(); + if (entry->GetScriptableFlag()) { + nsCOMPtr ii = entry->InterfaceInfo(); + aInterfaces.AppendElement(ii); + } + } +} diff --git a/xpcom/reflect/xptinfo/xptiTypelibGuts.cpp b/xpcom/reflect/xptinfo/xptiTypelibGuts.cpp new file mode 100644 index 000000000..e2965d0b1 --- /dev/null +++ b/xpcom/reflect/xptinfo/xptiTypelibGuts.cpp @@ -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/. */ + +/* Implementation of xptiTypelibGuts. */ + +#include "xptiprivate.h" +#include "mozilla/XPTInterfaceInfoManager.h" + +using namespace mozilla; + +// Ensure through static analysis that xptiTypelibGuts won't have a vtable. +template +class MOZ_NEEDS_NO_VTABLE_TYPE CheckNoVTable +{ +}; +CheckNoVTable gChecker; + +// static +xptiTypelibGuts* +xptiTypelibGuts::Create(XPTHeader* aHeader) +{ + NS_ASSERTION(aHeader, "bad param"); + size_t n = sizeof(xptiTypelibGuts) + + sizeof(xptiInterfaceEntry*) * (aHeader->num_interfaces - 1); + void* place = XPT_CALLOC8(gXPTIStructArena, n); + if (!place) + return nullptr; + return new(place) xptiTypelibGuts(aHeader); +} + +xptiInterfaceEntry* +xptiTypelibGuts::GetEntryAt(uint16_t i) +{ + static const nsID zeroIID = + { 0x0, 0x0, 0x0, { 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 } }; + + NS_ASSERTION(mHeader, "bad state"); + NS_ASSERTION(i < GetEntryCount(), "bad index"); + + xptiInterfaceEntry* r = mEntryArray[i]; + if (r) + return r; + + XPTInterfaceDirectoryEntry* iface = mHeader->interface_directory + i; + + XPTInterfaceInfoManager::xptiWorkingSet& set = + XPTInterfaceInfoManager::GetSingleton()->mWorkingSet; + + { + ReentrantMonitorAutoEnter monitor(set.mTableReentrantMonitor); + if (iface->iid.Equals(zeroIID)) + r = set.mNameTable.Get(iface->name); + else + r = set.mIIDTable.Get(iface->iid); + } + + if (r) + SetEntryAt(i, r); + + return r; +} + +const char* +xptiTypelibGuts::GetEntryNameAt(uint16_t i) +{ + NS_ASSERTION(mHeader, "bad state"); + NS_ASSERTION(i < GetEntryCount(), "bad index"); + + XPTInterfaceDirectoryEntry* iface = mHeader->interface_directory + i; + + return iface->name; +} diff --git a/xpcom/reflect/xptinfo/xptiWorkingSet.cpp b/xpcom/reflect/xptinfo/xptiWorkingSet.cpp new file mode 100644 index 000000000..10f81a4b2 --- /dev/null +++ b/xpcom/reflect/xptinfo/xptiWorkingSet.cpp @@ -0,0 +1,53 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim: set ts=8 sts=4 et sw=4 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* Implementation of xptiWorkingSet. */ + +#include "mozilla/XPTInterfaceInfoManager.h" + +#include "xptiprivate.h" +#include "nsString.h" + +using namespace mozilla; + +static const size_t XPTI_ARENA8_BLOCK_SIZE = 16 * 1024; +static const size_t XPTI_ARENA1_BLOCK_SIZE = 8 * 1024; + +static const uint32_t XPTI_HASHTABLE_LENGTH = 1024; + +XPTInterfaceInfoManager::xptiWorkingSet::xptiWorkingSet() + : mTableReentrantMonitor("xptiWorkingSet::mTableReentrantMonitor") + , mIIDTable(XPTI_HASHTABLE_LENGTH) + , mNameTable(XPTI_HASHTABLE_LENGTH) +{ + MOZ_COUNT_CTOR(xptiWorkingSet); + + gXPTIStructArena = XPT_NewArena(XPTI_ARENA8_BLOCK_SIZE, + XPTI_ARENA1_BLOCK_SIZE); +} + +void +XPTInterfaceInfoManager::xptiWorkingSet::InvalidateInterfaceInfos() +{ + ReentrantMonitorAutoEnter monitor(mTableReentrantMonitor); + for (auto iter = mNameTable.Iter(); !iter.Done(); iter.Next()) { + xptiInterfaceEntry* entry = iter.UserData(); + entry->LockedInvalidateInterfaceInfo(); + } +} + +XPTInterfaceInfoManager::xptiWorkingSet::~xptiWorkingSet() +{ + MOZ_COUNT_DTOR(xptiWorkingSet); + + // Only destroy the arena if we're doing leak stats. Why waste shutdown + // time touching pages if we don't have to? +#ifdef NS_FREE_PERMANENT_DATA + XPT_DestroyArena(gXPTIStructArena); +#endif +} + +XPTArena* gXPTIStructArena; diff --git a/xpcom/reflect/xptinfo/xptinfo.h b/xpcom/reflect/xptinfo/xptinfo.h new file mode 100644 index 000000000..1207a42bb --- /dev/null +++ b/xpcom/reflect/xptinfo/xptinfo.h @@ -0,0 +1,236 @@ +/* -*- 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/. */ + +/* XPTI_PUBLIC_API and XPTI_GetInterfaceInfoManager declarations. */ + +#ifndef xptiinfo_h___ +#define xptiinfo_h___ + +#include "nscore.h" +#include "xpt_struct.h" + +// Flyweight wrapper classes for xpt_struct.h structs. +// Everything here is dependent upon - and sensitive to changes in - +// xpcom/typelib/xpt/xpt_struct.h! + +class nsXPTType : public XPTTypeDescriptorPrefix +{ +// NO DATA - this a flyweight wrapper +public: + nsXPTType() + {} // random contents + MOZ_IMPLICIT nsXPTType(const XPTTypeDescriptorPrefix& prefix) + {*(XPTTypeDescriptorPrefix*)this = prefix;} + + MOZ_IMPLICIT nsXPTType(const uint8_t& prefix) + {*(uint8_t*)this = prefix;} + + nsXPTType& operator=(uint8_t val) + {flags = val; return *this;} + + nsXPTType& operator=(const nsXPTType& other) + {flags = other.flags; return *this;} + + operator uint8_t() const + {return flags;} + + // 'Arithmetic' here roughly means that the value is self-contained and + // doesn't depend on anything else in memory (ie: not a pointer, not an + // XPCOM object, not a jsval, etc). + // + // Supposedly this terminology comes from Harbison/Steele, but it's still + // a rather crappy name. We'd change it if it wasn't used all over the + // place in xptcall. :-( + bool IsArithmetic() const + {return flags <= T_WCHAR;} + + // We used to abuse 'pointer' flag bit in typelib format quite extensively. + // We've gotten rid of most of the cases, but there's still a fair amount + // of refactoring to be done in XPCWrappedJSClass before we can safely stop + // asking about this. In the mean time, we've got a temporary version of + // IsPointer() that should be equivalent to what's in the typelib. + bool deprecated_IsPointer() const + {return !IsArithmetic() && TagPart() != T_JSVAL;} + + bool IsInterfacePointer() const + { switch (TagPart()) { + default: + return false; + case T_INTERFACE: + case T_INTERFACE_IS: + return true; + } + } + + bool IsArray() const + {return TagPart() == T_ARRAY;} + + // 'Dependent' means that params of this type are dependent upon other + // params. e.g. an T_INTERFACE_IS is dependent upon some other param at + // runtime to say what the interface type of this param really is. + bool IsDependent() const + { switch (TagPart()) { + default: + return false; + case T_INTERFACE_IS: + case TD_ARRAY: + case T_PSTRING_SIZE_IS: + case T_PWSTRING_SIZE_IS: + return true; + } + } + + uint8_t TagPart() const + {return (uint8_t) (flags & XPT_TDP_TAGMASK);} + + enum + { + T_I8 = TD_INT8 , + T_I16 = TD_INT16 , + T_I32 = TD_INT32 , + T_I64 = TD_INT64 , + T_U8 = TD_UINT8 , + T_U16 = TD_UINT16 , + T_U32 = TD_UINT32 , + T_U64 = TD_UINT64 , + T_FLOAT = TD_FLOAT , + T_DOUBLE = TD_DOUBLE , + T_BOOL = TD_BOOL , + T_CHAR = TD_CHAR , + T_WCHAR = TD_WCHAR , + T_VOID = TD_VOID , + T_IID = TD_PNSIID , + T_DOMSTRING = TD_DOMSTRING , + T_CHAR_STR = TD_PSTRING , + T_WCHAR_STR = TD_PWSTRING , + T_INTERFACE = TD_INTERFACE_TYPE , + T_INTERFACE_IS = TD_INTERFACE_IS_TYPE, + T_ARRAY = TD_ARRAY , + T_PSTRING_SIZE_IS = TD_PSTRING_SIZE_IS , + T_PWSTRING_SIZE_IS = TD_PWSTRING_SIZE_IS , + T_UTF8STRING = TD_UTF8STRING , + T_CSTRING = TD_CSTRING , + T_ASTRING = TD_ASTRING , + T_JSVAL = TD_JSVAL + }; +// NO DATA - this a flyweight wrapper +}; + +class nsXPTParamInfo : public XPTParamDescriptor +{ +// NO DATA - this a flyweight wrapper +public: + MOZ_IMPLICIT nsXPTParamInfo(const XPTParamDescriptor& desc) + {*(XPTParamDescriptor*)this = desc;} + + + bool IsIn() const {return 0 != (XPT_PD_IS_IN(flags));} + bool IsOut() const {return 0 != (XPT_PD_IS_OUT(flags));} + bool IsRetval() const {return 0 != (XPT_PD_IS_RETVAL(flags));} + bool IsShared() const {return 0 != (XPT_PD_IS_SHARED(flags));} + + // Dipper types are one of the more inscrutable aspects of xpidl. In a + // nutshell, dippers are empty container objects, created and passed by + // the caller, and filled by the callee. The callee receives a fully- + // formed object, and thus does not have to construct anything. But + // the object is functionally empty, and the callee is responsible for + // putting something useful inside of it. + // + // XPIDL decides which types to make dippers. The list of these types + // is given in the isDipperType() function in typelib.py, and is currently + // limited to 4 string types. + // + // When a dipper type is declared as an 'out' parameter, xpidl internally + // converts it to an 'in', and sets the XPT_PD_DIPPER flag on it. For this + // reason, dipper types are sometimes referred to as 'out parameters + // masquerading as in'. The burden of maintaining this illusion falls mostly + // on XPConnect, which creates the empty containers, and harvest the results + // after the call. + bool IsDipper() const {return 0 != (XPT_PD_IS_DIPPER(flags));} + bool IsOptional() const {return 0 != (XPT_PD_IS_OPTIONAL(flags));} + const nsXPTType GetType() const {return type.prefix;} + + bool IsStringClass() const { + switch (GetType().TagPart()) { + case nsXPTType::T_ASTRING: + case nsXPTType::T_DOMSTRING: + case nsXPTType::T_UTF8STRING: + case nsXPTType::T_CSTRING: + return true; + default: + return false; + } + } + + // Whether this parameter is passed indirectly on the stack. This mainly + // applies to out/inout params, but we use it unconditionally for certain + // types. + bool IsIndirect() const {return IsOut() || + GetType().TagPart() == nsXPTType::T_JSVAL;} + + // NOTE: other activities on types are done via methods on nsIInterfaceInfo + +private: + nsXPTParamInfo(); // no implementation +// NO DATA - this a flyweight wrapper +}; + +class nsXPTMethodInfo : public XPTMethodDescriptor +{ +// NO DATA - this a flyweight wrapper +public: + MOZ_IMPLICIT nsXPTMethodInfo(const XPTMethodDescriptor& desc) + {*(XPTMethodDescriptor*)this = desc;} + + bool IsGetter() const {return 0 != (XPT_MD_IS_GETTER(flags) );} + bool IsSetter() const {return 0 != (XPT_MD_IS_SETTER(flags) );} + bool IsNotXPCOM() const {return 0 != (XPT_MD_IS_NOTXPCOM(flags));} + bool IsHidden() const {return 0 != (XPT_MD_IS_HIDDEN(flags) );} + bool WantsOptArgc() const {return 0 != (XPT_MD_WANTS_OPT_ARGC(flags));} + bool WantsContext() const {return 0 != (XPT_MD_WANTS_CONTEXT(flags));} + const char* GetName() const {return name;} + uint8_t GetParamCount() const {return num_args;} + /* idx was index before I got _sick_ of the warnings on Unix, sorry jband */ + const nsXPTParamInfo GetParam(uint8_t idx) const + { + NS_PRECONDITION(idx < GetParamCount(),"bad arg"); + return params[idx]; + } + const nsXPTParamInfo GetResult() const + {return result;} +private: + nsXPTMethodInfo(); // no implementation +// NO DATA - this a flyweight wrapper +}; + + +// forward declaration +struct nsXPTCMiniVariant; + +class nsXPTConstant : public XPTConstDescriptor +{ +// NO DATA - this a flyweight wrapper +public: + MOZ_IMPLICIT nsXPTConstant(const XPTConstDescriptor& desc) + {*(XPTConstDescriptor*)this = desc;} + + const char* GetName() const + {return name;} + + const nsXPTType GetType() const + {return type.prefix;} + + // XXX this is ugly. But sometimes you gotta do what you gotta do. + // A reinterpret_cast won't do the trick here. And this plain C cast + // works correctly and is safe enough. + // See http://bugzilla.mozilla.org/show_bug.cgi?id=49641 + const nsXPTCMiniVariant* GetValue() const + {return (nsXPTCMiniVariant*) &value;} +private: + nsXPTConstant(); // no implementation +// NO DATA - this a flyweight wrapper +}; + +#endif /* xptiinfo_h___ */ diff --git a/xpcom/reflect/xptinfo/xptiprivate.h b/xpcom/reflect/xptinfo/xptiprivate.h new file mode 100644 index 000000000..c32ef9c77 --- /dev/null +++ b/xpcom/reflect/xptinfo/xptiprivate.h @@ -0,0 +1,394 @@ +/* -*- 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/. */ + +/* Library-private header for Interface Info system. */ + +#ifndef xptiprivate_h___ +#define xptiprivate_h___ + +#include "nscore.h" +#include +#include "nsISupports.h" + +// this after nsISupports, to pick up IID +// so that xpt stuff doesn't try to define it itself... +#include "xpt_struct.h" +#include "xpt_xdr.h" + +#include "nsIInterfaceInfo.h" +#include "nsIInterfaceInfoManager.h" +#include "xptinfo.h" +#include "ShimInterfaceInfo.h" + +#include "nsIServiceManager.h" +#include "nsIFile.h" +#include "nsIDirectoryService.h" +#include "nsDirectoryServiceDefs.h" +#include "nsAppDirectoryServiceDefs.h" +#include "nsIWeakReference.h" + +#include "mozilla/ReentrantMonitor.h" +#include "mozilla/Mutex.h" +#include "mozilla/Attributes.h" + +#include "js/TypeDecls.h" + +#include "nsCRT.h" +#include "nsMemory.h" + +#include "nsCOMArray.h" +#include "nsQuickSort.h" + +#include "nsXPIDLString.h" + +#include "nsIInputStream.h" + +#include "nsHashKeys.h" +#include "nsDataHashtable.h" +#include "plstr.h" +#include "prprf.h" +#include "prio.h" +#include "prtime.h" +#include "prenv.h" + +#include +#include + +/***************************************************************************/ + +#if 0 && defined(DEBUG_jband) +#define LOG_RESOLVE(x) printf x +#define LOG_LOAD(x) printf x +#define LOG_AUTOREG(x) do{printf x; xptiInterfaceInfoManager::WriteToLog x;}while(0) +#else +#define LOG_RESOLVE(x) ((void)0) +#define LOG_LOAD(x) ((void)0) +#define LOG_AUTOREG(x) ((void)0) +#endif + +#if 1 && defined(DEBUG_jband) +#define SHOW_INFO_COUNT_STATS +#endif + +/***************************************************************************/ + +class xptiInterfaceInfo; +class xptiInterfaceEntry; +class xptiTypelibGuts; + +extern XPTArena* gXPTIStructArena; + +/***************************************************************************/ + +/***************************************************************************/ + +// No virtuals. +// These are always constructed in the struct arena using placement new. +// dtor need not be called. + +class xptiTypelibGuts +{ +public: + static xptiTypelibGuts* Create(XPTHeader* aHeader); + + XPTHeader* GetHeader() {return mHeader;} + uint16_t GetEntryCount() const {return mHeader->num_interfaces;} + + void SetEntryAt(uint16_t i, xptiInterfaceEntry* ptr) + { + NS_ASSERTION(mHeader,"bad state!"); + NS_ASSERTION(i < GetEntryCount(),"bad param!"); + mEntryArray[i] = ptr; + } + + xptiInterfaceEntry* GetEntryAt(uint16_t i); + const char* GetEntryNameAt(uint16_t i); + +private: + explicit xptiTypelibGuts(XPTHeader* aHeader) + : mHeader(aHeader) + { } + ~xptiTypelibGuts(); + +private: + XPTHeader* mHeader; // hold pointer into arena + xptiInterfaceEntry* mEntryArray[1]; // Always last. Sized to fit. +}; + +/***************************************************************************/ + +/***************************************************************************/ + +// This class exists to help xptiInterfaceInfo store a 4-state (2 bit) value +// and a set of bitflags in one 8bit value. See below. + +class xptiInfoFlags +{ + enum {STATE_MASK = 3}; +public: + explicit xptiInfoFlags(uint8_t n) : mData(n) {} + xptiInfoFlags(const xptiInfoFlags& r) : mData(r.mData) {} + + static uint8_t GetStateMask() + {return uint8_t(STATE_MASK);} + + void Clear() + {mData = 0;} + + uint8_t GetData() const + {return mData;} + + uint8_t GetState() const + {return mData & GetStateMask();} + + void SetState(uint8_t state) + {mData &= ~GetStateMask(); mData |= state;} + + void SetFlagBit(uint8_t flag, bool on) + {if(on) + mData |= ~GetStateMask() & flag; + else + mData &= GetStateMask() | ~flag;} + + bool GetFlagBit(uint8_t flag) const + {return (mData & flag) ? true : false;} + +private: + uint8_t mData; +}; + +/****************************************************/ + +// No virtual methods. +// We always create in the struct arena and construct using "placement new". +// No members need dtor calls. + +class xptiInterfaceEntry +{ +public: + static xptiInterfaceEntry* Create(const char* name, + const nsID& iid, + XPTInterfaceDescriptor* aDescriptor, + xptiTypelibGuts* aTypelib); + + enum { + PARTIALLY_RESOLVED = 1, + FULLY_RESOLVED = 2, + RESOLVE_FAILED = 3 + }; + + // Additional bit flags... + enum {SCRIPTABLE = 4, BUILTINCLASS = 8, HASNOTXPCOM = 16, + MAIN_PROCESS_SCRIPTABLE_ONLY = 32}; + + uint8_t GetResolveState() const {return mFlags.GetState();} + + bool IsFullyResolved() const + {return GetResolveState() == (uint8_t) FULLY_RESOLVED;} + + void SetScriptableFlag(bool on) + {mFlags.SetFlagBit(uint8_t(SCRIPTABLE),on);} + bool GetScriptableFlag() const + {return mFlags.GetFlagBit(uint8_t(SCRIPTABLE));} + void SetBuiltinClassFlag(bool on) + {mFlags.SetFlagBit(uint8_t(BUILTINCLASS),on);} + bool GetBuiltinClassFlag() const + {return mFlags.GetFlagBit(uint8_t(BUILTINCLASS));} + void SetMainProcessScriptableOnlyFlag(bool on) + {mFlags.SetFlagBit(uint8_t(MAIN_PROCESS_SCRIPTABLE_ONLY),on);} + bool GetMainProcessScriptableOnlyFlag() const + {return mFlags.GetFlagBit(uint8_t(MAIN_PROCESS_SCRIPTABLE_ONLY));} + + + // AddRef/Release are special and are not considered for the NOTXPCOM flag. + void SetHasNotXPCOMFlag() + { + mFlags.SetFlagBit(HASNOTXPCOM, true); + } + bool GetHasNotXPCOMFlag() const + { + return mFlags.GetFlagBit(HASNOTXPCOM); + } + + const nsID* GetTheIID() const {return &mIID;} + const char* GetTheName() const {return mName;} + + bool EnsureResolved() + {return IsFullyResolved() ? true : Resolve();} + + already_AddRefed InterfaceInfo(); + bool InterfaceInfoEquals(const xptiInterfaceInfo* info) const + {return info == mInfo;} + + void LockedInvalidateInterfaceInfo(); + void LockedInterfaceInfoDeathNotification() {mInfo = nullptr;} + + xptiInterfaceEntry* Parent() const { + NS_ASSERTION(IsFullyResolved(), "Parent() called while not resolved?"); + return mParent; + } + + const nsID& IID() const { return mIID; } + + ////////////////////// + // These non-virtual methods handle the delegated nsIInterfaceInfo methods. + + nsresult GetName(char * *aName); + nsresult GetIID(nsIID * *aIID); + nsresult IsScriptable(bool *_retval); + nsresult IsBuiltinClass(bool *_retval) { + *_retval = GetBuiltinClassFlag(); + return NS_OK; + } + nsresult IsMainProcessScriptableOnly(bool *_retval) { + *_retval = GetMainProcessScriptableOnlyFlag(); + return NS_OK; + } + // Except this one. + //nsresult GetParent(nsIInterfaceInfo * *aParent); + nsresult GetMethodCount(uint16_t *aMethodCount); + nsresult GetConstantCount(uint16_t *aConstantCount); + nsresult GetMethodInfo(uint16_t index, const nsXPTMethodInfo * *info); + nsresult GetMethodInfoForName(const char *methodName, uint16_t *index, const nsXPTMethodInfo * *info); + nsresult GetConstant(uint16_t index, JS::MutableHandleValue, char** constant); + nsresult GetInfoForParam(uint16_t methodIndex, const nsXPTParamInfo * param, nsIInterfaceInfo **_retval); + nsresult GetIIDForParam(uint16_t methodIndex, const nsXPTParamInfo * param, nsIID * *_retval); + nsresult GetTypeForParam(uint16_t methodIndex, const nsXPTParamInfo * param, uint16_t dimension, nsXPTType *_retval); + nsresult GetSizeIsArgNumberForParam(uint16_t methodIndex, const nsXPTParamInfo * param, uint16_t dimension, uint8_t *_retval); + nsresult GetInterfaceIsArgNumberForParam(uint16_t methodIndex, const nsXPTParamInfo * param, uint8_t *_retval); + nsresult IsIID(const nsIID * IID, bool *_retval); + nsresult GetNameShared(const char **name); + nsresult GetIIDShared(const nsIID * *iid); + nsresult IsFunction(bool *_retval); + nsresult HasAncestor(const nsIID * iid, bool *_retval); + nsresult GetIIDForParamNoAlloc(uint16_t methodIndex, const nsXPTParamInfo * param, nsIID *iid); + +private: + xptiInterfaceEntry(const char* name, + size_t nameLength, + const nsID& iid, + XPTInterfaceDescriptor* aDescriptor, + xptiTypelibGuts* aTypelib); + ~xptiInterfaceEntry(); + + void SetResolvedState(int state) + {mFlags.SetState(uint8_t(state));} + + bool Resolve(); + + // We only call these "*Locked" variants after locking. This is done to + // allow reentrace as files are loaded and various interfaces resolved + // without having to worry about the locked state. + + bool EnsureResolvedLocked() + {return IsFullyResolved() ? true : ResolveLocked();} + bool ResolveLocked(); + + // private helpers + + nsresult GetEntryForParam(uint16_t methodIndex, + const nsXPTParamInfo * param, + xptiInterfaceEntry** entry); + + nsresult GetTypeInArray(const nsXPTParamInfo* param, + uint16_t dimension, + const XPTTypeDescriptor** type); + + nsresult GetInterfaceIndexForParam(uint16_t methodIndex, + const nsXPTParamInfo* param, + uint16_t* interfaceIndex); + + already_AddRefed + GetShimForParam(uint16_t methodIndex, const nsXPTParamInfo* param); + +private: + nsID mIID; + XPTInterfaceDescriptor* mDescriptor; + + xptiTypelibGuts* mTypelib; + + xptiInterfaceEntry* mParent; // Valid only when fully resolved + + xptiInterfaceInfo* MOZ_UNSAFE_REF("The safety of this pointer is ensured " + "by the semantics of xptiWorkingSet.") + mInfo; // May come and go. + + uint16_t mMethodBaseIndex; + uint16_t mConstantBaseIndex; + + xptiInfoFlags mFlags; + + char mName[1]; // Always last. Sized to fit. +}; + +class xptiInterfaceInfo final : public nsIInterfaceInfo +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + + // Use delegation to implement (most!) of nsIInterfaceInfo. + NS_IMETHOD GetName(char * *aName) override { return !mEntry ? NS_ERROR_UNEXPECTED : mEntry->GetName(aName); } + NS_IMETHOD GetInterfaceIID(nsIID * *aIID) override { return !mEntry ? NS_ERROR_UNEXPECTED : mEntry->GetIID(aIID); } + NS_IMETHOD IsScriptable(bool *_retval) override { return !mEntry ? NS_ERROR_UNEXPECTED : mEntry->IsScriptable(_retval); } + NS_IMETHOD IsBuiltinClass(bool *_retval) override { return !mEntry ? NS_ERROR_UNEXPECTED : mEntry->IsBuiltinClass(_retval); } + NS_IMETHOD IsMainProcessScriptableOnly(bool *_retval) override { return !mEntry ? NS_ERROR_UNEXPECTED : mEntry->IsMainProcessScriptableOnly(_retval); } + // Except this one. + NS_IMETHOD GetParent(nsIInterfaceInfo * *aParent) override + { + if(!EnsureResolved() || !EnsureParent()) + return NS_ERROR_UNEXPECTED; + NS_IF_ADDREF(*aParent = mParent); + return NS_OK; + } + NS_IMETHOD GetMethodCount(uint16_t *aMethodCount) override { return !mEntry ? NS_ERROR_UNEXPECTED : mEntry->GetMethodCount(aMethodCount); } + NS_IMETHOD GetConstantCount(uint16_t *aConstantCount) override { return !mEntry ? NS_ERROR_UNEXPECTED : mEntry->GetConstantCount(aConstantCount); } + NS_IMETHOD GetMethodInfo(uint16_t index, const nsXPTMethodInfo * *info) override { return !mEntry ? NS_ERROR_UNEXPECTED : mEntry->GetMethodInfo(index, info); } + NS_IMETHOD GetMethodInfoForName(const char *methodName, uint16_t *index, const nsXPTMethodInfo * *info) override { return !mEntry ? NS_ERROR_UNEXPECTED : mEntry->GetMethodInfoForName(methodName, index, info); } + NS_IMETHOD GetConstant(uint16_t index, JS::MutableHandleValue constant, char** name) override { return !mEntry ? NS_ERROR_UNEXPECTED : mEntry->GetConstant(index, constant, name); } + NS_IMETHOD GetInfoForParam(uint16_t methodIndex, const nsXPTParamInfo * param, nsIInterfaceInfo **_retval) override { return !mEntry ? NS_ERROR_UNEXPECTED : mEntry->GetInfoForParam(methodIndex, param, _retval); } + NS_IMETHOD GetIIDForParam(uint16_t methodIndex, const nsXPTParamInfo * param, nsIID * *_retval) override { return !mEntry ? NS_ERROR_UNEXPECTED : mEntry->GetIIDForParam(methodIndex, param, _retval); } + NS_IMETHOD GetTypeForParam(uint16_t methodIndex, const nsXPTParamInfo * param, uint16_t dimension, nsXPTType *_retval) override { return !mEntry ? NS_ERROR_UNEXPECTED : mEntry->GetTypeForParam(methodIndex, param, dimension, _retval); } + NS_IMETHOD GetSizeIsArgNumberForParam(uint16_t methodIndex, const nsXPTParamInfo * param, uint16_t dimension, uint8_t *_retval) override { return !mEntry ? NS_ERROR_UNEXPECTED : mEntry->GetSizeIsArgNumberForParam(methodIndex, param, dimension, _retval); } + NS_IMETHOD GetInterfaceIsArgNumberForParam(uint16_t methodIndex, const nsXPTParamInfo * param, uint8_t *_retval) override { return !mEntry ? NS_ERROR_UNEXPECTED : mEntry->GetInterfaceIsArgNumberForParam(methodIndex, param, _retval); } + NS_IMETHOD IsIID(const nsIID * IID, bool *_retval) override { return !mEntry ? NS_ERROR_UNEXPECTED : mEntry->IsIID(IID, _retval); } + NS_IMETHOD GetNameShared(const char **name) override { return !mEntry ? NS_ERROR_UNEXPECTED : mEntry->GetNameShared(name); } + NS_IMETHOD GetIIDShared(const nsIID * *iid) override { return !mEntry ? NS_ERROR_UNEXPECTED : mEntry->GetIIDShared(iid); } + NS_IMETHOD IsFunction(bool *_retval) override { return !mEntry ? NS_ERROR_UNEXPECTED : mEntry->IsFunction(_retval); } + NS_IMETHOD HasAncestor(const nsIID * iid, bool *_retval) override { return !mEntry ? NS_ERROR_UNEXPECTED : mEntry->HasAncestor(iid, _retval); } + NS_IMETHOD GetIIDForParamNoAlloc(uint16_t methodIndex, const nsXPTParamInfo * param, nsIID *iid) override { return !mEntry ? NS_ERROR_UNEXPECTED : mEntry->GetIIDForParamNoAlloc(methodIndex, param, iid); } + +public: + explicit xptiInterfaceInfo(xptiInterfaceEntry* entry); + + void Invalidate(); + +private: + + ~xptiInterfaceInfo(); + + // Note that mParent might still end up as nullptr if we don't have one. + bool EnsureParent() + { + NS_ASSERTION(mEntry && mEntry->IsFullyResolved(), "bad EnsureParent call"); + return mParent || !mEntry->Parent() || BuildParent(); + } + + bool EnsureResolved() + { + return mEntry && mEntry->EnsureResolved(); + } + + bool BuildParent(); + + xptiInterfaceInfo(); // not implemented + +private: + xptiInterfaceEntry* mEntry; + RefPtr mParent; +}; + +/***************************************************************************/ + +#endif /* xptiprivate_h___ */ diff --git a/xpcom/rust/nsstring/Cargo.toml b/xpcom/rust/nsstring/Cargo.toml new file mode 100644 index 000000000..d86a1ad26 --- /dev/null +++ b/xpcom/rust/nsstring/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "nsstring" +version = "0.1.0" +authors = ["nobody@mozilla.com"] +license = "MPL-2.0" +description = "Rust bindings to xpcom string types" + +[dependencies] diff --git a/xpcom/rust/nsstring/gtest/Cargo.toml b/xpcom/rust/nsstring/gtest/Cargo.toml new file mode 100644 index 000000000..44897ec98 --- /dev/null +++ b/xpcom/rust/nsstring/gtest/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 = "../" } + +[lib] +path = "test.rs" diff --git a/xpcom/rust/nsstring/gtest/Test.cpp b/xpcom/rust/nsstring/gtest/Test.cpp new file mode 100644 index 000000000..93d2ee1d7 --- /dev/null +++ b/xpcom/rust/nsstring/gtest/Test.cpp @@ -0,0 +1,131 @@ +#include "gtest/gtest.h" +#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) +SIZE_ALIGN_CHECK(nsFixedString) +SIZE_ALIGN_CHECK(nsFixedCString) + +#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(mozilla::DeclVal().Member)); \ + EXPECT_EQ(size, alignof(decltype(mozilla::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, mFlags) +MEMBER_CHECK(nsCString, mData) +MEMBER_CHECK(nsCString, mLength) +MEMBER_CHECK(nsCString, mFlags) +MEMBER_CHECK(nsFixedString, mFixedCapacity) +MEMBER_CHECK(nsFixedString, mFixedBuf) +MEMBER_CHECK(nsFixedCString, mFixedCapacity) +MEMBER_CHECK(nsFixedCString, mFixedBuf) + +extern "C" void Rust_Test_NsStringFlags(uint32_t* f_none, + uint32_t* f_terminated, + uint32_t* f_voided, + uint32_t* f_shared, + uint32_t* f_owned, + uint32_t* f_fixed, + uint32_t* f_literal, + uint32_t* f_class_fixed); +TEST(RustNsString, NsStringFlags) { + uint32_t f_none, f_terminated, f_voided, f_shared, f_owned, f_fixed, f_literal, f_class_fixed; + Rust_Test_NsStringFlags(&f_none, &f_terminated, + &f_voided, &f_shared, + &f_owned, &f_fixed, + &f_literal, &f_class_fixed); + EXPECT_EQ(f_none, nsAString::F_NONE); + EXPECT_EQ(f_none, nsACString::F_NONE); + EXPECT_EQ(f_terminated, nsAString::F_TERMINATED); + EXPECT_EQ(f_terminated, nsACString::F_TERMINATED); + EXPECT_EQ(f_voided, nsAString::F_VOIDED); + EXPECT_EQ(f_voided, nsACString::F_VOIDED); + EXPECT_EQ(f_shared, nsAString::F_SHARED); + EXPECT_EQ(f_shared, nsACString::F_SHARED); + EXPECT_EQ(f_owned, nsAString::F_OWNED); + EXPECT_EQ(f_owned, nsACString::F_OWNED); + EXPECT_EQ(f_fixed, nsAString::F_FIXED); + EXPECT_EQ(f_fixed, nsACString::F_FIXED); + EXPECT_EQ(f_literal, nsAString::F_LITERAL); + EXPECT_EQ(f_literal, nsACString::F_LITERAL); + EXPECT_EQ(f_class_fixed, nsAString::F_CLASS_FIXED); + EXPECT_EQ(f_class_fixed, nsACString::F_CLASS_FIXED); +} + +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_FixedAssignFromCpp(); +TEST(RustNsString, FixedAssignFromCpp) { + Rust_FixedAssignFromCpp(); +} +extern "C" void Rust_AutoAssignFromCpp(); +TEST(RustNsString, AutoAssignFromCpp) { + Rust_AutoAssignFromCpp(); +} + +extern "C" void Rust_StringWrite(); +TEST(RustNsString, StringWrite) { + Rust_StringWrite(); +} diff --git a/xpcom/rust/nsstring/gtest/moz.build b/xpcom/rust/nsstring/gtest/moz.build new file mode 100644 index 000000000..5bed9e57e --- /dev/null +++ b/xpcom/rust/nsstring/gtest/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/. + +if CONFIG['MOZ_RUST']: + UNIFIED_SOURCES += [ + 'Test.cpp' + ] + +FINAL_LIBRARY = 'xul-gtest' diff --git a/xpcom/rust/nsstring/gtest/test.rs b/xpcom/rust/nsstring/gtest/test.rs new file mode 100644 index 000000000..2968a1be7 --- /dev/null +++ b/xpcom/rust/nsstring/gtest/test.rs @@ -0,0 +1,112 @@ +#![allow(non_snake_case)] + +#[macro_use] +extern crate nsstring; + +use std::fmt::Write; +use std::ffi::CString; +use std::os::raw::c_char; +use nsstring::*; + +fn nonfatal_fail(msg: String) { + extern "C" { + fn GTest_ExpectFailure(message: *const c_char); + } + unsafe { + GTest_ExpectFailure(CString::new(msg).unwrap().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 fn Rust_StringFromCpp(cs: *const nsACString, s: *const nsAString) { + unsafe { + expect_eq!(&*cs, "Hello, World!"); + expect_eq!(&*s, "Hello, World!"); + } +} + +#[no_mangle] +pub extern 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 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 fn Rust_FixedAssignFromCpp() { + let mut cs_buf: [u8; 64] = [0; 64]; + let cs_buf_ptr = &cs_buf as *const _ as usize; + let mut s_buf: [u16; 64] = [0; 64]; + let s_buf_ptr = &s_buf as *const _ as usize; + let mut cs = nsFixedCString::new(&mut cs_buf); + let mut s = nsFixedString::new(&mut s_buf); + unsafe { + Cpp_AssignFromCpp(&mut *cs, &mut *s); + } + expect_eq!(cs, "Hello, World!"); + expect_eq!(s, "Hello, World!"); + expect_eq!(cs.as_ptr() as usize, cs_buf_ptr); + expect_eq!(s.as_ptr() as usize, s_buf_ptr); +} + +#[no_mangle] +pub extern fn Rust_AutoAssignFromCpp() { + ns_auto_cstring!(cs); + ns_auto_string!(s); + unsafe { + Cpp_AssignFromCpp(&mut *cs, &mut *s); + } + expect_eq!(cs, "Hello, World!"); + expect_eq!(s, "Hello, World!"); +} + +#[no_mangle] +pub extern fn Rust_StringWrite() { + ns_auto_cstring!(cs); + ns_auto_string!(s); + + 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"); +} + diff --git a/xpcom/rust/nsstring/src/lib.rs b/xpcom/rust/nsstring/src/lib.rs new file mode 100644 index 000000000..cd518f3c5 --- /dev/null +++ b/xpcom/rust/nsstring/src/lib.rs @@ -0,0 +1,853 @@ +//! 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<'a>` for string struct members which don't leave rust, and +//! as an intermediate between rust string data structures (such as `String`, +//! `Vec`, `&str`, and `&[u16]`) and `&{mut,} nsA[C]String` (using +//! `ns[C]String::from(value)`). These conversions, when possible, will not +//! perform any allocations. +//! +//! Use `nsFixed[C]String` or `ns_auto_[c]string!` for dynamic stack allocated +//! strings which are expected to hold short string values. +//! +//! Use `*{const,mut} nsA[C]String` (`{const,} nsA[C]String*` in C++) for +//! function arguments passed across the rust/C++ language boundary. +//! +//! Use `ns[C]StringRepr` for string struct members which are shared between +//! rust and C++, but be careful, because this type lacks a `Drop` +//! implementation. +//! +//! # 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]String<'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]String`s can be constructed either with `ns[C]String::new()`, which +//! creates an empty `ns[C]String<'static>`, or through one of the provided +//! `From` implementations. Both string types may be constructed `From<&'a +//! str>`, with `nsCString` having a `'a` lifetime, as the storage is shared +//! with the `str`, while `nsString` has a `'static` lifetime, as its storage +//! has to be transcoded. +//! +//! When passing this type by reference, prefer passing a `&nsA[C]String` or +//! `&mut nsA[C]String`. to passing this type. +//! +//! This type is _not_ `#[repr(C)]`, as it has a `Drop` impl, which in versions +//! of `rustc < 1.13` adds drop flags to the struct, which messes up the layout, +//! making it unsafe to pass across the FFI boundary. The rust compiler will +//! warn if this type appears in `extern "C"` function definitions. +//! +//! 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 type is similar to the C++ type of the same name. +//! +//! ## `nsFixed[C]String<'a>` +//! +//! This type is a string type with fixed backing storage. It is created with +//! `nsFixed[C]String::new(buffer)`, passing a mutable reference to a buffer as +//! the argument. This buffer will be used as backing storage whenever the +//! resulting string will fit within it, falling back to heap allocations only +//! when the string size exceeds that of the backing buffer. +//! +//! Like `ns[C]String`, this type dereferences to `nsA[C]String` which provides +//! the methods for manipulating the type, and is not `#[repr(C)]`. +//! +//! When passing this type by reference, prefer passing a `&nsA[C]String` or +//! `&mut nsA[C]String`. to passing this type. +//! +//! This type is _not_ `#[repr(C)]`, as it has a `Drop` impl, which in versions +//! of `rustc < 1.13` adds drop flags to the struct, which messes up the layout, +//! making it unsafe to pass across the FFI boundary. The rust compiler will +//! warn if this type appears in `extern "C"` function definitions. +//! +//! 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 type is similar to the C++ type of the same name. +//! +//! ## `ns_auto_[c]string!($name)` +//! +//! This is a helper macro which defines a fixed size, (currently 64 character), +//! backing array on the stack, and defines a local variable with name `$name` +//! which is a `nsFixed[C]String` using this buffer as its backing storage. +//! +//! Usage of this macro is similar to the C++ type `nsAuto[C]String`, but could +//! not be implemented as a basic type due to the differences between rust and +//! C++'s move semantics. +//! +//! ## `ns[C]StringRepr` +//! +//! This type represents a C++ `ns[C]String`. This type is `#[repr(C)]` and is +//! safe to use in struct definitions which are shared across the language +//! boundary. It automatically dereferences to `&{mut,} nsA[C]String`, and thus +//! can be treated similarially to `ns[C]String`. +//! +//! If this type is dropped in rust, it will not free its backing storage. This +//! is because types implementing `Drop` have a drop flag added, which messes up +//! the layout of this type. When drop flags are removed, which should happen in +//! `rustc 1.13` (see rust-lang/rust#35764), this type will likely be removed, +//! and replaced with direct usage of `ns[C]String<'a>`, as its layout may be +//! identical. This module provides rust bindings to our xpcom ns[C]String +//! types. + +#![allow(non_camel_case_types)] + +use std::ops::{Deref, DerefMut}; +use std::marker::PhantomData; +use std::slice; +use std::ptr; +use std::mem; +use std::fmt; +use std::cmp; +use std::str; +use std::u32; + +////////////////////////////////// +// Internal Implemenation Flags // +////////////////////////////////// + +const F_NONE: u32 = 0; // no flags + +// data flags are in the lower 16-bits +const F_TERMINATED: u32 = 1 << 0; // IsTerminated returns true +const F_VOIDED: u32 = 1 << 1; // IsVoid returns true +const F_SHARED: u32 = 1 << 2; // mData points to a heap-allocated, shared buffer +const F_OWNED: u32 = 1 << 3; // mData points to a heap-allocated, raw buffer +const F_FIXED: u32 = 1 << 4; // mData points to a fixed-size writable, dependent buffer +const F_LITERAL: u32 = 1 << 5; // mData points to a string literal; F_TERMINATED will also be set + +// class flags are in the upper 16-bits +const F_CLASS_FIXED: u32 = 1 << 16; // indicates that |this| is of type nsTFixedString + +//////////////////////////////////// +// Generic String Bindings Macros // +//////////////////////////////////// + +macro_rules! define_string_types { + { + char_t = $char_t: ty; + AString = $AString: ident; + String = $String: ident; + FixedString = $FixedString: ident; + + StringRepr = $StringRepr: ident; + FixedStringRepr = $FixedStringRepr: ident; + AutoStringRepr = $AutoStringRepr: 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. Due to drop flags, our ns[C]String + /// type does not have identical layout. When drop flags are removed, + /// this type will likely be made a private implementation detail, and + /// its uses will be replaced with `ns[C]String`. + /// + /// This struct will leak its data if dropped from rust. See the module + /// documentation for more information on this type. + #[repr(C)] + pub struct $StringRepr { + data: *const $char_t, + length: u32, + flags: u32, + } + + impl Deref for $StringRepr { + type Target = $AString; + fn deref(&self) -> &$AString { + unsafe { + mem::transmute(self) + } + } + } + + impl DerefMut for $StringRepr { + fn deref_mut(&mut self) -> &mut $AString { + unsafe { + mem::transmute(self) + } + } + } + + /// The representation of a nsFixed[C]String type in C++. This type is + /// used internally by our definition of nsFixed[C]String to ensure layout + /// compatibility with the C++ nsFixed[C]String type. + #[repr(C)] + struct $FixedStringRepr { + base: $StringRepr, + capacity: u32, + buffer: *mut $char_t, + } + + /// 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 Deref for $AString { + type Target = [$char_t]; + fn deref(&self) -> &[$char_t] { + unsafe { + // This is legal, as all $AString values actually point to a + // $StringRepr + let this: &$StringRepr = mem::transmute(self); + if this.data.is_null() { + debug_assert!(this.length == 0); + // Use an arbitrary non-null value as the pointer + slice::from_raw_parts(0x1 as *const $char_t, 0) + } else { + slice::from_raw_parts(this.data, this.length as usize) + } + } + } + } + + 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<'a> cmp::PartialEq<$String<'a>> for $AString { + fn eq(&self, other: &$String<'a>) -> bool { + self.eq(&**other) + } + } + + impl<'a> cmp::PartialEq<$FixedString<'a>> for $AString { + fn eq(&self, other: &$FixedString<'a>) -> bool { + self.eq(&**other) + } + } + + pub struct $String<'a> { + hdr: $StringRepr, + _marker: PhantomData<&'a [$char_t]>, + } + + impl $String<'static> { + pub fn new() -> $String<'static> { + $String { + hdr: $StringRepr { + data: ptr::null(), + length: 0, + flags: F_NONE, + }, + _marker: PhantomData, + } + } + } + + impl<'a> Deref for $String<'a> { + type Target = $AString; + fn deref(&self) -> &$AString { + &self.hdr + } + } + + impl<'a> DerefMut for $String<'a> { + fn deref_mut(&mut self) -> &mut $AString { + &mut self.hdr + } + } + + impl<'a> From<&'a String> for $String<'a> { + fn from(s: &'a String) -> $String<'a> { + $String::from(&s[..]) + } + } + + impl<'a> From<&'a Vec<$char_t>> for $String<'a> { + fn from(s: &'a Vec<$char_t>) -> $String<'a> { + $String::from(&s[..]) + } + } + + impl<'a> From<&'a [$char_t]> for $String<'a> { + fn from(s: &'a [$char_t]) -> $String<'a> { + assert!(s.len() < (u32::MAX as usize)); + $String { + hdr: $StringRepr { + data: s.as_ptr(), + length: s.len() as u32, + flags: F_NONE, + }, + _marker: PhantomData, + } + } + } + + impl From> for $String<'static> { + fn from(s: Box<[$char_t]>) -> $String<'static> { + assert!(s.len() < (u32::MAX as usize)); + // SAFETY NOTE: This method produces an F_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 length = s.len() as u32; + let ptr = s.as_ptr(); + mem::forget(s); + $String { + hdr: $StringRepr { + data: ptr, + length: length, + flags: F_OWNED, + }, + _marker: PhantomData, + } + } + } + + impl From> for $String<'static> { + fn from(s: Vec<$char_t>) -> $String<'static> { + s.into_boxed_slice().into() + } + } + + impl<'a> From<&'a $AString> for $String<'static> { + fn from(s: &'a $AString) -> $String<'static> { + let mut string = $String::new(); + string.assign(s); + string + } + } + + impl<'a> fmt::Write for $String<'a> { + fn write_str(&mut self, s: &str) -> Result<(), fmt::Error> { + $AString::write_str(self, s) + } + } + + impl<'a> fmt::Display for $String<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + <$AString as fmt::Display>::fmt(self, f) + } + } + + impl<'a> fmt::Debug for $String<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + <$AString as fmt::Debug>::fmt(self, f) + } + } + + impl<'a> cmp::PartialEq for $String<'a> { + fn eq(&self, other: &$String<'a>) -> bool { + $AString::eq(self, other) + } + } + + impl<'a> cmp::PartialEq<[$char_t]> for $String<'a> { + fn eq(&self, other: &[$char_t]) -> bool { + $AString::eq(self, other) + } + } + + impl<'a, 'b> cmp::PartialEq<&'b [$char_t]> for $String<'a> { + fn eq(&self, other: &&'b [$char_t]) -> bool { + $AString::eq(self, *other) + } + } + + impl<'a> cmp::PartialEq for $String<'a> { + fn eq(&self, other: &str) -> bool { + $AString::eq(self, other) + } + } + + impl<'a, 'b> cmp::PartialEq<&'b str> for $String<'a> { + fn eq(&self, other: &&'b str) -> bool { + $AString::eq(self, *other) + } + } + + impl<'a> Drop for $String<'a> { + fn drop(&mut self) { + unsafe { + self.finalize(); + } + } + } + + /// A nsFixed[C]String is a string which uses a fixed size mutable + /// backing buffer for storing strings which will fit within that + /// buffer, rather than using heap allocations. + pub struct $FixedString<'a> { + hdr: $FixedStringRepr, + _marker: PhantomData<&'a mut [$char_t]>, + } + + impl<'a> $FixedString<'a> { + pub fn new(buf: &'a mut [$char_t]) -> $FixedString<'a> { + let len = buf.len(); + assert!(len < (u32::MAX as usize)); + let buf_ptr = buf.as_mut_ptr(); + $FixedString { + hdr: $FixedStringRepr { + base: $StringRepr { + data: ptr::null(), + length: 0, + flags: F_CLASS_FIXED, + }, + capacity: len as u32, + buffer: buf_ptr, + }, + _marker: PhantomData, + } + } + } + + impl<'a> Deref for $FixedString<'a> { + type Target = $AString; + fn deref(&self) -> &$AString { + &self.hdr.base + } + } + + impl<'a> DerefMut for $FixedString<'a> { + fn deref_mut(&mut self) -> &mut $AString { + &mut self.hdr.base + } + } + + impl<'a> fmt::Write for $FixedString<'a> { + fn write_str(&mut self, s: &str) -> Result<(), fmt::Error> { + $AString::write_str(self, s) + } + } + + impl<'a> fmt::Display for $FixedString<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + <$AString as fmt::Display>::fmt(self, f) + } + } + + impl<'a> fmt::Debug for $FixedString<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + <$AString as fmt::Debug>::fmt(self, f) + } + } + + impl<'a> cmp::PartialEq for $FixedString<'a> { + fn eq(&self, other: &$FixedString<'a>) -> bool { + $AString::eq(self, other) + } + } + + impl<'a> cmp::PartialEq<[$char_t]> for $FixedString<'a> { + fn eq(&self, other: &[$char_t]) -> bool { + $AString::eq(self, other) + } + } + + impl<'a, 'b> cmp::PartialEq<&'b [$char_t]> for $FixedString<'a> { + fn eq(&self, other: &&'b [$char_t]) -> bool { + $AString::eq(self, *other) + } + } + + impl<'a> cmp::PartialEq for $FixedString<'a> { + fn eq(&self, other: &str) -> bool { + $AString::eq(self, other) + } + } + + impl<'a, 'b> cmp::PartialEq<&'b str> for $FixedString<'a> { + fn eq(&self, other: &&'b str) -> bool { + $AString::eq(self, *other) + } + } + + impl<'a> Drop for $FixedString<'a> { + fn drop(&mut self) { + unsafe { + self.finalize(); + } + } + } + } +} + +/////////////////////////////////////////// +// Bindings for nsCString (u8 char type) // +/////////////////////////////////////////// + +define_string_types! { + char_t = u8; + + AString = nsACString; + String = nsCString; + FixedString = nsFixedCString; + + StringRepr = nsCStringRepr; + FixedStringRepr = nsFixedCStringRepr; + AutoStringRepr = nsAutoCStringRepr; +} + +impl nsACString { + /// Leaves the nsACString in an unstable state with a dangling data pointer. + /// Should only be used in drop implementations of rust types which wrap + /// this type. + unsafe fn finalize(&mut self) { + Gecko_FinalizeCString(self); + } + + pub fn assign(&mut self, other: &nsACString) { + unsafe { + Gecko_AssignCString(self as *mut _, other as *const _); + } + } + + pub fn assign_utf16(&mut self, other: &nsAString) { + self.assign(&nsCString::new()); + self.append_utf16(other); + } + + pub fn append(&mut self, other: &nsACString) { + unsafe { + Gecko_AppendCString(self as *mut _, other as *const _); + } + } + + pub fn append_utf16(&mut self, other: &nsAString) { + unsafe { + Gecko_AppendUTF16toCString(self as *mut _, other as *const _); + } + } + + pub unsafe fn as_str_unchecked(&self) -> &str { + str::from_utf8_unchecked(self) + } +} + +impl<'a> From<&'a str> for nsCString<'a> { + fn from(s: &'a str) -> nsCString<'a> { + s.as_bytes().into() + } +} + +impl From> for nsCString<'static> { + fn from(s: Box) -> nsCString<'static> { + s.into_string().into() + } +} + +impl From for nsCString<'static> { + fn from(s: String) -> nsCString<'static> { + 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(&nsCString::from(s)); + Ok(()) + } +} + +impl fmt::Display for nsACString { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + fmt::Display::fmt(&String::from_utf8_lossy(&self[..]), f) + } +} + +impl fmt::Debug for nsACString { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + fmt::Debug::fmt(&String::from_utf8_lossy(&self[..]), f) + } +} + +impl cmp::PartialEq for nsACString { + fn eq(&self, other: &str) -> bool { + &self[..] == other.as_bytes() + } +} + +#[macro_export] +macro_rules! ns_auto_cstring { + ($name:ident) => { + let mut buf: [u8; 64] = [0; 64]; + let mut $name = $crate::nsFixedCString::new(&mut buf); + } +} + +/////////////////////////////////////////// +// Bindings for nsString (u16 char type) // +/////////////////////////////////////////// + +define_string_types! { + char_t = u16; + + AString = nsAString; + String = nsString; + FixedString = nsFixedString; + + StringRepr = nsStringRepr; + FixedStringRepr = nsFixedStringRepr; + AutoStringRepr = nsAutoStringRepr; +} + +impl nsAString { + /// Leaves the nsAString in an unstable state with a dangling data pointer. + /// Should only be used in drop implementations of rust types which wrap + /// this type. + unsafe fn finalize(&mut self) { + Gecko_FinalizeString(self); + } + + pub fn assign(&mut self, other: &nsAString) { + unsafe { + Gecko_AssignString(self as *mut _, other as *const _); + } + } + + pub fn assign_utf8(&mut self, other: &nsACString) { + self.assign(&nsString::new()); + self.append_utf8(other); + } + + pub fn append(&mut self, other: &nsAString) { + unsafe { + Gecko_AppendString(self as *mut _, other as *const _); + } + } + + pub fn append_utf8(&mut self, other: &nsACString) { + unsafe { + Gecko_AppendUTF8toString(self as *mut _, other as *const _); + } + } +} + +// NOTE: The From impl for a string slice for nsString produces a <'static> +// lifetime, as it allocates. +impl<'a> From<&'a str> for nsString<'static> { + fn from(s: &'a str) -> nsString<'static> { + s.encode_utf16().collect::>().into() + } +} + +// 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_utf8(&nsCString::from(s)); + Ok(()) + } +} + +impl fmt::Display for nsAString { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + fmt::Display::fmt(&String::from_utf16_lossy(&self[..]), f) + } +} + +impl fmt::Debug for nsAString { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + fmt::Debug::fmt(&String::from_utf16_lossy(&self[..]), f) + } +} + +impl cmp::PartialEq for nsAString { + fn eq(&self, other: &str) -> bool { + other.encode_utf16().eq(self.iter().cloned()) + } +} + +#[macro_export] +macro_rules! ns_auto_string { + ($name:ident) => { + let mut buf: [u16; 64] = [0; 64]; + let mut $name = $crate::nsFixedString::new(&mut buf); + } +} + +// NOTE: These bindings currently only expose infallible operations. Perhaps +// consider allowing for fallible methods? +extern "C" { + // Gecko implementation in nsSubstring.cpp + fn Gecko_FinalizeCString(this: *mut nsACString); + fn Gecko_AssignCString(this: *mut nsACString, other: *const nsACString); + fn Gecko_AppendCString(this: *mut nsACString, other: *const nsACString); + + fn Gecko_FinalizeString(this: *mut nsAString); + fn Gecko_AssignString(this: *mut nsAString, other: *const nsAString); + fn Gecko_AppendString(this: *mut nsAString, other: *const nsAString); + + // Gecko implementation in nsReadableUtils.cpp + fn Gecko_AppendUTF16toCString(this: *mut nsACString, other: *const nsAString); + fn Gecko_AppendUTF8toString(this: *mut nsAString, other: *const nsACString); +} + +////////////////////////////////////// +// 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::{ + nsFixedCStringRepr, + nsFixedStringRepr, + nsCStringRepr, + nsStringRepr, + F_NONE, + F_TERMINATED, + F_VOIDED, + F_SHARED, + F_OWNED, + F_FIXED, + F_LITERAL, + F_CLASS_FIXED, + }; + 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 extern fn $fname(size: *mut usize, align: *mut usize) { + unsafe { + *size = mem::size_of::<$T>(); + *align = mem::align_of::<$T>(); + } + } + } + } + + size_align_check!(nsStringRepr, Rust_Test_ReprSizeAlign_nsString); + size_align_check!(nsCStringRepr, Rust_Test_ReprSizeAlign_nsCString); + size_align_check!(nsFixedStringRepr, Rust_Test_ReprSizeAlign_nsFixedString); + size_align_check!(nsFixedCStringRepr, Rust_Test_ReprSizeAlign_nsFixedCString); + + /// 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, $member:ident, $method:ident) => { + #[no_mangle] + #[allow(non_snake_case)] + pub extern fn $method(size: *mut usize, + align: *mut usize, + offset: *mut usize) { + unsafe { + // Create a temporary value of type T to get offsets, sizes + // and aligns off of + let tmp: $T = mem::zeroed(); + *size = mem::size_of_val(&tmp.$member); + *align = mem::align_of_val(&tmp.$member); + *offset = + (&tmp.$member as *const _ as usize) - + (&tmp as *const _ as usize); + mem::forget(tmp); + } + } + } + } + + member_check!(nsStringRepr, data, Rust_Test_Member_nsString_mData); + member_check!(nsStringRepr, length, Rust_Test_Member_nsString_mLength); + member_check!(nsStringRepr, flags, Rust_Test_Member_nsString_mFlags); + member_check!(nsCStringRepr, data, Rust_Test_Member_nsCString_mData); + member_check!(nsCStringRepr, length, Rust_Test_Member_nsCString_mLength); + member_check!(nsCStringRepr, flags, Rust_Test_Member_nsCString_mFlags); + member_check!(nsFixedStringRepr, capacity, Rust_Test_Member_nsFixedString_mFixedCapacity); + member_check!(nsFixedStringRepr, buffer, Rust_Test_Member_nsFixedString_mFixedBuf); + member_check!(nsFixedCStringRepr, capacity, Rust_Test_Member_nsFixedCString_mFixedCapacity); + member_check!(nsFixedCStringRepr, buffer, Rust_Test_Member_nsFixedCString_mFixedBuf); + + #[no_mangle] + #[allow(non_snake_case)] + pub extern fn Rust_Test_NsStringFlags(f_none: *mut u32, + f_terminated: *mut u32, + f_voided: *mut u32, + f_shared: *mut u32, + f_owned: *mut u32, + f_fixed: *mut u32, + f_literal: *mut u32, + f_class_fixed: *mut u32) { + unsafe { + *f_none = F_NONE; + *f_terminated = F_TERMINATED; + *f_voided = F_VOIDED; + *f_shared = F_SHARED; + *f_owned = F_OWNED; + *f_fixed = F_FIXED; + *f_literal = F_LITERAL; + *f_class_fixed = F_CLASS_FIXED; + } + } +} diff --git a/xpcom/string/README.html b/xpcom/string/README.html new file mode 100644 index 000000000..4a0927c65 --- /dev/null +++ b/xpcom/string/README.html @@ -0,0 +1,11 @@ + + + +

managing sequences of characters

+

+ +

+ + diff --git a/xpcom/string/crashtests/1113005-frame.html b/xpcom/string/crashtests/1113005-frame.html new file mode 100644 index 000000000..505fc22f1 --- /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 000000000..e377bb637 --- /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 000000000..4f2afd1a6 --- /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 000000000..3f89a0836 --- /dev/null +++ b/xpcom/string/crashtests/395651-1.html @@ -0,0 +1,31 @@ + + + + + +
+ + diff --git a/xpcom/string/crashtests/crashtests.list b/xpcom/string/crashtests/crashtests.list new file mode 100644 index 000000000..8562f1ad8 --- /dev/null +++ b/xpcom/string/crashtests/crashtests.list @@ -0,0 +1,3 @@ +load 394275-1.html +load 395651-1.html +load 1113005.html diff --git a/xpcom/string/moz.build b/xpcom/string/moz.build new file mode 100644 index 000000000..6ad7d7cc8 --- /dev/null +++ b/xpcom/string/moz.build @@ -0,0 +1,61 @@ +# -*- 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 += [ + 'nsAString.h', + 'nsCharTraits.h', + 'nsDependentString.h', + 'nsDependentSubstring.h', + 'nsEmbedString.h', + 'nsLiteralString.h', + 'nsPrintfCString.h', + 'nsPromiseFlatString.h', + 'nsReadableUtils.h', + 'nsString.h', + 'nsStringBuffer.h', + 'nsStringFwd.h', + 'nsStringIterator.h', + 'nsSubstring.h', + 'nsSubstringTuple.h', + 'nsTDependentString.h', + 'nsTDependentSubstring.h', + 'nsTLiteralString.h', + 'nsTPromiseFlatString.h', + 'nsTString.h', + 'nsTSubstring.h', + 'nsTSubstringTuple.h', + 'nsUTF8Utils.h', + 'nsXPCOMStrings.h', + 'nsXPIDLString.h', + 'string-template-def-char.h', + 'string-template-def-unichar.h', + 'string-template-undef.h', +] + +UNIFIED_SOURCES += [ + 'nsDependentString.cpp', + 'nsDependentSubstring.cpp', + 'nsPromiseFlatString.cpp', + 'nsReadableUtils.cpp', + 'nsString.cpp', + 'nsStringComparator.cpp', + 'nsStringObsolete.cpp', + 'nsSubstring.cpp', + 'nsSubstringTuple.cpp', +] + +# Are we targeting x86 or x86-64? If so, compile the SSE2 functions for +# nsUTF8Utils.cpp and nsReadableUtils.cpp. +if CONFIG['INTEL_ARCHITECTURE']: + SOURCES += ['nsUTF8UtilsSSE2.cpp'] + SOURCES['nsUTF8UtilsSSE2.cpp'].flags += CONFIG['SSE2_FLAGS'] + SOURCES += ['nsReadableUtilsSSE2.cpp'] + SOURCES['nsReadableUtilsSSE2.cpp'].flags += CONFIG['SSE2_FLAGS'] + +FINAL_LIBRARY = 'xul' diff --git a/xpcom/string/nsAString.h b/xpcom/string/nsAString.h new file mode 100644 index 000000000..0cbea0dc7 --- /dev/null +++ b/xpcom/string/nsAString.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/. */ +// IWYU pragma: private, include "nsString.h" + +#ifndef nsAString_h___ +#define nsAString_h___ + +#include "nsStringFwd.h" +#include "nsStringIterator.h" + +#include +#include + +#define kNotFound -1 + +// declare nsAString +#include "string-template-def-unichar.h" +#include "nsTSubstring.h" +#include "string-template-undef.h" + +// declare nsACString +#include "string-template-def-char.h" +#include "nsTSubstring.h" +#include "string-template-undef.h" + + +/** + * ASCII case-insensitive comparator. (for Unicode case-insensitive + * comparision, see nsUnicharUtils.h) + */ +class nsCaseInsensitiveCStringComparator + : public nsCStringComparator +{ +public: + nsCaseInsensitiveCStringComparator() + { + } + typedef char char_type; + + virtual int operator()(const char_type*, const char_type*, + uint32_t, uint32_t) const override; +}; + +class nsCaseInsensitiveCStringArrayComparator +{ +public: + template + bool Equals(const A& aStrA, const B& aStrB) const + { + return aStrA.Equals(aStrB, nsCaseInsensitiveCStringComparator()); + } +}; + +// included here for backwards compatibility +#ifndef nsSubstringTuple_h___ +#include "nsSubstringTuple.h" +#endif + +#endif // !defined(nsAString_h___) diff --git a/xpcom/string/nsCharTraits.h b/xpcom/string/nsCharTraits.h new file mode 100644 index 000000000..d93e1f5dc --- /dev/null +++ b/xpcom/string/nsCharTraits.h @@ -0,0 +1,587 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 |memcpy|, et al + +#include "nscore.h" // for |char16_t| + +// 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) +// 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; + + static void + assign(char_type& aLhs, char_type aRhs) + { + aLhs = aRhs; + } + + + // 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 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 char_type* + assign(char_type* aStr, size_t aN, char_type aChar) + { + char_type* result = aStr; + while (aN--) { + assign(*aStr++, aChar); + } + return result; + } + + 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; + } + + // 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; + + static void + assign(char_type& aLhs, char_type aRhs) + { + aLhs = aRhs; + } + + + // 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 char_type* + copyASCII(char_type* aStr1, const char* aStr2, size_t aN) + { + return copy(aStr1, aStr2, aN); + } + + static char_type* + assign(char_type* aStr, size_t aN, char_type aChar) + { + return static_cast(memset(aStr, to_int_type(aChar), 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); + } + + // 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 uint32_t + readable_distance(const InputIterator& aFirst, const InputIterator& aLast) + { + // assumes single fragment + return uint32_t(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 uint32_t + readable_distance(CharT* aStr) + { + return uint32_t(nsCharTraits::length(aStr)); + // return numeric_limits::max(); + } + + static uint32_t + readable_distance(CharT* aFirst, CharT* aLast) + { + return uint32_t(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, + uint32_t aN) + { + aIter.write(aStr, aN); + } +}; + +template +struct nsCharSinkTraits +{ + static void + write(CharT*& aIter, const CharT* aStr, uint32_t aN) + { + nsCharTraits::move(aIter, aStr, aN); + aIter += aN; + } +}; + +#endif // !defined(nsCharTraits_h___) diff --git a/xpcom/string/nsDependentString.cpp b/xpcom/string/nsDependentString.cpp new file mode 100644 index 000000000..52240d17b --- /dev/null +++ b/xpcom/string/nsDependentString.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 "nsDependentString.h" +#include "nsAlgorithm.h" + +// define nsDependentString +#include "string-template-def-unichar.h" +#include "nsTDependentString.cpp" +#include "string-template-undef.h" + +// define nsDependentCString +#include "string-template-def-char.h" +#include "nsTDependentString.cpp" +#include "string-template-undef.h" diff --git a/xpcom/string/nsDependentString.h b/xpcom/string/nsDependentString.h new file mode 100644 index 000000000..20b5997ef --- /dev/null +++ b/xpcom/string/nsDependentString.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 nsDependentString_h___ +#define nsDependentString_h___ + +#include "nsString.h" +#include "nsDebug.h" + +// declare nsDependentString +#include "string-template-def-unichar.h" +#include "nsTDependentString.h" +#include "string-template-undef.h" + +// declare nsDependentCString +#include "string-template-def-char.h" +#include "nsTDependentString.h" +#include "string-template-undef.h" + +#endif /* !defined(nsDependentString_h___) */ diff --git a/xpcom/string/nsDependentSubstring.cpp b/xpcom/string/nsDependentSubstring.cpp new file mode 100644 index 000000000..721cf8f6a --- /dev/null +++ b/xpcom/string/nsDependentSubstring.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 "nsDependentSubstring.h" +#include "nsAlgorithm.h" + +// define nsDependentSubstring +#include "string-template-def-unichar.h" +#include "nsTDependentSubstring.cpp" +#include "string-template-undef.h" + +// define nsDependentCSubstring +#include "string-template-def-char.h" +#include "nsTDependentSubstring.cpp" +#include "string-template-undef.h" diff --git a/xpcom/string/nsDependentSubstring.h b/xpcom/string/nsDependentSubstring.h new file mode 100644 index 000000000..078b8ab54 --- /dev/null +++ b/xpcom/string/nsDependentSubstring.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 nsDependentSubstring_h___ +#define nsDependentSubstring_h___ + +#include "nsSubstring.h" + +// declare nsDependentSubstring +#include "string-template-def-unichar.h" +#include "nsTDependentSubstring.h" +#include "string-template-undef.h" + +// declare nsDependentCSubstring +#include "string-template-def-char.h" +#include "nsTDependentSubstring.h" +#include "string-template-undef.h" + +#endif /* !defined(nsDependentSubstring_h___) */ diff --git a/xpcom/string/nsEmbedString.h b/xpcom/string/nsEmbedString.h new file mode 100644 index 000000000..caedd50cd --- /dev/null +++ b/xpcom/string/nsEmbedString.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 nsEmbedString_h___ +#define nsEmbedString_h___ + +#include "nsStringAPI.h" + +/** + * compatibility + */ +typedef nsString nsEmbedString; +typedef nsCString nsEmbedCString; + +#endif diff --git a/xpcom/string/nsLiteralString.h b/xpcom/string/nsLiteralString.h new file mode 100644 index 000000000..4a0b39107 --- /dev/null +++ b/xpcom/string/nsLiteralString.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 nsLiteralString_h___ +#define nsLiteralString_h___ + +#include "nscore.h" +#include "nsString.h" + +// declare nsLiteralString +#include "string-template-def-unichar.h" +#include "nsTLiteralString.h" +#include "string-template-undef.h" + +// declare nsLiteralCString +#include "string-template-def-char.h" +#include "nsTLiteralString.h" +#include "string-template-undef.h" + +#include "mozilla/Char16.h" + +#define NS_MULTILINE_LITERAL_STRING(s) static_cast(nsLiteralString(s)) +#define NS_MULTILINE_LITERAL_STRING_INIT(n,s) n(s) +#define NS_NAMED_MULTILINE_LITERAL_STRING(n,s) const nsLiteralString n(s) + +#define NS_LITERAL_STRING(s) static_cast(nsLiteralString(u"" s)) +#define NS_LITERAL_STRING_INIT(n,s) n(u"" s) +#define NS_NAMED_LITERAL_STRING(n,s) const nsLiteralString n(u"" s) + +#define NS_LITERAL_CSTRING(s) static_cast(nsLiteralCString("" s)) +#define NS_LITERAL_CSTRING_INIT(n,s) n("" s) +#define NS_NAMED_LITERAL_CSTRING(n,s) const nsLiteralCString n("" s) + +#endif /* !defined(nsLiteralString_h___) */ diff --git a/xpcom/string/nsPrintfCString.h b/xpcom/string/nsPrintfCString.h new file mode 100644 index 000000000..ce90ec497 --- /dev/null +++ b/xpcom/string/nsPrintfCString.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 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 nsFixedCString +{ + typedef nsCString string_type; + +public: + explicit nsPrintfCString(const char_type* aFormat, ...) + : nsFixedCString(mLocalBuffer, kLocalBufferSize, 0) + { + va_list ap; + va_start(ap, aFormat); + AppendPrintf(aFormat, ap); + va_end(ap); + } + +private: + static const uint32_t kLocalBufferSize = 16; + char_type mLocalBuffer[kLocalBufferSize]; +}; + +#endif // !defined(nsPrintfCString_h___) diff --git a/xpcom/string/nsPromiseFlatString.cpp b/xpcom/string/nsPromiseFlatString.cpp new file mode 100644 index 000000000..e66d2ef9f --- /dev/null +++ b/xpcom/string/nsPromiseFlatString.cpp @@ -0,0 +1,17 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsPromiseFlatString.h" + +// define nsPromiseFlatString +#include "string-template-def-unichar.h" +#include "nsTPromiseFlatString.cpp" +#include "string-template-undef.h" + +// define nsPromiseFlatCString +#include "string-template-def-char.h" +#include "nsTPromiseFlatString.cpp" +#include "string-template-undef.h" diff --git a/xpcom/string/nsPromiseFlatString.h b/xpcom/string/nsPromiseFlatString.h new file mode 100644 index 000000000..a025bfd26 --- /dev/null +++ b/xpcom/string/nsPromiseFlatString.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 nsPromiseFlatString_h___ +#define nsPromiseFlatString_h___ + +#include "nsString.h" + +// declare nsPromiseFlatString +#include "string-template-def-unichar.h" +#include "nsTPromiseFlatString.h" +#include "string-template-undef.h" + +// declare nsPromiseFlatCString +#include "string-template-def-char.h" +#include "nsTPromiseFlatString.h" +#include "string-template-undef.h" + +#endif /* !defined(nsPromiseFlatString_h___) */ diff --git a/xpcom/string/nsReadableUtils.cpp b/xpcom/string/nsReadableUtils.cpp new file mode 100644 index 000000000..524b1d7fe --- /dev/null +++ b/xpcom/string/nsReadableUtils.cpp @@ -0,0 +1,1383 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. 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 "nsReadableUtilsImpl.h" + +#include + +#include "mozilla/CheckedInt.h" + +#include "nscore.h" +#include "nsMemory.h" +#include "nsString.h" +#include "nsTArray.h" +#include "nsUTF8Utils.h" + +using mozilla::IsASCII; + +/** + * Fallback implementation for finding the first non-ASCII character in a + * UTF-16 string. + */ +static inline int32_t +FirstNonASCIIUnvectorized(const char16_t* aBegin, const char16_t* aEnd) +{ + typedef mozilla::NonASCIIParameters p; + const size_t kMask = p::mask(); + const uintptr_t kAlignMask = p::alignMask(); + const size_t kNumUnicharsPerWord = p::numUnicharsPerWord(); + + const char16_t* idx = aBegin; + + // Align ourselves to a word boundary. + for (; idx != aEnd && ((uintptr_t(idx) & kAlignMask) != 0); idx++) { + if (!IsASCII(*idx)) { + return idx - aBegin; + } + } + + // Check one word at a time. + const char16_t* wordWalkEnd = mozilla::aligned(aEnd, kAlignMask); + for (; idx != wordWalkEnd; idx += kNumUnicharsPerWord) { + const size_t word = *reinterpret_cast(idx); + if (word & kMask) { + return idx - aBegin; + } + } + + // Take care of the remainder one character at a time. + for (; idx != aEnd; idx++) { + if (!IsASCII(*idx)) { + return idx - aBegin; + } + } + + return -1; +} + +/* + * This function returns -1 if all characters in str are ASCII characters. + * Otherwise, it returns a value less than or equal to the index of the first + * ASCII character in str. For example, if first non-ASCII character is at + * position 25, it may return 25, 24, or 16. But it guarantees + * there are only ASCII characters before returned value. + */ +static inline int32_t +FirstNonASCII(const char16_t* aBegin, const char16_t* aEnd) +{ +#ifdef MOZILLA_MAY_SUPPORT_SSE2 + if (mozilla::supports_sse2()) { + return mozilla::SSE2::FirstNonASCII(aBegin, aEnd); + } +#endif + + return FirstNonASCIIUnvectorized(aBegin, aEnd); +} + +void +LossyCopyUTF16toASCII(const nsAString& aSource, nsACString& aDest) +{ + aDest.Truncate(); + LossyAppendUTF16toASCII(aSource, aDest); +} + +void +CopyASCIItoUTF16(const nsACString& aSource, nsAString& aDest) +{ + aDest.Truncate(); + AppendASCIItoUTF16(aSource, aDest); +} + +void +LossyCopyUTF16toASCII(const char16ptr_t aSource, nsACString& aDest) +{ + aDest.Truncate(); + if (aSource) { + LossyAppendUTF16toASCII(nsDependentString(aSource), aDest); + } +} + +void +CopyASCIItoUTF16(const char* aSource, nsAString& aDest) +{ + aDest.Truncate(); + if (aSource) { + AppendASCIItoUTF16(nsDependentCString(aSource), aDest); + } +} + +void +CopyUTF16toUTF8(const nsAString& aSource, nsACString& aDest) +{ + if (!CopyUTF16toUTF8(aSource, aDest, mozilla::fallible)) { + // Note that this may wildly underestimate the allocation that failed, as + // we report the length of aSource as UTF-16 instead of UTF-8. + aDest.AllocFailed(aDest.Length() + aSource.Length()); + } +} + +bool +CopyUTF16toUTF8(const nsAString& aSource, nsACString& aDest, + const mozilla::fallible_t& aFallible) +{ + aDest.Truncate(); + if (!AppendUTF16toUTF8(aSource, aDest, aFallible)) { + return false; + } + return true; +} + +void +CopyUTF8toUTF16(const nsACString& aSource, nsAString& aDest) +{ + aDest.Truncate(); + AppendUTF8toUTF16(aSource, aDest); +} + +void +CopyUTF16toUTF8(const char16ptr_t aSource, nsACString& aDest) +{ + aDest.Truncate(); + AppendUTF16toUTF8(aSource, aDest); +} + +void +CopyUTF8toUTF16(const char* aSource, nsAString& aDest) +{ + aDest.Truncate(); + AppendUTF8toUTF16(aSource, aDest); +} + +void +LossyAppendUTF16toASCII(const nsAString& aSource, nsACString& aDest) +{ + uint32_t old_dest_length = aDest.Length(); + aDest.SetLength(old_dest_length + aSource.Length()); + + nsAString::const_iterator fromBegin, fromEnd; + + nsACString::iterator dest; + aDest.BeginWriting(dest); + + dest.advance(old_dest_length); + + // right now, this won't work on multi-fragment destinations + LossyConvertEncoding16to8 converter(dest.get()); + + copy_string(aSource.BeginReading(fromBegin), aSource.EndReading(fromEnd), + converter); +} + +void +AppendASCIItoUTF16(const nsACString& aSource, nsAString& aDest) +{ + if (!AppendASCIItoUTF16(aSource, aDest, mozilla::fallible)) { + aDest.AllocFailed(aDest.Length() + aSource.Length()); + } +} + +bool +AppendASCIItoUTF16(const nsACString& aSource, nsAString& aDest, + const mozilla::fallible_t& aFallible) +{ + uint32_t old_dest_length = aDest.Length(); + if (!aDest.SetLength(old_dest_length + aSource.Length(), + aFallible)) { + return false; + } + + nsACString::const_iterator fromBegin, fromEnd; + + nsAString::iterator dest; + aDest.BeginWriting(dest); + + dest.advance(old_dest_length); + + // right now, this won't work on multi-fragment destinations + LossyConvertEncoding8to16 converter(dest.get()); + + copy_string(aSource.BeginReading(fromBegin), aSource.EndReading(fromEnd), + converter); + return true; +} + +void +LossyAppendUTF16toASCII(const char16ptr_t aSource, nsACString& aDest) +{ + if (aSource) { + LossyAppendUTF16toASCII(nsDependentString(aSource), aDest); + } +} + +bool +AppendASCIItoUTF16(const char* aSource, nsAString& aDest, const mozilla::fallible_t& aFallible) +{ + if (aSource) { + return AppendASCIItoUTF16(nsDependentCString(aSource), aDest, aFallible); + } + + return true; +} + +void +AppendASCIItoUTF16(const char* aSource, nsAString& aDest) +{ + if (aSource) { + AppendASCIItoUTF16(nsDependentCString(aSource), aDest); + } +} + +void +AppendUTF16toUTF8(const nsAString& aSource, nsACString& aDest) +{ + if (!AppendUTF16toUTF8(aSource, aDest, mozilla::fallible)) { + // Note that this may wildly underestimate the allocation that failed, as + // we report the length of aSource as UTF-16 instead of UTF-8. + aDest.AllocFailed(aDest.Length() + aSource.Length()); + } +} + +bool +AppendUTF16toUTF8(const nsAString& aSource, nsACString& aDest, + const mozilla::fallible_t& aFallible) +{ + // At 16 characters analysis showed better performance of both the all ASCII + // and non-ASCII cases, so we limit calling |FirstNonASCII| to strings of + // that length. + const nsAString::size_type kFastPathMinLength = 16; + + int32_t firstNonASCII = 0; + if (aSource.Length() >= kFastPathMinLength) { + firstNonASCII = FirstNonASCII(aSource.BeginReading(), aSource.EndReading()); + } + + if (firstNonASCII == -1) { + // This is all ASCII, we can use the more efficient lossy append. + mozilla::CheckedInt new_length(aSource.Length()); + new_length += aDest.Length(); + + if (!new_length.isValid() || + !aDest.SetCapacity(new_length.value(), aFallible)) { + return false; + } + + LossyAppendUTF16toASCII(aSource, aDest); + return true; + } + + nsAString::const_iterator source_start, source_end; + CalculateUTF8Size calculator; + aSource.BeginReading(source_start); + aSource.EndReading(source_end); + + // Skip the characters that we know are single byte. + source_start.advance(firstNonASCII); + + copy_string(source_start, + source_end, calculator); + + // Include the ASCII characters that were skipped in the count. + size_t count = calculator.Size() + firstNonASCII; + + if (count) { + auto old_dest_length = aDest.Length(); + // Grow the buffer if we need to. + mozilla::CheckedInt new_length(count); + new_length += old_dest_length; + + if (!new_length.isValid() || + !aDest.SetLength(new_length.value(), aFallible)) { + return false; + } + + // All ready? Time to convert + + nsAString::const_iterator ascii_end; + aSource.BeginReading(ascii_end); + + if (firstNonASCII >= static_cast(kFastPathMinLength)) { + // Use the more efficient lossy converter for the ASCII portion. + LossyConvertEncoding16to8 lossy_converter( + aDest.BeginWriting() + old_dest_length); + nsAString::const_iterator ascii_start; + aSource.BeginReading(ascii_start); + ascii_end.advance(firstNonASCII); + + copy_string(ascii_start, ascii_end, lossy_converter); + } else { + // Not using the lossy shortcut, we need to include the leading ASCII + // chars. + firstNonASCII = 0; + } + + ConvertUTF16toUTF8 converter( + aDest.BeginWriting() + old_dest_length + firstNonASCII); + copy_string(ascii_end, + aSource.EndReading(source_end), converter); + + NS_ASSERTION(converter.Size() == count - firstNonASCII, + "Unexpected disparity between CalculateUTF8Size and " + "ConvertUTF16toUTF8"); + } + + return true; +} + +void +AppendUTF8toUTF16(const nsACString& aSource, nsAString& aDest) +{ + if (!AppendUTF8toUTF16(aSource, aDest, mozilla::fallible)) { + aDest.AllocFailed(aDest.Length() + aSource.Length()); + } +} + +bool +AppendUTF8toUTF16(const nsACString& aSource, nsAString& aDest, + const mozilla::fallible_t& aFallible) +{ + nsACString::const_iterator source_start, source_end; + CalculateUTF8Length calculator; + copy_string(aSource.BeginReading(source_start), + aSource.EndReading(source_end), calculator); + + uint32_t count = calculator.Length(); + + // Avoid making the string mutable if we're appending an empty string + if (count) { + uint32_t old_dest_length = aDest.Length(); + + // Grow the buffer if we need to. + if (!aDest.SetLength(old_dest_length + count, aFallible)) { + return false; + } + + // All ready? Time to convert + + ConvertUTF8toUTF16 converter(aDest.BeginWriting() + old_dest_length); + copy_string(aSource.BeginReading(source_start), + aSource.EndReading(source_end), converter); + + NS_ASSERTION(converter.ErrorEncountered() || + converter.Length() == count, + "CalculateUTF8Length produced the wrong length"); + + if (converter.ErrorEncountered()) { + NS_ERROR("Input wasn't UTF8 or incorrect length was calculated"); + aDest.SetLength(old_dest_length); + } + } + + return true; +} + +void +AppendUTF16toUTF8(const char16ptr_t aSource, nsACString& aDest) +{ + if (aSource) { + AppendUTF16toUTF8(nsDependentString(aSource), aDest); + } +} + +void +AppendUTF8toUTF16(const char* aSource, nsAString& aDest) +{ + if (aSource) { + AppendUTF8toUTF16(nsDependentCString(aSource), aDest); + } +} + + +/** + * 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 (of the type specified by the second parameter) which you must free with |free|. + * + */ +template +inline +ToCharT* +AllocateStringCopy(const FromStringT& aSource, ToCharT*) +{ + return static_cast(moz_xmalloc( + (aSource.Length() + 1) * sizeof(ToCharT))); +} + + +char* +ToNewCString(const nsAString& aSource) +{ + char* result = AllocateStringCopy(aSource, (char*)0); + if (!result) { + return nullptr; + } + + nsAString::const_iterator fromBegin, fromEnd; + LossyConvertEncoding16to8 converter(result); + copy_string(aSource.BeginReading(fromBegin), aSource.EndReading(fromEnd), + converter).write_terminator(); + return result; +} + +char* +ToNewUTF8String(const nsAString& aSource, uint32_t* aUTF8Count) +{ + nsAString::const_iterator start, end; + CalculateUTF8Size calculator; + copy_string(aSource.BeginReading(start), aSource.EndReading(end), + calculator); + + if (aUTF8Count) { + *aUTF8Count = calculator.Size(); + } + + char* result = static_cast + (moz_xmalloc(calculator.Size() + 1)); + if (!result) { + return nullptr; + } + + ConvertUTF16toUTF8 converter(result); + copy_string(aSource.BeginReading(start), aSource.EndReading(end), + converter).write_terminator(); + NS_ASSERTION(calculator.Size() == converter.Size(), "length mismatch"); + + return result; +} + +char* +ToNewCString(const nsACString& aSource) +{ + // no conversion needed, just allocate a buffer of the correct length and copy into it + + char* result = AllocateStringCopy(aSource, (char*)0); + if (!result) { + return nullptr; + } + + nsACString::const_iterator fromBegin, fromEnd; + char* toBegin = result; + *copy_string(aSource.BeginReading(fromBegin), aSource.EndReading(fromEnd), + toBegin) = char(0); + return result; +} + +char16_t* +ToNewUnicode(const nsAString& aSource) +{ + // no conversion needed, just allocate a buffer of the correct length and copy into it + + char16_t* result = AllocateStringCopy(aSource, (char16_t*)0); + if (!result) { + return nullptr; + } + + nsAString::const_iterator fromBegin, fromEnd; + char16_t* toBegin = result; + *copy_string(aSource.BeginReading(fromBegin), aSource.EndReading(fromEnd), + toBegin) = char16_t(0); + return result; +} + +char16_t* +ToNewUnicode(const nsACString& aSource) +{ + char16_t* result = AllocateStringCopy(aSource, (char16_t*)0); + if (!result) { + return nullptr; + } + + nsACString::const_iterator fromBegin, fromEnd; + LossyConvertEncoding8to16 converter(result); + copy_string(aSource.BeginReading(fromBegin), aSource.EndReading(fromEnd), + converter).write_terminator(); + return result; +} + +uint32_t +CalcUTF8ToUnicodeLength(const nsACString& aSource) +{ + nsACString::const_iterator start, end; + CalculateUTF8Length calculator; + copy_string(aSource.BeginReading(start), aSource.EndReading(end), + calculator); + return calculator.Length(); +} + +char16_t* +UTF8ToUnicodeBuffer(const nsACString& aSource, char16_t* aBuffer, + uint32_t* aUTF16Count) +{ + nsACString::const_iterator start, end; + ConvertUTF8toUTF16 converter(aBuffer); + copy_string(aSource.BeginReading(start), + aSource.EndReading(end), + converter).write_terminator(); + if (aUTF16Count) { + *aUTF16Count = converter.Length(); + } + return aBuffer; +} + +char16_t* +UTF8ToNewUnicode(const nsACString& aSource, uint32_t* aUTF16Count) +{ + const uint32_t length = CalcUTF8ToUnicodeLength(aSource); + const size_t buffer_size = (length + 1) * sizeof(char16_t); + char16_t* buffer = static_cast(moz_xmalloc(buffer_size)); + if (!buffer) { + return nullptr; + } + + uint32_t copied; + UTF8ToUnicodeBuffer(aSource, buffer, &copied); + NS_ASSERTION(length == copied, "length mismatch"); + + if (aUTF16Count) { + *aUTF16Count = copied; + } + return buffer; +} + +char16_t* +CopyUnicodeTo(const nsAString& aSource, uint32_t aSrcOffset, char16_t* aDest, + uint32_t aLength) +{ + nsAString::const_iterator fromBegin, fromEnd; + char16_t* toBegin = aDest; + copy_string(aSource.BeginReading(fromBegin).advance(int32_t(aSrcOffset)), + aSource.BeginReading(fromEnd).advance(int32_t(aSrcOffset + aLength)), + toBegin); + return aDest; +} + +void +CopyUnicodeTo(const nsAString::const_iterator& aSrcStart, + const nsAString::const_iterator& aSrcEnd, + nsAString& aDest) +{ + aDest.SetLength(Distance(aSrcStart, aSrcEnd)); + + nsAString::char_iterator dest = aDest.BeginWriting(); + nsAString::const_iterator fromBegin(aSrcStart); + + copy_string(fromBegin, aSrcEnd, dest); +} + +void +AppendUnicodeTo(const nsAString::const_iterator& aSrcStart, + const nsAString::const_iterator& aSrcEnd, + nsAString& aDest) +{ + uint32_t oldLength = aDest.Length(); + aDest.SetLength(oldLength + Distance(aSrcStart, aSrcEnd)); + + nsAString::char_iterator dest = aDest.BeginWriting() + oldLength; + nsAString::const_iterator fromBegin(aSrcStart); + + copy_string(fromBegin, aSrcEnd, dest); +} + +bool +IsASCII(const nsAString& aString) +{ + static const char16_t NOT_ASCII = char16_t(~0x007F); + + + // Don't want to use |copy_string| for this task, since we can stop at the first non-ASCII character + + nsAString::const_iterator iter, done_reading; + aString.BeginReading(iter); + aString.EndReading(done_reading); + + const char16_t* c = iter.get(); + const char16_t* end = done_reading.get(); + + while (c < end) { + if (*c++ & NOT_ASCII) { + return false; + } + } + + return true; +} + +bool +IsASCII(const nsACString& aString) +{ + static const char NOT_ASCII = char(~0x7F); + + + // Don't want to use |copy_string| for this task, since we can stop at the first non-ASCII character + + nsACString::const_iterator iter, done_reading; + aString.BeginReading(iter); + aString.EndReading(done_reading); + + const char* c = iter.get(); + const char* end = done_reading.get(); + + while (c < end) { + if (*c++ & NOT_ASCII) { + return false; + } + } + + return true; +} + +bool +IsUTF8(const nsACString& aString, bool aRejectNonChar) +{ + nsReadingIterator done_reading; + aString.EndReading(done_reading); + + int32_t state = 0; + bool overlong = false; + bool surrogate = false; + bool nonchar = false; + uint16_t olupper = 0; // overlong byte upper bound. + uint16_t slower = 0; // surrogate byte lower bound. + + nsReadingIterator iter; + aString.BeginReading(iter); + + const char* ptr = iter.get(); + const char* end = done_reading.get(); + while (ptr < end) { + uint8_t c; + + if (0 == state) { + c = *ptr++; + + if (UTF8traits::isASCII(c)) { + continue; + } + + if (c <= 0xC1) { // [80-BF] where not expected, [C0-C1] for overlong. + return false; + } else if (UTF8traits::is2byte(c)) { + state = 1; + } else if (UTF8traits::is3byte(c)) { + state = 2; + if (c == 0xE0) { // to exclude E0[80-9F][80-BF] + overlong = true; + olupper = 0x9F; + } else if (c == 0xED) { // ED[A0-BF][80-BF] : surrogate codepoint + surrogate = true; + slower = 0xA0; + } else if (c == 0xEF) { // EF BF [BE-BF] : non-character + nonchar = true; + } + } else if (c <= 0xF4) { // XXX replace /w UTF8traits::is4byte when it's updated to exclude [F5-F7].(bug 199090) + state = 3; + nonchar = true; + if (c == 0xF0) { // to exclude F0[80-8F][80-BF]{2} + overlong = true; + olupper = 0x8F; + } else if (c == 0xF4) { // to exclude F4[90-BF][80-BF] + // actually not surrogates but codepoints beyond 0x10FFFF + surrogate = true; + slower = 0x90; + } + } else { + return false; // Not UTF-8 string + } + } + + if (nonchar && !aRejectNonChar) { + nonchar = false; + } + + while (ptr < end && state) { + c = *ptr++; + --state; + + // non-character : EF BF [BE-BF] or F[0-7] [89AB]F BF [BE-BF] + if (nonchar && + ((!state && c < 0xBE) || + (state == 1 && c != 0xBF) || + (state == 2 && 0x0F != (0x0F & c)))) { + nonchar = false; + } + + if (!UTF8traits::isInSeq(c) || (overlong && c <= olupper) || + (surrogate && slower <= c) || (nonchar && !state)) { + return false; // Not UTF-8 string + } + + overlong = surrogate = false; + } + } + return !state; // state != 0 at the end indicates an invalid UTF-8 seq. +} + +/** + * A character sink for in-place case conversion. + */ +class ConvertToUpperCase +{ +public: + typedef char value_type; + + uint32_t + write(const char* aSource, uint32_t aSourceLength) + { + char* cp = const_cast(aSource); + const char* end = aSource + aSourceLength; + while (cp != end) { + char ch = *cp; + if (ch >= 'a' && ch <= 'z') { + *cp = ch - ('a' - 'A'); + } + ++cp; + } + return aSourceLength; + } +}; + +void +ToUpperCase(nsCSubstring& aCString) +{ + ConvertToUpperCase converter; + char* start; + converter.write(aCString.BeginWriting(start), aCString.Length()); +} + +/** + * A character sink for copying with case conversion. + */ +class CopyToUpperCase +{ +public: + typedef char value_type; + + explicit CopyToUpperCase(nsACString::iterator& aDestIter, + const nsACString::iterator& aEndIter) + : mIter(aDestIter) + , mEnd(aEndIter) + { + } + + uint32_t + write(const char* aSource, uint32_t aSourceLength) + { + uint32_t len = XPCOM_MIN(uint32_t(mEnd - mIter), aSourceLength); + char* cp = mIter.get(); + const char* end = aSource + len; + while (aSource != end) { + char ch = *aSource; + if ((ch >= 'a') && (ch <= 'z')) { + *cp = ch - ('a' - 'A'); + } else { + *cp = ch; + } + ++aSource; + ++cp; + } + mIter.advance(len); + return len; + } + +protected: + nsACString::iterator& mIter; + const nsACString::iterator& mEnd; +}; + +void +ToUpperCase(const nsACString& aSource, nsACString& aDest) +{ + nsACString::const_iterator fromBegin, fromEnd; + nsACString::iterator toBegin, toEnd; + aDest.SetLength(aSource.Length()); + + CopyToUpperCase converter(aDest.BeginWriting(toBegin), aDest.EndWriting(toEnd)); + copy_string(aSource.BeginReading(fromBegin), aSource.EndReading(fromEnd), + converter); +} + +/** + * A character sink for case conversion. + */ +class ConvertToLowerCase +{ +public: + typedef char value_type; + + uint32_t + write(const char* aSource, uint32_t aSourceLength) + { + char* cp = const_cast(aSource); + const char* end = aSource + aSourceLength; + while (cp != end) { + char ch = *cp; + if ((ch >= 'A') && (ch <= 'Z')) { + *cp = ch + ('a' - 'A'); + } + ++cp; + } + return aSourceLength; + } +}; + +void +ToLowerCase(nsCSubstring& aCString) +{ + ConvertToLowerCase converter; + char* start; + converter.write(aCString.BeginWriting(start), aCString.Length()); +} + +/** + * A character sink for copying with case conversion. + */ +class CopyToLowerCase +{ +public: + typedef char value_type; + + explicit CopyToLowerCase(nsACString::iterator& aDestIter, + const nsACString::iterator& aEndIter) + : mIter(aDestIter) + , mEnd(aEndIter) + { + } + + uint32_t + write(const char* aSource, uint32_t aSourceLength) + { + uint32_t len = XPCOM_MIN(uint32_t(mEnd - mIter), aSourceLength); + char* cp = mIter.get(); + const char* end = aSource + len; + while (aSource != end) { + char ch = *aSource; + if ((ch >= 'A') && (ch <= 'Z')) { + *cp = ch + ('a' - 'A'); + } else { + *cp = ch; + } + ++aSource; + ++cp; + } + mIter.advance(len); + return len; + } + +protected: + nsACString::iterator& mIter; + const nsACString::iterator& mEnd; +}; + +void +ToLowerCase(const nsACString& aSource, nsACString& aDest) +{ + nsACString::const_iterator fromBegin, fromEnd; + nsACString::iterator toBegin, toEnd; + aDest.SetLength(aSource.Length()); + + CopyToLowerCase converter(aDest.BeginWriting(toBegin), aDest.EndWriting(toEnd)); + copy_string(aSource.BeginReading(fromBegin), aSource.EndReading(fromEnd), + converter); +} + +bool +ParseString(const nsACString& aSource, char aDelimiter, + nsTArray& aArray) +{ + nsACString::const_iterator start, end; + aSource.BeginReading(start); + aSource.EndReading(end); + + uint32_t oldLength = aArray.Length(); + + for (;;) { + nsACString::const_iterator delimiter = start; + FindCharInReadable(aDelimiter, delimiter, end); + + if (delimiter != start) { + if (!aArray.AppendElement(Substring(start, delimiter))) { + aArray.RemoveElementsAt(oldLength, aArray.Length() - oldLength); + return false; + } + } + + if (delimiter == end) { + break; + } + start = ++delimiter; + if (start == end) { + break; + } + } + + return true; +} + +template +bool +FindInReadable_Impl(const StringT& aPattern, IteratorT& aSearchStart, + IteratorT& aSearchEnd, const Comparator& 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, const Comparator& 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, + const nsStringComparator& aComparator) +{ + return FindInReadable_Impl(aPattern, aSearchStart, aSearchEnd, aComparator); +} + +bool +FindInReadable(const nsACString& aPattern, + nsACString::const_iterator& aSearchStart, + nsACString::const_iterator& aSearchEnd, + const 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) +{ + int32_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) +{ + int32_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; +} + +uint32_t +CountCharInReadable(const nsAString& aStr, char16_t aChar) +{ + uint32_t count = 0; + nsAString::const_iterator begin, end; + + aStr.BeginReading(begin); + aStr.EndReading(end); + + while (begin != end) { + if (*begin == aChar) { + ++count; + } + ++begin; + } + + return count; +} + +uint32_t +CountCharInReadable(const nsACString& aStr, char aChar) +{ + uint32_t count = 0; + nsACString::const_iterator begin, end; + + aStr.BeginReading(begin); + aStr.EndReading(end); + + while (begin != end) { + if (*begin == aChar) { + ++count; + } + ++begin; + } + + return count; +} + +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, + const 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, + const 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, + const 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, + const 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 nsAFlatString& +EmptyString() +{ + static const nsDependentString sEmpty(empty_buffer); + + return sEmpty; +} + +const nsAFlatCString& +EmptyCString() +{ + static const nsDependentCString sEmpty((const char*)empty_buffer); + + return sEmpty; +} + +const nsAFlatString& +NullString() +{ + static const nsXPIDLString sNull; + + return sNull; +} + +const nsAFlatCString& +NullCString() +{ + static const nsXPIDLCString sNull; + + return sNull; +} + +int32_t +CompareUTF8toUTF16(const nsASingleFragmentCString& aUTF8String, + const nsASingleFragmentString& aUTF16String) +{ + static const uint32_t NOT_ASCII = uint32_t(~0x7F); + + 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); + + while (u8 != u8end && u16 != u16end) { + // Cast away the signedness of *u8 to prevent signextension when + // converting to uint32_t + uint32_t c8_32 = (uint8_t)*u8; + + if (c8_32 & NOT_ASCII) { + bool err; + c8_32 = UTF8CharEnumerator::NextChar(&u8, u8end, &err); + if (err) { + return INT32_MIN; + } + + uint32_t c16_32 = UTF16CharEnumerator::NextChar(&u16, u16end); + // The above UTF16CharEnumerator::NextChar() calls can + // fail, but if it does for anything other than no data to + // look at (which can't happen here), it returns the + // Unicode replacement character 0xFFFD for the invalid + // data they were fed. Ignore that error and treat invalid + // UTF16 as 0xFFFD. + // + // This matches what our UTF16 to UTF8 conversion code + // does, and thus a UTF8 string that came from an invalid + // UTF16 string will compare equal to the invalid UTF16 + // string it came from. Same is true for any other UTF16 + // string differs only in the invalid part of the string. + + if (c8_32 != c16_32) { + return c8_32 < c16_32 ? -1 : 1; + } + } else { + if (c8_32 != *u16) { + return c8_32 > *u16 ? 1 : -1; + } + + ++u8; + ++u16; + } + } + + if (u8 != u8end) { + // We get to the end of the UTF16 string, but no to the end of + // the UTF8 string. The UTF8 string is longer than the UTF16 + // string + + return 1; + } + + if (u16 != u16end) { + // We get to the end of the UTF8 string, but no to the end of + // the UTF16 string. The UTF16 string is longer than the UTF8 + // string + + return -1; + } + + // The two strings match. + + return 0; +} + +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)); + } +} + +extern "C" { + +void Gecko_AppendUTF16toCString(nsACString* aThis, const nsAString* aOther) +{ + AppendUTF16toUTF8(*aOther, *aThis); +} + +void Gecko_AppendUTF8toString(nsAString* aThis, const nsACString* aOther) +{ + AppendUTF8toUTF16(*aOther, *aThis); +} + +} diff --git a/xpcom/string/nsReadableUtils.h b/xpcom/string/nsReadableUtils.h new file mode 100644 index 000000000..24824d927 --- /dev/null +++ b/xpcom/string/nsReadableUtils.h @@ -0,0 +1,428 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 "nsTArrayForwardDeclare.h" + +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()); +} + +void LossyCopyUTF16toASCII(const nsAString& aSource, nsACString& aDest); +void CopyASCIItoUTF16(const nsACString& aSource, nsAString& aDest); + +void LossyCopyUTF16toASCII(const char16ptr_t aSource, nsACString& aDest); +void CopyASCIItoUTF16(const char* aSource, nsAString& aDest); + +void CopyUTF16toUTF8(const nsAString& aSource, nsACString& aDest); +MOZ_MUST_USE bool CopyUTF16toUTF8(const nsAString& aSource, nsACString& aDest, + const mozilla::fallible_t&); +void CopyUTF8toUTF16(const nsACString& aSource, nsAString& aDest); + +void CopyUTF16toUTF8(const char16ptr_t aSource, nsACString& aDest); +void CopyUTF8toUTF16(const char* aSource, nsAString& aDest); + +void LossyAppendUTF16toASCII(const nsAString& aSource, nsACString& aDest); +void AppendASCIItoUTF16(const nsACString& aSource, nsAString& aDest); +MOZ_MUST_USE bool AppendASCIItoUTF16(const nsACString& aSource, + nsAString& aDest, + const mozilla::fallible_t&); + +void LossyAppendUTF16toASCII(const char16ptr_t aSource, nsACString& aDest); +MOZ_MUST_USE bool AppendASCIItoUTF16(const char* aSource, + nsAString& aDest, + const mozilla::fallible_t&); +void AppendASCIItoUTF16(const char* aSource, nsAString& aDest); + +void AppendUTF16toUTF8(const nsAString& aSource, nsACString& aDest); +MOZ_MUST_USE bool AppendUTF16toUTF8(const nsAString& aSource, + nsACString& aDest, + const mozilla::fallible_t&); +void AppendUTF8toUTF16(const nsACString& aSource, nsAString& aDest); +MOZ_MUST_USE bool AppendUTF8toUTF16(const nsACString& aSource, + nsAString& aDest, + const mozilla::fallible_t&); + +void AppendUTF16toUTF8(const char16ptr_t aSource, nsACString& aDest); +void AppendUTF8toUTF16(const char* aSource, nsAString& aDest); + +/** + * Returns a new |char| buffer containing a zero-terminated copy of |aSource|. + * + * Allocates and returns a new |char| buffer which you must free with |free|. + * Performs a lossy encoding conversion by chopping 16-bit wide characters down to 8-bits wide while copying |aSource| to your new buffer. + * This conversion is not well defined; but it reproduces legacy string behavior. + * 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); + + +/** + * Returns a new |char| buffer containing a zero-terminated copy of |aSource|. + * + * 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); + +/** + * Returns a new |char| buffer containing a zero-terminated copy of |aSource|. + * + * 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 + * 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); + + +/** + * Returns a new |char16_t| buffer containing a zero-terminated copy of + * |aSource|. + * + * 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); + + +/** + * Returns a new |char16_t| buffer containing a zero-terminated copy of |aSource|. + * + * 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 while copying |aSource| to your new buffer. + * This conversion is not well defined; but it reproduces legacy string behavior. + * The new buffer is zero-terminated, but that may not help you if |aSource| contains embedded nulls. + * + * @param aSource an 8-bit wide string (a C-string, NOT UTF-8) + * @return a new |char16_t| buffer you must free with |free|. + */ +char16_t* ToNewUnicode(const nsACString& aSource); + +/** + * Returns the required length for a char16_t buffer holding + * a copy of aSource, using UTF-8 to UTF-16 conversion. + * The length does NOT include any space for zero-termination. + * + * @param aSource an 8-bit wide string, UTF-8 encoded + * @return length of UTF-16 encoded string copy, not zero-terminated + */ +uint32_t CalcUTF8ToUnicodeLength(const nsACString& aSource); + +/** + * Copies the source string into the specified buffer, converting UTF-8 to + * UTF-16 in the process. The conversion is well defined for valid UTF-8 + * strings. + * The copied string will be zero-terminated! Any embedded nulls will be + * copied nonetheless. It is the caller's responsiblity to ensure the buffer + * is large enough to hold the string copy plus one char16_t for + * zero-termination! + * + * @see CalcUTF8ToUnicodeLength( const nsACString& ) + * @see UTF8ToNewUnicode( const nsACString&, uint32_t* ) + * + * @param aSource an 8-bit wide string, UTF-8 encoded + * @param aBuffer the buffer holding the converted string copy + * @param aUTF16Count receiving optionally the number of 16-bit units that + * were copied + * @return aBuffer pointer, for convenience + */ +char16_t* UTF8ToUnicodeBuffer(const nsACString& aSource, + char16_t* aBuffer, + uint32_t* aUTF16Count = nullptr); + +/** + * Returns a new |char16_t| buffer containing a zero-terminated copy + * of |aSource|. + * + * 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. This conversion is well defined + * for a valid UTF-8 string. 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); + +/** + * 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); + + +/** + * Copies 16-bit characters between iterators |aSrcStart| and + * |aSrcEnd| to the writable string |aDest|. Similar to the + * |nsString::Mid| method. + * + * After this operation |aDest| is not null terminated. + * + * @param aSrcStart start source iterator + * @param aSrcEnd end source iterator + * @param aDest destination for the copy + */ +void CopyUnicodeTo(const nsAString::const_iterator& aSrcStart, + const nsAString::const_iterator& aSrcEnd, + nsAString& aDest); + +/** + * Appends 16-bit characters between iterators |aSrcStart| and + * |aSrcEnd| to the writable string |aDest|. + * + * After this operation |aDest| is not null terminated. + * + * @param aSrcStart start source iterator + * @param aSrcEnd end source iterator + * @param aDest destination for the copy + */ +void AppendUnicodeTo(const nsAString::const_iterator& aSrcStart, + const nsAString::const_iterator& aSrcEnd, + nsAString& aDest); + +/** + * Returns |true| if |aString| contains only ASCII characters, that is, characters in the range (0x00, 0x7F). + * + * @param aString a 16-bit wide string to scan + */ +bool IsASCII(const nsAString& aString); + +/** + * Returns |true| if |aString| contains only ASCII characters, that is, characters in the range (0x00, 0x7F). + * + * @param aString a 8-bit wide string to scan + */ +bool IsASCII(const nsACString& aString); + +/** + * Returns |true| if |aString| is a valid UTF-8 string. + * XXX This is not bullet-proof and nor an all-purpose UTF-8 validator. + * It is mainly written to replace and roughly equivalent to + * + * str.Equals(NS_ConvertUTF16toUTF8(NS_ConvertUTF8toUTF16(str))) + * + * (see bug 191541) + * As such, it does not check for non-UTF-8 7bit encodings such as + * ISO-2022-JP and HZ. + * + * It rejects sequences with the following errors: + * + * byte sequences that cannot be decoded into characters according to + * UTF-8's rules (including cases where the input is part of a valid + * UTF-8 sequence but starts or ends mid-character) + * overlong sequences (i.e., cases where a character was encoded + * non-canonically by using more bytes than necessary) + * surrogate codepoints (i.e., the codepoints reserved for + representing astral characters in UTF-16) + * codepoints above the unicode range (i.e., outside the first 17 + * planes; higher than U+10FFFF), in accordance with + * http://tools.ietf.org/html/rfc3629 + * when aRejectNonChar is true (the default), any codepoint whose low + * 16 bits are 0xFFFE or 0xFFFF + + * + * @param aString an 8-bit wide string to scan + * @param aRejectNonChar a boolean to control the rejection of utf-8 + * non characters + */ +bool IsUTF8(const nsACString& aString, bool aRejectNonChar = true); + +bool ParseString(const nsACString& aAstring, char aDelimiter, + nsTArray& aArray); + +/** + * Converts case in place in the argument string. + */ +void ToUpperCase(nsACString&); + +void ToLowerCase(nsACString&); + +void ToUpperCase(nsCSubstring&); + +void ToLowerCase(nsCSubstring&); + +/** + * 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&, + const nsStringComparator& = nsDefaultStringComparator()); +bool FindInReadable(const nsACString& aPattern, nsACString::const_iterator&, + nsACString::const_iterator&, + const nsCStringComparator& = nsDefaultCStringComparator()); + +/* 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, + const nsStringComparator& aCompare = nsDefaultStringComparator()) +{ + 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, + const nsCStringComparator& aCompare = nsDefaultCStringComparator()) +{ + 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&, + const nsStringComparator& = nsDefaultStringComparator()); +bool RFindInReadable(const nsACString& aPattern, nsACString::const_iterator&, + nsACString::const_iterator&, + const nsCStringComparator& = nsDefaultCStringComparator()); + +/** +* 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); + +/** +* Finds the number of occurences of |aChar| in the string |aStr| +*/ +uint32_t CountCharInReadable(const nsAString& aStr, + char16_t aChar); +uint32_t CountCharInReadable(const nsACString& aStr, + char aChar); + +bool StringBeginsWith(const nsAString& aSource, const nsAString& aSubstring); +bool StringBeginsWith(const nsAString& aSource, const nsAString& aSubstring, + const nsStringComparator& aComparator); +bool StringBeginsWith(const nsACString& aSource, const nsACString& aSubstring); +bool StringBeginsWith(const nsACString& aSource, const nsACString& aSubstring, + const nsCStringComparator& aComparator); +bool StringEndsWith(const nsAString& aSource, const nsAString& aSubstring); +bool StringEndsWith(const nsAString& aSource, const nsAString& aSubstring, + const nsStringComparator& aComparator); +bool StringEndsWith(const nsACString& aSource, const nsACString& aSubstring); +bool StringEndsWith(const nsACString& aSource, const nsACString& aSubstring, + const nsCStringComparator& aComparator); + +const nsAFlatString& EmptyString(); +const nsAFlatCString& EmptyCString(); + +const nsAFlatString& NullString(); +const nsAFlatCString& NullCString(); + +/** +* 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. In case of fatal +* error (eg the strings are not valid UTF8 and UTF16 respectively), +* this method will return INT32_MIN. +*/ +int32_t CompareUTF8toUTF16(const nsASingleFragmentCString& aUTF8String, + const nsASingleFragmentString& aUTF16String); + +void AppendUCS4ToUTF16(const uint32_t aSource, nsAString& aDest); + +template +inline bool +EnsureStringLength(T& aStr, uint32_t aLen) +{ + aStr.SetLength(aLen); + return (aStr.Length() == aLen); +} + +#endif // !defined(nsReadableUtils_h___) diff --git a/xpcom/string/nsReadableUtilsImpl.h b/xpcom/string/nsReadableUtilsImpl.h new file mode 100644 index 000000000..ff1497b51 --- /dev/null +++ b/xpcom/string/nsReadableUtilsImpl.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/. */ + +#include + +namespace mozilla { + +inline bool IsASCII(char16_t aChar) { + return (aChar & 0xFF80) == 0; +} + +/** + * Provides a pointer before or equal to |aPtr| that is is suitably aligned. + */ +inline const char16_t* aligned(const char16_t* aPtr, const uintptr_t aMask) +{ + return reinterpret_cast( + reinterpret_cast(aPtr) & ~aMask); +} + +/** + * Structures for word-sized vectorization of ASCII checking for UTF-16 + * strings. + */ +template struct NonASCIIParameters; +template<> struct NonASCIIParameters<4> { + static inline size_t mask() { return 0xff80ff80; } + static inline uintptr_t alignMask() { return 0x3; } + static inline size_t numUnicharsPerWord() { return 2; } +}; + +template<> struct NonASCIIParameters<8> { + static inline size_t mask() { + static const uint64_t maskAsUint64 = UINT64_C(0xff80ff80ff80ff80); + // We have to explicitly cast this 64-bit value to a size_t, or else + // compilers for 32-bit platforms will warn about it being too large to fit + // in the size_t return type. (Fortunately, this code isn't actually + // invoked on 32-bit platforms -- they'll use the <4> specialization above. + // So it is, in fact, OK that this value is too large for a 32-bit size_t.) + return (size_t)maskAsUint64; + } + static inline uintptr_t alignMask() { return 0x7; } + static inline size_t numUnicharsPerWord() { return 4; } +}; + +namespace SSE2 { + +int32_t FirstNonASCII(const char16_t* aBegin, const char16_t* aEnd); + +} // namespace SSE2 +} // namespace mozilla diff --git a/xpcom/string/nsReadableUtilsSSE2.cpp b/xpcom/string/nsReadableUtilsSSE2.cpp new file mode 100644 index 000000000..fe01d57af --- /dev/null +++ b/xpcom/string/nsReadableUtilsSSE2.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 + +#include "nsReadableUtilsImpl.h" + +namespace mozilla { +namespace SSE2 { + +static inline bool +is_zero (__m128i x) +{ + return + _mm_movemask_epi8(_mm_cmpeq_epi8(x, _mm_setzero_si128())) == 0xffff; +} + +int32_t +FirstNonASCII(const char16_t* aBegin, const char16_t* aEnd) +{ + const size_t kNumUnicharsPerVector = sizeof(__m128i) / sizeof(char16_t); + typedef NonASCIIParameters p; + const size_t kMask = p::mask(); + const uintptr_t kXmmAlignMask = 0xf; + const uint16_t kShortMask = 0xff80; + const size_t kNumUnicharsPerWord = p::numUnicharsPerWord(); + + const char16_t* idx = aBegin; + + // Align ourselves to a 16-byte boundary as required by _mm_load_si128 + for (; idx != aEnd && ((uintptr_t(idx) & kXmmAlignMask) != 0); idx++) { + if (!IsASCII(*idx)) { + return idx - aBegin; + } + } + + // Check one XMM register (16 bytes) at a time. + const char16_t* vectWalkEnd = aligned(aEnd, kXmmAlignMask); + __m128i vectmask = _mm_set1_epi16(static_cast(kShortMask)); + for (; idx != vectWalkEnd; idx += kNumUnicharsPerVector) { + const __m128i vect = *reinterpret_cast(idx); + if (!is_zero(_mm_and_si128(vect, vectmask))) { + return idx - aBegin; + } + } + + // Check one word at a time. + const char16_t* wordWalkEnd = aligned(aEnd, p::alignMask()); + for(; idx != wordWalkEnd; idx += kNumUnicharsPerWord) { + const size_t word = *reinterpret_cast(idx); + if (word & kMask) { + return idx - aBegin; + } + } + + // Take care of the remainder one character at a time. + for (; idx != aEnd; idx++) { + if (!IsASCII(*idx)) { + return idx - aBegin; + } + } + + return -1; +} + +} // namespace SSE2 +} // namespace mozilla diff --git a/xpcom/string/nsString.cpp b/xpcom/string/nsString.cpp new file mode 100644 index 000000000..2759eb4ca --- /dev/null +++ b/xpcom/string/nsString.cpp @@ -0,0 +1,17 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. 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" + +// define nsString +#include "string-template-def-unichar.h" +#include "nsTString.cpp" +#include "string-template-undef.h" + +// define nsCString +#include "string-template-def-char.h" +#include "nsTString.cpp" +#include "string-template-undef.h" diff --git a/xpcom/string/nsString.h b/xpcom/string/nsString.h new file mode 100644 index 000000000..580e4113d --- /dev/null +++ b/xpcom/string/nsString.h @@ -0,0 +1,209 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 "mozilla/Attributes.h" + +#include "nsSubstring.h" +#include "nsDependentSubstring.h" +#include "nsReadableUtils.h" + +#include + +// enable support for the obsolete string API if not explicitly disabled +#ifndef MOZ_STRING_WITH_OBSOLETE_API +#define MOZ_STRING_WITH_OBSOLETE_API 1 +#endif + +#if MOZ_STRING_WITH_OBSOLETE_API +// radix values for ToInteger/AppendInt +#define kRadix10 (10) +#define kRadix16 (16) +#define kAutoDetect (100) +#define kRadixUnknown (kAutoDetect+1) +#define IGNORE_CASE (true) +#endif + + +// declare nsString, et. al. +#include "string-template-def-unichar.h" +#include "nsTString.h" +#include "string-template-undef.h" + +// declare nsCString, et. al. +#include "string-template-def-char.h" +#include "nsTString.h" +#include "string-template-undef.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"); + + +/** + * 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(aString, *this); + } + + NS_LossyConvertUTF16toASCII(const char16ptr_t aString, uint32_t aLength) + { + LossyAppendUTF16toASCII(Substring(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(aCString, *this); + } + + NS_ConvertASCIItoUTF16(const char* aCString, uint32_t aLength) + { + AppendASCIItoUTF16(Substring(aCString, aLength), *this); + } + + explicit NS_ConvertASCIItoUTF16(const nsACString& 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(aString, *this); + } + + NS_ConvertUTF16toUTF8(const char16ptr_t aString, uint32_t aLength) + { + AppendUTF16toUTF8(Substring(aString, aLength), *this); + } + + explicit NS_ConvertUTF16toUTF8(const nsAString& 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(aCString, *this); + } + + NS_ConvertUTF8toUTF16(const char* aCString, uint32_t aLength) + { + AppendUTF8toUTF16(Substring(aCString, aLength), *this); + } + + explicit NS_ConvertUTF8toUTF16(const nsACString& aCString) + { + AppendUTF8toUTF16(aCString, *this); + } + +private: + // NOT TO BE IMPLEMENTED + NS_ConvertUTF8toUTF16(char16_t) = delete; +}; + + +#ifdef MOZ_USE_CHAR16_WRAPPER + +inline char16_t* +wwc(wchar_t* aStr) +{ + return reinterpret_cast(aStr); +} + +inline wchar_t* +wwc(char16_t* aStr) +{ + return reinterpret_cast(aStr); +} + +inline const char16_t* +wwc(const wchar_t* aStr) +{ + return reinterpret_cast(aStr); +} + +inline const wchar_t* +wwc(const char16_t* aStr) +{ + return reinterpret_cast(aStr); +} + +#else + +inline char16_t* +wwc(char16_t* aStr) +{ + return aStr; +} + +inline const char16_t* +wwc(const char16_t* aStr) +{ + return aStr; +} + +#endif + +// the following are included/declared for backwards compatibility +typedef nsAutoString nsVoidableString; + +#include "nsDependentString.h" +#include "nsLiteralString.h" +#include "nsPromiseFlatString.h" + +// need to include these for backwards compatibility +#include "nsMemory.h" +#include +#include +#include "plhash.h" + +#endif // !defined(nsString_h___) diff --git a/xpcom/string/nsStringBuffer.h b/xpcom/string/nsStringBuffer.h new file mode 100644 index 000000000..432289bf6 --- /dev/null +++ b/xpcom/string/nsStringBuffer.h @@ -0,0 +1,160 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 "mozilla/Atomics.h" +#include "mozilla/MemoryReporting.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 mFlags 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; + + mozilla::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 + { + return mRefCount > 1; + } + + /** + * 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/nsStringComparator.cpp b/xpcom/string/nsStringComparator.cpp new file mode 100644 index 000000000..81f1629f8 --- /dev/null +++ b/xpcom/string/nsStringComparator.cpp @@ -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/. */ + +#include +#include "nsAString.h" +#include "plstr.h" + + +// define nsStringComparator +#include "string-template-def-unichar.h" +#include "nsTStringComparator.cpp" +#include "string-template-undef.h" + +// define nsCStringComparator +#include "string-template-def-char.h" +#include "nsTStringComparator.cpp" +#include "string-template-undef.h" + + +int +nsCaseInsensitiveCStringComparator::operator()(const char_type* aLhs, + const char_type* aRhs, + uint32_t aLhsLength, + uint32_t aRhsLength) const +{ + if (aLhsLength != aRhsLength) { + return (aLhsLength > aRhsLength) ? 1 : -1; + } + int32_t result = int32_t(PL_strncasecmp(aLhs, aRhs, aLhsLength)); + //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/nsStringFwd.h b/xpcom/string/nsStringFwd.h new file mode 100644 index 000000000..a9162f384 --- /dev/null +++ b/xpcom/string/nsStringFwd.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/. */ + +/* nsStringFwd.h --- forward declarations for string classes */ + +#ifndef nsStringFwd_h___ +#define nsStringFwd_h___ + +#include "nscore.h" + +#ifndef MOZILLA_INTERNAL_API +#error Internal string headers are not available from external-linkage code. +#endif + +/** + * double-byte (char16_t) string types + */ + +class nsAString; +class nsSubstringTuple; +class nsString; +class nsAutoString; +class nsDependentString; +class nsDependentSubstring; +class nsPromiseFlatString; +class nsStringComparator; +class nsDefaultStringComparator; +class nsXPIDLString; + + +/** + * single-byte (char) string types + */ + +class nsACString; +class nsCSubstringTuple; +class nsCString; +class nsAutoCString; +class nsDependentCString; +class nsDependentCSubstring; +class nsPromiseFlatCString; +class nsCStringComparator; +class nsDefaultCStringComparator; +class nsXPIDLCString; + + +/** + * typedefs for backwards compatibility + */ + +typedef nsAString nsSubstring; +typedef nsACString nsCSubstring; + +typedef nsString nsAFlatString; +typedef nsSubstring nsASingleFragmentString; + +typedef nsCString nsAFlatCString; +typedef nsCSubstring nsASingleFragmentCString; + + +#endif /* !defined(nsStringFwd_h___) */ diff --git a/xpcom/string/nsStringIterator.h b/xpcom/string/nsStringIterator.h new file mode 100644 index 000000000..e309a21e9 --- /dev/null +++ b/xpcom/string/nsStringIterator.h @@ -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/. */ + +#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 nsAString; + friend class nsACString; + + // 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() + { + } + // nsReadingIterator( const nsReadingIterator& ); // auto-generated copy-constructor OK + // nsReadingIterator& operator=( const nsReadingIterator& ); // auto-generated copy-assignment operator OK + + 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; + } +}; + +/** + * @see nsTAString + */ + +template +class nsWritingIterator +{ +public: + typedef nsWritingIterator self_type; + typedef ptrdiff_t difference_type; + typedef size_t size_type; + typedef CharT value_type; + typedef CharT* pointer; + typedef CharT& reference; + +private: + friend class nsAString; + friend class nsACString; + + // unfortunately, the API for nsWritingIterator 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. + + CharT* mStart; + CharT* mEnd; + CharT* mPosition; + +public: + nsWritingIterator() + { + } + // nsWritingIterator( const nsWritingIterator& ); // auto-generated copy-constructor OK + // nsWritingIterator& operator=( const nsWritingIterator& ); // auto-generated copy-assignment operator OK + + pointer get() const + { + return mPosition; + } + + reference 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 writing 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 writing 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 +struct nsCharSinkTraits> +{ + static void + write(nsWritingIterator& aIter, const CharT* aStr, uint32_t aN) + { + nsCharTraits::move(aIter.get(), aStr, aN); + aIter.advance(aN); + } +}; + +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(); +} + + +// +// |nsWritingIterator|s +// + +template +inline bool +operator==(const nsWritingIterator& aLhs, + const nsWritingIterator& aRhs) +{ + return aLhs.get() == aRhs.get(); +} + +template +inline bool +operator!=(const nsWritingIterator& aLhs, + const nsWritingIterator& aRhs) +{ + return aLhs.get() != aRhs.get(); +} + +#endif /* !defined(nsStringIterator_h___) */ diff --git a/xpcom/string/nsStringObsolete.cpp b/xpcom/string/nsStringObsolete.cpp new file mode 100644 index 000000000..bd6daacab --- /dev/null +++ b/xpcom/string/nsStringObsolete.cpp @@ -0,0 +1,1053 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. 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" + + +/** + * nsTString obsolete API support + */ + +#if MOZ_STRING_WITH_OBSOLETE_API + +#include "nsDependentString.h" +#include "nsDependentSubstring.h" +#include "nsReadableUtils.h" +#include "nsCRT.h" +#include "nsUTF8Utils.h" +#include "prdtoa.h" + +/* ***** BEGIN RICKG BLOCK ***** + * + * NOTE: This section of code was extracted from rickg's bufferRoutines.h file. + * For the most part it remains unmodified. We want to eliminate (or at + * least clean up) this code at some point. If you find the formatting + * in this section somewhat inconsistent, don't blame me! ;-) + */ + +// avoid STDC's tolower since it may do weird things with non-ASCII bytes +inline char +ascii_tolower(char aChar) +{ + if (aChar >= 'A' && aChar <= 'Z') + return aChar + ('a' - 'A'); + return aChar; +} + +//----------------------------------------------------------------------------- +// +// This set of methods is used to search a buffer looking for a char. +// + + +/** + * This methods cans the given buffer for the given char + * + * @update gess 02/17/00 + * @param aDest is the buffer to be searched + * @param aDestLength is the size (in char-units, not bytes) of the buffer + * @param anOffset is the start pos to begin searching + * @param aChar is the target character we're looking for + * @param aCount tells us how many characters to iterate through (which may be different than aLength); -1 means use full length. + * @return index of pos if found, else -1 (kNotFound) + */ +static int32_t +FindChar1(const char* aDest,uint32_t aDestLength,int32_t anOffset,const char16_t aChar,int32_t aCount) { + + if(anOffset < 0) + anOffset=0; + + if(aCount < 0) + aCount = (int32_t)aDestLength; + + if((aChar < 256) && (0 < aDestLength) && ((uint32_t)anOffset < aDestLength)) { + + //We'll only search if the given aChar is within the normal ascii a range, + //(Since this string is definitely within the ascii range). + + if(0 + */ +static +#ifdef __SUNPRO_CC +inline +#endif /* __SUNPRO_CC */ +int32_t +Compare1To1(const char* aStr1,const char* aStr2,uint32_t aCount,bool aIgnoreCase) { + int32_t result=0; + if(aIgnoreCase) + result=int32_t(PL_strncasecmp(aStr1, aStr2, aCount)); + else + result=nsCharTraits::compare(aStr1,aStr2,aCount); + + // alien comparisons may return out-of-bound answers + // instead of the -1, 0, 1 expected by most clients + if ( result < -1 ) + result = -1; + else if ( result > 1 ) + result = 1; + return result; +} + +/** + * This method compares the data in one buffer with another + * @update gess 01/04/99 + * @param aStr1 is the first buffer to be compared + * @param aStr2 is the 2nd buffer to be compared + * @param aCount is the number of chars to compare + * @param aIgnoreCase tells us whether to use a case-sensitive comparison + * @return -1,0,1 depending on <,==,> + */ +static +#ifdef __SUNPRO_CC +inline +#endif /* __SUNPRO_CC */ +int32_t +Compare2To2(const char16_t* aStr1,const char16_t* aStr2,uint32_t aCount){ + int32_t result; + + if ( aStr1 && aStr2 ) + result = nsCharTraits::compare(aStr1, aStr2, aCount); + + // The following cases are rare and survivable caller errors. + // Two null pointers are equal, but any string, even 0 length + // is greater than a null pointer. It might not really matter, + // but we pick something reasonable anyway. + else if ( !aStr1 && !aStr2 ) + result = 0; + else if ( aStr1 ) + result = 1; + else + result = -1; + + // alien comparisons may give answers outside the -1, 0, 1 expected by callers + if ( result < -1 ) + result = -1; + else if ( result > 1 ) + result = 1; + return result; +} + + +/** + * This method compares the data in one buffer with another + * @update gess 01/04/99 + * @param aStr1 is the first buffer to be compared + * @param aStr2 is the 2nd buffer to be compared + * @param aCount is the number of chars to compare + * @param aIgnoreCase tells us whether to use a case-sensitive comparison + * @return -1,0,1 depending on <,==,> + */ +static +#ifdef __SUNPRO_CC +inline +#endif /* __SUNPRO_CC */ +int32_t +Compare2To1(const char16_t* aStr1,const char* aStr2,uint32_t aCount,bool aIgnoreCase){ + const char16_t* s1 = aStr1; + const char *s2 = aStr2; + + if (aStr1 && aStr2) { + if (aCount != 0) { + do { + + char16_t c1 = *s1++; + char16_t c2 = char16_t((unsigned char)*s2++); + + if (c1 != c2) { +#ifdef DEBUG + // we won't warn on c1>=128 (the 2-byte value) because often + // it is just fine to compare an constant, ascii value (i.e. "body") + // against some non-ascii value (i.e. a unicode string that + // was downloaded from a web page) + if (aIgnoreCase && c2>=128) + NS_WARNING("got a non-ASCII string, but we can't do an accurate case conversion!"); +#endif + + // can't do case conversion on characters out of our range + if (aIgnoreCase && c1<128 && c2<128) { + + c1 = ascii_tolower(char(c1)); + c2 = ascii_tolower(char(c2)); + + if (c1 == c2) continue; + } + + if (c1 < c2) return -1; + return 1; + } + } while (--aCount); + } + } + return 0; +} + + +/** + * This method compares the data in one buffer with another + * @update gess 01/04/99 + * @param aStr1 is the first buffer to be compared + * @param aStr2 is the 2nd buffer to be compared + * @param aCount is the number of chars to compare + * @param aIgnoreCase tells us whether to use a case-sensitive comparison + * @return -1,0,1 depending on <,==,> + */ +inline int32_t +Compare1To2(const char* aStr1,const char16_t* aStr2,uint32_t aCount,bool aIgnoreCase){ + return Compare2To1(aStr2, aStr1, aCount, aIgnoreCase) * -1; +} + + +//----------------------------------------------------------------------------- +// +// This set of methods is used compress char sequences in a buffer... +// + + +/** + * This method compresses duplicate runs of a given char from the given buffer + * + * @update rickg 03.23.2000 + * @param aString is the buffer to be manipulated + * @param aLength is the length of the buffer + * @param aSet tells us which chars to compress from given buffer + * @param aEliminateLeading tells us whether to strip chars from the start of the buffer + * @param aEliminateTrailing tells us whether to strip chars from the start of the buffer + * @return the new length of the given buffer + */ +static int32_t +CompressChars1(char* aString,uint32_t aLength,const char* aSet){ + + char* from = aString; + char* end = aString + aLength; + char* to = from; + + //this code converts /n, /t, /r into normal space ' '; + //it also compresses runs of whitespace down to a single char... + if(aSet && aString && (0 < aLength)){ + uint32_t aSetLen=strlen(aSet); + + while (from < end) { + char theChar = *from++; + + *to++=theChar; //always copy this char... + + if((kNotFound!=FindChar1(aSet,aSetLen,0,theChar,aSetLen))){ + while (from < end) { + theChar = *from++; + if(kNotFound==FindChar1(aSet,aSetLen,0,theChar,aSetLen)){ + *to++ = theChar; + break; + } + } //while + } //if + } //if + *to = 0; + } + return to - aString; +} + + + +/** + * This method compresses duplicate runs of a given char from the given buffer + * + * @update rickg 03.23.2000 + * @param aString is the buffer to be manipulated + * @param aLength is the length of the buffer + * @param aSet tells us which chars to compress from given buffer + * @param aEliminateLeading tells us whether to strip chars from the start of the buffer + * @param aEliminateTrailing tells us whether to strip chars from the start of the buffer + * @return the new length of the given buffer + */ +static int32_t +CompressChars2(char16_t* aString,uint32_t aLength,const char* aSet) { + + char16_t* from = aString; + char16_t* end = from + aLength; + char16_t* to = from; + + //this code converts /n, /t, /r into normal space ' '; + //it also compresses runs of whitespace down to a single char... + if(aSet && aString && (0 < aLength)){ + uint32_t aSetLen=strlen(aSet); + + while (from < end) { + char16_t theChar = *from++; + + *to++=theChar; //always copy this char... + + if((theChar<256) && (kNotFound!=FindChar1(aSet,aSetLen,0,theChar,aSetLen))){ + while (from < end) { + theChar = *from++; + if(kNotFound==FindChar1(aSet,aSetLen,0,theChar,aSetLen)){ + *to++ = theChar; + break; + } + } //while + } //if + } //if + *to = 0; + } + return to - (char16_t*)aString; +} + +/** + * This method strips chars in a given set from the given buffer + * + * @update gess 01/04/99 + * @param aString is the buffer to be manipulated + * @param aLength is the length of the buffer + * @param aSet tells us which chars to compress from given buffer + * @param aEliminateLeading tells us whether to strip chars from the start of the buffer + * @param aEliminateTrailing tells us whether to strip chars from the start of the buffer + * @return the new length of the given buffer + */ +static int32_t +StripChars1(char* aString,uint32_t aLength,const char* aSet) { + + // XXX(darin): this code should defer writing until necessary. + + char* to = aString; + char* from = aString-1; + char* end = aString + aLength; + + if(aSet && aString && (0 < aLength)){ + uint32_t aSetLen=strlen(aSet); + while (++from < end) { + char theChar = *from; + if(kNotFound==FindChar1(aSet,aSetLen,0,theChar,aSetLen)){ + *to++ = theChar; + } + } + *to = 0; + } + return to - (char*)aString; +} + + +/** + * This method strips chars in a given set from the given buffer + * + * @update gess 01/04/99 + * @param aString is the buffer to be manipulated + * @param aLength is the length of the buffer + * @param aSet tells us which chars to compress from given buffer + * @param aEliminateLeading tells us whether to strip chars from the start of the buffer + * @param aEliminateTrailing tells us whether to strip chars from the start of the buffer + * @return the new length of the given buffer + */ +static int32_t +StripChars2(char16_t* aString,uint32_t aLength,const char* aSet) { + + // XXX(darin): this code should defer writing until necessary. + + char16_t* to = aString; + char16_t* from = aString-1; + char16_t* end = to + aLength; + + if(aSet && aString && (0 < aLength)){ + uint32_t aSetLen=strlen(aSet); + while (++from < end) { + char16_t theChar = *from; + //Note the test for ascii range below. If you have a real unicode char, + //and you're searching for chars in the (given) ascii string, there's no + //point in doing the real search since it's out of the ascii range. + if((255 +#ifndef __SUNPRO_CC +static +#endif /* !__SUNPRO_CC */ +CharT +GetFindInSetFilter( const CharT* set) +{ + CharT filter = ~CharT(0); // All bits set + while (*set) { + filter &= ~(*set); + ++set; + } + return filter; +} + +// This template class is used by our code to access rickg's buffer routines. +template struct nsBufferRoutines {}; + +template <> +struct nsBufferRoutines +{ + static + int32_t compare( const char* a, const char* b, uint32_t max, bool ic ) + { + return Compare1To1(a, b, max, ic); + } + + static + int32_t compare( const char* a, const char16_t* b, uint32_t max, bool ic ) + { + return Compare1To2(a, b, max, ic); + } + + static + int32_t find_char( const char* s, uint32_t max, int32_t offset, const char16_t c, int32_t count ) + { + return FindChar1(s, max, offset, c, count); + } + + static + int32_t rfind_char( const char* s, uint32_t max, int32_t offset, const char16_t c, int32_t count ) + { + return RFindChar1(s, max, offset, c, count); + } + + static + char get_find_in_set_filter( const char* set ) + { + return GetFindInSetFilter(set); + } + + static + int32_t strip_chars( char* s, uint32_t len, const char* set ) + { + return StripChars1(s, len, set); + } + + static + int32_t compress_chars( char* s, uint32_t len, const char* set ) + { + return CompressChars1(s, len, set); + } +}; + +template <> +struct nsBufferRoutines +{ + static + int32_t compare( const char16_t* a, const char16_t* b, uint32_t max, bool ic ) + { + NS_ASSERTION(!ic, "no case-insensitive compare here"); + return Compare2To2(a, b, max); + } + + static + int32_t compare( const char16_t* a, const char* b, uint32_t max, bool ic ) + { + return Compare2To1(a, b, max, ic); + } + + static + int32_t find_char( const char16_t* s, uint32_t max, int32_t offset, const char16_t c, int32_t count ) + { + return FindChar2(s, max, offset, c, count); + } + + static + int32_t rfind_char( const char16_t* s, uint32_t max, int32_t offset, const char16_t c, int32_t count ) + { + return RFindChar2(s, max, offset, c, count); + } + + static + char16_t get_find_in_set_filter( const char16_t* set ) + { + return GetFindInSetFilter(set); + } + + static + char16_t get_find_in_set_filter( const char* set ) + { + return (~char16_t(0)^~char(0)) | GetFindInSetFilter(set); + } + + static + int32_t strip_chars( char16_t* s, uint32_t max, const char* set ) + { + return StripChars2(s, max, set); + } + + static + int32_t compress_chars( char16_t* s, uint32_t len, const char* set ) + { + return CompressChars2(s, len, set); + } +}; + +//----------------------------------------------------------------------------- + +template +#ifndef __SUNPRO_CC +static +#endif /* !__SUNPRO_CC */ +int32_t +FindSubstring( const L* big, uint32_t bigLen, + const R* little, uint32_t littleLen, + bool ignoreCase ) +{ + if (littleLen > bigLen) + return kNotFound; + + int32_t i, max = int32_t(bigLen - littleLen); + for (i=0; i<=max; ++i, ++big) + { + if (nsBufferRoutines::compare(big, little, littleLen, ignoreCase) == 0) + return i; + } + + return kNotFound; +} + +template +#ifndef __SUNPRO_CC +static +#endif /* !__SUNPRO_CC */ +int32_t +RFindSubstring( const L* big, uint32_t bigLen, + const R* little, uint32_t littleLen, + bool ignoreCase ) +{ + if (littleLen > bigLen) + return kNotFound; + + int32_t i, max = int32_t(bigLen - littleLen); + + const L* iter = big + max; + for (i=max; iter >= big; --i, --iter) + { + if (nsBufferRoutines::compare(iter, little, littleLen, ignoreCase) == 0) + return i; + } + + return kNotFound; +} + +template +#ifndef __SUNPRO_CC +static +#endif /* !__SUNPRO_CC */ +int32_t +FindCharInSet( const CharT* data, uint32_t dataLen, const SetCharT* set ) +{ + CharT filter = nsBufferRoutines::get_find_in_set_filter(set); + + const CharT* end = data + dataLen; + for (const CharT* iter = data; iter < end; ++iter) + { + CharT currentChar = *iter; + if (currentChar & filter) + continue; // char is not in filter set; go on with next char. + + // test all chars + const SetCharT* charInSet = set; + CharT setChar = CharT(*charInSet); + while (setChar) + { + if (setChar == currentChar) + return iter - data; // found it! return index of the found char. + + setChar = CharT(*(++charInSet)); + } + } + return kNotFound; +} + +template +#ifndef __SUNPRO_CC +static +#endif /* !__SUNPRO_CC */ +int32_t +RFindCharInSet( const CharT* data, uint32_t dataLen, const SetCharT* set ) +{ + CharT filter = nsBufferRoutines::get_find_in_set_filter(set); + + for (const CharT* iter = data + dataLen - 1; iter >= data; --iter) + { + CharT currentChar = *iter; + if (currentChar & filter) + continue; // char is not in filter set; go on with next char. + + // test all chars + const CharT* charInSet = set; + CharT setChar = *charInSet; + while (setChar) + { + if (setChar == currentChar) + return iter - data; // found it! return index of the found char. + + setChar = *(++charInSet); + } + } + return kNotFound; +} + +/** + * this method changes the meaning of |offset| and |count|: + * + * upon return, + * |offset| specifies start of search range + * |count| specifies length of search range + */ +static void +Find_ComputeSearchRange( uint32_t bigLen, uint32_t littleLen, int32_t& offset, int32_t& count ) +{ + // |count| specifies how many iterations to make from |offset| + + if (offset < 0) + { + offset = 0; + } + else if (uint32_t(offset) > bigLen) + { + count = 0; + return; + } + + int32_t maxCount = bigLen - offset; + if (count < 0 || count > maxCount) + { + count = maxCount; + } + else + { + count += littleLen; + if (count > maxCount) + count = maxCount; + } +} + +/** + * this method changes the meaning of |offset| and |count|: + * + * upon entry, + * |offset| specifies the end point from which to search backwards + * |count| specifies the number of iterations from |offset| + * + * upon return, + * |offset| specifies start of search range + * |count| specifies length of search range + * + * + * EXAMPLE + * + * + -- littleLen=4 -- + + * : : + * |____|____|____|____|____|____|____|____|____|____|____|____| + * : : + * offset=5 bigLen=12 + * + * if count = 4, then we expect this function to return offset = 2 and + * count = 7. + * + */ +static void +RFind_ComputeSearchRange( uint32_t bigLen, uint32_t littleLen, int32_t& offset, int32_t& count ) +{ + if (littleLen > bigLen) + { + offset = 0; + count = 0; + return; + } + + if (offset < 0) + offset = bigLen - littleLen; + if (count < 0) + count = offset + 1; + + int32_t start = offset - count + 1; + if (start < 0) + start = 0; + + count = offset + littleLen - start; + offset = start; +} + +//----------------------------------------------------------------------------- + +// define nsString obsolete methods +#include "string-template-def-unichar.h" +#include "nsTStringObsolete.cpp" +#include "string-template-undef.h" + +// define nsCString obsolete methods +#include "string-template-def-char.h" +#include "nsTStringObsolete.cpp" +#include "string-template-undef.h" + +//----------------------------------------------------------------------------- + +// specialized methods: + +int32_t +nsString::Find( const nsAFlatString& aString, int32_t aOffset, int32_t aCount ) const +{ + // this method changes the meaning of aOffset and aCount: + Find_ComputeSearchRange(mLength, aString.Length(), aOffset, aCount); + + int32_t result = FindSubstring(mData + aOffset, aCount, static_cast(aString.get()), aString.Length(), false); + if (result != kNotFound) + result += aOffset; + return result; +} + +int32_t +nsString::Find( const char16_t* aString, int32_t aOffset, int32_t aCount ) const +{ + return Find(nsDependentString(aString), aOffset, aCount); +} + +int32_t +nsString::RFind( const nsAFlatString& aString, int32_t aOffset, int32_t aCount ) const +{ + // this method changes the meaning of aOffset and aCount: + RFind_ComputeSearchRange(mLength, aString.Length(), aOffset, aCount); + + int32_t result = RFindSubstring(mData + aOffset, aCount, static_cast(aString.get()), aString.Length(), false); + if (result != kNotFound) + result += aOffset; + return result; +} + +int32_t +nsString::RFind( const char16_t* aString, int32_t aOffset, int32_t aCount ) const +{ + return RFind(nsDependentString(aString), aOffset, aCount); +} + +int32_t +nsString::FindCharInSet( const char16_t* aSet, int32_t aOffset ) const +{ + if (aOffset < 0) + aOffset = 0; + else if (aOffset >= int32_t(mLength)) + return kNotFound; + + int32_t result = ::FindCharInSet(mData + aOffset, mLength - aOffset, aSet); + if (result != kNotFound) + result += aOffset; + return result; +} + +void +nsString::ReplaceChar( const char16_t* aSet, char16_t aNewChar ) +{ + if (!EnsureMutable()) // XXX do this lazily? + AllocFailed(mLength); + + char16_t* data = mData; + uint32_t lenRemaining = mLength; + + while (lenRemaining) + { + int32_t i = ::FindCharInSet(data, lenRemaining, aSet); + if (i == kNotFound) + break; + + data[i++] = aNewChar; + data += i; + lenRemaining -= i; + } +} + + +/** + * nsTString::Compare,CompareWithConversion,etc. + */ + +int32_t +nsCString::Compare( const char* aString, bool aIgnoreCase, int32_t aCount ) const +{ + uint32_t strLen = char_traits::length(aString); + + int32_t maxCount = int32_t(XPCOM_MIN(mLength, strLen)); + + int32_t compareCount; + if (aCount < 0 || aCount > maxCount) + compareCount = maxCount; + else + compareCount = aCount; + + int32_t result = + nsBufferRoutines::compare(mData, aString, compareCount, aIgnoreCase); + + if (result == 0 && + (aCount < 0 || strLen < uint32_t(aCount) || mLength < uint32_t(aCount))) + { + // Since the caller didn't give us a length to test, or strings shorter + // than aCount, and compareCount characters matched, we have to assume + // that the longer string is greater. + + if (mLength != strLen) + result = (mLength < strLen) ? -1 : 1; + } + return result; +} + +bool +nsString::EqualsIgnoreCase( const char* aString, int32_t aCount ) const +{ + uint32_t strLen = nsCharTraits::length(aString); + + int32_t maxCount = int32_t(XPCOM_MIN(mLength, strLen)); + + int32_t compareCount; + if (aCount < 0 || aCount > maxCount) + compareCount = maxCount; + else + compareCount = aCount; + + int32_t result = + nsBufferRoutines::compare(mData, aString, compareCount, true); + + if (result == 0 && + (aCount < 0 || strLen < uint32_t(aCount) || mLength < uint32_t(aCount))) + { + // Since the caller didn't give us a length to test, or strings shorter + // than aCount, and compareCount characters matched, we have to assume + // that the longer string is greater. + + if (mLength != strLen) + result = 1; // Arbitrarily using any number != 0 + } + return result == 0; +} + + +/** + * nsTString::ToDouble + */ + +double +nsCString::ToDouble(nsresult* aErrorCode) const +{ + double res = 0.0; + if (mLength > 0) + { + char *conv_stopped; + const char *str = mData; + // Use PR_strtod, not strtod, since we don't want locale involved. + res = PR_strtod(str, &conv_stopped); + if (conv_stopped == str+mLength) + *aErrorCode = NS_OK; + else // Not all the string was scanned + *aErrorCode = NS_ERROR_ILLEGAL_VALUE; + } + else + { + // The string was too short (0 characters) + *aErrorCode = NS_ERROR_ILLEGAL_VALUE; + } + return res; +} + +double +nsString::ToDouble(nsresult* aErrorCode) const +{ + return NS_LossyConvertUTF16toASCII(*this).ToDouble(aErrorCode); +} + + +/** + * nsTString::AssignWithConversion + */ + +void +nsCString::AssignWithConversion( const nsAString& aData ) +{ + LossyCopyUTF16toASCII(aData, *this); +} + +void +nsString::AssignWithConversion( const nsACString& aData ) +{ + CopyASCIItoUTF16(aData, *this); +} + +#endif // !MOZ_STRING_WITH_OBSOLETE_API diff --git a/xpcom/string/nsSubstring.cpp b/xpcom/string/nsSubstring.cpp new file mode 100644 index 000000000..5bc69f741 --- /dev/null +++ b/xpcom/string/nsSubstring.cpp @@ -0,0 +1,388 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifdef DEBUG +#define ENABLE_STRING_STATS +#endif + +#include "mozilla/Atomics.h" +#include "mozilla/MemoryReporting.h" + +#ifdef ENABLE_STRING_STATS +#include +#endif + +#include +#include "nsSubstring.h" +#include "nsString.h" +#include "nsStringBuffer.h" +#include "nsDependentString.h" +#include "nsMemory.h" +#include "prprf.h" +#include "nsStaticAtom.h" +#include "nsCOMPtr.h" + +#include "mozilla/IntegerPrintfMacros.h" +#ifdef XP_WIN +#include +#include +#define getpid() _getpid() +#define pthread_self() GetCurrentThreadId() +#else +#include +#include +#endif + +using mozilla::Atomic; + +// --------------------------------------------------------------------------- + +static const char16_t gNullChar = 0; + +char* const nsCharTraits::sEmptyBuffer = + (char*)const_cast(&gNullChar); +char16_t* const nsCharTraits::sEmptyBuffer = + const_cast(&gNullChar); + +// --------------------------------------------------------------------------- + +#ifdef ENABLE_STRING_STATS +class nsStringStats +{ +public: + nsStringStats() + : mAllocCount(0) + , mReallocCount(0) + , mFreeCount(0) + , mShareCount(0) + { + } + + ~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; + } + + 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"); + } + printf(" => Process ID: %" PRIuPTR ", Thread ID: %" PRIuPTR "\n", + uintptr_t(getpid()), uintptr_t(pthread_self())); + } + + Atomic mAllocCount; + Atomic mReallocCount; + Atomic mFreeCount; + Atomic mShareCount; + Atomic mAdoptCount; + Atomic mAdoptFreeCount; +}; +static nsStringStats gStringStats; +#define STRING_STAT_INCREMENT(_s) (gStringStats.m ## _s ## Count)++ +#else +#define STRING_STAT_INCREMENT(_s) +#endif + +// --------------------------------------------------------------------------- + +void +ReleaseData(void* aData, uint32_t aFlags) +{ + if (aFlags & nsSubstring::F_SHARED) { + nsStringBuffer::FromData(aData)->Release(); + } else if (aFlags & nsSubstring::F_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. +} + +// --------------------------------------------------------------------------- + +// XXX or we could make nsStringBuffer be a friend of nsTAString + +class nsAStringAccessor : public nsAString +{ +private: + nsAStringAccessor(); // NOT IMPLEMENTED + +public: + char_type* data() const + { + return mData; + } + size_type length() const + { + return mLength; + } + uint32_t flags() const + { + return mFlags; + } + + void set(char_type* aData, size_type aLen, uint32_t aFlags) + { + ReleaseData(mData, mFlags); + mData = aData; + mLength = aLen; + mFlags = aFlags; + } +}; + +class nsACStringAccessor : public nsACString +{ +private: + nsACStringAccessor(); // NOT IMPLEMENTED + +public: + char_type* data() const + { + return mData; + } + size_type length() const + { + return mLength; + } + uint32_t flags() const + { + return mFlags; + } + + void set(char_type* aData, size_type aLen, uint32_t aFlags) + { + ReleaseData(mData, mFlags); + mData = aData; + mLength = aLen; + mFlags = aFlags; + } +}; + +// --------------------------------------------------------------------------- + +void +nsStringBuffer::AddRef() +{ + ++mRefCount; + STRING_STAT_INCREMENT(Share); + NS_LOG_ADDREF(this, mRefCount, "nsStringBuffer", sizeof(*this)); +} + +void +nsStringBuffer::Release() +{ + int32_t count = --mRefCount; + NS_LOG_RELEASE(this, count, "nsStringBuffer"); + if (count == 0) { + 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 dont_AddRef(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) +{ + const nsAStringAccessor* accessor = + static_cast(&aStr); + + if (!(accessor->flags() & nsSubstring::F_SHARED)) { + return nullptr; + } + + return FromData(accessor->data()); +} + +nsStringBuffer* +nsStringBuffer::FromString(const nsACString& aStr) +{ + const nsACStringAccessor* accessor = + static_cast(&aStr); + + if (!(accessor->flags() & nsCSubstring::F_SHARED)) { + return nullptr; + } + + return FromData(accessor->data()); +} + +void +nsStringBuffer::ToString(uint32_t aLen, nsAString& aStr, + bool aMoveOwnership) +{ + char16_t* data = static_cast(Data()); + + nsAStringAccessor* accessor = static_cast(&aStr); + MOZ_DIAGNOSTIC_ASSERT(data[aLen] == char16_t(0), + "data should be null terminated"); + + // preserve class flags + uint32_t flags = accessor->flags(); + flags = (flags & 0xFFFF0000) | nsSubstring::F_SHARED | nsSubstring::F_TERMINATED; + + if (!aMoveOwnership) { + AddRef(); + } + accessor->set(data, aLen, flags); +} + +void +nsStringBuffer::ToString(uint32_t aLen, nsACString& aStr, + bool aMoveOwnership) +{ + char* data = static_cast(Data()); + + nsACStringAccessor* accessor = static_cast(&aStr); + MOZ_DIAGNOSTIC_ASSERT(data[aLen] == char(0), + "data should be null terminated"); + + // preserve class flags + uint32_t flags = accessor->flags(); + flags = (flags & 0xFFFF0000) | nsCSubstring::F_SHARED | nsCSubstring::F_TERMINATED; + + if (!aMoveOwnership) { + AddRef(); + } + accessor->set(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); +} + +// --------------------------------------------------------------------------- + + +// define nsSubstring +#include "string-template-def-unichar.h" +#include "nsTSubstring.cpp" +#include "string-template-undef.h" + +// define nsCSubstring +#include "string-template-def-char.h" +#include "nsTSubstring.cpp" +#include "string-template-undef.h" + +// Check that internal and external strings have the same size. +// See https://bugzilla.mozilla.org/show_bug.cgi?id=430581 + +#include "mozilla/Logging.h" +#include "nsXPCOMStrings.h" + +static_assert(sizeof(nsStringContainer_base) == sizeof(nsSubstring), + "internal and external strings must have the same size"); + +// Provide rust bindings to the nsA[C]String types +extern "C" { + +void Gecko_FinalizeCString(nsACString* aThis) +{ + aThis->~nsACString(); +} + +void Gecko_AssignCString(nsACString* aThis, const nsACString* aOther) +{ + aThis->Assign(*aOther); +} + +void Gecko_AppendCString(nsACString* aThis, const nsACString* aOther) +{ + aThis->Append(*aOther); +} + +void Gecko_FinalizeString(nsAString* aThis) +{ + aThis->~nsAString(); +} + +void Gecko_AssignString(nsAString* aThis, const nsAString* aOther) +{ + aThis->Assign(*aOther); +} + +void Gecko_AppendString(nsAString* aThis, const nsAString* aOther) +{ + aThis->Append(*aOther); +} + +} // extern "C" diff --git a/xpcom/string/nsSubstring.h b/xpcom/string/nsSubstring.h new file mode 100644 index 000000000..67125ba31 --- /dev/null +++ b/xpcom/string/nsSubstring.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 nsSubstring_h___ +#define nsSubstring_h___ + +#include "nsAString.h" + +#endif // !defined(nsSubstring_h___) diff --git a/xpcom/string/nsSubstringTuple.cpp b/xpcom/string/nsSubstringTuple.cpp new file mode 100644 index 000000000..3de928dda --- /dev/null +++ b/xpcom/string/nsSubstringTuple.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 "nsSubstringTuple.h" + +// convert fragment to |const substring_type&| +#define TO_SUBSTRING(_v) (*(_v)) + +// define nsSubstringTuple +#include "string-template-def-unichar.h" +#include "nsTSubstringTuple.cpp" +#include "string-template-undef.h" + +// define nsCSubstringTuple +#include "string-template-def-char.h" +#include "nsTSubstringTuple.cpp" +#include "string-template-undef.h" diff --git a/xpcom/string/nsSubstringTuple.h b/xpcom/string/nsSubstringTuple.h new file mode 100644 index 000000000..5a61cd831 --- /dev/null +++ b/xpcom/string/nsSubstringTuple.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 nsSubstringTuple_h___ +#define nsSubstringTuple_h___ + +#include "nsSubstring.h" + +// declare nsSubstringTuple +#include "string-template-def-unichar.h" +#include "nsTSubstringTuple.h" +#include "string-template-undef.h" + +// declare nsCSubstringTuple +#include "string-template-def-char.h" +#include "nsTSubstringTuple.h" +#include "string-template-undef.h" + +#endif // !defined(nsSubstringTuple_h___) diff --git a/xpcom/string/nsTDependentString.cpp b/xpcom/string/nsTDependentString.cpp new file mode 100644 index 000000000..2f3a095d1 --- /dev/null +++ b/xpcom/string/nsTDependentString.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/. */ + +void +nsTDependentString_CharT::Rebind(const string_type& str, uint32_t startPos) +{ + MOZ_ASSERT(str.Flags() & F_TERMINATED, "Unterminated flat string"); + + // If we currently own a buffer, release it. + Finalize(); + + size_type strLength = str.Length(); + + if (startPos > strLength) { + startPos = strLength; + } + + mData = const_cast(static_cast(str.Data())) + startPos; + mLength = strLength - startPos; + + SetDataFlags(str.Flags() & (F_TERMINATED | F_LITERAL)); +} diff --git a/xpcom/string/nsTDependentString.h b/xpcom/string/nsTDependentString.h new file mode 100644 index 000000000..44055d5ac --- /dev/null +++ b/xpcom/string/nsTDependentString.h @@ -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/. */ + + +/** + * nsTDependentString_CharT + * + * 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. + */ +class nsTDependentString_CharT : public nsTString_CharT +{ +public: + + typedef nsTDependentString_CharT self_type; + +public: + + /** + * constructors + */ + + nsTDependentString_CharT(const char_type* aStart, const char_type* aEnd) + : string_type(const_cast(aStart), + uint32_t(aEnd - aStart), F_TERMINATED) + { + AssertValidDependentString(); + } + + nsTDependentString_CharT(const char_type* aData, uint32_t aLength) + : string_type(const_cast(aData), aLength, F_TERMINATED) + { + AssertValidDependentString(); + } + +#if defined(CharT_is_PRUnichar) && defined(MOZ_USE_CHAR16_WRAPPER) + nsTDependentString_CharT(char16ptr_t aData, uint32_t aLength) + : nsTDependentString_CharT(static_cast(aData), aLength) + { + } +#endif + + explicit + nsTDependentString_CharT(const char_type* aData) + : string_type(const_cast(aData), + uint32_t(char_traits::length(aData)), F_TERMINATED) + { + AssertValidDependentString(); + } + +#if defined(CharT_is_PRUnichar) && defined(MOZ_USE_CHAR16_WRAPPER) + explicit + nsTDependentString_CharT(char16ptr_t aData) + : nsTDependentString_CharT(static_cast(aData)) + { + } +#endif + + nsTDependentString_CharT(const string_type& aStr, uint32_t aStartPos) + : string_type() + { + Rebind(aStr, aStartPos); + } + + // Create a nsTDependentSubstring to be bound later + nsTDependentString_CharT() + : string_type() + { + } + + // XXX are you sure?? + // auto-generated copy-constructor OK + // auto-generated copy-assignment operator OK + // auto-generated destructor OK + + + /** + * allow this class to be bound to a different string... + */ + + using nsTString_CharT::Rebind; + void Rebind(const char_type* aData) + { + Rebind(aData, uint32_t(char_traits::length(aData))); + } + + void Rebind(const char_type* aStart, const char_type* aEnd) + { + Rebind(aStart, uint32_t(aEnd - aStart)); + } + + void Rebind(const string_type&, uint32_t aStartPos); + +private: + + // NOT USED + nsTDependentString_CharT(const substring_tuple_type&) = delete; +}; diff --git a/xpcom/string/nsTDependentSubstring.cpp b/xpcom/string/nsTDependentSubstring.cpp new file mode 100644 index 000000000..b540c028d --- /dev/null +++ b/xpcom/string/nsTDependentSubstring.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/. */ + +void +nsTDependentSubstring_CharT::Rebind(const substring_type& str, + uint32_t startPos, uint32_t length) +{ + // If we currently own a buffer, release it. + Finalize(); + + size_type strLength = str.Length(); + + if (startPos > strLength) { + startPos = strLength; + } + + mData = const_cast(static_cast(str.Data())) + startPos; + mLength = XPCOM_MIN(length, strLength - startPos); + + SetDataFlags(F_NONE); +} + +void +nsTDependentSubstring_CharT::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. + Finalize(); + + mData = const_cast(static_cast(data)); + mLength = length; + SetDataFlags(F_NONE); +} diff --git a/xpcom/string/nsTDependentSubstring.h b/xpcom/string/nsTDependentSubstring.h new file mode 100644 index 000000000..dd28f32f9 --- /dev/null +++ b/xpcom/string/nsTDependentSubstring.h @@ -0,0 +1,124 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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" + +/** + * 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 + */ +class nsTDependentSubstring_CharT : public nsTSubstring_CharT +{ +public: + + typedef nsTDependentSubstring_CharT self_type; + +public: + + void Rebind(const substring_type&, uint32_t aStartPos, + uint32_t aLength = size_type(-1)); + + void Rebind(const char_type* aData, size_type aLength); + + void Rebind(const char_type* aStart, const char_type* aEnd) + { + Rebind(aStart, size_type(aEnd - aStart)); + } + + nsTDependentSubstring_CharT(const substring_type& aStr, uint32_t aStartPos, + uint32_t aLength = size_type(-1)) + : substring_type() + { + Rebind(aStr, aStartPos, aLength); + } + + nsTDependentSubstring_CharT(const char_type* aData, size_type aLength) + : substring_type(const_cast(aData), aLength, F_NONE) + { + } + + nsTDependentSubstring_CharT(const char_type* aStart, const char_type* aEnd) + : substring_type(const_cast(aStart), uint32_t(aEnd - aStart), + F_NONE) + { + } + +#if defined(CharT_is_PRUnichar) && defined(MOZ_USE_CHAR16_WRAPPER) + nsTDependentSubstring_CharT(char16ptr_t aData, size_type aLength) + : nsTDependentSubstring_CharT(static_cast(aData), aLength) + { + } + + nsTDependentSubstring_CharT(char16ptr_t aStart, char16ptr_t aEnd) + : nsTDependentSubstring_CharT(static_cast(aStart), + static_cast(aEnd)) + { + } +#endif + + nsTDependentSubstring_CharT(const const_iterator& aStart, + const const_iterator& aEnd) + : substring_type(const_cast(aStart.get()), + uint32_t(aEnd.get() - aStart.get()), F_NONE) + { + } + + // Create a nsTDependentSubstring to be bound later + nsTDependentSubstring_CharT() + : substring_type() + { + } + + // auto-generated copy-constructor OK (XXX really?? what about base class copy-ctor?) + +private: + // NOT USED + void operator=(const self_type&); // we're immutable, you can't assign into a substring +}; + +inline const nsTDependentSubstring_CharT +Substring(const nsTSubstring_CharT& aStr, uint32_t aStartPos, + uint32_t aLength = uint32_t(-1)) +{ + return nsTDependentSubstring_CharT(aStr, aStartPos, aLength); +} + +inline const nsTDependentSubstring_CharT +Substring(const nsReadingIterator& aStart, + const nsReadingIterator& aEnd) +{ + return nsTDependentSubstring_CharT(aStart.get(), aEnd.get()); +} + +inline const nsTDependentSubstring_CharT +Substring(const CharT* aData, uint32_t aLength) +{ + return nsTDependentSubstring_CharT(aData, aLength); +} + +inline const nsTDependentSubstring_CharT +Substring(const CharT* aStart, const CharT* aEnd) +{ + return nsTDependentSubstring_CharT(aStart, aEnd); +} + +inline const nsTDependentSubstring_CharT +StringHead(const nsTSubstring_CharT& aStr, uint32_t aCount) +{ + return nsTDependentSubstring_CharT(aStr, 0, aCount); +} + +inline const nsTDependentSubstring_CharT +StringTail(const nsTSubstring_CharT& aStr, uint32_t aCount) +{ + return nsTDependentSubstring_CharT(aStr, aStr.Length() - aCount, aCount); +} diff --git a/xpcom/string/nsTLiteralString.h b/xpcom/string/nsTLiteralString.h new file mode 100644 index 000000000..fa75ba829 --- /dev/null +++ b/xpcom/string/nsTLiteralString.h @@ -0,0 +1,41 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + + +/** + * nsTLiteralString_CharT + * + * Stores a null-terminated, immutable sequence of characters. + * + * Subclass of nsTString that restricts string value to a literal + * character sequence. This class does not own its data. The data is + * assumed to be permanent. In practice this is true because this code + * is only usable by and for libxul. + */ +class nsTLiteralString_CharT : public nsTString_CharT +{ +public: + + typedef nsTLiteralString_CharT self_type; + +public: + + /** + * constructor + */ + + template + explicit nsTLiteralString_CharT(const char_type (&aStr)[N]) + : string_type(const_cast(aStr), N - 1, F_TERMINATED | F_LITERAL) + { + } + +private: + + // NOT TO BE IMPLEMENTED + template + nsTLiteralString_CharT(char_type (&aStr)[N]) = delete; +}; diff --git a/xpcom/string/nsTPromiseFlatString.cpp b/xpcom/string/nsTPromiseFlatString.cpp new file mode 100644 index 000000000..f02fc925c --- /dev/null +++ b/xpcom/string/nsTPromiseFlatString.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/. */ + +void +nsTPromiseFlatString_CharT::Init(const substring_type& str) +{ + if (str.IsTerminated()) { + mData = const_cast(static_cast(str.Data())); + mLength = str.Length(); + mFlags = str.mFlags & (F_TERMINATED | F_LITERAL); + // does not promote F_VOIDED + } else { + Assign(str); + } +} diff --git a/xpcom/string/nsTPromiseFlatString.h b/xpcom/string/nsTPromiseFlatString.h new file mode 100644 index 000000000..d71ce148d --- /dev/null +++ b/xpcom/string/nsTPromiseFlatString.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/. */ + + +/** + * 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. + */ + +class nsTPromiseFlatString_CharT : public nsTString_CharT +{ +public: + + typedef nsTPromiseFlatString_CharT self_type; + +private: + + void Init(const substring_type&); + + // NOT TO BE IMPLEMENTED + void operator=(const self_type&) = delete; + + // NOT TO BE IMPLEMENTED + nsTPromiseFlatString_CharT() = delete; + + // NOT TO BE IMPLEMENTED + nsTPromiseFlatString_CharT(const string_type& aStr) = delete; + +public: + + explicit + nsTPromiseFlatString_CharT(const substring_type& aStr) + : string_type() + { + Init(aStr); + } + + explicit + nsTPromiseFlatString_CharT(const substring_tuple_type& aTuple) + : string_type() + { + // nothing else to do here except assign the value of the tuple + // into ourselves. + Assign(aTuple); + } +}; + +// 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_CharT +TPromiseFlatString_CharT(const T& aString) +{ + return nsTPromiseFlatString_CharT(aString); +} diff --git a/xpcom/string/nsTString.cpp b/xpcom/string/nsTString.cpp new file mode 100644 index 000000000..13dd2628e --- /dev/null +++ b/xpcom/string/nsTString.cpp @@ -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/. */ + +nsTAdoptingString_CharT& +nsTAdoptingString_CharT::operator=(const self_type& str) +{ + // This'll violate the constness of this argument, that's just + // the nature of this class... + self_type* mutable_str = const_cast(&str); + + if (str.mFlags & F_OWNED) { + // We want to do what Adopt() does, but without actually incrementing + // the Adopt count. Note that we can be a little more straightforward + // about this than Adopt() is, because we know that str.mData is + // non-null. Should we be able to assert that str is not void here? + NS_ASSERTION(str.mData, "String with null mData?"); + Finalize(); + mData = str.mData; + mLength = str.mLength; + SetDataFlags(F_TERMINATED | F_OWNED); + + // Make str forget the buffer we just took ownership of. + new (mutable_str) self_type(); + } else { + Assign(str); + + mutable_str->Truncate(); + } + + return *this; +} + +void +nsTString_CharT::Rebind(const char_type* data, size_type length) +{ + // If we currently own a buffer, release it. + Finalize(); + + mData = const_cast(data); + mLength = length; + SetDataFlags(F_TERMINATED); + AssertValidDependentString(); +} + diff --git a/xpcom/string/nsTString.h b/xpcom/string/nsTString.h new file mode 100644 index 000000000..6fbb9d3ad --- /dev/null +++ b/xpcom/string/nsTString.h @@ -0,0 +1,883 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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" + +/** + * 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. + */ +class nsTString_CharT : public nsTSubstring_CharT +{ +public: + + typedef nsTString_CharT self_type; + +public: + + /** + * constructors + */ + + nsTString_CharT() + : substring_type() + { + } + + explicit + nsTString_CharT(const char_type* aData, size_type aLength = size_type(-1)) + : substring_type() + { + Assign(aData, aLength); + } + +#if defined(CharT_is_PRUnichar) && defined(MOZ_USE_CHAR16_WRAPPER) + explicit + nsTString_CharT(char16ptr_t aStr, size_type aLength = size_type(-1)) + : substring_type() + { + Assign(static_cast(aStr), aLength); + } +#endif + + nsTString_CharT(const self_type& aStr) + : substring_type() + { + Assign(aStr); + } + + MOZ_IMPLICIT nsTString_CharT(const substring_tuple_type& aTuple) + : substring_type() + { + Assign(aTuple); + } + + explicit + nsTString_CharT(const substring_type& aReadable) + : substring_type() + { + Assign(aReadable); + } + + + // |operator=| does not inherit, so we must define our own + self_type& operator=(char_type aChar) + { + Assign(aChar); + return *this; + } + self_type& operator=(const char_type* aData) + { + Assign(aData); + return *this; + } + self_type& operator=(const self_type& aStr) + { + Assign(aStr); + return *this; + } +#if defined(CharT_is_PRUnichar) && defined(MOZ_USE_CHAR16_WRAPPER) + self_type& operator=(const char16ptr_t aStr) + { + Assign(static_cast(aStr)); + return *this; + } +#endif + self_type& operator=(const substring_type& aStr) + { + Assign(aStr); + return *this; + } + self_type& operator=(const substring_tuple_type& aTuple) + { + Assign(aTuple); + return *this; + } + + /** + * returns the null-terminated string + */ + +#if defined(CharT_is_PRUnichar) && defined(MOZ_USE_CHAR16_WRAPPER) + char16ptr_t get() const +#else + const char_type* get() const +#endif + { + return 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 + { + NS_ASSERTION(aIndex <= mLength, "index exceeds allowable range"); + return mData[aIndex]; + } + + char_type operator[](index_type aIndex) const + { + return CharAt(aIndex); + } + + +#if MOZ_STRING_WITH_OBSOLETE_API + + + /** + * Search for the given substring within this string. + * + * @param aString is substring to be sought in this + * @param aIgnoreCase selects case sensitivity + * @param aOffset tells us where in this string to start searching + * @param aCount tells us how far from the offset we are to search. Use + * -1 to search the whole string. + * @return offset in string, or kNotFound + */ + + int32_t Find(const nsCString& aString, bool aIgnoreCase = false, + int32_t aOffset = 0, int32_t aCount = -1) const; + int32_t Find(const char* aString, bool aIgnoreCase = false, + int32_t aOffset = 0, int32_t aCount = -1) const; + +#ifdef CharT_is_PRUnichar + int32_t Find(const nsAFlatString& aString, int32_t aOffset = 0, + int32_t aCount = -1) const; + int32_t Find(const char16_t* aString, int32_t aOffset = 0, + int32_t aCount = -1) const; +#ifdef MOZ_USE_CHAR16_WRAPPER + int32_t Find(char16ptr_t aString, int32_t aOffset = 0, + int32_t aCount = -1) const + { + return Find(static_cast(aString), aOffset, aCount); + } +#endif +#endif + + + /** + * This methods scans the string backwards, looking for the given string + * + * @param aString is substring to be sought in this + * @param aIgnoreCase tells us whether or not to do caseless compare + * @param aOffset tells us where in this string to start searching. + * Use -1 to search from the end of the string. + * @param aCount tells us how many iterations to make starting at the + * given offset. + * @return offset in string, or kNotFound + */ + + int32_t RFind(const nsCString& aString, bool aIgnoreCase = false, + int32_t aOffset = -1, int32_t aCount = -1) const; + int32_t RFind(const char* aCString, bool aIgnoreCase = false, + int32_t aOffset = -1, int32_t aCount = -1) const; + +#ifdef CharT_is_PRUnichar + int32_t RFind(const nsAFlatString& aString, int32_t aOffset = -1, + int32_t aCount = -1) const; + int32_t RFind(const char16_t* aString, int32_t aOffset = -1, + int32_t aCount = -1) const; +#endif + + + /** + * Search for given char within this string + * + * @param aChar is the character to search for + * @param aOffset tells us where in this string to start searching + * @param aCount tells us how far from the offset we are to search. + * Use -1 to search the whole string. + * @return offset in string, or kNotFound + */ + + // int32_t FindChar( char16_t aChar, int32_t aOffset=0, int32_t aCount=-1 ) const; + int32_t RFindChar(char16_t aChar, int32_t aOffset = -1, + int32_t aCount = -1) const; + + + /** + * This method searches this string for the first character found in + * the given string. + * + * @param aString 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 char* aString, int32_t aOffset = 0) const; + int32_t FindCharInSet(const self_type& aString, int32_t aOffset = 0) const + { + return FindCharInSet(aString.get(), aOffset); + } + +#ifdef CharT_is_PRUnichar + int32_t FindCharInSet(const char16_t* aString, int32_t aOffset = 0) const; +#endif + + + /** + * This method searches this string for the last character found in + * the given string. + * + * @param aString 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 char_type* aString, int32_t aOffset = -1) const; + int32_t RFindCharInSet(const self_type& aString, int32_t aOffset = -1) const + { + return RFindCharInSet(aString.get(), aOffset); + } + + + /** + * Compares a given string to this string. + * + * @param aString is the string to be compared + * @param aIgnoreCase tells us how to treat case + * @param aCount tells us how many chars to compare + * @return -1,0,1 + */ + +#ifdef CharT_is_char + int32_t Compare(const char* aString, bool aIgnoreCase = false, + int32_t aCount = -1) const; +#endif + + + /** + * Equality check between given string and this string. + * + * @param aString is the string to check + * @param aIgnoreCase tells us how to treat case + * @param aCount tells us how many chars to compare + * @return boolean + */ +#ifdef CharT_is_char + bool EqualsIgnoreCase(const char* aString, int32_t aCount = -1) const + { + return Compare(aString, true, aCount) == 0; + } +#else + bool EqualsIgnoreCase(const char* aString, int32_t aCount = -1) const; + + +#endif // !CharT_is_PRUnichar + + /** + * Perform string to double-precision float conversion. + * + * @param aErrorCode will contain error if one occurs + * @return double-precision float rep of string value + */ + double ToDouble(nsresult* aErrorCode) const; + + /** + * Perform string to single-precision float conversion. + * + * @param aErrorCode will contain error if one occurs + * @return single-precision float rep of string value + */ + float ToFloat(nsresult* aErrorCode) const + { + return (float)ToDouble(aErrorCode); + } + + + /** + * Perform string to int conversion. + * @param aErrorCode will contain error if one occurs + * @param aRadix tells us which radix to assume; kAutoDetect tells us to determine the radix for you. + * @return int rep of string value, and possible (out) error code + */ + int32_t ToInteger(nsresult* aErrorCode, uint32_t aRadix = kRadix10) const; + + /** + * Perform string to 64-bit int conversion. + * @param aErrorCode will contain error if one occurs + * @param aRadix tells us which radix to assume; kAutoDetect tells us to determine the radix for you. + * @return 64-bit int rep of string value, and possible (out) error code + */ + int64_t ToInteger64(nsresult* aErrorCode, uint32_t aRadix = kRadix10) const; + + + /** + * |Left|, |Mid|, and |Right| are annoying signatures that seem better almost + * any _other_ way than they are now. Consider these alternatives + * + * aWritable = aReadable.Left(17); // ...a member function that returns a |Substring| + * aWritable = Left(aReadable, 17); // ...a global function that returns a |Substring| + * Left(aReadable, 17, aWritable); // ...a global function that does the assignment + * + * as opposed to the current signature + * + * aReadable.Left(aWritable, 17); // ...a member function that does the assignment + * + * 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, uint32_t aStartPos, uint32_t 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(mLength, aCount); + return Mid(aResult, mLength - aCount, aCount); + } + + + /** + * 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, uint32_t aIndex); + + + /** + * These methods are used to remove all occurrences of the + * characters found in aSet from this string. + * + * @param aSet -- characters to be cut from this + */ + void StripChars(const char* aSet); + bool StripChars(const char* aSet, const fallible_t&); + + + /** + * This method strips whitespace throughout the string. + */ + void StripWhitespace(); + bool StripWhitespace(const fallible_t&); + + + /** + * swaps occurence of 1 string for another + */ + + void ReplaceChar(char_type aOldChar, char_type aNewChar); + void ReplaceChar(const char* aSet, char_type aNewChar); +#ifdef CharT_is_PRUnichar + void ReplaceChar(const char16_t* aSet, char16_t aNewChar); +#endif + /** + * 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); + MOZ_MUST_USE bool ReplaceSubstring(const self_type& aTarget, + const self_type& aNewValue, + const fallible_t&); + MOZ_MUST_USE bool ReplaceSubstring(const char_type* aTarget, + const char_type* aNewValue, + const fallible_t&); + + + /** + * This method trims characters found in aTrimSet from + * either end of the underlying string. + * + * @param aSet -- contains chars to be trimmed from both ends + * @param aEliminateLeading + * @param aEliminateTrailing + * @param aIgnoreQuotes -- if true, causes surrounding quotes to be ignored + * @return this + */ + void Trim(const char* aSet, bool aEliminateLeading = true, + bool aEliminateTrailing = 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 aEliminateLeading controls stripping of leading ws + * @param aEliminateTrailing controls stripping of trailing ws + */ + void CompressWhitespace(bool aEliminateLeading = true, + bool aEliminateTrailing = true); + + + /** + * assign/append/insert with _LOSSY_ conversion + */ + + void AssignWithConversion(const nsTAString_IncompatibleCharT& aString); + void AssignWithConversion(const incompatible_char_type* aData, + int32_t aLength = -1); + +#endif // !MOZ_STRING_WITH_OBSOLETE_API + + /** + * 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() + { + NS_ASSERTION(mData, "nsTDependentString must wrap a non-NULL buffer"); + NS_ASSERTION(mLength != size_type(-1), "nsTDependentString has bogus length"); + NS_ASSERTION(mData[mLength] == 0, + "nsTDependentString must wrap only null-terminated strings. " + "You are probably looking for nsTDependentSubstring."); + } + + +protected: + + explicit + nsTString_CharT(uint32_t aFlags) + : substring_type(aFlags) + { + } + + // allow subclasses to initialize fields directly + nsTString_CharT(char_type* aData, size_type aLength, uint32_t aFlags) + : substring_type(aData, aLength, aFlags) + { + } + + struct Segment { + uint32_t mBegin, mLength; + Segment(uint32_t aBegin, uint32_t aLength) + : mBegin(aBegin) + , mLength(aLength) + {} + }; +}; + + +class nsTFixedString_CharT : public nsTString_CharT +{ +public: + + typedef nsTFixedString_CharT self_type; + typedef nsTFixedString_CharT fixed_string_type; + +public: + + /** + * @param aData + * fixed-size buffer to be used by the string (the contents of + * this buffer may be modified by the string) + * @param aStorageSize + * the size of the fixed buffer + * @param aLength (optional) + * the length of the string already contained in the buffer + */ + + nsTFixedString_CharT(char_type* aData, size_type aStorageSize) + : string_type(aData, uint32_t(char_traits::length(aData)), + F_TERMINATED | F_FIXED | F_CLASS_FIXED) + , mFixedCapacity(aStorageSize - 1) + , mFixedBuf(aData) + { + } + + nsTFixedString_CharT(char_type* aData, size_type aStorageSize, + size_type aLength) + : string_type(aData, aLength, F_TERMINATED | F_FIXED | F_CLASS_FIXED) + , mFixedCapacity(aStorageSize - 1) + , mFixedBuf(aData) + { + // null-terminate + mFixedBuf[aLength] = char_type(0); + } + + // |operator=| does not inherit, so we must define our own + self_type& operator=(char_type aChar) + { + Assign(aChar); + return *this; + } + self_type& operator=(const char_type* aData) + { + Assign(aData); + return *this; + } + self_type& operator=(const substring_type& aStr) + { + Assign(aStr); + return *this; + } + self_type& operator=(const substring_tuple_type& aTuple) + { + Assign(aTuple); + return *this; + } + +protected: + + friend class nsTSubstring_CharT; + + size_type mFixedCapacity; + char_type* mFixedBuf; +}; + + +/** + * nsTAutoString_CharT + * + * Subclass of nsTString_CharT 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: + * nsAutoString for wide characters + * nsAutoCString for narrow characters + */ +class MOZ_NON_MEMMOVABLE nsTAutoString_CharT : public nsTFixedString_CharT +{ +public: + + typedef nsTAutoString_CharT self_type; + +public: + + /** + * constructors + */ + + nsTAutoString_CharT() + : fixed_string_type(mStorage, kDefaultStorageSize, 0) + { + } + + explicit + nsTAutoString_CharT(char_type aChar) + : fixed_string_type(mStorage, kDefaultStorageSize, 0) + { + Assign(aChar); + } + + explicit + nsTAutoString_CharT(const char_type* aData, size_type aLength = size_type(-1)) + : fixed_string_type(mStorage, kDefaultStorageSize, 0) + { + Assign(aData, aLength); + } + +#if defined(CharT_is_PRUnichar) && defined(MOZ_USE_CHAR16_WRAPPER) + explicit + nsTAutoString_CharT(char16ptr_t aData, size_type aLength = size_type(-1)) + : nsTAutoString_CharT(static_cast(aData), aLength) + { + } +#endif + + nsTAutoString_CharT(const self_type& aStr) + : fixed_string_type(mStorage, kDefaultStorageSize, 0) + { + Assign(aStr); + } + + explicit + nsTAutoString_CharT(const substring_type& aStr) + : fixed_string_type(mStorage, kDefaultStorageSize, 0) + { + Assign(aStr); + } + + MOZ_IMPLICIT nsTAutoString_CharT(const substring_tuple_type& aTuple) + : fixed_string_type(mStorage, kDefaultStorageSize, 0) + { + Assign(aTuple); + } + + // |operator=| does not inherit, so we must define our own + self_type& operator=(char_type aChar) + { + Assign(aChar); + return *this; + } + self_type& operator=(const char_type* aData) + { + Assign(aData); + return *this; + } +#if defined(CharT_is_PRUnichar) && defined(MOZ_USE_CHAR16_WRAPPER) + self_type& operator=(char16ptr_t aStr) + { + Assign(aStr); + return *this; + } +#endif + self_type& operator=(const self_type& aStr) + { + Assign(aStr); + return *this; + } + self_type& operator=(const substring_type& aStr) + { + Assign(aStr); + return *this; + } + self_type& operator=(const substring_tuple_type& aTuple) + { + Assign(aTuple); + return *this; + } + + enum + { + kDefaultStorageSize = 64 + }; + +private: + + char_type mStorage[kDefaultStorageSize]; +}; + + +// +// 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; + } + static Dont_Instantiate_nsTArray_of* + Destruct(Instead_Use_nsTArray_of* aE) + { + return 0; + } +}; + +/** + * nsTXPIDLString extends nsTString such that: + * + * (1) mData can be null + * (2) objects of this type can be automatically cast to |const CharT*| + * (3) getter_Copies method is supported to adopt data allocated with + * moz_xmalloc, such as "out string" parameters in XPIDL. + * + * NAMES: + * nsXPIDLString for wide characters + * nsXPIDLCString for narrow characters + */ +class nsTXPIDLString_CharT : public nsTString_CharT +{ +public: + + typedef nsTXPIDLString_CharT self_type; + +public: + + nsTXPIDLString_CharT() + : string_type(char_traits::sEmptyBuffer, 0, F_TERMINATED | F_VOIDED) + { + } + + // copy-constructor required to avoid default + nsTXPIDLString_CharT(const self_type& aStr) + : string_type(char_traits::sEmptyBuffer, 0, F_TERMINATED | F_VOIDED) + { + Assign(aStr); + } + + // return nullptr if we are voided +#if defined(CharT_is_PRUnichar) && defined(MOZ_USE_CHAR16_WRAPPER) + char16ptr_t get() const +#else + const char_type* get() const +#endif + { + return (mFlags & F_VOIDED) ? nullptr : mData; + } + + // this case operator is the reason why this class cannot just be a + // typedef for nsTString + operator const char_type*() const + { + return get(); + } + + // need this to diambiguous operator[int] + char_type operator[](int32_t aIndex) const + { + return CharAt(index_type(aIndex)); + } + + // |operator=| does not inherit, so we must define our own + self_type& operator=(char_type aChar) + { + Assign(aChar); + return *this; + } + self_type& operator=(const char_type* aStr) + { + Assign(aStr); + return *this; + } + self_type& operator=(const self_type& aStr) + { + Assign(aStr); + return *this; + } + self_type& operator=(const substring_type& aStr) + { + Assign(aStr); + return *this; + } + self_type& operator=(const substring_tuple_type& aTuple) + { + Assign(aTuple); + return *this; + } +}; + + +/** + * getter_Copies support for use with raw string out params: + * + * NS_IMETHOD GetBlah(char**); + * + * void some_function() + * { + * nsXPIDLCString blah; + * GetBlah(getter_Copies(blah)); + * // ... + * } + */ +class MOZ_STACK_CLASS nsTGetterCopies_CharT +{ +public: + typedef CharT char_type; + + explicit nsTGetterCopies_CharT(nsTSubstring_CharT& aStr) + : mString(aStr) + , mData(nullptr) + { + } + + ~nsTGetterCopies_CharT() + { + mString.Adopt(mData); // OK if mData is null + } + + operator char_type**() + { + return &mData; + } + +private: + nsTSubstring_CharT& mString; + char_type* mData; +}; + +inline nsTGetterCopies_CharT +getter_Copies(nsTSubstring_CharT& aString) +{ + return nsTGetterCopies_CharT(aString); +} + + +/** + * nsTAdoptingString extends nsTXPIDLString such that: + * + * (1) Adopt given string on construction or assignment, i.e. take + * the value of what's given, and make what's given forget its + * value. Note that this class violates constness in a few + * places. Be careful! + */ +class nsTAdoptingString_CharT : public nsTXPIDLString_CharT +{ +public: + + typedef nsTAdoptingString_CharT self_type; + +public: + + explicit nsTAdoptingString_CharT() + { + } + explicit nsTAdoptingString_CharT(char_type* aStr, + size_type aLength = size_type(-1)) + { + Adopt(aStr, aLength); + } + + // copy-constructor required to adopt on copy. Note that this + // will violate the constness of |aStr| in the operator=() + // call. |aStr| will be truncated as a side-effect of this + // constructor. + nsTAdoptingString_CharT(const self_type& aStr) + { + *this = aStr; + } + + // |operator=| does not inherit, so we must define our own + self_type& operator=(const substring_type& aStr) + { + Assign(aStr); + return *this; + } + self_type& operator=(const substring_tuple_type& aTuple) + { + Assign(aTuple); + return *this; + } + + // Adopt(), if possible, when assigning to a self_type&. Note + // that this violates the constness of aStr, aStr is always + // truncated when this operator is called. + self_type& operator=(const self_type& aStr); + +private: + self_type& operator=(const char_type* aData) = delete; + self_type& operator=(char_type* aData) = delete; +}; + diff --git a/xpcom/string/nsTStringComparator.cpp b/xpcom/string/nsTStringComparator.cpp new file mode 100644 index 000000000..3f383c65f --- /dev/null +++ b/xpcom/string/nsTStringComparator.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/. */ + +int NS_FASTCALL +Compare(const nsTSubstring_CharT::base_string_type& aLhs, + const nsTSubstring_CharT::base_string_type& aRhs, + const nsTStringComparator_CharT& comp) +{ + typedef nsTSubstring_CharT::size_type size_type; + + if (&aLhs == &aRhs) { + return 0; + } + + nsTSubstring_CharT::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; +} + +int +nsTDefaultStringComparator_CharT::operator()(const char_type* aLhs, + const char_type* aRhs, + uint32_t aLLength, + uint32_t aRLength) const +{ + return + aLLength == aRLength ? nsCharTraits::compare(aLhs, aRhs, aLLength) : + (aLLength > aRLength) ? 1 : -1; +} diff --git a/xpcom/string/nsTStringObsolete.cpp b/xpcom/string/nsTStringObsolete.cpp new file mode 100644 index 000000000..5a47ca310 --- /dev/null +++ b/xpcom/string/nsTStringObsolete.cpp @@ -0,0 +1,700 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. 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" + +/** + * nsTString::Find + * + * aOffset specifies starting index + * aCount specifies number of string compares (iterations) + */ + +int32_t +nsTString_CharT::Find( const nsCString& aString, bool aIgnoreCase, int32_t aOffset, int32_t aCount) const +{ + // this method changes the meaning of aOffset and aCount: + Find_ComputeSearchRange(mLength, aString.Length(), aOffset, aCount); + + int32_t result = FindSubstring(mData + aOffset, aCount, aString.get(), aString.Length(), aIgnoreCase); + if (result != kNotFound) + result += aOffset; + return result; +} + +int32_t +nsTString_CharT::Find( const char* aString, bool aIgnoreCase, int32_t aOffset, int32_t aCount) const +{ + return Find(nsDependentCString(aString), aIgnoreCase, aOffset, aCount); +} + + +/** + * nsTString::RFind + * + * aOffset specifies starting index + * aCount specifies number of string compares (iterations) + */ + +int32_t +nsTString_CharT::RFind( const nsCString& aString, bool aIgnoreCase, int32_t aOffset, int32_t aCount) const +{ + // this method changes the meaning of aOffset and aCount: + RFind_ComputeSearchRange(mLength, aString.Length(), aOffset, aCount); + + int32_t result = RFindSubstring(mData + aOffset, aCount, aString.get(), aString.Length(), aIgnoreCase); + if (result != kNotFound) + result += aOffset; + return result; +} + +int32_t +nsTString_CharT::RFind( const char* aString, bool aIgnoreCase, int32_t aOffset, int32_t aCount) const +{ + return RFind(nsDependentCString(aString), aIgnoreCase, aOffset, aCount); +} + + +/** + * nsTString::RFindChar + */ + +int32_t +nsTString_CharT::RFindChar( char16_t aChar, int32_t aOffset, int32_t aCount) const +{ + return nsBufferRoutines::rfind_char(mData, mLength, aOffset, aChar, aCount); +} + + +/** + * nsTString::FindCharInSet + */ + +int32_t +nsTString_CharT::FindCharInSet( const char* aSet, int32_t aOffset ) const +{ + if (aOffset < 0) + aOffset = 0; + else if (aOffset >= int32_t(mLength)) + return kNotFound; + + int32_t result = ::FindCharInSet(mData + aOffset, mLength - aOffset, aSet); + if (result != kNotFound) + result += aOffset; + return result; +} + + +/** + * nsTString::RFindCharInSet + */ + +int32_t +nsTString_CharT::RFindCharInSet( const CharT* aSet, int32_t aOffset ) const +{ + // We want to pass a "data length" to ::RFindCharInSet + if (aOffset < 0 || aOffset > int32_t(mLength)) + aOffset = mLength; + else + ++aOffset; + + return ::RFindCharInSet(mData, aOffset, aSet); +} + + +// it's a shame to replicate this code. it was done this way in the past +// to help performance. this function also gets to keep the rickg style +// indentation :-/ +int32_t +nsTString_CharT::ToInteger( nsresult* aErrorCode, uint32_t aRadix ) const +{ + CharT* cp=mData; + int32_t theRadix=10; // base 10 unless base 16 detected, or overriden (aRadix != kAutoDetect) + int32_t result=0; + bool negate=false; + CharT theChar=0; + + //initial value, override if we find an integer + *aErrorCode=NS_ERROR_ILLEGAL_VALUE; + + if(cp) { + + //begin by skipping over leading chars that shouldn't be part of the number... + + CharT* endcp=cp+mLength; + bool done=false; + + while((cp='A') && (theChar<='F')) { + if(10==theRadix) { + if(kAutoDetect==aRadix){ + theRadix=16; + cp=first; //backup + result=0; + haveValue = false; + } + else { + *aErrorCode=NS_ERROR_ILLEGAL_VALUE; + result=0; + break; + } + } + else { + result = (theRadix * result) + ((theChar-'A')+10); + haveValue = true; + } + } + else if((theChar>='a') && (theChar<='f')) { + if(10==theRadix) { + if(kAutoDetect==aRadix){ + theRadix=16; + cp=first; //backup + result=0; + haveValue = false; + } + else { + *aErrorCode=NS_ERROR_ILLEGAL_VALUE; + result=0; + break; + } + } + else { + result = (theRadix * result) + ((theChar-'a')+10); + haveValue = true; + } + } + else if((('X'==theChar) || ('x'==theChar)) && (!haveValue || result == 0)) { + continue; + } + else if((('#'==theChar) || ('+'==theChar)) && !haveValue) { + continue; + } + else { + //we've encountered a char that's not a legal number or sign + break; + } + + if (result < oldresult) { + // overflow! + *aErrorCode = NS_ERROR_ILLEGAL_VALUE; + result = 0; + break; + } + } //while + if(negate) + result=-result; + } //if + } + return result; +} + + +/** + * nsTString::ToInteger64 + */ +int64_t +nsTString_CharT::ToInteger64( nsresult* aErrorCode, uint32_t aRadix ) const +{ + CharT* cp=mData; + int32_t theRadix=10; // base 10 unless base 16 detected, or overriden (aRadix != kAutoDetect) + int64_t result=0; + bool negate=false; + CharT theChar=0; + + //initial value, override if we find an integer + *aErrorCode=NS_ERROR_ILLEGAL_VALUE; + + if(cp) { + + //begin by skipping over leading chars that shouldn't be part of the number... + + CharT* endcp=cp+mLength; + bool done=false; + + while((cp='A') && (theChar<='F')) { + if(10==theRadix) { + if(kAutoDetect==aRadix){ + theRadix=16; + cp=first; //backup + result=0; + haveValue = false; + } + else { + *aErrorCode=NS_ERROR_ILLEGAL_VALUE; + result=0; + break; + } + } + else { + result = (theRadix * result) + ((theChar-'A')+10); + haveValue = true; + } + } + else if((theChar>='a') && (theChar<='f')) { + if(10==theRadix) { + if(kAutoDetect==aRadix){ + theRadix=16; + cp=first; //backup + result=0; + haveValue = false; + } + else { + *aErrorCode=NS_ERROR_ILLEGAL_VALUE; + result=0; + break; + } + } + else { + result = (theRadix * result) + ((theChar-'a')+10); + haveValue = true; + } + } + else if((('X'==theChar) || ('x'==theChar)) && (!haveValue || result == 0)) { + continue; + } + else if((('#'==theChar) || ('+'==theChar)) && !haveValue) { + continue; + } + else { + //we've encountered a char that's not a legal number or sign + break; + } + + if (result < oldresult) { + // overflow! + *aErrorCode = NS_ERROR_ILLEGAL_VALUE; + result = 0; + break; + } + } //while + if(negate) + result=-result; + } //if + } + return result; +} + + +/** + * nsTString::Mid + */ + +uint32_t +nsTString_CharT::Mid( self_type& aResult, index_type aStartPos, size_type aLengthToCopy ) const +{ + if (aStartPos == 0 && aLengthToCopy >= mLength) + aResult = *this; + else + aResult = Substring(*this, aStartPos, aLengthToCopy); + + return aResult.mLength; +} + + +/** + * nsTString::SetCharAt + */ + +bool +nsTString_CharT::SetCharAt( char16_t aChar, uint32_t aIndex ) +{ + if (aIndex >= mLength) + return false; + + if (!EnsureMutable()) + AllocFailed(mLength); + + mData[aIndex] = CharT(aChar); + return true; +} + + +/** + * nsTString::StripChars,StripChar,StripWhitespace + */ + +void +nsTString_CharT::StripChars( const char* aSet ) +{ + if (!EnsureMutable()) + AllocFailed(mLength); + + mLength = nsBufferRoutines::strip_chars(mData, mLength, aSet); +} + +bool +nsTString_CharT::StripChars( const char* aSet, const fallible_t& ) +{ + if (!EnsureMutable()) { + return false; + } + + mLength = nsBufferRoutines::strip_chars(mData, mLength, aSet); + return true; +} + +void +nsTString_CharT::StripWhitespace() +{ + StripChars(kWhitespace); +} + +bool +nsTString_CharT::StripWhitespace(const fallible_t& aFallible) +{ + return StripChars(kWhitespace, aFallible); +} + +/** + * nsTString::ReplaceChar,ReplaceSubstring + */ + +void +nsTString_CharT::ReplaceChar( char_type aOldChar, char_type aNewChar ) +{ + if (!EnsureMutable()) // XXX do this lazily? + AllocFailed(mLength); + + for (uint32_t i=0; i nonMatching; + uint32_t i = 0; + uint32_t newLength = 0; + while (true) + { + int32_t r = FindSubstring(mData + i, mLength - i, static_cast(aTarget.Data()), aTarget.Length(), false); + int32_t until = (r == kNotFound) ? mLength - i : r; + nonMatching.AppendElement(Segment(i, until)); + newLength += until; + if (r == kNotFound) { + break; + } + + newLength += aNewValue.Length(); + i += r + aTarget.Length(); + if (i >= mLength) { + // Add an auxiliary entry at the end of the list to help as an edge case + // for the algorithms below. + nonMatching.AppendElement(Segment(mLength, 0)); + break; + } + } + + // 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 == mLength, + "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 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. + char_type* oldData; + uint32_t oldFlags; + if (!MutatePrep(XPCOM_MAX(mLength, newLength), &oldData, &oldFlags)) + return false; + if (oldData) { + // Copy all of the old data to the new buffer. + char_traits::copy(mData, oldData, mLength); + ::ReleaseData(oldData, oldFlags); + } + + 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 = mData + nonMatching[i].mBegin; + char_type* destinationSegmentPtr = 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 = mData + nonMatching[i].mBegin; + char_type* destinationSegmentPtr = 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. + mLength = newLength; + mData[mLength] = char_type(0); + + return true; +} + +/** + * nsTString::Trim + */ + +void +nsTString_CharT::Trim( const char* aSet, bool aTrimLeading, bool aTrimTrailing, bool aIgnoreQuotes ) +{ + // the old implementation worried about aSet being null :-/ + if (!aSet) + return; + + char_type* start = mData; + char_type* end = mData + mLength; + + // skip over quotes if requested + if (aIgnoreQuotes && mLength > 2 && mData[0] == mData[mLength - 1] && + (mData[0] == '\'' || mData[0] == '"')) + { + ++start; + --end; + } + + uint32_t setLen = nsCharTraits::length(aSet); + + if (aTrimLeading) + { + uint32_t cutStart = start - mData; + uint32_t cutLength = 0; + + // walk forward from start to end + for (; start != end; ++start, ++cutLength) + { + int32_t pos = FindChar1(aSet, setLen, 0, *start, setLen); + if (pos == kNotFound) + break; + } + + if (cutLength) + { + Cut(cutStart, cutLength); + + // reset iterators + start = mData + cutStart; + end = mData + mLength - cutStart; + } + } + + if (aTrimTrailing) + { + uint32_t cutEnd = end - mData; + uint32_t cutLength = 0; + + // walk backward from end to start + --end; + for (; end >= start; --end, ++cutLength) + { + int32_t pos = FindChar1(aSet, setLen, 0, *end, setLen); + if (pos == kNotFound) + break; + } + + if (cutLength) + Cut(cutEnd - cutLength, cutLength); + } +} + + +/** + * nsTString::CompressWhitespace + */ + +void +nsTString_CharT::CompressWhitespace( bool aTrimLeading, bool aTrimTrailing ) +{ + const char* set = kWhitespace; + + ReplaceChar(set, ' '); + Trim(set, aTrimLeading, aTrimTrailing); + + // this one does some questionable fu... just copying the old code! + mLength = nsBufferRoutines::compress_chars(mData, mLength, set); +} + + +/** + * nsTString::AssignWithConversion + */ + +void +nsTString_CharT::AssignWithConversion( const incompatible_char_type* aData, int32_t aLength ) +{ + // for compatibility with the old string implementation, we need to allow + // for a nullptr input buffer :-( + if (!aData) + { + Truncate(); + } + else + { + if (aLength < 0) + aLength = nsCharTraits::length(aData); + + AssignWithConversion(Substring(aData, aLength)); + } +} diff --git a/xpcom/string/nsTSubstring.cpp b/xpcom/string/nsTSubstring.cpp new file mode 100644 index 000000000..a3a830b9d --- /dev/null +++ b/xpcom/string/nsTSubstring.cpp @@ -0,0 +1,1089 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. 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/CheckedInt.h" +#include "mozilla/double-conversion.h" +#include "mozilla/MemoryReporting.h" + +using double_conversion::DoubleToStringConverter; + +const nsTSubstring_CharT::size_type nsTSubstring_CharT::kMaxCapacity = + (nsTSubstring_CharT::size_type(-1) / + 2 - sizeof(nsStringBuffer)) / + sizeof(nsTSubstring_CharT::char_type) - 2; + +#ifdef XPCOM_STRING_CONSTRUCTOR_OUT_OF_LINE +nsTSubstring_CharT::nsTSubstring_CharT(char_type* aData, size_type aLength, + uint32_t aFlags) + : mData(aData), + mLength(aLength), + mFlags(aFlags) +{ + MOZ_RELEASE_ASSERT(CheckCapacity(aLength), "String is too large."); + + if (aFlags & F_OWNED) { + STRING_STAT_INCREMENT(Adopt); + MOZ_LOG_CTOR(mData, "StringAdopt", 1); + } +} +#endif /* XPCOM_STRING_CONSTRUCTOR_OUT_OF_LINE */ + +/** + * helper function for down-casting a nsTSubstring to a nsTFixedString. + */ +inline const nsTFixedString_CharT* +AsFixedString(const nsTSubstring_CharT* aStr) +{ + return static_cast(aStr); +} + + +/** + * this function is called to prepare mData for writing. the given capacity + * indicates the required minimum storage size for mData, in sizeof(char_type) + * increments. this function returns true if the operation succeeds. it also + * returns the old data and old flags members if mData is newly allocated. + * the old data must be released by the caller. + */ +bool +nsTSubstring_CharT::MutatePrep(size_type aCapacity, char_type** aOldData, + uint32_t* aOldFlags) +{ + // initialize to no old data + *aOldData = nullptr; + *aOldFlags = 0; + + size_type curCapacity = Capacity(); + + // 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 (!CheckCapacity(aCapacity)) { + return false; + } + + // |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 (curCapacity != 0) { + if (aCapacity <= curCapacity) { + mFlags &= ~F_VOIDED; // mutation clears voided flag + return true; + } + } + + if (curCapacity < aCapacity) { + // 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; + } + + MOZ_ASSERT(XPCOM_MIN(temp, kMaxCapacity) >= aCapacity, + "should have hit the early return at the top"); + aCapacity = XPCOM_MIN(temp, kMaxCapacity); + } + + // + // several cases: + // + // (1) we have a shared buffer (mFlags & F_SHARED) + // (2) we have an owned buffer (mFlags & F_OWNED) + // (3) we have a fixed buffer (mFlags & F_FIXED) + // (4) we have a readonly buffer + // + // requiring that we in some cases preserve the data before creating + // a new buffer complicates things just a bit ;-) + // + + size_type storageSize = (aCapacity + 1) * sizeof(char_type); + + // case #1 + if (mFlags & F_SHARED) { + nsStringBuffer* hdr = nsStringBuffer::FromData(mData); + if (!hdr->IsReadonly()) { + nsStringBuffer* newHdr = nsStringBuffer::Realloc(hdr, storageSize); + if (!newHdr) { + return false; // out-of-memory (original header left intact) + } + + hdr = newHdr; + mData = (char_type*)hdr->Data(); + mFlags &= ~F_VOIDED; // mutation clears voided flag + return true; + } + } + + char_type* newData; + uint32_t newDataFlags; + + // if we have a fixed buffer of sufficient size, then use it. this helps + // avoid heap allocations. + if ((mFlags & F_CLASS_FIXED) && + (aCapacity < AsFixedString(this)->mFixedCapacity)) { + newData = AsFixedString(this)->mFixedBuf; + newDataFlags = F_TERMINATED | F_FIXED; + } else { + // if we reach here then, we must allocate a new buffer. we cannot + // make use of our F_OWNED or F_FIXED buffers because they are not + // large enough. + + nsStringBuffer* newHdr = + nsStringBuffer::Alloc(storageSize).take(); + if (!newHdr) { + return false; // we are still in a consistent state + } + + newData = (char_type*)newHdr->Data(); + newDataFlags = F_TERMINATED | F_SHARED; + } + + // save old data and flags + *aOldData = mData; + *aOldFlags = mFlags; + + mData = newData; + SetDataFlags(newDataFlags); + + // mLength does not change + + // though we are not necessarily terminated at the moment, now is probably + // still the best time to set F_TERMINATED. + + return true; +} + +void +nsTSubstring_CharT::Finalize() +{ + ::ReleaseData(mData, mFlags); + // mData, mLength, and mFlags are purposefully left dangling +} + +bool +nsTSubstring_CharT::ReplacePrep(index_type aCutStart, + size_type aCutLength, + size_type aNewLength) +{ + aCutLength = XPCOM_MIN(aCutLength, mLength - aCutStart); + + mozilla::CheckedInt newTotalLen = mLength; + newTotalLen += aNewLength; + newTotalLen -= aCutLength; + if (!newTotalLen.isValid()) { + return false; + } + + if (aCutStart == mLength && Capacity() > newTotalLen.value()) { + mFlags &= ~F_VOIDED; + mData[newTotalLen.value()] = char_type(0); + mLength = newTotalLen.value(); + return true; + } + + return ReplacePrepInternal(aCutStart, aCutLength, aNewLength, + newTotalLen.value()); +} + +bool +nsTSubstring_CharT::ReplacePrepInternal(index_type aCutStart, size_type aCutLen, + size_type aFragLen, size_type aNewLen) +{ + char_type* oldData; + uint32_t oldFlags; + if (!MutatePrep(aNewLen, &oldData, &oldFlags)) { + return false; // out-of-memory + } + + if (oldData) { + // determine whether or not we need to copy part of the old string + // over to the new string. + + if (aCutStart > 0) { + // copy prefix from old string + char_traits::copy(mData, oldData, aCutStart); + } + + if (aCutStart + aCutLen < mLength) { + // copy suffix from old string to new offset + size_type from = aCutStart + aCutLen; + size_type fromLen = mLength - from; + uint32_t to = aCutStart + aFragLen; + char_traits::copy(mData + to, oldData + from, fromLen); + } + + ::ReleaseData(oldData, oldFlags); + } else { + // original data remains intact + + // determine whether or not we need to move part of the existing string + // to make room for the requested hole. + if (aFragLen != aCutLen && aCutStart + aCutLen < mLength) { + uint32_t from = aCutStart + aCutLen; + uint32_t fromLen = mLength - from; + uint32_t to = aCutStart + aFragLen; + char_traits::move(mData + to, mData + from, fromLen); + } + } + + // add null terminator (mutable mData always has room for the null- + // terminator). + mData[aNewLen] = char_type(0); + mLength = aNewLen; + + return true; +} + +nsTSubstring_CharT::size_type +nsTSubstring_CharT::Capacity() const +{ + // return 0 to indicate an immutable or 0-sized buffer + + size_type capacity; + if (mFlags & F_SHARED) { + // if the string is readonly, then we pretend that it has no capacity. + nsStringBuffer* hdr = nsStringBuffer::FromData(mData); + if (hdr->IsReadonly()) { + capacity = 0; + } else { + capacity = (hdr->StorageSize() / sizeof(char_type)) - 1; + } + } else if (mFlags & F_FIXED) { + capacity = AsFixedString(this)->mFixedCapacity; + } else if (mFlags & F_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 = mLength; + } else { + capacity = 0; + } + + return capacity; +} + +bool +nsTSubstring_CharT::EnsureMutable(size_type aNewLen) +{ + if (aNewLen == size_type(-1) || aNewLen == mLength) { + if (mFlags & (F_FIXED | F_OWNED)) { + return true; + } + if ((mFlags & F_SHARED) && + !nsStringBuffer::FromData(mData)->IsReadonly()) { + return true; + } + + aNewLen = mLength; + } + return SetLength(aNewLen, mozilla::fallible); +} + +// --------------------------------------------------------------------------- + +// This version of Assign is optimized for single-character assignment. +void +nsTSubstring_CharT::Assign(char_type aChar) +{ + if (!ReplacePrep(0, mLength, 1)) { + AllocFailed(mLength); + } + + *mData = aChar; +} + +bool +nsTSubstring_CharT::Assign(char_type aChar, const fallible_t&) +{ + if (!ReplacePrep(0, mLength, 1)) { + return false; + } + + *mData = aChar; + return true; +} + +void +nsTSubstring_CharT::Assign(const char_type* aData) +{ + if (!Assign(aData, mozilla::fallible)) { + AllocFailed(char_traits::length(aData)); + } +} + +bool +nsTSubstring_CharT::Assign(const char_type* aData, const fallible_t&) +{ + return Assign(aData, size_type(-1), mozilla::fallible); +} + +void +nsTSubstring_CharT::Assign(const char_type* aData, size_type aLength) +{ + if (!Assign(aData, aLength, mozilla::fallible)) { + AllocFailed(aLength == size_type(-1) ? char_traits::length(aData) + : aLength); + } +} + +bool +nsTSubstring_CharT::Assign(const char_type* aData, size_type aLength, + const fallible_t& aFallible) +{ + if (!aData || aLength == 0) { + Truncate(); + return true; + } + + if (aLength == size_type(-1)) { + aLength = char_traits::length(aData); + } + + if (IsDependentOn(aData, aData + aLength)) { + return Assign(string_type(aData, aLength), aFallible); + } + + if (!ReplacePrep(0, mLength, aLength)) { + return false; + } + + char_traits::copy(mData, aData, aLength); + return true; +} + +void +nsTSubstring_CharT::AssignASCII(const char* aData, size_type aLength) +{ + if (!AssignASCII(aData, aLength, mozilla::fallible)) { + AllocFailed(aLength); + } +} + +bool +nsTSubstring_CharT::AssignASCII(const char* aData, size_type aLength, + const fallible_t& aFallible) +{ + // A Unicode string can't depend on an ASCII string buffer, + // so this dependence check only applies to CStrings. +#ifdef CharT_is_char + if (IsDependentOn(aData, aData + aLength)) { + return Assign(string_type(aData, aLength), aFallible); + } +#endif + + if (!ReplacePrep(0, mLength, aLength)) { + return false; + } + + char_traits::copyASCII(mData, aData, aLength); + return true; +} + +void +nsTSubstring_CharT::AssignLiteral(const char_type* aData, size_type aLength) +{ + ::ReleaseData(mData, mFlags); + mData = const_cast(aData); + mLength = aLength; + SetDataFlags(F_TERMINATED | F_LITERAL); +} + +void +nsTSubstring_CharT::Assign(const self_type& aStr) +{ + if (!Assign(aStr, mozilla::fallible)) { + AllocFailed(aStr.Length()); + } +} + +bool +nsTSubstring_CharT::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(); + mFlags |= aStr.mFlags & F_VOIDED; + return true; + } + + if (aStr.mFlags & F_SHARED) { + // nice! we can avoid a string copy :-) + + // |aStr| should be null-terminated + NS_ASSERTION(aStr.mFlags & F_TERMINATED, "shared, but not terminated"); + + ::ReleaseData(mData, mFlags); + + mData = aStr.mData; + mLength = aStr.mLength; + SetDataFlags(F_TERMINATED | F_SHARED); + + // get an owning reference to the mData + nsStringBuffer::FromData(mData)->AddRef(); + return true; + } else if (aStr.mFlags & F_LITERAL) { + MOZ_ASSERT(aStr.mFlags & F_TERMINATED, "Unterminated literal"); + + AssignLiteral(aStr.mData, aStr.mLength); + return true; + } + + // else, treat this like an ordinary assignment. + return Assign(aStr.Data(), aStr.Length(), aFallible); +} + +void +nsTSubstring_CharT::Assign(const substring_tuple_type& aTuple) +{ + if (!Assign(aTuple, mozilla::fallible)) { + AllocFailed(aTuple.Length()); + } +} + +bool +nsTSubstring_CharT::Assign(const substring_tuple_type& aTuple, + const fallible_t& aFallible) +{ + if (aTuple.IsDependentOn(mData, mData + mLength)) { + // take advantage of sharing here... + return Assign(string_type(aTuple), aFallible); + } + + size_type length = aTuple.Length(); + + // don't use ReplacePrep here because it changes the length + char_type* oldData; + uint32_t oldFlags; + if (!MutatePrep(length, &oldData, &oldFlags)) { + return false; + } + + if (oldData) { + ::ReleaseData(oldData, oldFlags); + } + + aTuple.WriteTo(mData, length); + mData[length] = 0; + mLength = length; + return true; +} + +void +nsTSubstring_CharT::Adopt(char_type* aData, size_type aLength) +{ + if (aData) { + ::ReleaseData(mData, mFlags); + + if (aLength == size_type(-1)) { + aLength = char_traits::length(aData); + } + + MOZ_RELEASE_ASSERT(CheckCapacity(aLength), "adopting a too-long string"); + + mData = aData; + mLength = aLength; + SetDataFlags(F_TERMINATED | F_OWNED); + + STRING_STAT_INCREMENT(Adopt); + // Treat this as construction of a "StringAdopt" object for leak + // tracking purposes. + MOZ_LOG_CTOR(mData, "StringAdopt", 1); + } else { + SetIsVoid(true); + } +} + + +// This version of Replace is optimized for single-character replacement. +void +nsTSubstring_CharT::Replace(index_type aCutStart, size_type aCutLength, + char_type aChar) +{ + aCutStart = XPCOM_MIN(aCutStart, Length()); + + if (ReplacePrep(aCutStart, aCutLength, 1)) { + mData[aCutStart] = aChar; + } +} + +bool +nsTSubstring_CharT::Replace(index_type aCutStart, size_type aCutLength, + char_type aChar, + const fallible_t&) +{ + aCutStart = XPCOM_MIN(aCutStart, Length()); + + if (!ReplacePrep(aCutStart, aCutLength, 1)) { + return false; + } + + mData[aCutStart] = aChar; + + return true; +} + +void +nsTSubstring_CharT::Replace(index_type aCutStart, size_type aCutLength, + const char_type* aData, size_type aLength) +{ + if (!Replace(aCutStart, aCutLength, aData, aLength, + mozilla::fallible)) { + AllocFailed(Length() - aCutLength + 1); + } +} + +bool +nsTSubstring_CharT::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 (IsDependentOn(aData, aData + aLength)) { + nsTAutoString_CharT temp(aData, aLength); + return Replace(aCutStart, aCutLength, temp, aFallible); + } + } + + aCutStart = XPCOM_MIN(aCutStart, Length()); + + bool ok = ReplacePrep(aCutStart, aCutLength, aLength); + if (!ok) { + return false; + } + + if (aLength > 0) { + char_traits::copy(mData + aCutStart, aData, aLength); + } + + return true; +} + +void +nsTSubstring_CharT::ReplaceASCII(index_type aCutStart, size_type aCutLength, + const char* aData, size_type aLength) +{ + if (!ReplaceASCII(aCutStart, aCutLength, aData, aLength, mozilla::fallible)) { + AllocFailed(Length() - aCutLength + 1); + } +} + +bool +nsTSubstring_CharT::ReplaceASCII(index_type aCutStart, size_type aCutLength, + const char* aData, size_type aLength, + const fallible_t& aFallible) +{ + if (aLength == size_type(-1)) { + aLength = strlen(aData); + } + + // A Unicode string can't depend on an ASCII string buffer, + // so this dependence check only applies to CStrings. +#ifdef CharT_is_char + if (IsDependentOn(aData, aData + aLength)) { + nsTAutoString_CharT temp(aData, aLength); + return Replace(aCutStart, aCutLength, temp, aFallible); + } +#endif + + aCutStart = XPCOM_MIN(aCutStart, Length()); + + bool ok = ReplacePrep(aCutStart, aCutLength, aLength); + if (!ok) { + return false; + } + + if (aLength > 0) { + char_traits::copyASCII(mData + aCutStart, aData, aLength); + } + + return true; +} + +void +nsTSubstring_CharT::Replace(index_type aCutStart, size_type aCutLength, + const substring_tuple_type& aTuple) +{ + if (aTuple.IsDependentOn(mData, mData + mLength)) { + nsTAutoString_CharT temp(aTuple); + Replace(aCutStart, aCutLength, temp); + return; + } + + size_type length = aTuple.Length(); + + aCutStart = XPCOM_MIN(aCutStart, Length()); + + if (ReplacePrep(aCutStart, aCutLength, length) && length > 0) { + aTuple.WriteTo(mData + aCutStart, length); + } +} + +void +nsTSubstring_CharT::ReplaceLiteral(index_type aCutStart, size_type aCutLength, + const char_type* aData, size_type aLength) +{ + aCutStart = XPCOM_MIN(aCutStart, Length()); + + if (!aCutStart && aCutLength == Length()) { + AssignLiteral(aData, aLength); + } else if (ReplacePrep(aCutStart, aCutLength, aLength) && aLength > 0) { + char_traits::copy(mData + aCutStart, aData, aLength); + } +} + +void +nsTSubstring_CharT::SetCapacity(size_type aCapacity) +{ + if (!SetCapacity(aCapacity, mozilla::fallible)) { + AllocFailed(aCapacity); + } +} + +bool +nsTSubstring_CharT::SetCapacity(size_type aCapacity, const fallible_t&) +{ + // capacity does not include room for the terminating null char + + // if our capacity is reduced to zero, then free our buffer. + if (aCapacity == 0) { + ::ReleaseData(mData, mFlags); + mData = char_traits::sEmptyBuffer; + mLength = 0; + SetDataFlags(F_TERMINATED); + return true; + } + + char_type* oldData; + uint32_t oldFlags; + if (!MutatePrep(aCapacity, &oldData, &oldFlags)) { + return false; // out-of-memory + } + + // compute new string length + size_type newLen = XPCOM_MIN(mLength, aCapacity); + + if (oldData) { + // preserve old data + if (mLength > 0) { + char_traits::copy(mData, oldData, newLen); + } + + ::ReleaseData(oldData, oldFlags); + } + + // adjust mLength if our buffer shrunk down in size + if (newLen < mLength) { + mLength = newLen; + } + + // always null-terminate here, even if the buffer got longer. this is + // for backwards compat with the old string implementation. + mData[aCapacity] = char_type(0); + + return true; +} + +void +nsTSubstring_CharT::SetLength(size_type aLength) +{ + SetCapacity(aLength); + mLength = aLength; +} + +bool +nsTSubstring_CharT::SetLength(size_type aLength, const fallible_t& aFallible) +{ + if (!SetCapacity(aLength, aFallible)) { + return false; + } + + mLength = aLength; + return true; +} + +void +nsTSubstring_CharT::SetIsVoid(bool aVal) +{ + if (aVal) { + Truncate(); + mFlags |= F_VOIDED; + } else { + mFlags &= ~F_VOIDED; + } +} + +bool +nsTSubstring_CharT::Equals(const self_type& aStr) const +{ + return mLength == aStr.mLength && + char_traits::compare(mData, aStr.mData, mLength) == 0; +} + +bool +nsTSubstring_CharT::Equals(const self_type& aStr, + const comparator_type& aComp) const +{ + return mLength == aStr.mLength && + aComp(mData, aStr.mData, mLength, aStr.mLength) == 0; +} + +bool +nsTSubstring_CharT::Equals(const char_type* aData) const +{ + // unfortunately, some callers pass null :-( + if (!aData) { + NS_NOTREACHED("null data pointer"); + return mLength == 0; + } + + // XXX avoid length calculation? + size_type length = char_traits::length(aData); + return mLength == length && + char_traits::compare(mData, aData, mLength) == 0; +} + +bool +nsTSubstring_CharT::Equals(const char_type* aData, + const comparator_type& aComp) const +{ + // unfortunately, some callers pass null :-( + if (!aData) { + NS_NOTREACHED("null data pointer"); + return mLength == 0; + } + + // XXX avoid length calculation? + size_type length = char_traits::length(aData); + return mLength == length && aComp(mData, aData, mLength, length) == 0; +} + +bool +nsTSubstring_CharT::EqualsASCII(const char* aData, size_type aLen) const +{ + return mLength == aLen && + char_traits::compareASCII(mData, aData, aLen) == 0; +} + +bool +nsTSubstring_CharT::EqualsASCII(const char* aData) const +{ + return char_traits::compareASCIINullTerminated(mData, mLength, aData) == 0; +} + +bool +nsTSubstring_CharT::LowerCaseEqualsASCII(const char* aData, + size_type aLen) const +{ + return mLength == aLen && + char_traits::compareLowerCaseToASCII(mData, aData, aLen) == 0; +} + +bool +nsTSubstring_CharT::LowerCaseEqualsASCII(const char* aData) const +{ + return char_traits::compareLowerCaseToASCIINullTerminated(mData, + mLength, + aData) == 0; +} + +nsTSubstring_CharT::size_type +nsTSubstring_CharT::CountChar(char_type aChar) const +{ + const char_type* start = mData; + const char_type* end = mData + mLength; + + return NS_COUNT(start, end, aChar); +} + +int32_t +nsTSubstring_CharT::FindChar(char_type aChar, index_type aOffset) const +{ + if (aOffset < mLength) { + const char_type* result = char_traits::find(mData + aOffset, + mLength - aOffset, aChar); + if (result) { + return result - mData; + } + } + return -1; +} + +void +nsTSubstring_CharT::StripChar(char_type aChar, int32_t aOffset) +{ + if (mLength == 0 || aOffset >= int32_t(mLength)) { + return; + } + + if (!EnsureMutable()) { // XXX do this lazily? + AllocFailed(mLength); + } + + // XXX(darin): this code should defer writing until necessary. + + char_type* to = mData + aOffset; + char_type* from = mData + aOffset; + char_type* end = mData + mLength; + + while (from < end) { + char_type theChar = *from++; + if (aChar != theChar) { + *to++ = theChar; + } + } + *to = char_type(0); // add the null + mLength = to - mData; +} + +void +nsTSubstring_CharT::StripChars(const char_type* aChars, uint32_t aOffset) +{ + if (aOffset >= uint32_t(mLength)) { + return; + } + + if (!EnsureMutable()) { // XXX do this lazily? + AllocFailed(mLength); + } + + // XXX(darin): this code should defer writing until necessary. + + char_type* to = mData + aOffset; + char_type* from = mData + aOffset; + char_type* end = mData + 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 + mLength = to - mData; +} + +int +nsTSubstring_CharT::AppendFunc(void* aArg, const char* aStr, uint32_t aLen) +{ + self_type* self = static_cast(aArg); + + // NSPR sends us the final null terminator even though we don't want it + if (aLen && aStr[aLen - 1] == '\0') { + --aLen; + } + + self->AppendASCII(aStr, aLen); + + return aLen; +} + +void +nsTSubstring_CharT::AppendPrintf(const char* aFormat, ...) +{ + va_list ap; + va_start(ap, aFormat); + uint32_t r = PR_vsxprintf(AppendFunc, this, aFormat, ap); + if (r == (uint32_t)-1) { + NS_RUNTIMEABORT("Allocation or other failure in PR_vsxprintf"); + } + va_end(ap); +} + +void +nsTSubstring_CharT::AppendPrintf(const char* aFormat, va_list aAp) +{ + uint32_t r = PR_vsxprintf(AppendFunc, this, aFormat, aAp); + if (r == (uint32_t)-1) { + NS_RUNTIMEABORT("Allocation or other failure in PR_vsxprintf"); + } +} + +/* hack to make sure we define FormatWithoutTrailingZeros only once */ +#ifdef CharT_is_PRUnichar +// 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::EMIT_POSITIVE_EXPONENT_SIGN, + "Infinity", + "NaN", + 'e', + -6, 21, + 6, 1); + double_conversion::StringBuilder builder(aBuf, sizeof(aBuf)); + bool exponential_notation = false; + converter.ToPrecision(aDouble, aPrecision, &exponential_notation, &builder); + int length = builder.position(); + char* formattedDouble = builder.Finalize(); + + // If we have a shorter string than aPrecision, it means we have a special + // value (NaN or Infinity). All other numbers will be formatted with at + // least aPrecision digits. + if (length <= aPrecision) { + return length; + } + + char* end = formattedDouble + length; + char* decimalPoint = strchr(aBuf, '.'); + // No trailing zeros to remove. + if (!decimalPoint) { + return length; + } + + if (MOZ_UNLIKELY(exponential_notation)) { + // We need to check for cases like 1.00000e-10 (yes, this is + // disgusting). + char* exponent = end - 1; + for (; ; --exponent) { + if (*exponent == 'e') { + break; + } + } + char* zerosBeforeExponent = exponent - 1; + for (; zerosBeforeExponent != decimalPoint; --zerosBeforeExponent) { + if (*zerosBeforeExponent != '0') { + break; + } + } + if (zerosBeforeExponent == decimalPoint) { + --zerosBeforeExponent; + } + // Slide the exponent to the left over the trailing zeros. Don't + // worry about copying the trailing NUL character. + size_t exponentSize = end - exponent; + memmove(zerosBeforeExponent + 1, exponent, exponentSize); + length -= exponent - (zerosBeforeExponent + 1); + } else { + char* trailingZeros = end - 1; + for (; trailingZeros != decimalPoint; --trailingZeros) { + if (*trailingZeros != '0') { + break; + } + } + if (trailingZeros == decimalPoint) { + --trailingZeros; + } + length -= end - (trailingZeros + 1); + } + + return length; +} +#endif /* CharT_is_PRUnichar */ + +void +nsTSubstring_CharT::AppendFloat(float aFloat) +{ + char buf[40]; + int length = FormatWithoutTrailingZeros(buf, aFloat, 6); + AppendASCII(buf, length); +} + +void +nsTSubstring_CharT::AppendFloat(double aFloat) +{ + char buf[40]; + int length = FormatWithoutTrailingZeros(buf, aFloat, 15); + AppendASCII(buf, length); +} + +size_t +nsTSubstring_CharT::SizeOfExcludingThisIfUnshared( + mozilla::MallocSizeOf aMallocSizeOf) const +{ + if (mFlags & F_SHARED) { + return nsStringBuffer::FromData(mData)-> + SizeOfIncludingThisIfUnshared(aMallocSizeOf); + } + if (mFlags & F_OWNED) { + return aMallocSizeOf(mData); + } + + // If we reach here, exactly one of the following must be true: + // - F_VOIDED is set, and mData points to sEmptyBuffer; + // - F_FIXED is set, and mData points to a buffer within a string + // object (e.g. nsAutoString); + // - None of F_SHARED, F_OWNED, F_FIXED is set, and mData points to a buffer + // owned by something else. + // + // In all three cases, we don't measure it. + return 0; +} + +size_t +nsTSubstring_CharT::SizeOfExcludingThisEvenIfShared( + mozilla::MallocSizeOf aMallocSizeOf) const +{ + // This is identical to SizeOfExcludingThisIfUnshared except for the + // F_SHARED case. + if (mFlags & F_SHARED) { + return nsStringBuffer::FromData(mData)-> + SizeOfIncludingThisEvenIfShared(aMallocSizeOf); + } + if (mFlags & F_OWNED) { + return aMallocSizeOf(mData); + } + return 0; +} + +size_t +nsTSubstring_CharT::SizeOfIncludingThisIfUnshared( + mozilla::MallocSizeOf aMallocSizeOf) const +{ + return aMallocSizeOf(this) + SizeOfExcludingThisIfUnshared(aMallocSizeOf); +} + +size_t +nsTSubstring_CharT::SizeOfIncludingThisEvenIfShared( + mozilla::MallocSizeOf aMallocSizeOf) const +{ + return aMallocSizeOf(this) + SizeOfExcludingThisEvenIfShared(aMallocSizeOf); +} + diff --git a/xpcom/string/nsTSubstring.h b/xpcom/string/nsTSubstring.h new file mode 100644 index 000000000..a08036b1f --- /dev/null +++ b/xpcom/string/nsTSubstring.h @@ -0,0 +1,1186 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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" + +#include "mozilla/Casting.h" +#include "mozilla/MemoryReporting.h" + +#ifndef MOZILLA_INTERNAL_API +#error Cannot use internal string classes without MOZILLA_INTERNAL_API defined. Use the frozen header nsStringAPI.h instead. +#endif + +/** + * The base for string comparators + */ +class nsTStringComparator_CharT +{ +public: + typedef CharT char_type; + + nsTStringComparator_CharT() + { + } + + virtual int operator()(const char_type*, const char_type*, + uint32_t, uint32_t) const = 0; +}; + + +/** + * The default string comparator (case-sensitive comparision) + */ +class nsTDefaultStringComparator_CharT + : public nsTStringComparator_CharT +{ +public: + typedef CharT char_type; + + nsTDefaultStringComparator_CharT() + { + } + + virtual int operator()(const char_type*, const char_type*, + uint32_t, uint32_t) const override; +}; + +/** + * nsTSubstring is the most abstract class in the string 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 + * + * Many of the accessors on nsTSubstring are inlined as an optimization. + */ +class nsTSubstring_CharT +{ +public: + typedef mozilla::fallible_t fallible_t; + + typedef CharT char_type; + + typedef nsCharTraits char_traits; + typedef char_traits::incompatible_char_type incompatible_char_type; + + typedef nsTSubstring_CharT self_type; + typedef self_type abstract_string_type; + typedef self_type base_string_type; + + typedef self_type substring_type; + typedef nsTSubstringTuple_CharT substring_tuple_type; + typedef nsTString_CharT string_type; + + typedef nsReadingIterator const_iterator; + typedef nsWritingIterator iterator; + + typedef nsTStringComparator_CharT comparator_type; + + typedef char_type* char_iterator; + typedef const char_type* const_char_iterator; + + typedef uint32_t size_type; + typedef uint32_t index_type; + +public: + + // this acts like a virtual destructor + ~nsTSubstring_CharT() + { + Finalize(); + } + + /** + * reading iterators + */ + + const_char_iterator BeginReading() const + { + return mData; + } + 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; + } + + + /** + * writing iterators + */ + + char_iterator BeginWriting() + { + if (!EnsureMutable()) { + AllocFailed(mLength); + } + + return mData; + } + + char_iterator BeginWriting(const fallible_t&) + { + return EnsureMutable() ? mData : char_iterator(0); + } + + char_iterator EndWriting() + { + if (!EnsureMutable()) { + AllocFailed(mLength); + } + + return mData + mLength; + } + + char_iterator EndWriting(const fallible_t&) + { + return EnsureMutable() ? (mData + mLength) : char_iterator(0); + } + + char_iterator& BeginWriting(char_iterator& aIter) + { + return aIter = BeginWriting(); + } + + char_iterator& BeginWriting(char_iterator& aIter, const fallible_t& aFallible) + { + return aIter = BeginWriting(aFallible); + } + + char_iterator& EndWriting(char_iterator& aIter) + { + return aIter = EndWriting(); + } + + char_iterator& EndWriting(char_iterator& aIter, const fallible_t& aFallible) + { + return aIter = EndWriting(aFallible); + } + + /** + * deprecated writing iterators + */ + + iterator& BeginWriting(iterator& aIter) + { + char_type* data = BeginWriting(); + aIter.mStart = data; + aIter.mEnd = data + mLength; + aIter.mPosition = aIter.mStart; + return aIter; + } + + iterator& EndWriting(iterator& aIter) + { + char_type* data = BeginWriting(); + aIter.mStart = data; + aIter.mEnd = data + mLength; + aIter.mPosition = aIter.mEnd; + return aIter; + } + + /** + * accessors + */ + + // returns pointer to string data (not necessarily null-terminated) +#if defined(CharT_is_PRUnichar) && defined(MOZ_USE_CHAR16_WRAPPER) + char16ptr_t Data() const +#else + const char_type* Data() const +#endif + { + return mData; + } + + size_type Length() const + { + return mLength; + } + + uint32_t Flags() const + { + return mFlags; + } + + bool IsEmpty() const + { + return mLength == 0; + } + + bool IsLiteral() const + { + return (mFlags & F_LITERAL) != 0; + } + + bool IsVoid() const + { + return (mFlags & F_VOIDED) != 0; + } + + bool IsTerminated() const + { + return (mFlags & F_TERMINATED) != 0; + } + + char_type CharAt(index_type aIndex) const + { + NS_ASSERTION(aIndex < mLength, "index exceeds allowable range"); + return mData[aIndex]; + } + + char_type operator[](index_type aIndex) const + { + return CharAt(aIndex); + } + + char_type First() const + { + NS_ASSERTION(mLength > 0, "|First()| called on an empty string"); + return mData[0]; + } + + inline char_type Last() const + { + NS_ASSERTION(mLength > 0, "|Last()| called on an empty string"); + return mData[mLength - 1]; + } + + size_type NS_FASTCALL CountChar(char_type) const; + int32_t NS_FASTCALL FindChar(char_type, index_type aOffset = 0) const; + + inline bool Contains(char_type aChar) const + { + return FindChar(aChar) != kNotFound; + } + + /** + * equality + */ + + bool NS_FASTCALL Equals(const self_type&) const; + bool NS_FASTCALL Equals(const self_type&, const comparator_type&) const; + + bool NS_FASTCALL Equals(const char_type* aData) const; + bool NS_FASTCALL Equals(const char_type* aData, + const comparator_type& aComp) const; + +#if defined(CharT_is_PRUnichar) && defined(MOZ_USE_CHAR16_WRAPPER) + bool NS_FASTCALL Equals(char16ptr_t aData) const + { + return Equals(static_cast(aData)); + } + bool NS_FASTCALL Equals(char16ptr_t aData, const 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; + + // EqualsLiteral must ONLY be applied to an actual literal string, or + // a char array *constant* declared without an explicit size. + // Do not attempt to use it with a regular char* pointer, or with a + // non-constant char array variable. Use EqualsASCII for them. + // The template trick to acquire the array length 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); + } + + // 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 applied to an actual + // literal string, or a char array *constant* declared without an + // explicit size. Do not attempt to use it with a regular char* + // pointer, or with a non-constant char array variable. Use + // LowerCaseEqualsASCII for them. + template + inline bool LowerCaseEqualsLiteral(const char (&aStr)[N]) const + { + return LowerCaseEqualsASCII(aStr, N - 1); + } + + /** + * assignment + */ + + void NS_FASTCALL Assign(char_type aChar); + MOZ_MUST_USE bool NS_FASTCALL Assign(char_type aChar, const fallible_t&); + + void NS_FASTCALL Assign(const char_type* aData); + MOZ_MUST_USE bool NS_FASTCALL Assign(const char_type* aData, + const fallible_t&); + + void NS_FASTCALL Assign(const char_type* aData, size_type aLength); + MOZ_MUST_USE bool NS_FASTCALL Assign(const char_type* aData, + size_type aLength, const fallible_t&); + + void NS_FASTCALL Assign(const self_type&); + MOZ_MUST_USE bool NS_FASTCALL Assign(const self_type&, const fallible_t&); + + void NS_FASTCALL Assign(const substring_tuple_type&); + MOZ_MUST_USE bool NS_FASTCALL Assign(const substring_tuple_type&, + const fallible_t&); + +#if defined(CharT_is_PRUnichar) && defined(MOZ_USE_CHAR16_WRAPPER) + void Assign(char16ptr_t aData) + { + Assign(static_cast(aData)); + } + + void Assign(char16ptr_t aData, size_type aLength) + { + Assign(static_cast(aData), aLength); + } + + MOZ_MUST_USE 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); + MOZ_MUST_USE bool NS_FASTCALL AssignASCII(const char* aData, + size_type aLength, + const fallible_t&); + + void NS_FASTCALL AssignASCII(const char* aData) + { + AssignASCII(aData, mozilla::AssertedCast(strlen(aData))); + } + MOZ_MUST_USE bool NS_FASTCALL AssignASCII(const char* aData, + const fallible_t& aFallible) + { + return AssignASCII(aData, + mozilla::AssertedCast(strlen(aData)), + aFallible); + } + + // AssignLiteral must ONLY be applied to an actual literal string, or + // a char array *constant* declared without an explicit size. + // Do not attempt to use it with a regular char* pointer, or with a + // non-constant char array variable. Use AssignASCII for those. + // There are not fallible version of these methods because they only really + // apply to small allocations that we wouldn't want to check anyway. + template + void AssignLiteral(const char_type (&aStr)[N]) + { + AssignLiteral(aStr, N - 1); + } +#ifdef CharT_is_PRUnichar + template + void AssignLiteral(const char (&aStr)[N]) + { + AssignASCII(aStr, N - 1); + } +#endif + + self_type& operator=(char_type aChar) + { + Assign(aChar); + return *this; + } + self_type& operator=(const char_type* aData) + { + Assign(aData); + return *this; + } +#if defined(CharT_is_PRUnichar) && defined(MOZ_USE_CHAR16_WRAPPER) + 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=(const substring_tuple_type& aTuple) + { + Assign(aTuple); + return *this; + } + + 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); + MOZ_MUST_USE 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)); + MOZ_MUST_USE 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()); + } + MOZ_MUST_USE 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); + + void NS_FASTCALL ReplaceASCII(index_type aCutStart, size_type aCutLength, + const char* aData, + size_type aLength = size_type(-1)); + + MOZ_MUST_USE bool NS_FASTCALL ReplaceASCII(index_type aCutStart, size_type aCutLength, + const char* aData, + size_type aLength, + const fallible_t&); + + // ReplaceLiteral must ONLY be applied to an actual literal string. + // Do not attempt to use it with a regular char* pointer, or with a char + // array variable. Use Replace or ReplaceASCII for those. + template + void ReplaceLiteral(index_type aCutStart, size_type aCutLength, + const char_type (&aStr)[N]) + { + ReplaceLiteral(aCutStart, aCutLength, aStr, N - 1); + } + + void Append(char_type aChar) + { + Replace(mLength, 0, aChar); + } + MOZ_MUST_USE bool Append(char_type aChar, const fallible_t& aFallible) + { + return Replace(mLength, 0, aChar, aFallible); + } + void Append(const char_type* aData, size_type aLength = size_type(-1)) + { + Replace(mLength, 0, aData, aLength); + } + MOZ_MUST_USE bool Append(const char_type* aData, size_type aLength, + const fallible_t& aFallible) + { + return Replace(mLength, 0, aData, aLength, aFallible); + } + +#if defined(CharT_is_PRUnichar) && defined(MOZ_USE_CHAR16_WRAPPER) + void Append(char16ptr_t aData, size_type aLength = size_type(-1)) + { + Append(static_cast(aData), aLength); + } +#endif + + void Append(const self_type& aStr) + { + Replace(mLength, 0, aStr); + } + MOZ_MUST_USE bool Append(const self_type& aStr, const fallible_t& aFallible) + { + return Replace(mLength, 0, aStr, aFallible); + } + void Append(const substring_tuple_type& aTuple) + { + Replace(mLength, 0, aTuple); + } + + void AppendASCII(const char* aData, size_type aLength = size_type(-1)) + { + ReplaceASCII(mLength, 0, aData, aLength); + } + + MOZ_MUST_USE bool AppendASCII(const char* aData, const fallible_t& aFallible) + { + return ReplaceASCII(mLength, 0, aData, size_type(-1), aFallible); + } + + MOZ_MUST_USE bool AppendASCII(const char* aData, size_type aLength, const fallible_t& aFallible) + { + return ReplaceASCII(mLength, 0, aData, aLength, aFallible); + } + + /** + * Append a formatted string to the current string. Uses the format + * codes documented in prprf.h + */ + void AppendPrintf(const char* aFormat, ...); + void AppendPrintf(const char* aFormat, va_list aAp); + void AppendInt(int32_t aInteger) + { + AppendPrintf("%d", aInteger); + } + void AppendInt(int32_t aInteger, int aRadix) + { + const char* fmt = aRadix == 10 ? "%d" : aRadix == 8 ? "%o" : "%x"; + AppendPrintf(fmt, aInteger); + } + void AppendInt(uint32_t aInteger) + { + AppendPrintf("%u", aInteger); + } + void AppendInt(uint32_t aInteger, int aRadix) + { + const char* fmt = aRadix == 10 ? "%u" : aRadix == 8 ? "%o" : "%x"; + AppendPrintf(fmt, aInteger); + } + void AppendInt(int64_t aInteger) + { + AppendPrintf("%lld", aInteger); + } + void AppendInt(int64_t aInteger, int aRadix) + { + const char* fmt = aRadix == 10 ? "%lld" : aRadix == 8 ? "%llo" : "%llx"; + AppendPrintf(fmt, aInteger); + } + void AppendInt(uint64_t aInteger) + { + AppendPrintf("%llu", aInteger); + } + void AppendInt(uint64_t aInteger, int aRadix) + { + const char* fmt = aRadix == 10 ? "%llu" : aRadix == 8 ? "%llo" : "%llx"; + AppendPrintf(fmt, aInteger); + } + + /** + * Append the given float to this string + */ + void NS_FASTCALL AppendFloat(float aFloat); + void NS_FASTCALL AppendFloat(double aFloat); +public: + + // AppendLiteral must ONLY be applied to an actual literal string. + // Do not attempt to use it with a regular char* pointer, or with a char + // array variable. Use Append or AppendASCII for those. + template + void AppendLiteral(const char_type (&aStr)[N]) + { + ReplaceLiteral(mLength, 0, aStr, N - 1); + } +#ifdef CharT_is_PRUnichar + template + void AppendLiteral(const char (&aStr)[N]) + { + AppendASCII(aStr, N - 1); + } + + template + MOZ_MUST_USE bool AppendLiteral(const char (&aStr)[N], const fallible_t& aFallible) + { + return AppendASCII(aStr, N - 1, aFallible); + } +#endif + + self_type& operator+=(char_type aChar) + { + Append(aChar); + return *this; + } + self_type& operator+=(const char_type* aData) + { + Append(aData); + return *this; + } +#if defined(CharT_is_PRUnichar) && defined(MOZ_USE_CHAR16_WRAPPER) + 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(CharT_is_PRUnichar) && defined(MOZ_USE_CHAR16_WRAPPER) + 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 applied to an actual literal string. + // Do not attempt to use it with a regular char* pointer, or with a char + // array variable. Use Insert for those. + 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); + } + + + /** + * buffer sizing + */ + + /** + * Attempts to set the capacity to the given size in number of + * characters, without affecting the length of the string. + * There is no need to include room for the null terminator: it is + * the job of the string class. + * Also ensures that the buffer is mutable. + */ + void NS_FASTCALL SetCapacity(size_type aNewCapacity); + MOZ_MUST_USE bool NS_FASTCALL SetCapacity(size_type aNewCapacity, + const fallible_t&); + + void NS_FASTCALL SetLength(size_type aNewLength); + MOZ_MUST_USE bool NS_FASTCALL SetLength(size_type aNewLength, + const fallible_t&); + + void Truncate(size_type aNewLength = 0) + { + NS_ASSERTION(aNewLength <= mLength, "Truncate cannot make string longer"); + SetLength(aNewLength); + } + + + /** + * 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 = mData; + return 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) ? mLength : aNewLen); + } + + *aData = mData; + return mLength; + } + + size_type GetMutableData(char_type** aData, size_type aNewLen, const fallible_t&) + { + if (!EnsureMutable(aNewLen)) { + *aData = nullptr; + return 0; + } + + *aData = mData; + return mLength; + } + +#if defined(CharT_is_PRUnichar) && defined(MOZ_USE_CHAR16_WRAPPER) + size_type GetMutableData(wchar_t** aData, size_type aNewLen = size_type(-1)) + { + return GetMutableData(reinterpret_cast(aData), aNewLen); + } + + size_type GetMutableData(wchar_t** aData, size_type aNewLen, + const fallible_t& aFallible) + { + return GetMutableData(reinterpret_cast(aData), aNewLen, + aFallible); + } +#endif + + + /** + * 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); + + /** + * This method is used to remove all occurrences of aChar from this + * string. + * + * @param aChar -- char to be stripped + * @param aOffset -- where in this string to start stripping chars + */ + + void StripChar(char_type aChar, int32_t aOffset = 0); + + /** + * This method is used to remove all occurrences of aChars from this + * string. + * + * @param aChars -- chars to be stripped + * @param aOffset -- where in this string to start stripping chars + */ + + void StripChars(const char_type* aChars, uint32_t aOffset = 0); + + /** + * If the string uses a shared buffer, this method + * clears the pointer without releasing the buffer. + */ + void ForgetSharedBuffer() + { + if (mFlags & nsSubstring::F_SHARED) { + mData = char_traits::sEmptyBuffer; + mLength = 0; + mFlags = F_TERMINATED; + } + } + +public: + + /** + * this is public to support automatic conversion of tuple to string + * base type, which helps avoid converting to nsTAString. + */ + MOZ_IMPLICIT nsTSubstring_CharT(const substring_tuple_type& aTuple) + : mData(nullptr) + , mLength(0) + , mFlags(F_NONE) + { + Assign(aTuple); + } + + /** + * allows for direct initialization of a nsTSubstring object. + * + * NOTE: this constructor is declared public _only_ for convenience + * inside the string implementation. + */ + // XXXbz or can I just include nscore.h and use NS_BUILD_REFCNT_LOGGING? +#if defined(DEBUG) || defined(FORCE_BUILD_REFCNT_LOGGING) +#define XPCOM_STRING_CONSTRUCTOR_OUT_OF_LINE + nsTSubstring_CharT(char_type* aData, size_type aLength, uint32_t aFlags); +#else +#undef XPCOM_STRING_CONSTRUCTOR_OUT_OF_LINE + nsTSubstring_CharT(char_type* aData, size_type aLength, uint32_t aFlags) + : mData(aData) + , mLength(aLength) + , mFlags(aFlags) + { + MOZ_RELEASE_ASSERT(CheckCapacity(aLength), "String is too large."); + } +#endif /* DEBUG || FORCE_BUILD_REFCNT_LOGGING */ + + 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(mozilla::IsSame::value, + "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: + + friend class nsTObsoleteAStringThunk_CharT; + friend class nsTSubstringTuple_CharT; + + // XXX GCC 3.4 needs this :-( + friend class nsTPromiseFlatString_CharT; + + char_type* mData; + size_type mLength; + uint32_t mFlags; + + // default initialization + nsTSubstring_CharT() + : mData(char_traits::sEmptyBuffer) + , mLength(0) + , mFlags(F_TERMINATED) + { + } + + // version of constructor that leaves mData and mLength uninitialized + explicit + nsTSubstring_CharT(uint32_t aFlags) + : mFlags(aFlags) + { + } + + // copy-constructor, constructs as dependent on given object + // (NOTE: this is for internal use only) + nsTSubstring_CharT(const self_type& aStr) + : mData(aStr.mData) + , mLength(aStr.mLength) + , mFlags(aStr.mFlags & (F_TERMINATED | F_VOIDED)) + { + } + + /** + * 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(); + + /** + * this function prepares mData to be mutated. + * + * @param aCapacity specifies the required capacity of mData + * @param aOldData returns null or the old value of mData + * @param aOldFlags returns 0 or the old value of mFlags + * + * if mData is already mutable and of sufficient capacity, then this + * function will return immediately. otherwise, it will either resize + * mData or allocate a new shared buffer. if it needs to allocate a + * new buffer, then it will return the old buffer and the corresponding + * flags. this allows the caller to decide when to free the old data. + * + * this function returns false if is unable to allocate sufficient + * memory. + * + * XXX we should expose a way for subclasses to free old_data. + */ + bool NS_FASTCALL MutatePrep(size_type aCapacity, + char_type** aOldData, uint32_t* aOldFlags); + + /** + * 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. + */ + MOZ_MUST_USE bool ReplacePrep(index_type aCutStart, + size_type aCutLength, + size_type aNewLength); + + MOZ_MUST_USE 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. + */ + MOZ_MUST_USE bool NS_FASTCALL EnsureMutable( + size_type aNewLen = size_type(-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: + */ + return (aStart < (mData + mLength) && aEnd > mData); + } + + /** + * Checks if the given capacity is valid for this string type. + */ + static MOZ_MUST_USE bool CheckCapacity(size_type aCapacity) { + if (aCapacity > kMaxCapacity) { + // Also assert for |aCapacity| equal to |size_type(-1)|, since we used to + // use that value to flag immutability. + NS_ASSERTION(aCapacity != size_type(-1), "Bogus capacity"); + return false; + } + + return true; + } + + /** + * this helper function stores the specified dataFlags in mFlags + */ + void SetDataFlags(uint32_t aDataFlags) + { + NS_ASSERTION((aDataFlags & 0xFFFF0000) == 0, "bad flags"); + mFlags = aDataFlags | (mFlags & 0xFFFF0000); + } + + void NS_FASTCALL ReplaceLiteral(index_type aCutStart, size_type aCutLength, + const char_type* aData, size_type aLength); + + static int AppendFunc(void* aArg, const char* aStr, uint32_t aLen); + + static const size_type kMaxCapacity; +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); + + // mFlags is a bitwise combination of the following flags. the meaning + // and interpretation of these flags is an implementation detail. + // + // NOTE: these flags are declared public _only_ for convenience inside + // the string implementation. + + enum + { + F_NONE = 0, // no flags + + // data flags are in the lower 16-bits + F_TERMINATED = 1 << 0, // IsTerminated returns true + F_VOIDED = 1 << 1, // IsVoid returns true + F_SHARED = 1 << 2, // mData points to a heap-allocated, shared buffer + F_OWNED = 1 << 3, // mData points to a heap-allocated, raw buffer + F_FIXED = 1 << 4, // mData points to a fixed-size writable, dependent buffer + F_LITERAL = 1 << 5, // mData points to a string literal; F_TERMINATED will also be set + + // class flags are in the upper 16-bits + F_CLASS_FIXED = 1 << 16 // indicates that |this| is of type nsTFixedString + }; + + // + // 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. + // + // "shared buffer" A shared buffer is one that the string class + // allocates. When it allocates a shared 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 flags: + // + // F_SHARED, F_OWNED, and F_FIXED are all mutually exlusive. They + // indicate the allocation type of mData. If none of these flags + // are set, then the string buffer is dependent. + // + // F_SHARED, F_OWNED, or F_FIXED imply F_TERMINATED. This is because + // the string classes always allocate null-terminated buffers, and + // non-terminated substrings are always dependent. + // + // F_VOIDED implies F_TERMINATED, and moreover it implies that mData + // points to char_traits::sEmptyBuffer. Therefore, F_VOIDED is + // mutually exclusive with F_SHARED, F_OWNED, and F_FIXED. + // +}; + +int NS_FASTCALL +Compare(const nsTSubstring_CharT::base_string_type& aLhs, + const nsTSubstring_CharT::base_string_type& aRhs, + const nsTStringComparator_CharT& = nsTDefaultStringComparator_CharT()); + + +inline bool +operator!=(const nsTSubstring_CharT::base_string_type& aLhs, + const nsTSubstring_CharT::base_string_type& aRhs) +{ + return !aLhs.Equals(aRhs); +} + +inline bool +operator!=(const nsTSubstring_CharT::base_string_type& aLhs, + const nsTSubstring_CharT::char_type* aRhs) +{ + return !aLhs.Equals(aRhs); +} + +inline bool +operator<(const nsTSubstring_CharT::base_string_type& aLhs, + const nsTSubstring_CharT::base_string_type& aRhs) +{ + return Compare(aLhs, aRhs) < 0; +} + +inline bool +operator<=(const nsTSubstring_CharT::base_string_type& aLhs, + const nsTSubstring_CharT::base_string_type& aRhs) +{ + return Compare(aLhs, aRhs) <= 0; +} + +inline bool +operator==(const nsTSubstring_CharT::base_string_type& aLhs, + const nsTSubstring_CharT::base_string_type& aRhs) +{ + return aLhs.Equals(aRhs); +} + +inline bool +operator==(const nsTSubstring_CharT::base_string_type& aLhs, + const nsTSubstring_CharT::char_type* aRhs) +{ + return aLhs.Equals(aRhs); +} + + +inline bool +operator>=(const nsTSubstring_CharT::base_string_type& aLhs, + const nsTSubstring_CharT::base_string_type& aRhs) +{ + return Compare(aLhs, aRhs) >= 0; +} + +inline bool +operator>(const nsTSubstring_CharT::base_string_type& aLhs, + const nsTSubstring_CharT::base_string_type& aRhs) +{ + return Compare(aLhs, aRhs) > 0; +} diff --git a/xpcom/string/nsTSubstringTuple.cpp b/xpcom/string/nsTSubstringTuple.cpp new file mode 100644 index 000000000..2a84a9a4e --- /dev/null +++ b/xpcom/string/nsTSubstringTuple.cpp @@ -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/. */ + +#include "mozilla/CheckedInt.h" + +/** + * computes the aggregate string length + */ + +nsTSubstringTuple_CharT::size_type +nsTSubstringTuple_CharT::Length() const +{ + mozilla::CheckedInt len; + if (mHead) { + len = mHead->Length(); + } else { + len = TO_SUBSTRING(mFragA).Length(); + } + + len += TO_SUBSTRING(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. + */ + +void +nsTSubstringTuple_CharT::WriteTo(char_type* aBuf, uint32_t aBufLen) const +{ + const substring_type& b = TO_SUBSTRING(mFragB); + + MOZ_RELEASE_ASSERT(aBufLen >= b.Length(), "buffer too small"); + uint32_t headLen = aBufLen - b.Length(); + if (mHead) { + mHead->WriteTo(aBuf, headLen); + } else { + const substring_type& a = TO_SUBSTRING(mFragA); + + MOZ_RELEASE_ASSERT(a.Length() == headLen, "buffer incorrectly sized"); + char_traits::copy(aBuf, a.Data(), a.Length()); + } + + char_traits::copy(aBuf + headLen, b.Data(), b.Length()); + +#if 0 + // we need to write out data into |aBuf|, ending at |aBuf + aBufLen|. So our + // data needs to precede |aBuf + aBufLen| exactly. We trust that the buffer + // was properly sized! + + const substring_type& b = TO_SUBSTRING(mFragB); + + NS_ASSERTION(aBufLen >= b.Length(), "buffer is too small"); + char_traits::copy(aBuf + aBufLen - b.Length(), b.Data(), b.Length()); + + aBufLen -= b.Length(); + + if (mHead) { + mHead->WriteTo(aBuf, aBufLen); + } else { + const substring_type& a = TO_SUBSTRING(mFragA); + NS_ASSERTION(aBufLen == a.Length(), "buffer is too small"); + char_traits::copy(aBuf, a.Data(), a.Length()); + } +#endif +} + + +/** + * returns true if this tuple is dependent on (i.e., overlapping with) + * the given char sequence. + */ + +bool +nsTSubstringTuple_CharT::IsDependentOn(const char_type* aStart, + const char_type* aEnd) const +{ + // we aStart with the right-most fragment since it is faster to check. + + if (TO_SUBSTRING(mFragB).IsDependentOn(aStart, aEnd)) { + return true; + } + + if (mHead) { + return mHead->IsDependentOn(aStart, aEnd); + } + + return TO_SUBSTRING(mFragA).IsDependentOn(aStart, aEnd); +} diff --git a/xpcom/string/nsTSubstringTuple.h b/xpcom/string/nsTSubstringTuple.h new file mode 100644 index 000000000..5d24e2159 --- /dev/null +++ b/xpcom/string/nsTSubstringTuple.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/. */ +// IWYU pragma: private, include "nsString.h" + +/** + * nsTSubstringTuple_CharT + * + * 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. + */ +class nsTSubstringTuple_CharT +{ +public: + + typedef CharT char_type; + typedef nsCharTraits char_traits; + + typedef nsTSubstringTuple_CharT self_type; + typedef nsTSubstring_CharT substring_type; + typedef nsTSubstring_CharT base_string_type; + typedef uint32_t size_type; + +public: + + nsTSubstringTuple_CharT(const base_string_type* aStrA, + const base_string_type* aStrB) + : mHead(nullptr) + , mFragA(aStrA) + , mFragB(aStrB) + { + } + + nsTSubstringTuple_CharT(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, uint32_t 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; + +private: + + const self_type* mHead; + const base_string_type* mFragA; + const base_string_type* mFragB; +}; + +inline const nsTSubstringTuple_CharT +operator+(const nsTSubstringTuple_CharT::base_string_type& aStrA, + const nsTSubstringTuple_CharT::base_string_type& aStrB) +{ + return nsTSubstringTuple_CharT(&aStrA, &aStrB); +} + +inline const nsTSubstringTuple_CharT +operator+(const nsTSubstringTuple_CharT& aHead, + const nsTSubstringTuple_CharT::base_string_type& aStrB) +{ + return nsTSubstringTuple_CharT(aHead, &aStrB); +} diff --git a/xpcom/string/nsUTF8Utils.h b/xpcom/string/nsUTF8Utils.h new file mode 100644 index 000000000..9f38fa555 --- /dev/null +++ b/xpcom/string/nsUTF8Utils.h @@ -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/. */ +#ifndef nsUTF8Utils_h_ +#define nsUTF8Utils_h_ + +// 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 "nscore.h" +#include "mozilla/Assertions.h" +#include "mozilla/SSE.h" +#include "mozilla/TypeTraits.h" + +#include "nsCharTraits.h" + +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; + } +}; + +/** + * Extract the next UCS-4 character from the buffer and return it. The + * pointer passed in is advanced to the start of the next character in the + * buffer. If non-null, the parameters err and overlong are filled in to + * indicate that the character was represented by an overlong sequence, or + * that an error occurred. + */ + +class UTF8CharEnumerator +{ +public: + static uint32_t NextChar(const char** aBuffer, const char* aEnd, bool* aErr) + { + NS_ASSERTION(aBuffer && *aBuffer, "null buffer!"); + + const char* p = *aBuffer; + *aErr = false; + + if (p >= aEnd) { + *aErr = true; + + return 0; + } + + char c = *p++; + + if (UTF8traits::isASCII(c)) { + *aBuffer = p; + return c; + } + + uint32_t ucs4; + uint32_t minUcs4; + int32_t state = 0; + + if (!CalcState(c, ucs4, minUcs4, state)) { + NS_ERROR("Not a UTF-8 string. This code should only be used for converting from known UTF-8 strings."); + *aErr = true; + + return 0; + } + + while (state--) { + if (p == aEnd) { + *aErr = true; + + return 0; + } + + c = *p++; + + if (!AddByte(c, state, ucs4)) { + *aErr = true; + + return 0; + } + } + + if (ucs4 < minUcs4) { + // Overlong sequence + ucs4 = UCS2_REPLACEMENT_CHAR; + } else if (ucs4 >= 0xD800 && + (ucs4 <= 0xDFFF || ucs4 >= UCS_END)) { + // Surrogates and code points outside the Unicode range. + ucs4 = UCS2_REPLACEMENT_CHAR; + } + + *aBuffer = p; + return ucs4; + } + +private: + static bool CalcState(char aChar, uint32_t& aUcs4, uint32_t& aMinUcs4, + int32_t& aState) + { + if (UTF8traits::is2byte(aChar)) { + aUcs4 = (uint32_t(aChar) << 6) & 0x000007C0L; + aState = 1; + aMinUcs4 = 0x00000080; + } else if (UTF8traits::is3byte(aChar)) { + aUcs4 = (uint32_t(aChar) << 12) & 0x0000F000L; + aState = 2; + aMinUcs4 = 0x00000800; + } else if (UTF8traits::is4byte(aChar)) { + aUcs4 = (uint32_t(aChar) << 18) & 0x001F0000L; + aState = 3; + aMinUcs4 = 0x00010000; + } else if (UTF8traits::is5byte(aChar)) { + aUcs4 = (uint32_t(aChar) << 24) & 0x03000000L; + aState = 4; + aMinUcs4 = 0x00200000; + } else if (UTF8traits::is6byte(aChar)) { + aUcs4 = (uint32_t(aChar) << 30) & 0x40000000L; + aState = 5; + aMinUcs4 = 0x04000000; + } else { + return false; + } + + return true; + } + + static bool AddByte(char aChar, int32_t aState, uint32_t& aUcs4) + { + if (UTF8traits::isInSeq(aChar)) { + int32_t shift = aState * 6; + aUcs4 |= (uint32_t(aChar) & 0x3F) << shift; + return true; + } + + return false; + } +}; + + +/** + * Extract the next UCS-4 character from the buffer and return it. The + * pointer passed in is advanced to the start of the next character in the + * buffer. If non-null, the err parameter is filled in if an error occurs. + * + * If an error occurs that causes UCS2_REPLACEMENT_CHAR to be returned, then + * the buffer will be updated to move only a single UCS-2 character. + * + * Any other error returns 0 and does not move the buffer position. + */ + + +class UTF16CharEnumerator +{ +public: + static uint32_t NextChar(const char16_t** aBuffer, const char16_t* aEnd, + bool* aErr = nullptr) + { + NS_ASSERTION(aBuffer && *aBuffer, "null buffer!"); + + const char16_t* p = *aBuffer; + + if (p >= aEnd) { + NS_ERROR("No input to work with"); + if (aErr) { + *aErr = true; + } + + return 0; + } + + char16_t c = *p++; + + if (!IS_SURROGATE(c)) { // U+0000 - U+D7FF,U+E000 - U+FFFF + if (aErr) { + *aErr = false; + } + *aBuffer = p; + return c; + } else if (NS_IS_HIGH_SURROGATE(c)) { // U+D800 - U+DBFF + if (p == aEnd) { + // Found a high surrogate at the end of the buffer. Flag this + // as an error and return the Unicode replacement + // character 0xFFFD. + + NS_WARNING("Unexpected end of buffer after high surrogate"); + + if (aErr) { + *aErr = true; + } + *aBuffer = p; + return 0xFFFD; + } + + // D800- DBFF - High Surrogate + char16_t h = c; + + c = *p++; + + if (NS_IS_LOW_SURROGATE(c)) { + // DC00- DFFF - Low Surrogate + // N = (H - D800) *400 + 10000 + (L - DC00) + uint32_t ucs4 = SURROGATE_TO_UCS4(h, c); + if (aErr) { + *aErr = false; + } + *aBuffer = p; + return ucs4; + } else { + // Found a high surrogate followed by something other than + // a low surrogate. Flag this as an error and return the + // Unicode replacement character 0xFFFD. Note that the + // pointer to the next character points to the second 16-bit + // value, not beyond it, as per Unicode 5.0.0 Chapter 3 C10, + // only the first code unit of an illegal sequence must be + // treated as an illegally terminated code unit sequence + // (also Chapter 3 D91, "isolated [not paired and ill-formed] + // UTF-16 code units in the range D800..DFFF are ill-formed"). + NS_WARNING("got a High Surrogate but no low surrogate"); + + if (aErr) { + *aErr = true; + } + *aBuffer = p - 1; + return 0xFFFD; + } + } else { // U+DC00 - U+DFFF + // DC00- DFFF - Low Surrogate + + // Found a low surrogate w/o a preceding high surrogate. Flag + // this as an error and return the Unicode replacement + // character 0xFFFD. + + NS_WARNING("got a low Surrogate but no high surrogate"); + if (aErr) { + *aErr = true; + } + *aBuffer = p; + return 0xFFFD; + } + + MOZ_ASSERT_UNREACHABLE("Impossible UCS-2 character value."); + } +}; + + +/** + * A character sink (see |copy_string| in nsAlgorithm.h) for converting + * UTF-8 to UTF-16 + */ +class ConvertUTF8toUTF16 +{ +public: + typedef char value_type; + typedef char16_t buffer_type; + + explicit ConvertUTF8toUTF16(buffer_type* aBuffer) + : mStart(aBuffer), mBuffer(aBuffer), mErrorEncountered(false) + { + } + + size_t Length() const + { + return mBuffer - mStart; + } + + bool ErrorEncountered() const + { + return mErrorEncountered; + } + + void write(const value_type* aStart, uint32_t aN) + { + if (mErrorEncountered) { + return; + } + + // algorithm assumes utf8 units won't + // be spread across fragments + const value_type* p = aStart; + const value_type* end = aStart + aN; + buffer_type* out = mBuffer; + for (; p != end /* && *p */;) { + bool err; + uint32_t ucs4 = UTF8CharEnumerator::NextChar(&p, end, &err); + + if (err) { + mErrorEncountered = true; + mBuffer = out; + return; + } + + if (ucs4 >= PLANE1_BASE) { + *out++ = (buffer_type)H_SURROGATE(ucs4); + *out++ = (buffer_type)L_SURROGATE(ucs4); + } else { + *out++ = ucs4; + } + } + mBuffer = out; + } + + void write_terminator() + { + *mBuffer = buffer_type(0); + } + +private: + buffer_type* const mStart; + buffer_type* mBuffer; + bool mErrorEncountered; +}; + +/** + * A character sink (see |copy_string| in nsAlgorithm.h) for computing + * the length of the UTF-16 string equivalent to a UTF-8 string. + */ +class CalculateUTF8Length +{ +public: + typedef char value_type; + + CalculateUTF8Length() + : mLength(0), mErrorEncountered(false) + { + } + + size_t Length() const + { + return mLength; + } + + void write(const value_type* aStart, uint32_t aN) + { + // ignore any further requests + if (mErrorEncountered) { + return; + } + + // algorithm assumes utf8 units won't + // be spread across fragments + const value_type* p = aStart; + const value_type* end = aStart + aN; + for (; p < end /* && *p */; ++mLength) { + if (UTF8traits::isASCII(*p)) { + p += 1; + } else if (UTF8traits::is2byte(*p)) { + p += 2; + } else if (UTF8traits::is3byte(*p)) { + p += 3; + } else if (UTF8traits::is4byte(*p)) { + // Because a UTF-8 sequence of 4 bytes represents a codepoint + // greater than 0xFFFF, it will become a surrogate pair in the + // UTF-16 string, so add 1 more to mLength. + // This doesn't happen with is5byte and is6byte because they + // are illegal UTF-8 sequences (greater than 0x10FFFF) so get + // converted to a single replacement character. + + // However, there is one case when a 4 byte UTF-8 sequence will + // only generate 2 UTF-16 bytes. If we have a properly encoded + // sequence, but with an invalid value (too small or too big), + // that will result in a replacement character being written + // This replacement character is encoded as just 1 single + // UTF-16 character, which is 2 bytes. + + // The below code therefore only adds 1 to mLength if the UTF8 + // data will produce a decoded character which is greater than + // or equal to 0x010000 and less than 0x0110000. + + // A 4byte UTF8 character is encoded as + // 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx + // Bit 1-3 on the first byte, and bit 5-6 on the second byte, + // map to bit 17-21 in the final result. If these bits are + // between 0x01 and 0x11, that means that the final result is + // between 0x010000 and 0x110000. The below code reads these + // bits out and assigns them to c, but shifted up 4 bits to + // avoid having to shift twice. + + // It doesn't matter what to do in the case where p + 4 > end + // since no UTF16 characters will be written in that case by + // ConvertUTF8toUTF16. Likewise it doesn't matter what we do if + // any of the surrogate bits are wrong since no UTF16 + // characters will be written in that case either. + + if (p + 4 <= end) { + uint32_t c = ((uint32_t)(p[0] & 0x07)) << 6 | + ((uint32_t)(p[1] & 0x30)); + if (c >= 0x010 && c < 0x110) { + ++mLength; + } + } + + p += 4; + } else if (UTF8traits::is5byte(*p)) { + p += 5; + } else if (UTF8traits::is6byte(*p)) { + p += 6; + } else { // error + ++mLength; // to account for the decrement below + break; + } + } + if (p != end) { + NS_ERROR("Not a UTF-8 string. This code should only be used for converting from known UTF-8 strings."); + --mLength; // The last multi-byte char wasn't complete, discard it. + mErrorEncountered = true; + } + } + +private: + size_t mLength; + bool mErrorEncountered; +}; + +/** + * A character sink (see |copy_string| in nsAlgorithm.h) for + * converting UTF-16 to UTF-8. Treats invalid UTF-16 data as 0xFFFD + * (0xEFBFBD in UTF-8). + */ +class ConvertUTF16toUTF8 +{ +public: + typedef char16_t value_type; + typedef char buffer_type; + + // The error handling here is more lenient than that in + // |ConvertUTF8toUTF16|, but it's that way for backwards + // compatibility. + + explicit ConvertUTF16toUTF8(buffer_type* aBuffer) + : mStart(aBuffer), mBuffer(aBuffer) + { + } + + size_t Size() const + { + return mBuffer - mStart; + } + + void write(const value_type* aStart, uint32_t aN) + { + buffer_type* out = mBuffer; // gcc isn't smart enough to do this! + + for (const value_type* p = aStart, *end = aStart + aN; p < end; ++p) { + value_type c = *p; + if (!(c & 0xFF80)) { // U+0000 - U+007F + *out++ = (char)c; + } else if (!(c & 0xF800)) { // U+0100 - U+07FF + *out++ = 0xC0 | (char)(c >> 6); + *out++ = 0x80 | (char)(0x003F & c); + } else if (!IS_SURROGATE(c)) { // U+0800 - U+D7FF,U+E000 - U+FFFF + *out++ = 0xE0 | (char)(c >> 12); + *out++ = 0x80 | (char)(0x003F & (c >> 6)); + *out++ = 0x80 | (char)(0x003F & c); + } else if (NS_IS_HIGH_SURROGATE(c)) { // U+D800 - U+DBFF + // D800- DBFF - High Surrogate + value_type h = c; + + ++p; + if (p == end) { + // Treat broken characters as the Unicode + // replacement character 0xFFFD (0xEFBFBD in + // UTF-8) + *out++ = '\xEF'; + *out++ = '\xBF'; + *out++ = '\xBD'; + + NS_WARNING("String ending in half a surrogate pair!"); + + break; + } + c = *p; + + if (NS_IS_LOW_SURROGATE(c)) { + // DC00- DFFF - Low Surrogate + // N = (H - D800) *400 + 10000 + ( L - DC00 ) + uint32_t ucs4 = SURROGATE_TO_UCS4(h, c); + + // 0001 0000-001F FFFF + *out++ = 0xF0 | (char)(ucs4 >> 18); + *out++ = 0x80 | (char)(0x003F & (ucs4 >> 12)); + *out++ = 0x80 | (char)(0x003F & (ucs4 >> 6)); + *out++ = 0x80 | (char)(0x003F & ucs4); + } else { + // Treat broken characters as the Unicode + // replacement character 0xFFFD (0xEFBFBD in + // UTF-8) + *out++ = '\xEF'; + *out++ = '\xBF'; + *out++ = '\xBD'; + + // The pointer to the next character points to the second + // 16-bit value, not beyond it, as per Unicode 5.0.0 + // Chapter 3 C10, only the first code unit of an illegal + // sequence must be treated as an illegally terminated + // code unit sequence (also Chapter 3 D91, "isolated [not + // paired and ill-formed] UTF-16 code units in the range + // D800..DFFF are ill-formed"). + p--; + + NS_WARNING("got a High Surrogate but no low surrogate"); + } + } else { // U+DC00 - U+DFFF + // Treat broken characters as the Unicode replacement + // character 0xFFFD (0xEFBFBD in UTF-8) + *out++ = '\xEF'; + *out++ = '\xBF'; + *out++ = '\xBD'; + + // DC00- DFFF - Low Surrogate + NS_WARNING("got a low Surrogate but no high surrogate"); + } + } + + mBuffer = out; + } + + void write_terminator() + { + *mBuffer = buffer_type(0); + } + +private: + buffer_type* const mStart; + buffer_type* mBuffer; +}; + +/** + * A character sink (see |copy_string| in nsAlgorithm.h) for computing + * the number of bytes a UTF-16 would occupy in UTF-8. Treats invalid + * UTF-16 data as 0xFFFD (0xEFBFBD in UTF-8). + */ +class CalculateUTF8Size +{ +public: + typedef char16_t value_type; + + CalculateUTF8Size() + : mSize(0) + { + } + + size_t Size() const + { + return mSize; + } + + void write(const value_type* aStart, uint32_t aN) + { + // Assume UCS2 surrogate pairs won't be spread across fragments. + for (const value_type* p = aStart, *end = aStart + aN; p < end; ++p) { + value_type c = *p; + if (!(c & 0xFF80)) { // U+0000 - U+007F + mSize += 1; + } else if (!(c & 0xF800)) { // U+0100 - U+07FF + mSize += 2; + } else if (0xD800 != (0xF800 & c)) { // U+0800 - U+D7FF,U+E000 - U+FFFF + mSize += 3; + } else if (0xD800 == (0xFC00 & c)) { // U+D800 - U+DBFF + ++p; + if (p == end) { + // Treat broken characters as the Unicode + // replacement character 0xFFFD (0xEFBFBD in + // UTF-8) + mSize += 3; + + NS_WARNING("String ending in half a surrogate pair!"); + + break; + } + c = *p; + + if (0xDC00 == (0xFC00 & c)) { + mSize += 4; + } else { + // Treat broken characters as the Unicode + // replacement character 0xFFFD (0xEFBFBD in + // UTF-8) + mSize += 3; + + // The next code unit is the second 16-bit value, not + // the one beyond it, as per Unicode 5.0.0 Chapter 3 C10, + // only the first code unit of an illegal sequence must + // be treated as an illegally terminated code unit + // sequence (also Chapter 3 D91, "isolated [not paired and + // ill-formed] UTF-16 code units in the range D800..DFFF + // are ill-formed"). + p--; + + NS_WARNING("got a high Surrogate but no low surrogate"); + } + } else { // U+DC00 - U+DFFF + // Treat broken characters as the Unicode replacement + // character 0xFFFD (0xEFBFBD in UTF-8) + mSize += 3; + + NS_WARNING("got a low Surrogate but no high surrogate"); + } + } + } + +private: + size_t mSize; +}; + +#ifdef MOZILLA_INTERNAL_API +/** + * A character sink that performs a |reinterpret_cast|-style conversion + * from char to char16_t. + */ +class LossyConvertEncoding8to16 +{ +public: + typedef char value_type; + typedef char input_type; + typedef char16_t output_type; + +public: + explicit LossyConvertEncoding8to16(char16_t* aDestination) : + mDestination(aDestination) + { + } + + void + write(const char* aSource, uint32_t aSourceLength) + { +#ifdef MOZILLA_MAY_SUPPORT_SSE2 + if (mozilla::supports_sse2()) { + write_sse2(aSource, aSourceLength); + return; + } +#endif + const char* done_writing = aSource + aSourceLength; + while (aSource < done_writing) { + *mDestination++ = (char16_t)(unsigned char)(*aSource++); + } + } + + void + write_sse2(const char* aSource, uint32_t aSourceLength); + + void + write_terminator() + { + *mDestination = (char16_t)(0); + } + +private: + char16_t* mDestination; +}; + +/** + * A character sink that performs a |reinterpret_cast|-style conversion + * from char16_t to char. + */ +class LossyConvertEncoding16to8 +{ +public: + typedef char16_t value_type; + typedef char16_t input_type; + typedef char output_type; + + explicit LossyConvertEncoding16to8(char* aDestination) + : mDestination(aDestination) + { + } + + void + write(const char16_t* aSource, uint32_t aSourceLength) + { +#ifdef MOZILLA_MAY_SUPPORT_SSE2 + if (mozilla::supports_sse2()) { + write_sse2(aSource, aSourceLength); + return; + } +#endif + const char16_t* done_writing = aSource + aSourceLength; + while (aSource < done_writing) { + *mDestination++ = (char)(*aSource++); + } + } + +#ifdef MOZILLA_MAY_SUPPORT_SSE2 + void + write_sse2(const char16_t* aSource, uint32_t aSourceLength); +#endif + + void + write_terminator() + { + *mDestination = '\0'; + } + +private: + char* mDestination; +}; +#endif // MOZILLA_INTERNAL_API + + +template +inline UnsignedT +RewindToPriorUTF8Codepoint(const Char* utf8Chars, UnsignedT index) +{ + static_assert(mozilla::IsSame::value || + mozilla::IsSame::value || + mozilla::IsSame::value, + "UTF-8 data must be in 8-bit units"); + static_assert(mozilla::IsUnsigned::value, "index type must be unsigned"); + while (index > 0 && (utf8Chars[index] & 0xC0) == 0x80) + --index; + + return index; +} + +#endif /* !defined(nsUTF8Utils_h_) */ diff --git a/xpcom/string/nsUTF8UtilsSSE2.cpp b/xpcom/string/nsUTF8UtilsSSE2.cpp new file mode 100644 index 000000000..daf2c56b0 --- /dev/null +++ b/xpcom/string/nsUTF8UtilsSSE2.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 "nscore.h" +#include "nsAlgorithm.h" +#include +#include + +void +LossyConvertEncoding16to8::write_sse2(const char16_t* aSource, + uint32_t aSourceLength) +{ + char* dest = mDestination; + + // Align source to a 16-byte boundary. + uint32_t i = 0; + uint32_t alignLen = + XPCOM_MIN(aSourceLength, + uint32_t(-NS_PTR_TO_INT32(aSource) & 0xf) / sizeof(char16_t)); + for (; i < alignLen; ++i) { + dest[i] = static_cast(aSource[i]); + } + + // Walk 64 bytes (four XMM registers) at a time. + __m128i vectmask = _mm_set1_epi16(0x00ff); + for (; aSourceLength - i > 31; i += 32) { + __m128i source1 = _mm_load_si128(reinterpret_cast(aSource + i)); + source1 = _mm_and_si128(source1, vectmask); + + __m128i source2 = _mm_load_si128(reinterpret_cast(aSource + i + 8)); + source2 = _mm_and_si128(source2, vectmask); + + __m128i source3 = _mm_load_si128(reinterpret_cast(aSource + i + 16)); + source3 = _mm_and_si128(source3, vectmask); + + __m128i source4 = _mm_load_si128(reinterpret_cast(aSource + i + 24)); + source4 = _mm_and_si128(source4, vectmask); + + + // Pack the source data. SSE2 views this as a saturating uint16_t to + // uint8_t conversion, but since we masked off the high-order byte of every + // uint16_t, we're really just grabbing the low-order bytes of source1 and + // source2. + __m128i packed1 = _mm_packus_epi16(source1, source2); + __m128i packed2 = _mm_packus_epi16(source3, source4); + + // This store needs to be unaligned since there's no guarantee that the + // alignment we did above for the source will align the destination. + _mm_storeu_si128(reinterpret_cast<__m128i*>(dest + i), packed1); + _mm_storeu_si128(reinterpret_cast<__m128i*>(dest + i + 16), packed2); + } + + // Finish up the rest. + for (; i < aSourceLength; ++i) { + dest[i] = static_cast(aSource[i]); + } + + mDestination += i; +} + +void +LossyConvertEncoding8to16::write_sse2(const char* aSource, + uint32_t aSourceLength) +{ + char16_t* dest = mDestination; + + // Align source to a 16-byte boundary. We choose to align source rather than + // dest because we'd rather have our loads than our stores be fast. You have + // to wait for a load to complete, but you can keep on moving after issuing a + // store. + uint32_t i = 0; + uint32_t alignLen = XPCOM_MIN(aSourceLength, + uint32_t(-NS_PTR_TO_INT32(aSource) & 0xf)); + for (; i < alignLen; ++i) { + dest[i] = static_cast(aSource[i]); + } + + // Walk 32 bytes (two XMM registers) at a time. + for (; aSourceLength - i > 31; i += 32) { + __m128i source1 = _mm_load_si128(reinterpret_cast(aSource + i)); + __m128i source2 = _mm_load_si128(reinterpret_cast(aSource + i + 16)); + + // Interleave 0s in with the bytes of source to create lo and hi. + __m128i lo1 = _mm_unpacklo_epi8(source1, _mm_setzero_si128()); + __m128i hi1 = _mm_unpackhi_epi8(source1, _mm_setzero_si128()); + __m128i lo2 = _mm_unpacklo_epi8(source2, _mm_setzero_si128()); + __m128i hi2 = _mm_unpackhi_epi8(source2, _mm_setzero_si128()); + + // store lo and hi into dest. + _mm_storeu_si128(reinterpret_cast<__m128i*>(dest + i), lo1); + _mm_storeu_si128(reinterpret_cast<__m128i*>(dest + i + 8), hi1); + _mm_storeu_si128(reinterpret_cast<__m128i*>(dest + i + 16), lo2); + _mm_storeu_si128(reinterpret_cast<__m128i*>(dest + i + 24), hi2); + } + + // Finish up whatever's left. + for (; i < aSourceLength; ++i) { + dest[i] = static_cast(aSource[i]); + } + + mDestination += i; +} diff --git a/xpcom/string/nsXPCOMStrings.h b/xpcom/string/nsXPCOMStrings.h new file mode 100644 index 000000000..493e092d6 --- /dev/null +++ b/xpcom/string/nsXPCOMStrings.h @@ -0,0 +1,748 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 nsXPCOMStrings_h__ +#define nsXPCOMStrings_h__ + +#include +#include "nscore.h" +#include + +/** + * nsXPCOMStrings.h + * + * This file describes a minimal API for working with XPCOM's abstract + * string classes. It divorces the consumer from having any run-time + * dependency on the implementation details of the abstract string types. + */ + +#include "nscore.h" + +/* The base string types */ +class nsAString; +class nsACString; + +/* ------------------------------------------------------------------------- */ + +/** + * nsStringContainer + * + * This is an opaque data type that is large enough to hold the canonical + * implementation of nsAString. The binary structure of this class is an + * implementation detail. + * + * The string data stored in a string container is always single fragment + * and may be null-terminated depending on how it is initialized. + * + * Typically, string containers are allocated on the stack for temporary + * use. However, they can also be malloc'd if necessary. In either case, + * a string container is not useful until it has been initialized with a + * call to NS_StringContainerInit. The following example shows how to use + * a string container to call a function that takes a |nsAString &| out-param. + * + * nsresult GetBlah(nsAString &aBlah); + * + * nsresult MyCode() + * { + * nsresult rv; + * + * nsStringContainer sc; + * rv = NS_StringContainerInit(sc); + * if (NS_FAILED(rv)) + * return rv; + * + * rv = GetBlah(sc); + * if (NS_SUCCEEDED(rv)) + * { + * const char16_t *data; + * NS_StringGetData(sc, &data); + * // + * // |data| now points to the result of the GetBlah function + * // + * } + * + * NS_StringContainerFinish(sc); + * return rv; + * } + * + * The following example show how to use a string container to pass a string + * parameter to a function taking a |const nsAString &| in-param. + * + * nsresult SetBlah(const nsAString &aBlah); + * + * nsresult MyCode() + * { + * nsresult rv; + * + * nsStringContainer sc; + * rv = NS_StringContainerInit(sc); + * if (NS_FAILED(rv)) + * return rv; + * + * const char16_t kData[] = {'x','y','z','\0'}; + * rv = NS_StringSetData(sc, kData, sizeof(kData)/2 - 1); + * if (NS_SUCCEEDED(rv)) + * rv = SetBlah(sc); + * + * NS_StringContainerFinish(sc); + * return rv; + * } + */ +class nsStringContainer; + + +/** + * This struct is never used directly. It is designed to have the same + * size as nsString. It can be stack and heap allocated and the internal + * functions cast it to nsString. + * While this practice is a strict aliasing violation, it doesn't seem to + * cause problems since the the struct is only accessed via the casts to + * nsString. + * We use protected instead of private to avoid compiler warnings about + * the members being unused. + */ +struct nsStringContainer_base +{ +protected: + void* d1; + uint32_t d2; + uint32_t d3; +}; + +/** + * Flags that may be OR'd together to pass to NS_StringContainerInit2: + */ +enum +{ + /* Data passed into NS_StringContainerInit2 is not copied; instead, the + * string references the passed in data pointer directly. The caller must + * ensure that the data is valid for the lifetime of the string container. + * This flag should not be combined with NS_STRING_CONTAINER_INIT_ADOPT. */ + NS_STRING_CONTAINER_INIT_DEPEND = (1 << 1), + + /* Data passed into NS_StringContainerInit2 is not copied; instead, the + * string takes ownership over the data pointer. The caller must have + * allocated the data array using the XPCOM memory allocator (nsMemory). + * This flag should not be combined with NS_STRING_CONTAINER_INIT_DEPEND. */ + NS_STRING_CONTAINER_INIT_ADOPT = (1 << 2), + + /* Data passed into NS_StringContainerInit2 is a substring that is not + * null-terminated. */ + NS_STRING_CONTAINER_INIT_SUBSTRING = (1 << 3) +}; + +/** + * NS_StringContainerInit + * + * @param aContainer string container reference + * @return NS_OK if string container successfully initialized + * + * This function may allocate additional memory for aContainer. When + * aContainer is no longer needed, NS_StringContainerFinish should be called. + */ +XPCOM_API(nsresult) NS_StringContainerInit(nsStringContainer& aContainer); + +/** + * NS_StringContainerInit2 + * + * @param aContainer string container reference + * @param aData character buffer (may be null) + * @param aDataLength number of characters stored at aData (may pass + * UINT32_MAX if aData is null-terminated) + * @param aFlags flags affecting how the string container is + * initialized. this parameter is ignored when aData + * is null. otherwise, if this parameter is 0, then + * aData is copied into the string. + * + * This function resembles NS_StringContainerInit but provides further + * options that permit more efficient memory usage. When aContainer is + * no longer needed, NS_StringContainerFinish should be called. + * + * NOTE: NS_StringContainerInit2(container, nullptr, 0, 0) is equivalent to + * NS_StringContainerInit(container). + */ +XPCOM_API(nsresult) NS_StringContainerInit2(nsStringContainer& aContainer, + const char16_t* aData = nullptr, + uint32_t aDataLength = UINT32_MAX, + uint32_t aFlags = 0); + +/** + * NS_StringContainerFinish + * + * @param aContainer string container reference + * + * This function frees any memory owned by aContainer. + */ +XPCOM_API(void) NS_StringContainerFinish(nsStringContainer& aContainer); + +/* ------------------------------------------------------------------------- */ + +/** + * NS_StringGetData + * + * This function returns a const character pointer to the string's internal + * buffer, the length of the string, and a boolean value indicating whether + * or not the buffer is null-terminated. + * + * @param aStr abstract string reference + * @param aData out param that will hold the address of aStr's + * internal buffer + * @param aTerminated if non-null, this out param will be set to indicate + * whether or not aStr's internal buffer is null- + * terminated + * @return length of aStr's internal buffer + */ +XPCOM_API(uint32_t) NS_StringGetData(const nsAString& aStr, + const char16_t** aData, + bool* aTerminated = nullptr); + +/** + * NS_StringGetMutableData + * + * This function provides mutable access to a string's internal buffer. It + * returns a pointer to an array of characters that may be modified. The + * returned pointer remains valid until the string object is passed to some + * other string function. + * + * Optionally, this function may be used to resize the string's internal + * buffer. The aDataLength parameter specifies the requested length of the + * string's internal buffer. By passing some value other than UINT32_MAX, + * the caller can request that the buffer be resized to the specified number of + * characters before returning. The caller is not responsible for writing a + * null-terminator. + * + * @param aStr abstract string reference + * @param aDataLength number of characters to resize the string's internal + * buffer to or UINT32_MAX if no resizing is needed + * @param aData out param that upon return holds the address of aStr's + * internal buffer or null if the function failed + * @return number of characters or zero if the function failed + * + * This function does not necessarily null-terminate aStr after resizing its + * internal buffer. The behavior depends on the implementation of the abstract + * string, aStr. If aStr is a reference to a nsStringContainer, then its data + * will be null-terminated by this function. + */ +XPCOM_API(uint32_t) NS_StringGetMutableData(nsAString& aStr, + uint32_t aDataLength, + char16_t** aData); + +/** + * NS_StringCloneData + * + * This function returns a null-terminated copy of the string's + * internal buffer. + * + * @param aStr abstract string reference + * @return null-terminated copy of the string's internal buffer + * (it must be free'd using using free) + */ +XPCOM_API(char16_t*) NS_StringCloneData(const nsAString& aStr); + +/** + * NS_StringSetData + * + * This function copies aData into aStr. + * + * @param aStr abstract string reference + * @param aData character buffer + * @param aDataLength number of characters to copy from source string (pass + * UINT32_MAX to copy until end of aData, designated by + * a null character) + * @return NS_OK if function succeeded + * + * This function does not necessarily null-terminate aStr after copying data + * from aData. The behavior depends on the implementation of the abstract + * string, aStr. If aStr is a reference to a nsStringContainer, then its data + * will be null-terminated by this function. + */ +XPCOM_API(nsresult) NS_StringSetData(nsAString& aStr, const char16_t* aData, + uint32_t aDataLength = UINT32_MAX); + +/** + * NS_StringSetDataRange + * + * This function copies aData into a section of aStr. As a result it can be + * used to insert new characters into the string. + * + * @param aStr abstract string reference + * @param aCutOffset starting index where the string's existing data + * is to be overwritten (pass UINT32_MAX to cause + * aData to be appended to the end of aStr, in which + * case the value of aCutLength is ignored). + * @param aCutLength number of characters to overwrite starting at + * aCutOffset (pass UINT32_MAX to overwrite until the + * end of aStr). + * @param aData character buffer (pass null to cause this function + * to simply remove the "cut" range) + * @param aDataLength number of characters to copy from source string (pass + * UINT32_MAX to copy until end of aData, designated by + * a null character) + * @return NS_OK if function succeeded + * + * This function does not necessarily null-terminate aStr after copying data + * from aData. The behavior depends on the implementation of the abstract + * string, aStr. If aStr is a reference to a nsStringContainer, then its data + * will be null-terminated by this function. + */ +XPCOM_API(nsresult) NS_StringSetDataRange(nsAString& aStr, + uint32_t aCutOffset, uint32_t aCutLength, + const char16_t* aData, + uint32_t aDataLength = UINT32_MAX); + +/** + * NS_StringCopy + * + * This function makes aDestStr have the same value as aSrcStr. It is + * provided as an optimization. + * + * @param aDestStr abstract string reference to be modified + * @param aSrcStr abstract string reference containing source string + * @return NS_OK if function succeeded + * + * This function does not necessarily null-terminate aDestStr after copying + * data from aSrcStr. The behavior depends on the implementation of the + * abstract string, aDestStr. If aDestStr is a reference to a + * nsStringContainer, then its data will be null-terminated by this function. + */ +XPCOM_API(nsresult) NS_StringCopy(nsAString& aDestStr, + const nsAString& aSrcStr); + +/** + * NS_StringAppendData + * + * This function appends data to the existing value of aStr. + * + * @param aStr abstract string reference to be modified + * @param aData character buffer + * @param aDataLength number of characters to append (pass UINT32_MAX to + * append until a null-character is encountered) + * @return NS_OK if function succeeded + * + * This function does not necessarily null-terminate aStr upon completion. + * The behavior depends on the implementation of the abstract string, aStr. + * If aStr is a reference to a nsStringContainer, then its data will be null- + * terminated by this function. + */ +inline NS_HIDDEN_(nsresult) +NS_StringAppendData(nsAString& aStr, const char16_t* aData, + uint32_t aDataLength = UINT32_MAX) +{ + return NS_StringSetDataRange(aStr, UINT32_MAX, 0, aData, aDataLength); +} + +/** + * NS_StringInsertData + * + * This function inserts data into the existing value of aStr at the specified + * offset. + * + * @param aStr abstract string reference to be modified + * @param aOffset specifies where in the string to insert aData + * @param aData character buffer + * @param aDataLength number of characters to append (pass UINT32_MAX to + * append until a null-character is encountered) + * @return NS_OK if function succeeded + * + * This function does not necessarily null-terminate aStr upon completion. + * The behavior depends on the implementation of the abstract string, aStr. + * If aStr is a reference to a nsStringContainer, then its data will be null- + * terminated by this function. + */ +inline NS_HIDDEN_(nsresult) +NS_StringInsertData(nsAString& aStr, uint32_t aOffset, const char16_t* aData, + uint32_t aDataLength = UINT32_MAX) +{ + return NS_StringSetDataRange(aStr, aOffset, 0, aData, aDataLength); +} + +/** + * NS_StringCutData + * + * This function shortens the existing value of aStr, by removing characters + * at the specified offset. + * + * @param aStr abstract string reference to be modified + * @param aCutOffset specifies where in the string to insert aData + * @param aCutLength number of characters to remove + * @return NS_OK if function succeeded + */ +inline NS_HIDDEN_(nsresult) +NS_StringCutData(nsAString& aStr, uint32_t aCutOffset, uint32_t aCutLength) +{ + return NS_StringSetDataRange(aStr, aCutOffset, aCutLength, nullptr, 0); +} + +/** + * NS_StringSetIsVoid + * + * This function marks a string as being a "void string". Any data in the + * string will be lost. + */ +XPCOM_API(void) NS_StringSetIsVoid(nsAString& aStr, const bool aIsVoid); + +/** + * NS_StringGetIsVoid + * + * This function provides a way to test if a string is a "void string", as + * marked by NS_StringSetIsVoid. + */ +XPCOM_API(bool) NS_StringGetIsVoid(const nsAString& aStr); + +/* ------------------------------------------------------------------------- */ + +/** + * nsCStringContainer + * + * This is an opaque data type that is large enough to hold the canonical + * implementation of nsACString. The binary structure of this class is an + * implementation detail. + * + * The string data stored in a string container is always single fragment + * and may be null-terminated depending on how it is initialized. + * + * @see nsStringContainer for use cases and further documentation. + */ +class nsCStringContainer; + +/** + * Flags that may be OR'd together to pass to NS_StringContainerInit2: + */ +enum +{ + /* Data passed into NS_CStringContainerInit2 is not copied; instead, the + * string references the passed in data pointer directly. The caller must + * ensure that the data is valid for the lifetime of the string container. + * This flag should not be combined with NS_CSTRING_CONTAINER_INIT_ADOPT. */ + NS_CSTRING_CONTAINER_INIT_DEPEND = (1 << 1), + + /* Data passed into NS_CStringContainerInit2 is not copied; instead, the + * string takes ownership over the data pointer. The caller must have + * allocated the data array using the XPCOM memory allocator (nsMemory). + * This flag should not be combined with NS_CSTRING_CONTAINER_INIT_DEPEND. */ + NS_CSTRING_CONTAINER_INIT_ADOPT = (1 << 2), + + /* Data passed into NS_CStringContainerInit2 is a substring that is not + * null-terminated. */ + NS_CSTRING_CONTAINER_INIT_SUBSTRING = (1 << 3) +}; + +/** + * NS_CStringContainerInit + * + * @param aContainer string container reference + * @return NS_OK if string container successfully initialized + * + * This function may allocate additional memory for aContainer. When + * aContainer is no longer needed, NS_CStringContainerFinish should be called. + */ +XPCOM_API(nsresult) NS_CStringContainerInit(nsCStringContainer& aContainer); + +/** + * NS_CStringContainerInit2 + * + * @param aContainer string container reference + * @param aData character buffer (may be null) + * @param aDataLength number of characters stored at aData (may pass + * UINT32_MAX if aData is null-terminated) + * @param aFlags flags affecting how the string container is + * initialized. this parameter is ignored when aData + * is null. otherwise, if this parameter is 0, then + * aData is copied into the string. + * + * This function resembles NS_CStringContainerInit but provides further + * options that permit more efficient memory usage. When aContainer is + * no longer needed, NS_CStringContainerFinish should be called. + * + * NOTE: NS_CStringContainerInit2(container, nullptr, 0, 0) is equivalent to + * NS_CStringContainerInit(container). + */ +XPCOM_API(nsresult) NS_CStringContainerInit2(nsCStringContainer& aContainer, + const char* aData = nullptr, + uint32_t aDataLength = UINT32_MAX, + uint32_t aFlags = 0); + +/** + * NS_CStringContainerFinish + * + * @param aContainer string container reference + * + * This function frees any memory owned by aContainer. + */ +XPCOM_API(void) NS_CStringContainerFinish(nsCStringContainer& aContainer); + +/* ------------------------------------------------------------------------- */ + +/** + * NS_CStringGetData + * + * This function returns a const character pointer to the string's internal + * buffer, the length of the string, and a boolean value indicating whether + * or not the buffer is null-terminated. + * + * @param aStr abstract string reference + * @param aData out param that will hold the address of aStr's + * internal buffer + * @param aTerminated if non-null, this out param will be set to indicate + * whether or not aStr's internal buffer is null- + * terminated + * @return length of aStr's internal buffer + */ +XPCOM_API(uint32_t) NS_CStringGetData(const nsACString& aStr, + const char** aData, + bool* aTerminated = nullptr); + +/** + * NS_CStringGetMutableData + * + * This function provides mutable access to a string's internal buffer. It + * returns a pointer to an array of characters that may be modified. The + * returned pointer remains valid until the string object is passed to some + * other string function. + * + * Optionally, this function may be used to resize the string's internal + * buffer. The aDataLength parameter specifies the requested length of the + * string's internal buffer. By passing some value other than UINT32_MAX, + * the caller can request that the buffer be resized to the specified number of + * characters before returning. The caller is not responsible for writing a + * null-terminator. + * + * @param aStr abstract string reference + * @param aDataLength number of characters to resize the string's internal + * buffer to or UINT32_MAX if no resizing is needed + * @param aData out param that upon return holds the address of aStr's + * internal buffer or null if the function failed + * @return number of characters or zero if the function failed + * + * This function does not necessarily null-terminate aStr after resizing its + * internal buffer. The behavior depends on the implementation of the abstract + * string, aStr. If aStr is a reference to a nsStringContainer, then its data + * will be null-terminated by this function. + */ +XPCOM_API(uint32_t) NS_CStringGetMutableData(nsACString& aStr, + uint32_t aDataLength, + char** aData); + +/** + * NS_CStringCloneData + * + * This function returns a null-terminated copy of the string's + * internal buffer. + * + * @param aStr abstract string reference + * @return null-terminated copy of the string's internal buffer + * (it must be free'd using using free) + */ +XPCOM_API(char*) NS_CStringCloneData(const nsACString& aStr); + +/** + * NS_CStringSetData + * + * This function copies aData into aStr. + * + * @param aStr abstract string reference + * @param aData character buffer + * @param aDataLength number of characters to copy from source string (pass + * UINT32_MAX to copy until end of aData, designated by + * a null character) + * @return NS_OK if function succeeded + * + * This function does not necessarily null-terminate aStr after copying data + * from aData. The behavior depends on the implementation of the abstract + * string, aStr. If aStr is a reference to a nsStringContainer, then its data + * will be null-terminated by this function. + */ +XPCOM_API(nsresult) NS_CStringSetData(nsACString& aStr, const char* aData, + uint32_t aDataLength = UINT32_MAX); + +/** + * NS_CStringSetDataRange + * + * This function copies aData into a section of aStr. As a result it can be + * used to insert new characters into the string. + * + * @param aStr abstract string reference + * @param aCutOffset starting index where the string's existing data + * is to be overwritten (pass UINT32_MAX to cause + * aData to be appended to the end of aStr, in which + * case the value of aCutLength is ignored). + * @param aCutLength number of characters to overwrite starting at + * aCutOffset (pass UINT32_MAX to overwrite until the + * end of aStr). + * @param aData character buffer (pass null to cause this function + * to simply remove the "cut" range) + * @param aDataLength number of characters to copy from source string (pass + * UINT32_MAX to copy until end of aData, designated by + * a null character) + * @return NS_OK if function succeeded + * + * This function does not necessarily null-terminate aStr after copying data + * from aData. The behavior depends on the implementation of the abstract + * string, aStr. If aStr is a reference to a nsStringContainer, then its data + * will be null-terminated by this function. + */ +XPCOM_API(nsresult) NS_CStringSetDataRange(nsACString& aStr, + uint32_t aCutOffset, + uint32_t aCutLength, + const char* aData, + uint32_t aDataLength = UINT32_MAX); + +/** + * NS_CStringCopy + * + * This function makes aDestStr have the same value as aSrcStr. It is + * provided as an optimization. + * + * @param aDestStr abstract string reference to be modified + * @param aSrcStr abstract string reference containing source string + * @return NS_OK if function succeeded + * + * This function does not necessarily null-terminate aDestStr after copying + * data from aSrcStr. The behavior depends on the implementation of the + * abstract string, aDestStr. If aDestStr is a reference to a + * nsStringContainer, then its data will be null-terminated by this function. + */ +XPCOM_API(nsresult) NS_CStringCopy(nsACString& aDestStr, + const nsACString& aSrcStr); + +/** + * NS_CStringAppendData + * + * This function appends data to the existing value of aStr. + * + * @param aStr abstract string reference to be modified + * @param aData character buffer + * @param aDataLength number of characters to append (pass UINT32_MAX to + * append until a null-character is encountered) + * @return NS_OK if function succeeded + * + * This function does not necessarily null-terminate aStr upon completion. + * The behavior depends on the implementation of the abstract string, aStr. + * If aStr is a reference to a nsStringContainer, then its data will be null- + * terminated by this function. + */ +inline NS_HIDDEN_(nsresult) +NS_CStringAppendData(nsACString& aStr, const char* aData, + uint32_t aDataLength = UINT32_MAX) +{ + return NS_CStringSetDataRange(aStr, UINT32_MAX, 0, aData, aDataLength); +} + +/** + * NS_CStringInsertData + * + * This function inserts data into the existing value of aStr at the specified + * offset. + * + * @param aStr abstract string reference to be modified + * @param aOffset specifies where in the string to insert aData + * @param aData character buffer + * @param aDataLength number of characters to append (pass UINT32_MAX to + * append until a null-character is encountered) + * @return NS_OK if function succeeded + * + * This function does not necessarily null-terminate aStr upon completion. + * The behavior depends on the implementation of the abstract string, aStr. + * If aStr is a reference to a nsStringContainer, then its data will be null- + * terminated by this function. + */ +inline NS_HIDDEN_(nsresult) +NS_CStringInsertData(nsACString& aStr, uint32_t aOffset, const char* aData, + uint32_t aDataLength = UINT32_MAX) +{ + return NS_CStringSetDataRange(aStr, aOffset, 0, aData, aDataLength); +} + +/** + * NS_CStringCutData + * + * This function shortens the existing value of aStr, by removing characters + * at the specified offset. + * + * @param aStr abstract string reference to be modified + * @param aCutOffset specifies where in the string to insert aData + * @param aCutLength number of characters to remove + * @return NS_OK if function succeeded + */ +inline NS_HIDDEN_(nsresult) +NS_CStringCutData(nsACString& aStr, uint32_t aCutOffset, uint32_t aCutLength) +{ + return NS_CStringSetDataRange(aStr, aCutOffset, aCutLength, nullptr, 0); +} + +/** + * NS_CStringSetIsVoid + * + * This function marks a string as being a "void string". Any data in the + * string will be lost. + */ +XPCOM_API(void) NS_CStringSetIsVoid(nsACString& aStr, const bool aIsVoid); + +/** + * NS_CStringGetIsVoid + * + * This function provides a way to test if a string is a "void string", as + * marked by NS_CStringSetIsVoid. + */ +XPCOM_API(bool) NS_CStringGetIsVoid(const nsACString& aStr); + +/* ------------------------------------------------------------------------- */ + +/** + * Encodings that can be used with the following conversion routines. + */ +enum nsCStringEncoding +{ + /* Conversion between ASCII and UTF-16 assumes that all bytes in the source + * string are 7-bit ASCII and can be inflated to UTF-16 by inserting null + * bytes. Reverse conversion is done by truncating every other byte. The + * conversion may result in loss and/or corruption of information if the + * strings do not strictly contain ASCII data. */ + NS_CSTRING_ENCODING_ASCII = 0, + + /* Conversion between UTF-8 and UTF-16 is non-lossy. */ + NS_CSTRING_ENCODING_UTF8 = 1, + + /* Conversion from UTF-16 to the native filesystem charset may result in a + * loss of information. No attempt is made to protect against data loss in + * this case. The native filesystem charset applies to strings passed to + * the "Native" method variants on nsIFile. */ + NS_CSTRING_ENCODING_NATIVE_FILESYSTEM = 2 +}; + +/** + * NS_CStringToUTF16 + * + * This function converts the characters in a nsACString to an array of UTF-16 + * characters, in the platform endianness. The result is stored in a nsAString + * object. + * + * @param aSource abstract string reference containing source string + * @param aSrcEncoding character encoding of the source string + * @param aDest abstract string reference to hold the result + */ +XPCOM_API(nsresult) NS_CStringToUTF16(const nsACString& aSource, + nsCStringEncoding aSrcEncoding, + nsAString& aDest); + +/** + * NS_UTF16ToCString + * + * This function converts the UTF-16 characters in a nsAString to a single-byte + * encoding. The result is stored in a nsACString object. In some cases this + * conversion may be lossy. In such cases, the conversion may succeed with a + * return code indicating loss of information. The exact behavior is not + * specified at this time. + * + * @param aSource abstract string reference containing source string + * @param aDestEncoding character encoding of the resulting string + * @param aDest abstract string reference to hold the result + */ +XPCOM_API(nsresult) NS_UTF16ToCString(const nsAString& aSource, + nsCStringEncoding aDestEncoding, + nsACString& aDest); + +#endif // nsXPCOMStrings_h__ diff --git a/xpcom/string/nsXPIDLString.h b/xpcom/string/nsXPIDLString.h new file mode 100644 index 000000000..f5821cdb0 --- /dev/null +++ b/xpcom/string/nsXPIDLString.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 nsXPIDLString_h___ +#define nsXPIDLString_h___ + +#include "nsString.h" + +#endif /* !defined(nsXPIDLString_h___) */ diff --git a/xpcom/string/string-template-def-char.h b/xpcom/string/string-template-def-char.h new file mode 100644 index 000000000..82f70d0fb --- /dev/null +++ b/xpcom/string/string-template-def-char.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/. */ +// IWYU pragma: private, include "nsString.h" + +#define CharT char +#define CharT_is_char 1 +#define nsTAString_IncompatibleCharT nsAString +#define nsTString_CharT nsCString +#define nsTFixedString_CharT nsFixedCString +#define nsTAutoString_CharT nsAutoCString +#define nsTSubstring_CharT nsACString +#define nsTSubstringTuple_CharT nsCSubstringTuple +#define nsTStringComparator_CharT nsCStringComparator +#define nsTDefaultStringComparator_CharT nsDefaultCStringComparator +#define nsTDependentString_CharT nsDependentCString +#define nsTDependentSubstring_CharT nsDependentCSubstring +#define nsTLiteralString_CharT nsLiteralCString +#define nsTXPIDLString_CharT nsXPIDLCString +#define nsTGetterCopies_CharT nsCGetterCopies +#define nsTAdoptingString_CharT nsAdoptingCString +#define nsTPromiseFlatString_CharT nsPromiseFlatCString +#define TPromiseFlatString_CharT PromiseFlatCString diff --git a/xpcom/string/string-template-def-unichar.h b/xpcom/string/string-template-def-unichar.h new file mode 100644 index 000000000..a21e16d09 --- /dev/null +++ b/xpcom/string/string-template-def-unichar.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/. */ +// IWYU pragma: private, include "nsString.h" + +#define CharT char16_t +#define CharT_is_PRUnichar 1 +#define nsTAString_IncompatibleCharT nsACString +#define nsTString_CharT nsString +#define nsTFixedString_CharT nsFixedString +#define nsTAutoString_CharT nsAutoString +#define nsTSubstring_CharT nsAString +#define nsTSubstringTuple_CharT nsSubstringTuple +#define nsTStringComparator_CharT nsStringComparator +#define nsTDefaultStringComparator_CharT nsDefaultStringComparator +#define nsTDependentString_CharT nsDependentString +#define nsTDependentSubstring_CharT nsDependentSubstring +#define nsTLiteralString_CharT nsLiteralString +#define nsTXPIDLString_CharT nsXPIDLString +#define nsTGetterCopies_CharT nsGetterCopies +#define nsTAdoptingString_CharT nsAdoptingString +#define nsTPromiseFlatString_CharT nsPromiseFlatString +#define TPromiseFlatString_CharT PromiseFlatString diff --git a/xpcom/string/string-template-undef.h b/xpcom/string/string-template-undef.h new file mode 100644 index 000000000..d62cd2278 --- /dev/null +++ b/xpcom/string/string-template-undef.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/. */ +// IWYU pragma: private, include "nsString.h" + +#undef CharT +#undef CharT_is_PRUnichar +#undef CharT_is_char +#undef nsTAString_IncompatibleCharT +#undef nsTString_CharT +#undef nsTFixedString_CharT +#undef nsTAutoString_CharT +#undef nsTSubstring_CharT +#undef nsTSubstringTuple_CharT +#undef nsTStringComparator_CharT +#undef nsTDefaultStringComparator_CharT +#undef nsTDependentString_CharT +#undef nsTDependentSubstring_CharT +#undef nsTLiteralString_CharT +#undef nsTXPIDLString_CharT +#undef nsTGetterCopies_CharT +#undef nsTAdoptingString_CharT +#undef nsTPromiseFlatString_CharT +#undef TPromiseFlatString_CharT diff --git a/xpcom/system/moz.build b/xpcom/system/moz.build new file mode 100644 index 000000000..8a4f88efe --- /dev/null +++ b/xpcom/system/moz.build @@ -0,0 +1,26 @@ +# -*- 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', + 'nsIDeviceSensors.idl', + 'nsIGConfService.idl', + 'nsIGeolocationProvider.idl', + 'nsIGIOService.idl', + 'nsIGSettingsService.idl', + 'nsIHapticFeedback.idl', + 'nsIPackageKitService.idl', + 'nsIPlatformInfo.idl', + 'nsIXULAppInfo.idl', + 'nsIXULRuntime.idl', +] + +if CONFIG['MOZ_CRASHREPORTER']: + XPIDL_SOURCES += [ + 'nsICrashReporter.idl', + ] + +XPIDL_MODULE = 'xpcom_system' diff --git a/xpcom/system/nsIBlocklistService.idl b/xpcom/system/nsIBlocklistService.idl new file mode 100644 index 000000000..b70038431 --- /dev/null +++ b/xpcom/system/nsIBlocklistService.idl @@ -0,0 +1,140 @@ +/* -*- 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 nsIPluginTag; +interface nsIVariant; + +[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; + // Indicates that the item is considered outdated, and there is a known + // update available. + const unsigned long STATE_OUTDATED = 3; + // Indicates that the item is vulnerable and there is an update. + const unsigned long STATE_VULNERABLE_UPDATE_AVAILABLE = 4; + // Indicates that the item is vulnerable and there is no update. + const unsigned long STATE_VULNERABLE_NO_UPDATE = 5; + + /** + * Determine if an item is blocklisted + * @param addon + * The addon item to be checked. + * @param appVersion + * The version of the application we are checking in the blocklist. + * If this parameter is null, the version of the running application + * is used. + * @param toolkitVersion + * The version of the toolkit we are checking in the blocklist. + * If this parameter is null, the version of the running toolkit + * is used. + * @returns true if the item is compatible with this version of the + * application or this version of the toolkit, false, otherwise. + */ + boolean isAddonBlocklisted(in jsval addon, + [optional] in AString appVersion, + [optional] in AString toolkitVersion); + + /** + * Determine the blocklist state of an add-on + * @param id + * The addon item to be checked. + * @param appVersion + * The version of the application we are checking in the blocklist. + * If this parameter is null, the version of the running application + * is used. + * @param toolkitVersion + * The version of the toolkit we are checking in the blocklist. + * If this parameter is null, the version of the running toolkit + * is used. + * @returns The STATE constant. + */ + unsigned long getAddonBlocklistState(in jsval addon, + [optional] in AString appVersion, + [optional] in AString toolkitVersion); + + /** + * Determine the blocklist state of a plugin + * @param plugin + * The plugin to get the state for + * @param appVersion + * The version of the application we are checking in the blocklist. + * If this parameter is null, the version of the running application + * is used. + * @param toolkitVersion + * The version of the toolkit we are checking in the blocklist. + * If this parameter is null, the version of the running toolkit + * is used. + * @returns The STATE constant. + */ + unsigned long getPluginBlocklistState(in nsIPluginTag plugin, + [optional] in AString appVersion, + [optional] in AString toolkitVersion); + + /** + * Determine the blocklist web page of an add-on. + * @param addon + * The addon item whose url is required. + * @returns The URL of the description page. + */ + AString getAddonBlocklistURL(in jsval addon, + [optional] in AString appVersion, + [optional] in AString toolkitVersion); + + /** + * Determine the blocklist web page of a plugin. + * @param plugin + * The blocked plugin that we are determining the web page for. + * @returns The URL of the description page. + */ + AString getPluginBlocklistURL(in nsIPluginTag plugin); + + /** + * Determine the blocklist infoURL of a plugin. + * @param plugin + * The blocked plugin that we are determining the infoURL for. + * @returns The preferred URL to present the user, or |null| if + * it is not available. + */ + AString getPluginInfoURL(in nsIPluginTag plugin); +}; + +/** + * nsIBlocklistPrompt is used, if available, by the default implementation of + * nsIBlocklistService to display a confirmation UI to the user before blocking + * extensions/plugins. + */ +[scriptable, uuid(ba915921-b9c0-400d-8e4f-ca1b80c5699a)] +interface nsIBlocklistPrompt : nsISupports +{ + /** + * Prompt the user about newly blocked addons. The prompt is then resposible + * for soft-blocking any addons that need to be afterwards + * + * @param aAddons + * An array of addons and plugins that are blocked. These are javascript + * objects with properties: + * name - the plugin or extension name, + * version - the version of the extension or plugin, + * icon - the plugin or extension icon, + * disable - can be used by the nsIBlocklistPrompt to allows users to decide + * whether a soft-blocked add-on should be disabled, + * blocked - true if the item is hard-blocked, false otherwise, + * item - the nsIPluginTag or Addon object + * @param aCount + * The number of addons + */ + void prompt([array, size_is(aCount)] in nsIVariant aAddons, + [optional] in uint32_t aCount); +}; diff --git a/xpcom/system/nsICrashReporter.idl b/xpcom/system/nsICrashReporter.idl new file mode 100644 index 000000000..c2f590098 --- /dev/null +++ b/xpcom/system/nsICrashReporter.idl @@ -0,0 +1,135 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. 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 enabled; + + /** + * 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; + + /** + * Add some extra data to be submitted with a crash report. + * + * @param key + * Name of the data to be added. + * @param data + * Data to be added. + * + * @throw NS_ERROR_NOT_INITIALIZED if crash reporting not initialized + * @throw NS_ERROR_INVALID_ARG if key or data contain invalid characters. + * Invalid characters for key are '=' and + * '\n'. Invalid character for data is '\0'. + */ + void annotateCrashReport(in AUTF8String key, in AUTF8String data); + + /** + * 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(); + + /** + * Set the telemetry session ID which is recorded in crash metadata. This is + * saved in the crash manager and telemetry but is not submitted as a + * crash-stats annotation. + */ + void setTelemetrySessionId(in AUTF8String id); +}; diff --git a/xpcom/system/nsIDeviceSensors.idl b/xpcom/system/nsIDeviceSensors.idl new file mode 100644 index 000000000..dcb67cb92 --- /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/nsIGConfService.idl b/xpcom/system/nsIGConfService.idl new file mode 100644 index 000000000..ccc02610f --- /dev/null +++ b/xpcom/system/nsIGConfService.idl @@ -0,0 +1,50 @@ +/* -*- 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(5009acae-6973-48c3-b6d6-52c692cc5d9d)] +interface nsIGConfService : nsISupports +{ + /* Basic registry access */ + boolean getBool(in AUTF8String key); + AUTF8String getString(in AUTF8String key); + long getInt(in AUTF8String key); + float getFloat(in AUTF8String key); + + /* + * Use this to return any list items in GConf, this will return + * an array of UTF16 nsISupportsString's. + */ + nsIArray getStringList(in AUTF8String key); + + void setBool(in AUTF8String key, in boolean value); + void setString(in AUTF8String key, in AUTF8String value); + void setInt(in AUTF8String key, in long value); + void setFloat(in AUTF8String key, in float value); + + /* + * Look up the handler for a protocol under the + * /desktop/gnome/url-handlers hierarchy. + */ + AUTF8String getAppForProtocol(in AUTF8String scheme, out boolean enabled); + + /* + * Check whether the handler for a scheme requires a terminal to run. + */ + boolean handlerRequiresTerminal(in AUTF8String scheme); + + /* + * Set the handler for a protocol, marking it as enabled. + * This removes any GnomeVFSMimeApp association for the protocol. + */ + void setAppForProtocol(in AUTF8String scheme, in AUTF8String command); +}; + +%{C++ +#define NS_GCONFSERVICE_CONTRACTID "@mozilla.org/gnome-gconf-service;1" +%} diff --git a/xpcom/system/nsIGIOService.idl b/xpcom/system/nsIGIOService.idl new file mode 100644 index 000000000..cbbdabe7e --- /dev/null +++ b/xpcom/system/nsIGIOService.idl @@ -0,0 +1,82 @@ +/* -*- 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 nsIUTF8StringEnumerator; +interface nsIURI; + +/* 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 : nsISupports +{ + 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 name; + readonly attribute AUTF8String command; + readonly attribute long expectsURIs; // see constants above + readonly attribute nsIUTF8StringEnumerator supportedURISchemes; + + void launch(in AUTF8String uri); + 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, 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 */ + nsIGIOMimeApp getAppForURIScheme(in AUTF8String aURIScheme); + + /* Obtain the preferred application for opening a given MIME type */ + nsIGIOMimeApp getAppForMimeType(in AUTF8String mimeType); + + /* Obtain the preferred application for opening a given MIME type */ + nsIGIOMimeApp createAppFromCommand(in AUTF8String cmd, + in AUTF8String appName); + + /* Obtain a description for the given MIME type */ + AUTF8String getDescriptionForMimeType(in AUTF8String mimeType); + + /*** Misc. methods ***/ + + /* Open the given URI in the default application */ + void showURI(in nsIURI uri); + [noscript] void showURIForInput(in ACString uri); + + /* Open path in file manager using org.freedesktop.FileManager1 interface */ + [noscript] void orgFreedesktopFileManager1ShowItems(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 000000000..26d86a77e --- /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 000000000..29044266d --- /dev/null +++ b/xpcom/system/nsIGeolocationProvider.idl @@ -0,0 +1,83 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. 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 nsIDOMWindow; +interface nsIDOMElement; +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. + */ + 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 000000000..25d0d8e7b --- /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/nsIPackageKitService.idl b/xpcom/system/nsIPackageKitService.idl new file mode 100644 index 000000000..5cd3494a3 --- /dev/null +++ b/xpcom/system/nsIPackageKitService.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" + +interface nsIArray; +interface nsIObserver; + +[scriptable, uuid(89bb04f6-ce2a-11e3-8f4f-60a44c717042)] +interface nsIPackageKitService : nsISupports +{ + + /* PackageKit installation methods */ + /* See https://github.com/nekohayo/gnome-packagekit/blob/master/src/org.freedesktop.PackageKit.xml */ + const unsigned long PK_INSTALL_PACKAGE_NAMES = 0; + const unsigned long PK_INSTALL_MIME_TYPES = 1; + const unsigned long PK_INSTALL_FONTCONFIG_RESOURCES = 2; + const unsigned long PK_INSTALL_GSTREAMER_RESOURCES = 3; + const unsigned long PK_INSTALL_METHOD_COUNT = 4; + + /* Ask to install a list of packages via PackageKit + * @param packageKitMethod + * The PackageKit installation method + * @param packageArray + * A nonempty array of strings describing the list of packages to + * install. + * @param An object implementing nsIObserver that will be notified with + * a message of topic "packagekit-install". The message data + * contains the error returned by PackageKit if the installation + * fails and is null otherwise. + * + * This function may raise an NS_ERROR_INVALID_ARG, NS_ERROR_FAILURE or + * NS_ERROR_OUT_OF_MEMORY exception. Otherwise, the observer will be notified + * when the operation completes. + * + */ + void installPackages(in unsigned long packageKitMethod, + in nsIArray packageArray, + in nsIObserver observer); +}; + +%{C++ +#define NS_PACKAGEKITSERVICE_CONTRACTID "@mozilla.org/packagekit-service;1" +%} diff --git a/xpcom/system/nsIPlatformInfo.idl b/xpcom/system/nsIPlatformInfo.idl new file mode 100644 index 000000000..fba78b6ec --- /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/nsIXULAppInfo.idl b/xpcom/system/nsIXULAppInfo.idl new file mode 100644 index 000000000..1ea9208a3 --- /dev/null +++ b/xpcom/system/nsIXULAppInfo.idl @@ -0,0 +1,53 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. 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 nsXREAppData.vendor + * @returns an empty string if nsXREAppData.vendor is not set. + */ + readonly attribute ACString vendor; + + /** + * @see nsXREAppData.name + */ + readonly attribute ACString name; + + /** + * @see nsXREAppData.ID + * @returns an empty string if nsXREAppData.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 nsXREAppData.version + * @returns an empty string if nsXREAppData.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 nsXREAppData.UAName + * @returns an empty string if nsXREAppData.UAName is not set. + */ + readonly attribute ACString UAName; +}; diff --git a/xpcom/system/nsIXULRuntime.idl b/xpcom/system/nsIXULRuntime.idl new file mode 100644 index 000000000..fda0e696f --- /dev/null +++ b/xpcom/system/nsIXULRuntime.idl @@ -0,0 +1,176 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. 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(); +} + +%} + +/** + * 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(a1b2e167-b748-42bf-ba85-996ec39062b9)] +interface nsIXULRuntime : nsISupports +{ + /** + * Whether the application was launched in safe mode. + */ + readonly attribute boolean inSafeMode; + + /** + * 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_PLUGIN = 1; + 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; + + /** + * 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; + + /** + * If true, browser tabs may be opened by default in a different process + * from the main browser UI. + */ + readonly attribute boolean browserTabsRemoteAutostart; + + /** + * A numeric value indicating whether multiprocess might be blocked. + * Possible values can be found at nsAppRunner.cpp. A value of 0 + * represents not blocking. + */ + readonly attribute unsigned long multiprocessBlockPolicy; + + /** + * If true, the accessibility service is running. + */ + readonly attribute boolean accessibilityEnabled; + + /** + * Indicates whether the current Firefox build is 64-bit. + */ + readonly attribute boolean is64Bit; + + /** + * 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; + + /** + * Local ID of the minidump generated when the process crashed + * on the previous run. Can be passed directly to CrashSubmit.submit. + */ + readonly attribute DOMString lastRunCrashID; + + /** + * True if this is RELEASE_OR_BETA. + */ + readonly attribute boolean isReleaseOrBeta; + + /** + * True if this build uses official branding (MOZ_OFFICIAL_BRANDING). + */ + readonly attribute boolean isOfficialBranding; + + /** + * 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 this is an official build (MOZILLA_OFFICIAL). + */ + readonly attribute boolean isOfficial; + + /** + * True if Windows DLL blocklist initialized correctly. This is + * primarily for automated testing purposes. + */ + readonly attribute boolean windowsDLLBlocklistStatus; +}; diff --git a/xpcom/tests/Makefile.in b/xpcom/tests/Makefile.in new file mode 100644 index 000000000..75796b93b --- /dev/null +++ b/xpcom/tests/Makefile.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/. + +# Make sure we have symbols in case we need to debug these. +MOZ_DEBUG_SYMBOLS = 1 + +include $(topsrcdir)/config/rules.mk + +ifneq (,$(SIMPLE_PROGRAMS)) +libs:: + $(INSTALL) $(SIMPLE_PROGRAMS) $(DEPTH)/_tests/xpcshell/$(relativesrcdir)/unit +endif diff --git a/xpcom/tests/NotXPCOMTest.idl b/xpcom/tests/NotXPCOMTest.idl new file mode 100644 index 000000000..75d1e73da --- /dev/null +++ b/xpcom/tests/NotXPCOMTest.idl @@ -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/. */ + +#include "nsISupports.idl" + +[scriptable, uuid(93142a4f-e4cf-424a-b833-e638f87d2607)] +interface ScriptableOK : nsISupports +{ + void method1(); +}; + +[scriptable, uuid(237d01a3-771e-4c6e-adf9-c97f9aab2950)] +interface ScriptableWithNotXPCOM : nsISupports +{ + [notxpcom] void method2(); +}; + +[scriptable, uuid(4f06ec60-3bb3-4712-ab18-b2b595285558)] +interface ScriptableWithNotXPCOMBase : ScriptableWithNotXPCOM +{ + void method3(); +}; diff --git a/xpcom/tests/RegFactory.cpp b/xpcom/tests/RegFactory.cpp new file mode 100644 index 000000000..0abb7a4e8 --- /dev/null +++ b/xpcom/tests/RegFactory.cpp @@ -0,0 +1,130 @@ +/* -*- 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 "plstr.h" +#include "prlink.h" +#include "nsIComponentRegistrar.h" +#include "nsIComponentManager.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_InitXPCOM2(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 000000000..cdb7f00c0 --- /dev/null +++ b/xpcom/tests/SizeTest01.cpp @@ -0,0 +1,107 @@ +// Test01.cpp + +#include "nsIDOMNode.h" +#include "nsCOMPtr.h" +#include "nsString.h" + +NS_DEF_PTR(nsIDOMNode); + + /* + 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( nsIDOMNode* 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|. + */ + + nsIDOMNode* node = aDOMNode; + NS_IF_ADDREF(node); + + if ( node ) + node->GetNodeName(*aResult); + + NS_IF_RELEASE(node); + } + +void +Test01_raw_optimized( nsIDOMNode* 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; + + nsIDOMNode* node = aDOMNode; + NS_ADDREF(node); + node->GetNodeName(*aResult); + NS_RELEASE(node); + } + +void +Test01_nsCOMPtr( nsIDOMNode* aDOMNode, nsString* aResult ) + // m120, w46/34 + { + nsCOMPtr node = aDOMNode; + + if ( node ) + node->GetNodeName(*aResult); + } + +void +Test01_nsCOMPtr_optimized( nsIDOMNode* 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 000000000..9fcd1a0ca --- /dev/null +++ b/xpcom/tests/SizeTest02.cpp @@ -0,0 +1,89 @@ +// Test02.cpp + +#include "nsIDOMNode.h" +#include "nsCOMPtr.h" +#include "nsString.h" + +NS_DEF_PTR(nsIDOMNode); + + /* + 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; + + nsIDOMNode* node = 0; + nsresult status = aDOMNode->QueryInterface(NS_GET_IID(nsIDOMNode), (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; + + nsIDOMNode* node; + nsresult status = aDOMNode->QueryInterface(NS_GET_IID(nsIDOMNode), (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 000000000..e1593171e --- /dev/null +++ b/xpcom/tests/SizeTest03.cpp @@ -0,0 +1,97 @@ +// Test03.cpp + +#include "nsIDOMNode.h" +#include "nsCOMPtr.h" +#include "nsString.h" + +NS_DEF_PTR(nsIDOMNode); + + /* + 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( nsIDOMNode* 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; + + nsIDOMNode* 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( nsIDOMNode* aDOMNode, nsString* aResult ) + // m124, w48 + { +// if ( !aDOMNode || !aResult ) +// return NS_ERROR_NULL_POINTER; + + nsIDOMNode* parent; + nsresult status = aDOMNode->GetParentNode(&parent); + + if ( NS_SUCCEEDED(status) ) + { + parent->GetNodeName(*aResult); + NS_RELEASE(parent); + } + +// return status; + } + + +void // nsresult +Test03_nsCOMPtr( nsIDOMNode* 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( nsIDOMNode* aDOMNode, nsString* aResult ) + // m112, w50/45 + { +// if ( !aDOMNode || !aResult ) +// return NS_ERROR_NULL_POINTER; + + nsIDOMNode* 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 000000000..e045f29f6 --- /dev/null +++ b/xpcom/tests/SizeTest04.cpp @@ -0,0 +1,68 @@ +// Test04.cpp + +#include "nsIDOMNode.h" +#include "nsCOMPtr.h" + +NS_DEF_PTR(nsIDOMNode); + + /* + 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( nsIDOMNode* newNode ); + + private: + nsIDOMNode* 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( nsIDOMNode* newNode ) + // m120, w36 + { + NS_IF_ADDREF(newNode); + NS_IF_RELEASE(mNode); + mNode = newNode; + +// return NS_OK; + } + + + +class Test04_nsCOMPtr + { + public: + void /*nsresult*/ SetNode( nsIDOMNode* newNode ); + + private: + nsCOMPtr mNode; + }; + +void // nsresult +Test04_nsCOMPtr::SetNode( nsIDOMNode* newNode ) + // m36, w13/13 + { + mNode = newNode; + } diff --git a/xpcom/tests/SizeTest05.cpp b/xpcom/tests/SizeTest05.cpp new file mode 100644 index 000000000..1307b1c26 --- /dev/null +++ b/xpcom/tests/SizeTest05.cpp @@ -0,0 +1,74 @@ +// Test05.cpp + +#include "nsIDOMNode.h" +#include "nsCOMPtr.h" + +NS_DEF_PTR(nsIDOMNode); + + /* + Windows: + raw, nsCOMPtr 21 bytes + + Macintosh: + Raw, nsCOMPtr 64 bytes + */ + +class Test05_Raw + { + public: + Test05_Raw(); + ~Test05_Raw(); + + void /*nsresult*/ GetNode( nsIDOMNode** aNode ); + + private: + nsIDOMNode* 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( nsIDOMNode** 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( nsIDOMNode** aNode ); + + private: + nsCOMPtr mNode; + }; + +void // nsresult +Test05_nsCOMPtr::GetNode( nsIDOMNode** 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 000000000..67b57277d --- /dev/null +++ b/xpcom/tests/SizeTest06.cpp @@ -0,0 +1,150 @@ +// 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 000000000..a9a9a258b --- /dev/null +++ b/xpcom/tests/TestArguments.cpp @@ -0,0 +1,25 @@ +#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("\"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 000000000..9526934c1 --- /dev/null +++ b/xpcom/tests/TestBlockingProcess.cpp @@ -0,0 +1,8 @@ +#include + +int main() +{ + char tmp; + 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 000000000..753e0233a --- /dev/null +++ b/xpcom/tests/TestHarness.h @@ -0,0 +1,292 @@ +/* -*- 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 "prenv.h" +#include "nsComponentManagerUtils.h" +#include "nsServiceManagerUtils.h" +#include "nsCOMPtr.h" +#include "nsAutoPtr.h" +#include "nsStringGlue.h" +#include "nsAppDirectoryServiceDefs.h" +#include "nsDirectoryServiceDefs.h" +#include "nsDirectoryServiceUtils.h" +#include "nsIDirectoryService.h" +#include "nsIFile.h" +#include "nsIProperties.h" +#include "nsIObserverService.h" +#include "nsXULAppAPI.h" +#include +#include +#include + +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. + */ +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. + */ +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); + + nsresult rv = NS_InitXPCOM2(&mServMgr, nullptr, this); + if (NS_FAILED(rv)) + { + fail("NS_InitXPCOM2 returned failure code 0x%x", rv); + mServMgr = nullptr; + return; + } + } + + ~ScopedXPCOM() + { + // If we created a profile directory, we need to remove it. + if (mProfD) { + nsCOMPtr os = + do_GetService(NS_OBSERVERSERVICE_CONTRACTID); + MOZ_RELEASE_ASSERT(os); + MOZ_ALWAYS_SUCCEEDS(os->NotifyObservers(nullptr, "profile-change-net-teardown", nullptr)); + MOZ_ALWAYS_SUCCEEDS(os->NotifyObservers(nullptr, "profile-change-teardown", nullptr)); + MOZ_ALWAYS_SUCCEEDS(os->NotifyObservers(nullptr, "profile-before-change", nullptr)); + MOZ_ALWAYS_SUCCEEDS(os->NotifyObservers(nullptr, "profile-before-change-qm", nullptr)); + MOZ_ALWAYS_SUCCEEDS(os->NotifyObservers(nullptr, "profile-before-change-telemetry", nullptr)); + + if (NS_FAILED(mProfD->Remove(true))) { + NS_WARNING("Problem removing profile directory"); + } + + mProfD = nullptr; + } + + if (mServMgr) + { + NS_RELEASE(mServMgr); + nsresult rv = NS_ShutdownXPCOM(nullptr); + if (NS_FAILED(rv)) + { + fail("XPCOM shutdown failed with code 0x%x", rv); + exit(1); + } + } + + printf("Finished running %s tests.\n", mTestName); + } + + bool failed() + { + return mServMgr == nullptr; + } + + 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(NS_LITERAL_STRING("cpp-unit-profd")); + 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.Equals("Resources")) { + mGREBinD->SetNativeLeafName(NS_LITERAL_CSTRING("MacOS")); + } +#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; + nsIServiceManager* mServMgr; + 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/TestPRIntN.cpp b/xpcom/tests/TestPRIntN.cpp new file mode 100644 index 000000000..afc155d70 --- /dev/null +++ b/xpcom/tests/TestPRIntN.cpp @@ -0,0 +1,33 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. 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 000000000..d00771ba7 --- /dev/null +++ b/xpcom/tests/TestQuickReturn.cpp @@ -0,0 +1,8 @@ +#include + +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 000000000..177c83dbd --- /dev/null +++ b/xpcom/tests/TestShutdown.cpp @@ -0,0 +1,41 @@ +/* -*- 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[]) +{ + nsresult rv; + nsIServiceManager* servMgr; + rv = NS_InitXPCOM2(&servMgr, nullptr, nullptr); + NS_ASSERTION(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); + NS_ASSERTION(NS_SUCCEEDED(rv), "NS_ShutdownXPCOM failed"); +} diff --git a/xpcom/tests/TestStackCrawl.cpp b/xpcom/tests/TestStackCrawl.cpp new file mode 100644 index 000000000..e1fe37db7 --- /dev/null +++ b/xpcom/tests/TestStackCrawl.cpp @@ -0,0 +1,11 @@ +#include + +#include "nsISupportsUtils.h" +#include "nsTraceRefcnt.h" + +int main(int argc, char* argv[]) +{ + nsTraceRefcnt::WalkTheStack(stdout); + return 0; +} + diff --git a/xpcom/tests/TestStreamUtils.cpp b/xpcom/tests/TestStreamUtils.cpp new file mode 100644 index 000000000..72640474f --- /dev/null +++ b/xpcom/tests/TestStreamUtils.cpp @@ -0,0 +1,74 @@ +/* -*- 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 "nsIPipe.h" +#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_InitXPCOM2(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 000000000..35d80a7f4 --- /dev/null +++ b/xpcom/tests/TestUnicodeArguments.cpp @@ -0,0 +1,77 @@ +/** + * 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 000000000..5bde37900 --- /dev/null +++ b/xpcom/tests/TestWinReg.js @@ -0,0 +1,57 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 = Components.interfaces.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 = Components.classes["@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 000000000..ffeada605 --- /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/bug656331_component/TestComponent.cpp b/xpcom/tests/bug656331_component/TestComponent.cpp new file mode 100644 index 000000000..987d21451 --- /dev/null +++ b/xpcom/tests/bug656331_component/TestComponent.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 "mozilla/ModuleUtils.h" + +// f18fb09b-28b4-4435-bc5b-8027f18df743 +#define NS_TESTING_CID \ +{ 0xf18fb09b, 0x28b4, 0x4435, \ + { 0xbc, 0x5b, 0x80, 0x27, 0xf1, 0x8d, 0xf7, 0x43 } } + +NS_DEFINE_NAMED_CID(NS_TESTING_CID); + +static nsresult +DummyConstructorFunc(nsISupports* aOuter, const nsIID& aIID, void** aResult) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +static const mozilla::Module::CIDEntry kTestCIDs[] = { + { &kNS_TESTING_CID, false, nullptr, DummyConstructorFunc }, + { nullptr } +}; + +static const mozilla::Module kTestModule = { + 3, /* faking mozilla::Module::kVersion with a value that will never be used */ + kTestCIDs +}; + +NSMODULE_DEFN(dummy) = &kTestModule; diff --git a/xpcom/tests/bug656331_component/bug656331.manifest b/xpcom/tests/bug656331_component/bug656331.manifest new file mode 100644 index 000000000..fb1991a56 --- /dev/null +++ b/xpcom/tests/bug656331_component/bug656331.manifest @@ -0,0 +1,2 @@ +#filter substitution +binary-component @LIBRARY_FILENAME@ diff --git a/xpcom/tests/bug656331_component/moz.build b/xpcom/tests/bug656331_component/moz.build new file mode 100644 index 000000000..e986f3de0 --- /dev/null +++ b/xpcom/tests/bug656331_component/moz.build @@ -0,0 +1,26 @@ +# -*- 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/. + +FINAL_TARGET = '_tests/xpcshell/xpcom/tests/unit' +EXTRA_PP_COMPONENTS += [ + 'bug656331.manifest', +] + +SOURCES += [ + 'TestComponent.cpp', +] + +XPCOMBinaryComponent('test656331') + +DEFINES['LIBRARY_FILENAME'] = '%s%s%s' % ( + CONFIG['DLL_PREFIX'], + LIBRARY_NAME, + CONFIG['DLL_SUFFIX'] +) + +# Need to link with CoreFoundation on Mac +if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa': + OS_LIBS += CONFIG['TK_LIBS'] diff --git a/xpcom/tests/component/TestComponent.cpp b/xpcom/tests/component/TestComponent.cpp new file mode 100644 index 000000000..85b8860ac --- /dev/null +++ b/xpcom/tests/component/TestComponent.cpp @@ -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/. */ + +#include "mozilla/ModuleUtils.h" + +#define NS_TESTING_CID \ +{ 0x335fb596, 0xe52d, 0x418f, \ + { 0xb0, 0x1c, 0x1b, 0xf1, 0x6c, 0xe5, 0xe7, 0xe4 } } +#define NS_NONEXISTENT_CID \ +{ 0x1e61fb15, 0xead4, 0x45cd, \ + { 0x80, 0x13, 0x40, 0x99, 0xa7, 0x10, 0xa2, 0xfa } } + +NS_DEFINE_NAMED_CID(NS_TESTING_CID); +NS_DEFINE_NAMED_CID(NS_NONEXISTENT_CID); + +static nsresult +DummyConstructorFunc(nsISupports* aOuter, const nsIID& aIID, void** aResult) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +static const mozilla::Module::CIDEntry kTestCIDs[] = { + { &kNS_TESTING_CID, false, nullptr, DummyConstructorFunc }, + { &kNS_TESTING_CID, false, nullptr, DummyConstructorFunc }, + { nullptr } +}; + +static const mozilla::Module::ContractIDEntry kTestContractIDs[] = { + { "@testing/foo", &kNS_NONEXISTENT_CID }, + { nullptr } +}; + +static const mozilla::Module kTestModule = { + mozilla::Module::kVersion, + kTestCIDs, + kTestContractIDs +}; + +NSMODULE_DEFN(dummy) = &kTestModule; + + diff --git a/xpcom/tests/component/moz.build b/xpcom/tests/component/moz.build new file mode 100644 index 000000000..62e88502c --- /dev/null +++ b/xpcom/tests/component/moz.build @@ -0,0 +1,26 @@ +# -*- 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/. + +FINAL_TARGET = '_tests/xpcshell/xpcom/tests/unit' +EXTRA_PP_COMPONENTS += [ + 'testcomponent.manifest', +] + +SOURCES += [ + 'TestComponent.cpp', +] + +XPCOMBinaryComponent('testcomponent') + +DEFINES['LIBRARY_FILENAME'] = '%s%s%s' % ( + CONFIG['DLL_PREFIX'], + LIBRARY_NAME, + CONFIG['DLL_SUFFIX'] +) + +# Need to link with CoreFoundation on Mac +if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa': + OS_LIBS += CONFIG['TK_LIBS'] diff --git a/xpcom/tests/component/testcomponent.manifest b/xpcom/tests/component/testcomponent.manifest new file mode 100644 index 000000000..a570e4c18 --- /dev/null +++ b/xpcom/tests/component/testcomponent.manifest @@ -0,0 +1,4 @@ +#filter substitution +binary-component @LIBRARY_FILENAME@ +binary-component @LIBRARY_FILENAME@ +binary-component @LIBRARY_FILENAME@ diff --git a/xpcom/tests/component_no_aslr/Makefile.in b/xpcom/tests/component_no_aslr/Makefile.in new file mode 100644 index 000000000..f08d6ad8a --- /dev/null +++ b/xpcom/tests/component_no_aslr/Makefile.in @@ -0,0 +1,8 @@ +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +include $(topsrcdir)/config/rules.mk + +LDFLAGS := $(filter-out -DYNAMICBASE,$(LDFLAGS)) -DYNAMICBASE:NO diff --git a/xpcom/tests/component_no_aslr/TestComponent.cpp b/xpcom/tests/component_no_aslr/TestComponent.cpp new file mode 100644 index 000000000..6fbfd316a --- /dev/null +++ b/xpcom/tests/component_no_aslr/TestComponent.cpp @@ -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/. */ + +#include "mozilla/ModuleUtils.h" + +#define NS_TESTING_CID \ +{ 0x335fb596, 0xe52d, 0x418f, \ + { 0xb0, 0x1c, 0x1b, 0xf1, 0x6c, 0xe5, 0xe7, 0xe4 } } + +NS_DEFINE_NAMED_CID(NS_TESTING_CID); + +static nsresult +DummyConstructorFunc(nsISupports* aOuter, const nsIID& aIID, void** aResult) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +static const mozilla::Module::CIDEntry kTestCIDs[] = { + { &kNS_TESTING_CID, false, nullptr, DummyConstructorFunc }, + { nullptr } +}; + +static const mozilla::Module kTestModule = { + mozilla::Module::kVersion, + kTestCIDs +}; + +NSMODULE_DEFN(dummy) = &kTestModule; + + diff --git a/xpcom/tests/component_no_aslr/moz.build b/xpcom/tests/component_no_aslr/moz.build new file mode 100644 index 000000000..45e69c105 --- /dev/null +++ b/xpcom/tests/component_no_aslr/moz.build @@ -0,0 +1,26 @@ +# -*- 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/. + +FINAL_TARGET = '_tests/xpcshell/xpcom/tests/unit' +EXTRA_PP_COMPONENTS += [ + 'testcompnoaslr.manifest', +] + +SOURCES += [ + 'TestComponent.cpp', +] + +XPCOMBinaryComponent('testcompnoaslr') + +DEFINES['LIBRARY_FILENAME'] = '%s%s%s' % ( + CONFIG['DLL_PREFIX'], + LIBRARY_NAME, + CONFIG['DLL_SUFFIX'] +) + +# Need to link with CoreFoundation on Mac +if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa': + OS_LIBS += CONFIG['TK_LIBS'] diff --git a/xpcom/tests/component_no_aslr/testcompnoaslr.manifest b/xpcom/tests/component_no_aslr/testcompnoaslr.manifest new file mode 100644 index 000000000..fb1991a56 --- /dev/null +++ b/xpcom/tests/component_no_aslr/testcompnoaslr.manifest @@ -0,0 +1,2 @@ +#filter substitution +binary-component @LIBRARY_FILENAME@ diff --git a/xpcom/tests/external/TestMinStringAPI.cpp b/xpcom/tests/external/TestMinStringAPI.cpp new file mode 100644 index 000000000..28bc0b1c1 --- /dev/null +++ b/xpcom/tests/external/TestMinStringAPI.cpp @@ -0,0 +1,1009 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. 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 "nsStringAPI.h" +#include "nsXPCOM.h" +#include "nsMemory.h" + +static const char kAsciiData[] = "Hello World"; + +static const char16_t kUnicodeData[] = + {'H','e','l','l','o',' ','W','o','r','l','d','\0'}; + +static bool test_basic_1() + { + nsCStringContainer s; + NS_CStringContainerInit(s); + + const char *ptr; + uint32_t len; + char *clone; + + NS_CStringGetData(s, &ptr); + if (ptr == nullptr || *ptr != '\0') + { + NS_ERROR("unexpected result"); + return false; + } + + NS_CStringSetData(s, kAsciiData, UINT32_MAX); + len = NS_CStringGetData(s, &ptr); + if (ptr == nullptr || strcmp(ptr, kAsciiData) != 0) + { + NS_ERROR("unexpected result"); + return false; + } + if (len != sizeof(kAsciiData)-1) + { + NS_ERROR("unexpected result"); + return false; + } + + clone = NS_CStringCloneData(s); + if (ptr == nullptr || strcmp(ptr, kAsciiData) != 0) + { + NS_ERROR("unexpected result"); + return false; + } + free(clone); + + nsCStringContainer temp; + NS_CStringContainerInit(temp); + NS_CStringCopy(temp, s); + + len = NS_CStringGetData(temp, &ptr); + if (ptr == nullptr || strcmp(ptr, kAsciiData) != 0) + { + NS_ERROR("unexpected result"); + return false; + } + if (len != sizeof(kAsciiData)-1) + { + NS_ERROR("unexpected result"); + return false; + } + + NS_CStringContainerFinish(temp); + + NS_CStringContainerFinish(s); + return true; + } + +static bool test_basic_2() + { + nsStringContainer s; + NS_StringContainerInit(s); + + const char16_t *ptr; + uint32_t len; + char16_t *clone; + + NS_StringGetData(s, &ptr); + if (ptr == nullptr || *ptr != '\0') + { + NS_ERROR("unexpected result"); + return false; + } + + NS_StringSetData(s, kUnicodeData, UINT32_MAX); + len = NS_StringGetData(s, &ptr); + if (len != sizeof(kUnicodeData)/2 - 1) + { + NS_ERROR("unexpected result"); + return false; + } + if (ptr == nullptr || memcmp(ptr, kUnicodeData, sizeof(kUnicodeData)) != 0) + { + NS_ERROR("unexpected result"); + return false; + } + + clone = NS_StringCloneData(s); + if (ptr == nullptr || memcmp(ptr, kUnicodeData, sizeof(kUnicodeData)) != 0) + { + NS_ERROR("unexpected result"); + return false; + } + free(clone); + + nsStringContainer temp; + NS_StringContainerInit(temp); + NS_StringCopy(temp, s); + + len = NS_StringGetData(temp, &ptr); + if (len != sizeof(kUnicodeData)/2 - 1) + { + NS_ERROR("unexpected result"); + return false; + } + if (ptr == nullptr || memcmp(ptr, kUnicodeData, sizeof(kUnicodeData)) != 0) + { + NS_ERROR("unexpected result"); + return false; + } + + NS_StringContainerFinish(temp); + + NS_StringContainerFinish(s); + + return true; + } + +static bool test_convert() + { + nsStringContainer s; + NS_StringContainerInit(s); + NS_StringSetData(s, kUnicodeData, sizeof(kUnicodeData)/2 - 1); + + nsCStringContainer temp; + NS_CStringContainerInit(temp); + + const char *data; + + NS_UTF16ToCString(s, NS_CSTRING_ENCODING_ASCII, temp); + NS_CStringGetData(temp, &data); + if (strcmp(data, kAsciiData) != 0) + return false; + + NS_UTF16ToCString(s, NS_CSTRING_ENCODING_UTF8, temp); + NS_CStringGetData(temp, &data); + if (strcmp(data, kAsciiData) != 0) + return false; + + NS_CStringContainerFinish(temp); + + NS_StringContainerFinish(s); + return true; + } + +static bool test_append() + { + nsCStringContainer s; + NS_CStringContainerInit(s); + + NS_CStringSetData(s, "foo"); + NS_CStringAppendData(s, "bar"); + + NS_CStringContainerFinish(s); + return true; + } + +// Replace all occurrences of |matchVal| with |newVal| +static void ReplaceSubstring( nsACString& str, + const nsACString& matchVal, + const nsACString& newVal ) + { + const char* sp; + const char* mp; + const char* np; + uint32_t sl = NS_CStringGetData(str, &sp); + uint32_t ml = NS_CStringGetData(matchVal, &mp); + uint32_t nl = NS_CStringGetData(newVal, &np); + + for (const char* iter = sp; iter <= sp + sl - ml; ++iter) + { + if (memcmp(iter, mp, ml) == 0) + { + uint32_t offset = iter - sp; + + NS_CStringSetDataRange(str, offset, ml, np, nl); + + sl = NS_CStringGetData(str, &sp); + + iter = sp + offset + nl - 1; + } + } + } + +static bool test_replace_driver(const char *strVal, + const char *matchVal, + const char *newVal, + const char *finalVal) + { + nsCStringContainer a; + NS_CStringContainerInit(a); + NS_CStringSetData(a, strVal); + + nsCStringContainer b; + NS_CStringContainerInit(b); + NS_CStringSetData(b, matchVal); + + nsCStringContainer c; + NS_CStringContainerInit(c); + NS_CStringSetData(c, newVal); + + ReplaceSubstring(a, b, c); + + const char *data; + NS_CStringGetData(a, &data); + if (strcmp(data, finalVal) != 0) + return false; + + NS_CStringContainerFinish(c); + NS_CStringContainerFinish(b); + NS_CStringContainerFinish(a); + return true; + } + +static bool test_replace() + { + bool rv; + + rv = test_replace_driver("hello world, hello again!", + "hello", + "goodbye", + "goodbye world, goodbye again!"); + if (!rv) + return rv; + + rv = test_replace_driver("foofoofoofoo!", + "foo", + "bar", + "barbarbarbar!"); + if (!rv) + return rv; + + rv = test_replace_driver("foo bar systems", + "xyz", + "crazy", + "foo bar systems"); + if (!rv) + return rv; + + rv = test_replace_driver("oh", + "xyz", + "crazy", + "oh"); + if (!rv) + return rv; + + return true; + } + +static const char* kWhitespace="\f\t\r\n "; + +static void +CompressWhitespace(nsACString &str) + { + const char *p; + int32_t i, len = (int32_t) NS_CStringGetData(str, &p); + + // trim leading whitespace + + for (i=0; i0) + { + NS_CStringCutData(str, 0, i); + len = (int32_t) NS_CStringGetData(str, &p); + } + + // trim trailing whitespace + + for (i=len-1; i>=0; --i) + { + if (!strchr(kWhitespace, (char) p[i])) + break; + } + + if (++i < len) + NS_CStringCutData(str, i, len - i); + } + +static bool test_compress_ws() + { + nsCStringContainer s; + NS_CStringContainerInit(s); + NS_CStringSetData(s, " \thello world\r \n"); + CompressWhitespace(s); + const char *d; + NS_CStringGetData(s, &d); + bool rv = !strcmp(d, "hello world"); + if (!rv) + printf("=> \"%s\"\n", d); + NS_CStringContainerFinish(s); + return rv; + } + +static bool test_depend() + { + static const char kData[] = "hello world"; + + nsCStringContainer s; + NS_ENSURE_SUCCESS( + NS_CStringContainerInit2(s, kData, sizeof(kData)-1, + NS_CSTRING_CONTAINER_INIT_DEPEND), + false); + + const char *sd; + NS_CStringGetData(s, &sd); + + bool rv = (sd == kData); + NS_CStringContainerFinish(s); + return rv; + } + +static bool test_depend_sub() + { + static const char kData[] = "hello world"; + + nsCStringContainer s; + NS_ENSURE_SUCCESS( + NS_CStringContainerInit2(s, kData, sizeof(kData)-1, + NS_CSTRING_CONTAINER_INIT_DEPEND | + NS_CSTRING_CONTAINER_INIT_SUBSTRING), + false); + + bool terminated; + const char *sd; + uint32_t len = NS_CStringGetData(s, &sd, &terminated); + + bool rv = (sd == kData && len == sizeof(kData)-1 && !terminated); + NS_CStringContainerFinish(s); + return rv; + } + +static bool test_adopt() + { + static const char kData[] = "hello world"; + + char *data = (char *) nsMemory::Clone(kData, sizeof(kData)); + if (!data) + return false; + + nsCStringContainer s; + NS_ENSURE_SUCCESS( + NS_CStringContainerInit2(s, data, UINT32_MAX, + NS_CSTRING_CONTAINER_INIT_ADOPT), + false); // leaks data on failure *shrug* + + const char *sd; + NS_CStringGetData(s, &sd); + + bool rv = (sd == data); + NS_CStringContainerFinish(s); + return rv; + } + +static bool test_adopt_sub() + { + static const char kData[] = "hello world"; + + char *data = (char *) nsMemory::Clone(kData, sizeof(kData)-1); + if (!data) + return false; + + nsCStringContainer s; + NS_ENSURE_SUCCESS( + NS_CStringContainerInit2(s, data, sizeof(kData)-1, + NS_CSTRING_CONTAINER_INIT_ADOPT | + NS_CSTRING_CONTAINER_INIT_SUBSTRING), + false); // leaks data on failure *shrug* + + bool terminated; + const char *sd; + uint32_t len = NS_CStringGetData(s, &sd, &terminated); + + bool rv = (sd == data && len == sizeof(kData)-1 && !terminated); + NS_CStringContainerFinish(s); + return rv; + } + +static bool test_mutation() + { + nsCStringContainer s; + NS_CStringContainerInit(s); + + const char kText[] = "Every good boy does fine."; + + char *buf; + uint32_t len = NS_CStringGetMutableData(s, sizeof(kText) - 1, &buf); + if (!buf || len != sizeof(kText) - 1) + return false; + memcpy(buf, kText, sizeof(kText)); + + const char *data; + NS_CStringGetData(s, &data); + if (strcmp(data, kText) != 0) + return false; + + uint32_t newLen = len + 1; + len = NS_CStringGetMutableData(s, newLen, &buf); + if (!buf || len != newLen) + return false; + + buf[len - 1] = '.'; + + NS_CStringGetData(s, &data); + if (strncmp(data, kText, len - 1) != 0 || data[len - 1] != '.') + return false; + + NS_CStringContainerFinish(s); + return true; + } + +static bool test_ascii() +{ + nsCString testCString; + testCString.AppendASCII(kAsciiData); + if (!testCString.EqualsLiteral(kAsciiData)) + return false; + + testCString.AssignASCII(kAsciiData); + if (!testCString.LowerCaseEqualsLiteral("hello world")) + return false; + + nsString testString; + testString.AppendASCII(kAsciiData); + if (!testString.EqualsLiteral(kAsciiData)) + return false; + + testString.AssignASCII(kAsciiData); + if (!testString.LowerCaseEqualsLiteral("hello world")) + return false; + + return true; +} + +static bool test_chars() +{ + nsCString testCString(kAsciiData); + if (testCString.First() != 'H') + return false; + if (testCString.Last() != 'd') + return false; + testCString.SetCharAt('u', 8); + if (!testCString.EqualsASCII("Hello Would")) + return false; + + nsString testString(kUnicodeData); + if (testString.First() != 'H') + return false; + if (testString.Last() != 'd') + return false; + testString.SetCharAt('u', 8); + if (!testString.EqualsASCII("Hello Would")) + return false; + + return true; +} + +static bool test_stripchars() +{ + nsCString test(kAsciiData); + test.StripChars("ld"); + if (!test.EqualsLiteral("Heo Wor")) + return false; + + test.Assign(kAsciiData); + test.StripWhitespace(); + if (!test.EqualsLiteral("HelloWorld")) + return false; + + return true; +} + +static bool test_trim() +{ + static const char kWS[] = "\n\t\r "; + static const char kTestString[] = " \n\tTesting...\n\r"; + + nsCString test1(kTestString); + nsCString test2(kTestString); + nsCString test3(kTestString); + + test1.Trim(kWS); + test2.Trim(kWS, true, false); + test3.Trim(kWS, false, true); + + if (!test1.EqualsLiteral("Testing...")) + return false; + + if (!test2.EqualsLiteral("Testing...\n\r")) + return false; + + if (!test3.EqualsLiteral(" \n\tTesting...")) + return false; + + return true; +} + +static bool test_find() +{ + nsString uni(kUnicodeData); + + static const char kHello[] = "Hello"; + static const char khello[] = "hello"; + static const char kBye[] = "Bye!"; + + int32_t found; + + found = uni.Find(kHello); + if (found != 0) + return false; + + found = uni.Find(khello, false); + if (found != -1) + return false; + + found = uni.Find(khello, true); + if (found != 0) + return false; + + found = uni.Find(kBye); + if (found != -1) + return false; + + found = uni.Find(NS_LITERAL_STRING("World")); + if (found != 6) + return false; + + found = uni.Find(uni); + if (found != 0) + return false; + + return true; +} + +static bool test_compressws() +{ + nsString check(NS_LITERAL_STRING(" \tTesting \n\t1\n 2 3\n ")); + CompressWhitespace(check); + return check.EqualsLiteral("Testing 1 2 3"); +} + +static bool test_comparisons() +{ + bool result; + + // nsString + + NS_NAMED_LITERAL_STRING(shortString1, "Foo"); + NS_NAMED_LITERAL_STRING(shortString2, "Bar"); + NS_NAMED_LITERAL_STRING(shortString3, "Bar"); + NS_NAMED_LITERAL_STRING(shortString4, "bar"); + NS_NAMED_LITERAL_STRING(longString, "FooBar"); + + // == + + result = (shortString1 == shortString2); + if (result) + return false; + + result = (shortString2 == shortString3); + if (!result) + return false; + + result = (shortString3 == shortString4); + if (result) + return false; + + result = (shortString1 == longString); + if (result) + return false; + + result = (longString == shortString1); + if (result) + return false; + + // != + + result = (shortString1 != shortString2); + if (!result) + return false; + + result = (shortString2 != shortString3); + if (result) + return false; + + result = (shortString3 != shortString4); + if (!result) + return false; + + result = (shortString1 != longString); + if (!result) + return false; + + result = (longString != shortString1); + if (!result) + return false; + + // < + + result = (shortString1 < shortString2); + if (result) + return false; + + result = (shortString2 < shortString1); + if (!result) + return false; + + result = (shortString1 < longString); + if (!result) + return false; + + result = (longString < shortString1); + if (result) + return false; + + result = (shortString2 < shortString3); + if (result) + return false; + + result = (shortString3 < shortString4); + if (!result) + return false; + + result = (shortString4 < shortString3); + if (result) + return false; + + // <= + + result = (shortString1 <= shortString2); + if (result) + return false; + + result = (shortString2 <= shortString1); + if (!result) + return false; + + result = (shortString1 <= longString); + if (!result) + return false; + + result = (longString <= shortString1); + if (result) + return false; + + result = (shortString2 <= shortString3); + if (!result) + return false; + + result = (shortString3 <= shortString4); + if (!result) + return false; + + result = (shortString4 <= shortString3); + if (result) + return false; + + // > + + result = (shortString1 > shortString2); + if (!result) + return false; + + result = (shortString2 > shortString1); + if (result) + return false; + + result = (shortString1 > longString); + if (result) + return false; + + result = (longString > shortString1); + if (!result) + return false; + + result = (shortString2 > shortString3); + if (result) + return false; + + result = (shortString3 > shortString4); + if (result) + return false; + + result = (shortString4 > shortString3); + if (!result) + return false; + + // >= + + result = (shortString1 >= shortString2); + if (!result) + return false; + + result = (shortString2 >= shortString1); + if (result) + return false; + + result = (shortString1 >= longString); + if (result) + return false; + + result = (longString >= shortString1); + if (!result) + return false; + + result = (shortString2 >= shortString3); + if (!result) + return false; + + result = (shortString3 >= shortString4); + if (result) + return false; + + result = (shortString4 >= shortString3); + if (!result) + return false; + + // nsCString + + NS_NAMED_LITERAL_CSTRING(shortCString1, "Foo"); + NS_NAMED_LITERAL_CSTRING(shortCString2, "Bar"); + NS_NAMED_LITERAL_CSTRING(shortCString3, "Bar"); + NS_NAMED_LITERAL_CSTRING(shortCString4, "bar"); + NS_NAMED_LITERAL_CSTRING(longCString, "FooBar"); + + // == + + result = (shortCString1 == shortCString2); + if (result) + return false; + + result = (shortCString2 == shortCString3); + if (!result) + return false; + + result = (shortCString3 == shortCString4); + if (result) + return false; + + result = (shortCString1 == longCString); + if (result) + return false; + + result = (longCString == shortCString1); + if (result) + return false; + + // != + + result = (shortCString1 != shortCString2); + if (!result) + return false; + + result = (shortCString2 != shortCString3); + if (result) + return false; + + result = (shortCString3 != shortCString4); + if (!result) + return false; + + result = (shortCString1 != longCString); + if (!result) + return false; + + result = (longCString != shortCString1); + if (!result) + return false; + + // < + + result = (shortCString1 < shortCString2); + if (result) + return false; + + result = (shortCString2 < shortCString1); + if (!result) + return false; + + result = (shortCString1 < longCString); + if (!result) + return false; + + result = (longCString < shortCString1); + if (result) + return false; + + result = (shortCString2 < shortCString3); + if (result) + return false; + + result = (shortCString3 < shortCString4); + if (!result) + return false; + + result = (shortCString4 < shortCString3); + if (result) + return false; + + // <= + + result = (shortCString1 <= shortCString2); + if (result) + return false; + + result = (shortCString2 <= shortCString1); + if (!result) + return false; + + result = (shortCString1 <= longCString); + if (!result) + return false; + + result = (longCString <= shortCString1); + if (result) + return false; + + result = (shortCString2 <= shortCString3); + if (!result) + return false; + + result = (shortCString3 <= shortCString4); + if (!result) + return false; + + result = (shortCString4 <= shortCString3); + if (result) + return false; + + // > + + result = (shortCString1 > shortCString2); + if (!result) + return false; + + result = (shortCString2 > shortCString1); + if (result) + return false; + + result = (shortCString1 > longCString); + if (result) + return false; + + result = (longCString > shortCString1); + if (!result) + return false; + + result = (shortCString2 > shortCString3); + if (result) + return false; + + result = (shortCString3 > shortCString4); + if (result) + return false; + + result = (shortCString4 > shortCString3); + if (!result) + return false; + + // >= + + result = (shortCString1 >= shortCString2); + if (!result) + return false; + + result = (shortCString2 >= shortCString1); + if (result) + return false; + + result = (shortCString1 >= longCString); + if (result) + return false; + + result = (longCString >= shortCString1); + if (!result) + return false; + + result = (shortCString2 >= shortCString3); + if (!result) + return false; + + result = (shortCString3 >= shortCString4); + if (result) + return false; + + result = (shortCString4 >= shortCString3); + if (!result) + return false; + + return true; +} + +static bool test_parse_string_helper(const char* str, char separator, int len, + const char* s1, const char* s2) +{ + nsCString data(str); + nsTArray results; + if (!ParseString(data, separator, results)) + return false; + if (int(results.Length()) != len) + return false; + const char* strings[] = { s1, s2 }; + for (int i = 0; i < len; ++i) { + if (!results[i].Equals(strings[i])) + return false; + } + return true; +} + +static bool test_parse_string_helper0(const char* str, char separator) +{ + return test_parse_string_helper(str, separator, 0, nullptr, nullptr); +} + +static bool test_parse_string_helper1(const char* str, char separator, const char* s1) +{ + return test_parse_string_helper(str, separator, 1, s1, nullptr); +} + +static bool test_parse_string_helper2(const char* str, char separator, const char* s1, const char* s2) +{ + return test_parse_string_helper(str, separator, 2, s1, s2); +} + +static bool test_parse_string() +{ + return 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"); +} + +//---- + +typedef bool (*TestFunc)(); + +static const struct Test + { + const char* name; + TestFunc func; + } +tests[] = + { + { "test_basic_1", test_basic_1 }, + { "test_basic_2", test_basic_2 }, + { "test_convert", test_convert }, + { "test_append", test_append }, + { "test_replace", test_replace }, + { "test_compress_ws", test_compress_ws }, + { "test_depend", test_depend }, + { "test_depend_sub", test_depend_sub }, + { "test_adopt", test_adopt }, + { "test_adopt_sub", test_adopt_sub }, + { "test_mutation", test_mutation }, + { "test_ascii", test_ascii }, + { "test_chars", test_chars }, + { "test_stripchars", test_stripchars }, + { "test_trim", test_trim }, + { "test_find", test_find }, + { "test_compressws", test_compressws }, + { "test_comparisons", test_comparisons }, + { "test_parse_string", test_parse_string }, + { nullptr, nullptr } + }; + +//---- + +int main(int argc, char **argv) + { + int count = 1; + if (argc > 1) + count = atoi(argv[1]); + + while (count--) + { + for (const Test* t = tests; t->name != nullptr; ++t) + { + printf("%25s : %s\n", t->name, t->func() ? "SUCCESS" : "FAILURE"); + } + } + + return 0; + } diff --git a/xpcom/tests/external/moz.build b/xpcom/tests/external/moz.build new file mode 100644 index 000000000..d64560b58 --- /dev/null +++ b/xpcom/tests/external/moz.build @@ -0,0 +1,9 @@ +# -*- 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/. + +GeckoSimplePrograms([ + 'TestMinStringAPI', +]) diff --git a/xpcom/tests/gtest/Helpers.cpp b/xpcom/tests/gtest/Helpers.cpp new file mode 100644 index 000000000..e06ef901b --- /dev/null +++ b/xpcom/tests/gtest/Helpers.cpp @@ -0,0 +1,133 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 "nsIOutputStream.h" +#include "nsStreamUtils.h" +#include "nsTArray.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_TRUE(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_TRUE(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() +{ +} + +NS_IMETHODIMP +OutputStreamCallback::OnOutputStreamReady(nsIAsyncOutputStream* aStream) +{ + mCalled = true; + return NS_OK; +} + +NS_IMPL_ISUPPORTS(InputStreamCallback, nsIInputStreamCallback); + +InputStreamCallback::InputStreamCallback() + : mCalled(false) +{ +} + +InputStreamCallback::~InputStreamCallback() +{ +} + +NS_IMETHODIMP +InputStreamCallback::OnInputStreamReady(nsIAsyncInputStream* aStream) +{ + mCalled = true; + return NS_OK; +} + +} // namespace testing diff --git a/xpcom/tests/gtest/Helpers.h b/xpcom/tests/gtest/Helpers.h new file mode 100644 index 000000000..9cee1b825 --- /dev/null +++ b/xpcom/tests/gtest/Helpers.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 __Helpers_h +#define __Helpers_h + +#include "nsIAsyncInputStream.h" +#include "nsIAsyncOutputStream.h" +#include "nsString.h" +#include "nsTArrayForwardDeclare.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 +}; + +} // namespace testing + +#endif // __Helpers_h diff --git a/xpcom/tests/gtest/TestAllocReplacement.cpp b/xpcom/tests/gtest/TestAllocReplacement.cpp new file mode 100644 index 000000000..7bcf3d4ad --- /dev/null +++ b/xpcom/tests/gtest/TestAllocReplacement.cpp @@ -0,0 +1,175 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. 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/Function.h" +#include "mozilla/mozalloc.h" +#include "mozilla/ScopeExit.h" +#include "nsCOMPtr.h" +#include "nsIMemoryReporter.h" +#include "nsServiceManagerUtils.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's statistics tracking: we measure +// the size of the jemalloc heap using nsIMemoryReporterManager, +// allocate a chunk of memory with whatever function is supposed to be +// hooked, and then ask for the size of the jemalloc heap again. If the +// function has been hooked correctly, then the heap size should be +// different between the two measurements. We can also check the +// hooking of |free| and similar functions: once we free() the returned +// pointer, we can measure the jemalloc heap size again, expecting it to +// be identical to the size prior to the allocation. +// +// If we're not using jemalloc, then nsIMemoryReporterManager will +// simply report an error, and we will ignore the entire test. +// +// This strategy is not perfect: it relies on GTests being +// single-threaded, which they are, and no other threads doing +// allocation during the test, which is uncertain, as XPCOM has started +// up during gtests, and who knows what might be going on behind the +// scenes. This latter assumption, however, does not seem to be a +// problem in practice. +#if defined(MOZ_MEMORY) +#define ALLOCATION_ASSERT(b) ASSERT_TRUE((b)) +#else +#define ALLOCATION_ASSERT(b) (void)(b) +#endif + +#define ASSERT_ALLOCATION_HAPPENED(lambda) \ + ALLOCATION_ASSERT(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; + +// We declare this function MOZ_NEVER_INLINE to work around optimizing +// compilers. If we permitted inlining here, then the compiler might +// inline both this function and the calls to the function pointers we +// pass in, giving something like: +// +// void* p = malloc(...); +// ...do nothing with p except check nullptr-ness... +// free(p); +// +// and the optimizer can delete the calls to malloc and free entirely, +// which would make checking that the jemalloc heap had never changed +// difficult. +static MOZ_NEVER_INLINE bool +ValidateHookedAllocation(void* (*aAllocator)(void), + void (*aFreeFunction)(void*)) +{ + nsCOMPtr manager = + do_GetService("@mozilla.org/memory-reporter-manager;1"); + + int64_t before = 0; + nsresult rv = manager->GetHeapAllocated(&before); + if (NS_FAILED(rv)) { + return false; + } + + { + void* p = aAllocator(); + + if (!p) { + return false; + } + + int64_t after = 0; + rv = manager->GetHeapAllocated(&after); + + // Regardless of whether that call succeeded or failed, we are done with + // the allocated buffer now. + aFreeFunction(p); + + if (NS_FAILED(rv)) { + return false; + } + + // Verify that our heap stats have changed. + if ((before + int64_t(kAllocAmount)) != after) { + return false; + } + } + + // Verify that freeing the allocated pointer resets our heap to what it + // was before. + int64_t after = 0; + rv = manager->GetHeapAllocated(&after); + if (NS_FAILED(rv)) { + return false; + } + + return before == after; +} + +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) \ + ALLOCATION_ASSERT(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/TestAtoms.cpp b/xpcom/tests/gtest/TestAtoms.cpp new file mode 100644 index 000000000..da3cc5ea2 --- /dev/null +++ b/xpcom/tests/gtest/TestAtoms.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/. */ + +#include "mozilla/ArrayUtils.h" + +#include "nsIAtom.h" +#include "nsString.h" +#include "UTFStrings.h" +#include "nsIServiceManager.h" +#include "nsStaticAtom.h" + +#include "gtest/gtest.h" + +using namespace mozilla; + +namespace TestAtoms { + +TEST(Atoms, Basic) +{ + for (unsigned int i = 0; i < ArrayLength(ValidStrings); ++i) { + nsDependentString str16(ValidStrings[i].m16); + nsDependentCString str8(ValidStrings[i].m8); + + nsCOMPtr 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) { + nsCOMPtr atom16 = NS_Atomize(ValidStrings[i].m16); + nsCOMPtr atom8 = NS_Atomize(ValidStrings[i].m8); + EXPECT_EQ(atom16, atom8); + } +} + +TEST(Atoms, BufferSharing) +{ + nsString unique; + unique.AssignLiteral("this is a unique string !@#$"); + + nsCOMPtr atom = NS_Atomize(unique); + + EXPECT_EQ(unique.get(), atom->GetUTF16String()); +} + +TEST(Atoms, Null) +{ + nsAutoString str(NS_LITERAL_STRING("string with a \0 char")); + nsDependentString strCut(str.get()); + + EXPECT_FALSE(str.Equals(strCut)); + + nsCOMPtr atomCut = NS_Atomize(strCut); + nsCOMPtr 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(); + + { + nsCOMPtr atom16 = NS_Atomize(Invalid16Strings[i].m16); + EXPECT_TRUE(atom16->Equals(nsDependentString(Invalid16Strings[i].m16))); + } + + EXPECT_EQ(count, NS_GetNumberOfAtoms()); + } + + for (unsigned int i = 0; i < ArrayLength(Invalid8Strings); ++i) { + nsrefcnt count = NS_GetNumberOfAtoms(); + + { + nsCOMPtr atom8 = NS_Atomize(Invalid8Strings[i].m8); + nsCOMPtr atom16 = NS_Atomize(Invalid8Strings[i].m16); + EXPECT_EQ(atom16, atom8); + EXPECT_TRUE(atom16->Equals(nsDependentString(Invalid8Strings[i].m16))); + } + + EXPECT_EQ(count, NS_GetNumberOfAtoms()); + } + +// Don't run this test in debug builds as that intentionally asserts. +#ifndef DEBUG + nsCOMPtr emptyAtom = NS_Atomize(""); + + for (unsigned int i = 0; i < ArrayLength(Malformed8Strings); ++i) { + nsrefcnt count = NS_GetNumberOfAtoms(); + + nsCOMPtr atom8 = NS_Atomize(Malformed8Strings[i]); + EXPECT_EQ(atom8, emptyAtom); + 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?!" + +bool +isStaticAtom(nsIAtom* 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(); + + nsCOMPtr thirdDynamic = NS_Atomize(THIRD_ATOM_STR); + + EXPECT_FALSE(isStaticAtom(thirdDynamic)); + + EXPECT_TRUE(thirdDynamic); + EXPECT_EQ(NS_GetNumberOfAtoms(), count + 1); +} + +} diff --git a/xpcom/tests/gtest/TestAutoPtr.cpp b/xpcom/tests/gtest/TestAutoPtr.cpp new file mode 100644 index 000000000..362ba55c5 --- /dev/null +++ b/xpcom/tests/gtest/TestAutoPtr.cpp @@ -0,0 +1,220 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +// 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 "nsAutoPtr.h" +#include "gtest/gtest.h" + +class TestObjectBaseA { + public: + // Virtual dtor for deleting through base class pointer + virtual ~TestObjectBaseA() { } + void MemberFunction(int, int*, int&) + { + } + virtual void VirtualMemberFunction(int, int*, int&) { }; + virtual void VirtualConstMemberFunction(int, int*, int&) const { }; + int fooA; +}; + +class TestObjectBaseB { + public: + // Virtual dtor for deleting through base class pointer + virtual ~TestObjectBaseB() { } + int fooB; +}; + +class TestObject : public TestObjectBaseA, public TestObjectBaseB { + public: + TestObject() + { + } + + // Virtual dtor for deleting through base class pointer + virtual ~TestObject() + { + destructed++; + } + + virtual void VirtualMemberFunction(int, int*, int&) override + { + } + virtual void VirtualConstMemberFunction(int, int*, int&) const override + { + } + + static int destructed; + static const void* last_ptr; +}; + +int TestObject::destructed = 0; +const void* TestObject::last_ptr = nullptr; + +static void CreateTestObject(TestObject **aResult) +{ + *aResult = new TestObject(); +} + +static void DoSomethingWithTestObject(TestObject *aIn) +{ + TestObject::last_ptr = static_cast(aIn); +} + +static void DoSomethingWithConstTestObject(const TestObject *aIn) +{ + TestObject::last_ptr = static_cast(aIn); +} + +static void DoSomethingWithTestObjectBaseB(TestObjectBaseB *aIn) +{ + TestObject::last_ptr = static_cast(aIn); +} + +static void DoSomethingWithConstTestObjectBaseB(const TestObjectBaseB *aIn) +{ + TestObject::last_ptr = static_cast(aIn); +} + +TEST(AutoPtr, Assignment) +{ + TestObject::destructed = 0; + + { + nsAutoPtr pobj( new TestObject() ); + } + + ASSERT_EQ(1, TestObject::destructed); + + { + nsAutoPtr pobj( new TestObject() ); + pobj = new TestObject(); + ASSERT_EQ(2, TestObject::destructed); + } + + ASSERT_EQ(3, TestObject::destructed); +} + +TEST(AutoPtr, getter_Transfers) +{ + TestObject::destructed = 0; + + { + nsAutoPtr ptr; + CreateTestObject(getter_Transfers(ptr)); + } + + ASSERT_EQ(1, TestObject::destructed); +} + +TEST(AutoPtr, Casting) +{ + // This comparison is always false, as it should be. The extra parens + // suppress a -Wunreachable-code warning about printf being unreachable. + ASSERT_NE(((void*)(TestObject*)0x1000), + ((void*)(TestObjectBaseB*)(TestObject*)0x1000)); + + { + nsAutoPtr p1(new TestObject()); + TestObjectBaseB *p2 = p1; + ASSERT_NE(static_cast(p1), + static_cast(p2)); + ASSERT_EQ(p1, p2); + ASSERT_FALSE(p1 != p2); + ASSERT_EQ(p2, p1); + ASSERT_FALSE(p2 != p1); + } + + { + TestObject *p1 = new TestObject(); + nsAutoPtr p2(p1); + ASSERT_EQ(p1, p2); + ASSERT_FALSE(p1 != p2); + ASSERT_EQ(p2, p1); + ASSERT_FALSE(p2 != p1); + } +} + +TEST(AutoPtr, Forget) +{ + TestObject::destructed = 0; + + { + nsAutoPtr pobj( new TestObject() ); + nsAutoPtr pobj2( pobj.forget() ); + ASSERT_EQ(0, TestObject::destructed); + } + + ASSERT_EQ(1, TestObject::destructed); +} + +TEST(AutoPtr, Construction) +{ + TestObject::destructed = 0; + + { + nsAutoPtr pobj(new TestObject()); + } + + ASSERT_EQ(1, TestObject::destructed); +} + +TEST(AutoPtr, ImplicitConversion) +{ + // This test is basically successful if it builds. We add a few assertions + // to make gtest happy. + TestObject::destructed = 0; + + { + nsAutoPtr pobj(new TestObject()); + DoSomethingWithTestObject(pobj); + DoSomethingWithConstTestObject(pobj); + } + + ASSERT_EQ(1, TestObject::destructed); + + { + nsAutoPtr pobj(new TestObject()); + DoSomethingWithTestObjectBaseB(pobj); + DoSomethingWithConstTestObjectBaseB(pobj); + } + + ASSERT_EQ(2, TestObject::destructed); + + { + const nsAutoPtr pobj(new TestObject()); + DoSomethingWithTestObject(pobj); + DoSomethingWithConstTestObject(pobj); + } + + ASSERT_EQ(3, TestObject::destructed); + + { + const nsAutoPtr pobj(new TestObject()); + DoSomethingWithTestObjectBaseB(pobj); + DoSomethingWithConstTestObjectBaseB(pobj); + } + + ASSERT_EQ(4, TestObject::destructed); +} + +TEST(AutoPtr, ArrowOperator) +{ + // This test is basically successful if it builds. We add a few assertions + // to make gtest happy. + TestObject::destructed = 0; + + { + int test = 1; + void (TestObjectBaseA::*fPtr)( int, int*, int& ) = &TestObjectBaseA::MemberFunction; + void (TestObjectBaseA::*fVPtr)( int, int*, int& ) = &TestObjectBaseA::VirtualMemberFunction; + void (TestObjectBaseA::*fVCPtr)( int, int*, int& ) const = &TestObjectBaseA::VirtualConstMemberFunction; + nsAutoPtr pobj(new TestObject()); + (pobj->*fPtr)(test, &test, test); + (pobj->*fVPtr)(test, &test, test); + (pobj->*fVCPtr)(test, &test, test); + } + + ASSERT_EQ(1, TestObject::destructed); +} diff --git a/xpcom/tests/gtest/TestAutoRef.cpp b/xpcom/tests/gtest/TestAutoRef.cpp new file mode 100644 index 000000000..49042e8fb --- /dev/null +++ b/xpcom/tests/gtest/TestAutoRef.cpp @@ -0,0 +1,56 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +// 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 "nsAutoRef.h" +#include "gtest/gtest.h" + +struct TestObjectA { +public: + TestObjectA() : mRefCnt(0) { + } + + ~TestObjectA() { + EXPECT_EQ(mRefCnt, 0); + } + +public: + int mRefCnt; +}; + +template <> +class nsAutoRefTraits : public nsPointerRefTraits +{ +public: + static int mTotalRefsCnt; + + static void Release(TestObjectA *ptr) { + ptr->mRefCnt--; + if (ptr->mRefCnt == 0) { + delete ptr; + } + } + + static void AddRef(TestObjectA *ptr) { + ptr->mRefCnt++; + } +}; + +int nsAutoRefTraits::mTotalRefsCnt = 0; + +TEST(AutoRef, Assignment) +{ + { + nsCountedRef a(new TestObjectA()); + ASSERT_EQ(a->mRefCnt, 1); + + nsCountedRef b; + ASSERT_EQ(b.get(), nullptr); + + a.swap(b); + ASSERT_EQ(b->mRefCnt, 1); + ASSERT_EQ(a.get(), nullptr); + } +} diff --git a/xpcom/tests/gtest/TestBase64.cpp b/xpcom/tests/gtest/TestBase64.cpp new file mode 100644 index 000000000..d8105619b --- /dev/null +++ b/xpcom/tests/gtest/TestBase64.cpp @@ -0,0 +1,291 @@ +/* -*- 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 "nsIScriptableBase64Encoder.h" +#include "nsIInputStream.h" +#include "nsString.h" + +#include "gtest/gtest.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, + "QUxMIFRIRVNFIFdPUkxEUyBBUkUgWU9VUlMgRVhDRVBUIEVVUk9QQS4gQVRURU1QVCBOTyBMQU5ESU5HIFRIRVJFLg==" + ), + // Terminator + Test( + nullptr, + nullptr + ) + }; + +class FakeInputStream final : public nsIInputStream +{ + ~FakeInputStream() {} + +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::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, Test) +{ + 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_TRUE(NS_SUCCEEDED(rv)); + + stream->Reset(); + + rv = encoder->EncodeToCString(stream, 0, string); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + + stream->CheckTest(wideString); + stream->CheckTest(string); + } while (stream->NextTest()); +} diff --git a/xpcom/tests/gtest/TestCOMArray.cpp b/xpcom/tests/gtest/TestCOMArray.cpp new file mode 100644 index 000000000..6703bf9d1 --- /dev/null +++ b/xpcom/tests/gtest/TestCOMArray.cpp @@ -0,0 +1,286 @@ +/* -*- 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 "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); +} diff --git a/xpcom/tests/gtest/TestCOMPtr.cpp b/xpcom/tests/gtest/TestCOMPtr.cpp new file mode 100644 index 000000000..e17331c65 --- /dev/null +++ b/xpcom/tests/gtest/TestCOMPtr.cpp @@ -0,0 +1,466 @@ +/* -*- 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/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(); + NS_IMETHOD_(MozExternalRefCountType) Release(); + NS_IMETHOD QueryInterface( const nsIID&, void** ); + + 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; +} + +nsresult +CreateIFoo( void** result ) +// a typical factory function (that calls AddRef) +{ + IFoo* foop = new IFoo; + + foop->AddRef(); + *result = foop; + + return NS_OK; +} + +void +set_a_IFoo( nsCOMPtr* result ) +{ + nsCOMPtr foop( do_QueryInterface(new IFoo) ); + *result = foop; +} + +nsCOMPtr +return_a_IFoo() +{ + nsCOMPtr foop( do_QueryInterface(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(); + virtual ~IBar(); + + NS_IMETHOD QueryInterface( const nsIID&, void** ); + + 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() +{ +} + +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; +} + + + +nsresult +CreateIBar( void** result ) + // a typical factory function (that calls AddRef) +{ + IBar* barp = new IBar; + + barp->AddRef(); + *result = barp; + + return NS_OK; +} + +void +AnIFooPtrPtrContext( IFoo** ) +{ +} + +void +AVoidPtrPtrContext( void** ) +{ +} + +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_TRUE(NS_SUCCEEDED(rv)); + ASSERT_TRUE(barP); + + IFoo* fooP = 0; + rv = barP->QueryInterface(NS_GET_IID(IFoo), reinterpret_cast(&fooP)); + ASSERT_TRUE(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_TRUE(NS_SUCCEEDED(rv)); + ASSERT_TRUE(barP); + + nsCOMPtr fooP( do_QueryInterface(barP, &rv) ); + ASSERT_TRUE(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(new IFoo) ); + ASSERT_EQ(foop->refcount_, (unsigned int)1); + ASSERT_EQ(IFoo::total_constructions_, 1); + ASSERT_EQ(IFoo::total_destructions_, 0); + + foop = do_QueryInterface(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(new IBar) ); + mozilla::Unused << foop; + } + + ASSERT_EQ(IBar::total_destructions_, 1); +} + +void Comparison() +{ + IFoo::total_constructions_ = 0; + IFoo::total_destructions_ = 0; + + { + nsCOMPtr foo1p( do_QueryInterface(new IFoo) ); + nsCOMPtr foo2p( do_QueryInterface(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); +} + +void DontAddRef() +{ + { + IFoo* raw_foo1p = new IFoo; + raw_foo1p->AddRef(); + + IFoo* 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(new IFoo) ); + ASSERT_TRUE(fooP); + + ASSERT_EQ(IFoo::total_constructions_, 5); + ASSERT_EQ(IFoo::total_destructions_, 4); + + nsCOMPtr fooP2( fooP.forget() ); + 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(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(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(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 000000000..b7513e2ae --- /dev/null +++ b/xpcom/tests/gtest/TestCOMPtrEq.cpp @@ -0,0 +1,79 @@ +/* -*- 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 000000000..9fa731404 --- /dev/null +++ b/xpcom/tests/gtest/TestCRT.cpp @@ -0,0 +1,86 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. 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. +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, int n) +{ + int clib = PL_strcmp(s1, s2); + + int clib_n = PL_strncmp(s1, s2, n); + + nsAutoString t1,t2; + t1.AssignWithConversion(s1); + t2.AssignWithConversion(s2); + const char16_t* us1 = t1.get(); + const char16_t* us2 = t2.get(); + + int u2 = nsCRT::strcmp(us1, us2); + + int u2_n = nsCRT::strncmp(us1, us2, n); + + EXPECT_EQ(sign(clib), sign(u2)); + EXPECT_EQ(sign(clib), sign(u2_n)); + EXPECT_EQ(sign(clib), sign(clib_n)); +} + +struct Test { + const char* s1; + const char* s2; + int 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 }, +}; +#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 000000000..b8087e82b --- /dev/null +++ b/xpcom/tests/gtest/TestCallTemplates.cpp @@ -0,0 +1,104 @@ +/* -*- 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 "nsIComponentManager.h" +#include "nsIServiceManager.h" +#include "nsWeakReference.h" +#include "nsIInterfaceRequestor.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsComponentManagerUtils.h" +#include "nsServiceManagerUtils.h" +#include "nsAutoPtr.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) + +class nsTestService final : public nsITestService, + public nsSupportsWeakReference +{ + ~nsTestService() {} + 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); + +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. + */ + + NS_NOTREACHED("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); + nsISupportsWeakReference *mySupportsWeakRef; + CallQueryInterface(myTestService, &mySupportsWeakRef); + + nsCOMPtr mySupportsCOMPtr = mySupportsPtr; + CallQueryInterface(mySupportsCOMPtr, &myITestService); + + RefPtr myTestServiceRefPtr = myTestService; + CallQueryInterface(myTestServiceRefPtr, &mySupportsWeakRef); + + /* Test CallQueryReferent */ + + nsIWeakReference *myWeakRef = + static_cast(mySupportsPtr); + CallQueryReferent(myWeakRef, &myITestService); + + /* Test CallCreateInstance */ + + CallCreateInstance(kTestServiceCID, mySupportsPtr, &myITestService); + CallCreateInstance(kTestServiceCID, &myITestService); + CallCreateInstance(NS_TEST_SERVICE_CONTRACTID, mySupportsPtr, + &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 000000000..de4dd5ea3 --- /dev/null +++ b/xpcom/tests/gtest/TestCloneInputStream.cpp @@ -0,0 +1,200 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. 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/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_TRUE(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_TRUE(NS_SUCCEEDED(rv)); + + nsCOMPtr clone; + rv = NS_CloneInputStream(stream, getter_AddRefs(clone)); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + + testing::ConsumeAndValidateStream(stream, inputString); + testing::ConsumeAndValidateStream(clone, inputString); +} + +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_TRUE(NS_SUCCEEDED(rv)); + + // Take advantage of nsBufferedInputStream being non-cloneable right + // now. If this changes in the future, then we need a different stream + // type in this test. + nsCOMPtr stream; + rv = NS_NewBufferedInputStream(getter_AddRefs(stream), base, 4096); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + + nsCOMPtr cloneable = do_QueryInterface(stream); + ASSERT_TRUE(cloneable == nullptr); + + nsCOMPtr clone; + rv = NS_CloneInputStream(stream, getter_AddRefs(clone)); + ASSERT_TRUE(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_TRUE(NS_SUCCEEDED(rv)); + + // Take advantage of nsBufferedInputStream being non-cloneable right + // now. If this changes in the future, then we need a different stream + // type in this test. + nsCOMPtr stream; + rv = NS_NewBufferedInputStream(getter_AddRefs(stream), base, 4096); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + + nsCOMPtr cloneable = do_QueryInterface(stream); + ASSERT_TRUE(cloneable == nullptr); + + nsCOMPtr clone; + nsCOMPtr replacement; + rv = NS_CloneInputStream(stream, getter_AddRefs(clone), + getter_AddRefs(replacement)); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + ASSERT_TRUE(clone != nullptr); + ASSERT_TRUE(replacement != nullptr); + ASSERT_TRUE(stream.get() != replacement.get()); + ASSERT_TRUE(clone.get() != replacement.get()); + + stream = replacement.forget(); + + // 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_TRUE(NS_SUCCEEDED(rv)); + } while(available < inputString.Length()); + + testing::ConsumeAndValidateStream(stream, inputString); + testing::ConsumeAndValidateStream(clone, inputString); +} + +TEST(CloneInputStream, CloneMultiplexStream) +{ + nsCOMPtr stream = + do_CreateInstance("@mozilla.org/io/multiplex-input-stream;1"); + 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_TRUE(NS_SUCCEEDED(rv)); + + rv = stream->AppendStream(base); + ASSERT_TRUE(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_TRUE(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_TRUE(NS_SUCCEEDED(rv)); + + nsCOMPtr clone2; + rv = NS_CloneInputStream(stream, getter_AddRefs(clone2)); + ASSERT_TRUE(NS_FAILED(rv)); +} + +TEST(CloneInputStream, CloneMultiplexStreamPartial) +{ + nsCOMPtr stream = + do_CreateInstance("@mozilla.org/io/multiplex-input-stream;1"); + 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_TRUE(NS_SUCCEEDED(rv)); + + rv = stream->AppendStream(base); + ASSERT_TRUE(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_TRUE(NS_SUCCEEDED(rv)); + + nsCOMPtr clone; + rv = NS_CloneInputStream(stream, getter_AddRefs(clone)); + ASSERT_TRUE(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_TRUE(NS_FAILED(rv)); + + // Fail at the end. + nsAutoCString consumed; + rv = NS_ConsumeStream(stream, UINT32_MAX, consumed); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + + rv = NS_CloneInputStream(stream, getter_AddRefs(clone)); + ASSERT_TRUE(NS_FAILED(rv)); +} diff --git a/xpcom/tests/gtest/TestDeadlockDetector.cpp b/xpcom/tests/gtest/TestDeadlockDetector.cpp new file mode 100644 index 000000000..646ee3e1d --- /dev/null +++ b/xpcom/tests/gtest/TestDeadlockDetector.cpp @@ -0,0 +1,322 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: sw=4 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 "nsTArray.h" +#include "nsMemory.h" + +#include "mozilla/CondVar.h" +#include "mozilla/ReentrantMonitor.h" +#include "mozilla/Mutex.h" + +#ifdef MOZ_CRASHREPORTER +#include "nsCOMPtr.h" +#include "nsICrashReporter.h" +#include "nsServiceManagerUtils.h" +#endif + +#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); +} + +// This global variable is defined in toolkit/xre/nsSigHandlers.cpp. +extern unsigned int _gdb_sleep_duration; + +/** + * Simple test fixture that makes sure the gdb sleep setup in the + * ah crap handler is bypassed during the death tests. + */ +class DeadlockDetectorTest : public ::testing::Test +{ +protected: + void SetUp() final { + mOldSleepDuration = _gdb_sleep_duration; + _gdb_sleep_duration = 0; + } + + void TearDown() final { + _gdb_sleep_duration = mOldSleepDuration; + } + +private: + unsigned int mOldSleepDuration; +}; + +void DisableCrashReporter() +{ +#ifdef MOZ_CRASHREPORTER + nsCOMPtr crashreporter = + do_GetService("@mozilla.org/toolkit/crash-reporter;1"); + if (crashreporter) { + crashreporter->SetEnabled(false); + } +#endif +} + +//----------------------------------------------------------------------------- +// Single-threaded sanity tests + +// Stupidest possible deadlock. +int +Sanity_Child() +{ + DisableCrashReporter(); + + mozilla::Mutex m1("dd.sanity.m1"); + m1.Lock(); + m1.Lock(); + return 0; // not reached +} + +TEST_F(DeadlockDetectorTest, 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. +int +Sanity2_Child() +{ + DisableCrashReporter(); + + mozilla::Mutex m1("dd.sanity2.m1"); + mozilla::Mutex m2("dd.sanity2.m2"); + m1.Lock(); + m2.Lock(); + m1.Lock(); + return 0; // not reached +} + +TEST_F(DeadlockDetectorTest, 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); +} + +int +Sanity3_Child() +{ + DisableCrashReporter(); + + mozilla::Mutex m1("dd.sanity3.m1"); + mozilla::Mutex m2("dd.sanity3.m2"); + mozilla::Mutex m3("dd.sanity3.m3"); + mozilla::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(DeadlockDetectorTest, 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); +} + +int +Sanity4_Child() +{ + DisableCrashReporter(); + + mozilla::ReentrantMonitor m1("dd.sanity4.m1"); + mozilla::Mutex m2("dd.sanity4.m2"); + m1.Enter(); + m2.Lock(); + m1.Enter(); + return 0; +} + +TEST_F(DeadlockDetectorTest, 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); +} + +//----------------------------------------------------------------------------- +// 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; +}; + +static void +TwoThreads_thread(void* arg) +{ + ThreadState* state = static_cast(arg); + + mozilla::Mutex* ttM1 = state->locks[0]; + mozilla::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() +{ + DisableCrashReporter(); + + nsTArray locks = { + new mozilla::Mutex("dd.twothreads.m1"), + new mozilla::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(DeadlockDetectorTest, 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); +} + +static void +ContentionNoDeadlock_thread(void* arg) +{ + 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; + } +} + +int +ContentionNoDeadlock_Child() +{ + 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 mozilla::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(DeadlockDetectorTest, 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 000000000..e25217223 --- /dev/null +++ b/xpcom/tests/gtest/TestDeadlockDetectorScalability.cpp @@ -0,0 +1,170 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: sw=4 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) + NS_RUNTIMEABORT("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) + NS_RUNTIMEABORT("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/TestEncoding.cpp b/xpcom/tests/gtest/TestEncoding.cpp new file mode 100644 index 000000000..0671284ee --- /dev/null +++ b/xpcom/tests/gtest/TestEncoding.cpp @@ -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/. */ + +#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/TestEscapeURL.cpp b/xpcom/tests/gtest/TestEscapeURL.cpp new file mode 100644 index 000000000..2a8faf5f0 --- /dev/null +++ b/xpcom/tests/gtest/TestEscapeURL.cpp @@ -0,0 +1,69 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. 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" + +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(EscapeURL, 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(EscapeURL, 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(EscapeURL, 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"); + } +} diff --git a/xpcom/tests/gtest/TestExpirationTracker.cpp b/xpcom/tests/gtest/TestExpirationTracker.cpp new file mode 100644 index 000000000..a6a0e146c --- /dev/null +++ b/xpcom/tests/gtest/TestExpirationTracker.cpp @@ -0,0 +1,185 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. 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 "nsMemory.h" +#include "nsAutoPtr.h" +#include "nsString.h" +#include "nsDirectoryServiceDefs.h" +#include "nsDirectoryServiceUtils.h" +#include "nsComponentManagerUtils.h" +#include "nsXPCOM.h" +#include "nsIFile.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 slackMS = 30; // allow this much error + +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() { + 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) { + 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 - slackMS; + uint32_t upperBoundMS = K*(periodMS + sleepPeriodMS) + slackMS; + if (logging) { + printf("Checking: %d-%d = %d [%d,%d]\n", + now, aObj->mLastUsed, timeDiffMS, lowerBoundMS, upperBoundMS); + } + if (timeDiffMS < lowerBoundMS || timeDiffMS > upperBoundMS) { + EXPECT_TRUE(timeDiffMS < periodMS && 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 000000000..7e7b4f4a1 --- /dev/null +++ b/xpcom/tests/gtest/TestFile.cpp @@ -0,0 +1,477 @@ +/* -*- 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" +#include "nsDirectoryServiceDefs.h" +#include "nsDirectoryServiceUtils.h" + +#include "gtest/gtest.h" + +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; +} + +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"); + return file.forget(); +} + +static nsCString FixName(const char* aName) +{ + nsCString name; + for (uint32_t i = 0; aName[i]; ++i) { + char 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_FALSE(NS_SUCCEEDED(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) +{ + 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; + } + + rv = file->Remove(aRecursive); + if (!VerifyResult(rv, "Remove")) + return false; + + 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; + + nsAutoCString path; + nsresult rv = file->GetNativePath(path); + VerifyResult(rv, "GetNativePath"); + path.Append(FixName("/./..")); + rv = file->InitWithNativePath(path); + VerifyResult(rv, "InitWithNativePath"); + rv = file->Normalize(); + VerifyResult(rv, "Normalize"); + rv = file->GetNativePath(path); + VerifyResult(rv, "GetNativePath (after normalization)"); + + nsAutoCString basePath; + rv = aBase->GetNativePath(basePath); + VerifyResult(rv, "GetNativePath (base)"); + + EXPECT_TRUE(path.Equals(basePath)) << "Incorrect normalization: " << path.get() << " - " << basePath.get(); + if (!path.Equals(basePath)) { + return false; + } + + return true; +} + +TEST(TestFile, Tests) +{ + nsCOMPtr base; + nsresult rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(base)); + ASSERT_TRUE(VerifyResult(rv, "Getting temp directory")); + + rv = base->AppendNative(nsDependentCString("mozfiletests")); + ASSERT_TRUE(VerifyResult(rv, "Appending mozfiletests to temp directory name")); + + // 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 + 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 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 directoreis + ASSERT_TRUE(TestCopy(base, subdir, "file4.txt", "file5.txt")); + + // Run normalization tests while the directory exists + ASSERT_TRUE(TestNormalizeNativePath(base, subdir)); + + // Test recursive directory removal + ASSERT_TRUE(TestRemove(base, "subdir", true)); + + 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)); + + // Clean up temporary stuff + rv = base->Remove(true); + VerifyResult(rv, "Cleaning up temp directory"); +} diff --git a/xpcom/tests/gtest/TestHashtables.cpp b/xpcom/tests/gtest/TestHashtables.cpp new file mode 100644 index 000000000..394812631 --- /dev/null +++ b/xpcom/tests/gtest/TestHashtables.cpp @@ -0,0 +1,435 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. 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 "nsDataHashtable.h" +#include "nsInterfaceHashtable.h" +#include "nsClassHashtable.h" + +#include "nsCOMPtr.h" +#include "nsISupports.h" +#include "nsCOMArray.h" +#include "mozilla/Attributes.h" + +#include "gtest/gtest.h" + +namespace TestHashtables { + +class TestUniChar // for nsClassHashtable +{ +public: + explicit TestUniChar(uint32_t aWord) + { + mWord = aWord; + } + + ~TestUniChar() + { + } + + uint32_t GetChar() const { return mWord; } + +private: + uint32_t mWord; +}; + +struct EntityNode { + const char* mStr; // never owns buffer + uint32_t mUnicode; +}; + +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() { } + + 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; +} + +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); +} + +// +// 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(); + NS_IMETHOD_(MozExternalRefCountType) Release(); + NS_IMETHOD QueryInterface( const nsIID&, void** ); + + 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; +} + +nsresult +CreateIFoo( IFoo** result ) + // a typical factory function (that calls AddRef) + { + IFoo* foop = new IFoo(); + + foop->AddRef(); + *result = foop; + + return NS_OK; + } + +} // 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(Hashtables, DataHashtable) +{ + // check a data-hashtable + nsDataHashtable UniToEntity(ENTITY_COUNT); + + for (uint32_t i = 0; i < ENTITY_COUNT; ++i) { + UniToEntity.Put(gEntities[i].mUnicode, gEntities[i].mStr); + } + + const char* str; + + for (uint32_t i = 0; i < ENTITY_COUNT; ++i) { + ASSERT_TRUE(UniToEntity.Get(gEntities[i].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, ClassHashtable) +{ + // check a class-hashtable + nsClassHashtable EntToUniClass(ENTITY_COUNT); + + for (uint32_t i = 0; i < ENTITY_COUNT; ++i) { + TestUniChar* temp = new TestUniChar(gEntities[i].mUnicode); + EntToUniClass.Put(nsDependentCString(gEntities[i].mStr), temp); + } + + TestUniChar* myChar; + + for (uint32_t i = 0; i < ENTITY_COUNT; ++i) { + ASSERT_TRUE(EntToUniClass.Get(nsDependentCString(gEntities[i].mStr), &myChar)); + } + + ASSERT_FALSE(EntToUniClass.Get(NS_LITERAL_CSTRING("xxxx"), &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, DataHashtableWithInterfaceKey) +{ + // check a data-hashtable with an interface key + nsDataHashtable 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.Put(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 (uint32_t i = 0; i < ENTITY_COUNT; ++i) { + nsCOMPtr foo; + CreateIFoo(getter_AddRefs(foo)); + foo->SetString(nsDependentCString(gEntities[i].mStr)); + + UniToEntClass2.Put(gEntities[i].mUnicode, foo); + } + + for (uint32_t i = 0; i < ENTITY_COUNT; ++i) { + nsCOMPtr myEnt; + ASSERT_TRUE(UniToEntClass2.Get(gEntities[i].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)); +} diff --git a/xpcom/tests/gtest/TestID.cpp b/xpcom/tests/gtest/TestID.cpp new file mode 100644 index 000000000..cb56554dc --- /dev/null +++ b/xpcom/tests/gtest/TestID.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 "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)); + + char* cp = id.ToString(); + ASSERT_NE(cp, nullptr); + ASSERT_STREQ(cp, ids[4*(i/4) + 3]); + + free(cp); + } +} diff --git a/xpcom/tests/gtest/TestNSPRLogModulesParser.cpp b/xpcom/tests/gtest/TestNSPRLogModulesParser.cpp new file mode 100644 index 000000000..4fd924175 --- /dev/null +++ b/xpcom/tests/gtest/TestNSPRLogModulesParser.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 "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, 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); +} diff --git a/xpcom/tests/gtest/TestNsRefPtr.cpp b/xpcom/tests/gtest/TestNsRefPtr.cpp new file mode 100644 index 000000000..a085c2966 --- /dev/null +++ b/xpcom/tests/gtest/TestNsRefPtr.cpp @@ -0,0 +1,479 @@ +/* -*- 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(); + NS_IMETHOD_(MozExternalRefCountType) Release(); + NS_IMETHOD QueryInterface( const nsIID&, void** ); + 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 +{ +} + +nsresult +CreateFoo( void** result ) + // a typical factory function (that calls AddRef) +{ + Foo* foop = new Foo; + + foop->AddRef(); + *result = foop; + + return NS_OK; +} + +void +set_a_Foo( RefPtr* result ) +{ + assert(result); + + RefPtr foop( do_QueryObject(new Foo) ); + *result = foop; +} + +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(); + virtual ~Bar(); + + NS_IMETHOD QueryInterface( const nsIID&, void** ) override; + + virtual void VirtualMemberFunction( int, int*, int& ) override; + virtual 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( fooP.forget() ); + 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; + + { + Foo* raw_foo1p = new Foo; + raw_foo1p->AddRef(); + + Foo* 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() + { + } + 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 +{ +void AnFooPtrPtrContext(Foo**) { } +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 000000000..d907619be --- /dev/null +++ b/xpcom/tests/gtest/TestObserverArray.cpp @@ -0,0 +1,167 @@ +/* -*- 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" + +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) + +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 + */ + +} diff --git a/xpcom/tests/gtest/TestObserverService.cpp b/xpcom/tests/gtest/TestObserverService.cpp new file mode 100644 index 000000000..d4eb51a7e --- /dev/null +++ b/xpcom/tests/gtest/TestObserverService.cpp @@ -0,0 +1,288 @@ +/* -*- 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 "nsIComponentManager.h" +#include "nsIObserverService.h" +#include "nsIObserver.h" +#include "nsISimpleEnumerator.h" +#include "nsComponentManagerUtils.h" + +#include "nsCOMPtr.h" +#include "nsString.h" +#include "nsWeakReference.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() {} +}; + +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(NS_LITERAL_STRING("A")); + nsresult rv = svc->AddObserver(a, "Foo", false); + testResult(rv); + + // Add a few weak ref. + RefPtr b = new TestObserver(NS_LITERAL_STRING("B")); + rv = svc->AddObserver(b, "Bar", true); + testResult(rv); +} + +TEST(ObserverService, RemoveObserver) +{ + nsCOMPtr svc = + do_CreateInstance("@mozilla.org/observer-service;1"); + + RefPtr a = new TestObserver(NS_LITERAL_STRING("A")); + RefPtr b = new TestObserver(NS_LITERAL_STRING("B")); + RefPtr c = new TestObserver(NS_LITERAL_STRING("C")); + + svc->AddObserver(a, "Foo", false); + svc->AddObserver(b, "Foo", true); + + // Remove from non-existent topic. + nsresult rv = svc->RemoveObserver(a, "Bar"); + ASSERT_TRUE(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_TRUE(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(NS_LITERAL_STRING("A")); + 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(NS_LITERAL_STRING("A")); + testResult(svc->AddObserver(a, "Foo", false)); + } + + const size_t kBarCount = kFooCount / 2; + for (size_t i = 0; i < kBarCount; i++) { + RefPtr a = new TestObserver(NS_LITERAL_STRING("A")); + 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(NS_LITERAL_STRING("A")); + 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(NS_LITERAL_STRING("A")); + RefPtr b = new TestObserver(NS_LITERAL_STRING("B")); + + // 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(NS_LITERAL_STRING("A")); + testResult(svc->AddObserver(a, "Foo", true)); + RefPtr b = new TestObserver(NS_LITERAL_STRING("B")); + 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(NS_LITERAL_STRING("Observer-A")); + RefPtr bObserver = new TestObserver(NS_LITERAL_STRING("Observer-B")); + + // 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. + NS_NAMED_LITERAL_STRING(dataA, "Testing Notify(observer-A, topic-A)"); + aObserver->mExpectedData = dataA; + bObserver->mExpectedData = dataA; + nsresult rv = + svc->NotifyObservers(ToSupports(aObserver), topicA.get(), dataA.get()); + testResult(rv); + ASSERT_EQ(aObserver->mObservations, 1); + ASSERT_EQ(bObserver->mObservations, 1); + + // Notify topicB. + NS_NAMED_LITERAL_STRING(dataB, "Testing Notify(observer-B, topic-B)"); + bObserver->mExpectedData = dataB; + rv = svc->NotifyObservers(ToSupports(bObserver), topicB.get(), dataB.get()); + 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.get()); + 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.get()); + testResult(rv); + ASSERT_EQ(aObserver->mObservations, 1); + ASSERT_EQ(bObserver->mObservations, 3); +} diff --git a/xpcom/tests/gtest/TestPLDHash.cpp b/xpcom/tests/gtest/TestPLDHash.cpp new file mode 100644 index 000000000..e7a73ae1b --- /dev/null +++ b/xpcom/tests/gtest/TestPLDHash.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 "PLDHashTable.h" +#include "nsCOMPtr.h" +#include "nsServiceManagerUtils.h" +#include "gtest/gtest.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 + +// This global variable is defined in toolkit/xre/nsSigHandlers.cpp. +extern unsigned int _gdb_sleep_duration; +#endif + +#ifdef MOZ_CRASHREPORTER +#include "nsICrashReporter.h" +#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. +void +TestCrashyOperation(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. + unsigned int old_gdb_sleep_duration = _gdb_sleep_duration; + _gdb_sleep_duration = 0; + + 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. +#ifdef MOZ_CRASHREPORTER + nsCOMPtr crashreporter = + do_GetService("@mozilla.org/toolkit/crash-reporter;1"); + if (crashreporter) { + crashreporter->SetEnabled(false); + } +#endif + + // Child: perform the crashy operation. + fprintf(stderr, "TestCrashyOperation: The following crash is expected. Do not panic.\n"); + aCrashyOperation(); + fprintf(stderr, "TestCrashyOperation: didn't crash?!\n"); + 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 'exited' failure: %d\n", 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 'signaled' failure: %d\n", signum); + ASSERT_TRUE(false); + } + } + + _gdb_sleep_duration = old_gdb_sleep_duration; +#endif +} + +void +InitCapacityOk_InitialLengthTooBig() +{ + PLDHashTable t(PLDHashTable::StubOps(), sizeof(PLDHashEntryStub), + PLDHashTable::kMaxInitialLength + 1); +} + +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 << 23, (uint32_t)1 << 8); +} + +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 << 23, (uint32_t)1 << 7); + + // Try a too-large capacity (which aborts). + TestCrashyOperation(InitCapacityOk_InitialLengthTooBig); + + // Try a large capacity combined with a large entry size that when multiplied + // overflow (causing abort). + TestCrashyOperation(InitCapacityOk_InitialEntryStoreTooBig); + + // 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); + + t1 = mozilla::Move(t1); // self-move + + t1 = mozilla::Move(t2); // empty overwritten with empty + + PLDHashTable t3(&trivialOps, sizeof(PLDHashEntryStub)); + PLDHashTable t4(&trivialOps, sizeof(PLDHashEntryStub)); + t3.Add((const void*)88); + + t3 = mozilla::Move(t4); // non-empty overwritten with empty + + PLDHashTable t5(&trivialOps, sizeof(PLDHashEntryStub)); + PLDHashTable t6(&trivialOps, sizeof(PLDHashEntryStub)); + t6.Add((const void*)88); + + t5 = mozilla::Move(t6); // empty overwritten with non-empty + + PLDHashTable t7(&trivialOps, sizeof(PLDHashEntryStub)); + PLDHashTable t8(mozilla::Move(t7)); // new table constructed with uninited + + PLDHashTable t9(&trivialOps, sizeof(PLDHashEntryStub)); + t9.Add((const void*)88); + PLDHashTable t10(mozilla::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(mozilla::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)); +} + +// 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 000000000..87b923008 --- /dev/null +++ b/xpcom/tests/gtest/TestPipes.cpp @@ -0,0 +1,1097 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. 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/ReentrantMonitor.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 "nsISeekableStream.h" +#include "nsIThread.h" +#include "nsIRunnable.h" +#include "nsStreamUtils.h" +#include "nsString.h" +#include "nsThreadUtils.h" +#include "prprf.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 nsIRunnable { +public: + NS_DECL_THREADSAFE_ISUPPORTS + + 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) : mIn(in), mCount(0) { + } + + uint32_t GetBytesRead() { return mCount; } + +private: + ~nsReceiver() {} + +protected: + nsCOMPtr mIn; + uint32_t mCount; +}; + +NS_IMPL_ISUPPORTS(nsReceiver, nsIRunnable) + +nsresult +TestPipe(nsIInputStream* in, nsIOutputStream* out) +{ + RefPtr receiver = new nsReceiver(in); + if (!receiver) + return NS_ERROR_OUT_OF_MEMORY; + + nsresult rv; + + nsCOMPtr thread; + rv = NS_NewThread(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; + char *buf = PR_smprintf("%d %s", i, kTestPattern); + uint32_t len = strlen(buf); + rv = WriteAll(out, buf, len, &writeCount); + if (gTrace) { + printf("wrote: "); + for (uint32_t j = 0; j < writeCount; j++) { + putc(buf[j], stdout); + } + printf("\n"); + } + PR_smprintf_free(buf); + 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 nsIRunnable { +public: + NS_DECL_THREADSAFE_ISUPPORTS + + 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) : 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() {} + +protected: + nsCOMPtr mIn; + uint32_t mReceived; + ReentrantMonitor* mMon; +}; + +NS_IMPL_ISUPPORTS(nsShortReader, nsIRunnable) + +nsresult +TestShortWrites(nsIInputStream* in, nsIOutputStream* out) +{ + RefPtr receiver = new nsShortReader(in); + if (!receiver) + return NS_ERROR_OUT_OF_MEMORY; + + nsresult rv; + + nsCOMPtr thread; + rv = NS_NewThread(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; + char* buf = PR_smprintf("%d %s", i, kTestPattern); + uint32_t len = strlen(buf); + len = len * rand() / RAND_MAX; + len = std::min(1u, len); + rv = WriteAll(out, buf, len, &writeCount); + if (NS_FAILED(rv)) return rv; + EXPECT_EQ(writeCount, len); + total += writeCount; + + if (gTrace) + printf("wrote %d bytes: %s\n", writeCount, buf); + PR_smprintf_free(buf); + //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 nsIRunnable +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + + 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) + : mIn(in), mOut(out), mCount(0) { + } + +private: + ~nsPump() {} + +protected: + nsCOMPtr mIn; + nsCOMPtr mOut; + uint32_t mCount; +}; + +NS_IMPL_ISUPPORTS(nsPump, nsIRunnable) + +TEST(Pipes, ChainedPipes) +{ + nsresult rv; + if (gTrace) { + printf("TestChainedPipes\n"); + } + + nsCOMPtr in1; + nsCOMPtr out1; + rv = NS_NewPipe(getter_AddRefs(in1), getter_AddRefs(out1), 20, 1999); + if (NS_FAILED(rv)) return; + + nsCOMPtr in2; + nsCOMPtr out2; + rv = NS_NewPipe(getter_AddRefs(in2), getter_AddRefs(out2), 200, 401); + if (NS_FAILED(rv)) return; + + RefPtr pump = new nsPump(in1, out2); + if (pump == nullptr) return; + + nsCOMPtr thread; + rv = NS_NewThread(getter_AddRefs(thread), pump); + if (NS_FAILED(rv)) return; + + RefPtr receiver = new nsReceiver(in2); + if (receiver == nullptr) return; + + nsCOMPtr receiverThread; + rv = NS_NewThread(getter_AddRefs(receiverThread), receiver); + if (NS_FAILED(rv)) return; + + uint32_t total = 0; + for (uint32_t i = 0; i < ITERATIONS; i++) { + uint32_t writeCount; + char* buf = PR_smprintf("%d %s", i, kTestPattern); + uint32_t len = strlen(buf); + len = len * rand() / RAND_MAX; + len = std::max(1u, len); + rv = WriteAll(out1, buf, len, &writeCount); + if (NS_FAILED(rv)) return; + EXPECT_EQ(writeCount, len); + total += writeCount; + + if (gTrace) + printf("wrote %d bytes: %s\n", writeCount, buf); + + PR_smprintf_free(buf); + } + if (gTrace) { + printf("wrote total of %d bytes\n", total); + } + rv = out1->Close(); + if (NS_FAILED(rv)) return; + + thread->Shutdown(); + receiverThread->Shutdown(); +} + +//////////////////////////////////////////////////////////////////////////////// + +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"); + } + rv = NS_NewPipe(getter_AddRefs(in), getter_AddRefs(out), segSize, bufSize); + EXPECT_TRUE(NS_SUCCEEDED(rv)); + rv = TestPipe(in, out); + EXPECT_TRUE(NS_SUCCEEDED(rv)); + + if (gTrace) { + printf("Testing short writes...\n"); + } + rv = NS_NewPipe(getter_AddRefs(in), getter_AddRefs(out), segSize, bufSize); + EXPECT_TRUE(NS_SUCCEEDED(rv)); + rv = TestShortWrites(in, out); + EXPECT_TRUE(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); + + nsresult rv = NS_NewPipe(getter_AddRefs(reader), getter_AddRefs(writer), + aSegmentSize, maxSize); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + + 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. + nsresult rv = NS_NewPipe(getter_AddRefs(reader), getter_AddRefs(writer), + aSegmentSize, maxSize, + true, false); // non-blocking - reader, writer + ASSERT_TRUE(NS_SUCCEEDED(rv)); + + 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. + for (uint32_t i = 0; i < aNumInitialClones; ++i) { + nsCOMPtr* clone = streamList.AppendElement(); + rv = cloneable->Clone(getter_AddRefs(*clone)); + ASSERT_TRUE(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_TRUE(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_TRUE(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; + + nsresult rv = NS_NewPipe2(getter_AddRefs(reader), getter_AddRefs(writer), + true, true, // non-blocking - reader, writer + segmentSize, numSegments); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + + nsTArray inputData; + testing::CreateData(segmentSize, inputData); + + uint32_t numWritten = 0; + rv = writer->Write(inputData.Elements(), inputData.Length(), &numWritten); + ASSERT_TRUE(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_TRUE(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; + + nsresult rv = NS_NewPipe2(getter_AddRefs(reader), getter_AddRefs(writer), + true, true, // non-blocking - reader, writer + segmentSize, numSegments); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + + nsCOMPtr clone; + rv = NS_CloneInputStream(reader, getter_AddRefs(clone)); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + + nsTArray inputData; + testing::CreateData(segmentSize, inputData); + + uint32_t numWritten = 0; + rv = writer->Write(inputData.Elements(), inputData.Length(), &numWritten); + ASSERT_TRUE(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_TRUE(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_TRUE(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_TRUE(NS_FAILED(rv)); + + cb = new testing::OutputStreamCallback(); + rv = writer->AsyncWait(cb, 0, 0, nullptr); + ASSERT_TRUE(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; + + nsresult rv = NS_NewPipe2(getter_AddRefs(reader), getter_AddRefs(writer), + true, true, // non-blocking - reader, writer + segmentSize, numSegments); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + + nsCOMPtr clone; + rv = NS_CloneInputStream(reader, getter_AddRefs(clone)); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + + nsTArray inputData; + testing::CreateData(segmentSize, inputData); + + uint32_t numWritten = 0; + rv = writer->Write(inputData.Elements(), inputData.Length(), &numWritten); + ASSERT_TRUE(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_TRUE(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_TRUE(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_TRUE(NS_FAILED(rv)); + + cb = new testing::OutputStreamCallback(); + rv = writer->AsyncWait(cb, 0, 0, nullptr); + ASSERT_TRUE(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_TRUE(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_TRUE(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_TRUE(NS_SUCCEEDED(rv)); + + // The stream is again non-writeable. + cb = new testing::OutputStreamCallback(); + rv = writer->AsyncWait(cb, 0, 0, nullptr); + ASSERT_TRUE(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_TRUE(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; + + nsresult rv = NS_NewPipe2(getter_AddRefs(reader), getter_AddRefs(writer), + true, true, // non-blocking - reader, writer + segmentSize, numSegments); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + + nsTArray inputData; + testing::CreateData(segmentSize, inputData); + + RefPtr cb = + new testing::InputStreamCallback(); + + rv = reader->AsyncWait(cb, 0, 0, nullptr); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + + ASSERT_FALSE(cb->Called()); + + uint32_t numWritten = 0; + rv = writer->Write(inputData.Elements(), inputData.Length(), &numWritten); + ASSERT_TRUE(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; + + nsresult rv = NS_NewPipe2(getter_AddRefs(reader), getter_AddRefs(writer), + true, true, // non-blocking - reader, writer + segmentSize, numSegments); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + + nsCOMPtr clone; + rv = NS_CloneInputStream(reader, getter_AddRefs(clone)); + ASSERT_TRUE(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_TRUE(NS_SUCCEEDED(rv)); + + ASSERT_FALSE(cb->Called()); + + rv = asyncClone->AsyncWait(cb2, 0, 0, nullptr); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + + ASSERT_FALSE(cb2->Called()); + + uint32_t numWritten = 0; + rv = writer->Write(inputData.Elements(), inputData.Length(), &numWritten); + ASSERT_TRUE(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; + + nsresult rv = NS_NewPipe(getter_AddRefs(reader), getter_AddRefs(writer), + aSegmentSize, maxSize); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + + nsTArray inputData; + + testing::CreateData(aDataSize, inputData); + + uint32_t numWritten = 0; + rv = writer->Write(inputData.Elements(), inputData.Length(), &numWritten); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + + nsTArray outputData; + + uint32_t numRead = 0; + rv = reader->ReadSegments(CloseDuringReadFunc, &outputData, + inputData.Length(), &numRead); + ASSERT_TRUE(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; + + nsresult rv = NS_NewPipe(getter_AddRefs(reader), getter_AddRefs(writer)); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + + 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 000000000..eeb2f1e09 --- /dev/null +++ b/xpcom/tests/gtest/TestPriorityQueue.cpp @@ -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/. */ + +#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); + + 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; + 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/TestRacingServiceManager.cpp b/xpcom/tests/gtest/TestRacingServiceManager.cpp new file mode 100644 index 000000000..b0638db02 --- /dev/null +++ b/xpcom/tests/gtest/TestRacingServiceManager.cpp @@ -0,0 +1,300 @@ +/* -*- 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 "mozilla/Module.h" +#include "nsXULAppAPI.h" +#include "nsIThread.h" +#include "nsIComponentRegistrar.h" + +#include "nsAutoPtr.h" +#include "nsThreadUtils.h" +#include "nsXPCOMCIDInternal.h" +#include "pratom.h" +#include "prmon.h" +#include "mozilla/Assertions.h" +#include "mozilla/Attributes.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() {} + +public: + NS_DECL_THREADSAFE_ISUPPORTS + + Factory() : mFirstComponentCreated(false) { } + + NS_IMETHOD CreateInstance(nsISupports* aDelegate, + const nsIID& aIID, + void** aResult) override; + + NS_IMETHOD LockFactory(bool aLock) override { + return NS_OK; + } + + bool mFirstComponentCreated; +}; + +NS_IMPL_ISUPPORTS(Factory, nsIFactory) + +class Component1 final : public nsISupports +{ + ~Component1() {} + +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() {} + +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(nsISupports* aDelegate, + 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_FALSE(aDelegate, NS_ERROR_NO_AGGREGATION); + 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() : 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; + +static already_AddRefed +CreateFactory(const mozilla::Module& module, const mozilla::Module::CIDEntry& entry) +{ + if (!gFactory) { + gFactory = new Factory(); + NS_ADDREF(gFactory); + } + nsCOMPtr ret = gFactory; + return ret.forget(); +} + +static const mozilla::Module::CIDEntry kLocalCIDs[] = { + { &kFactoryCID1, false, CreateFactory, nullptr }, + { &kFactoryCID2, false, CreateFactory, nullptr }, + { nullptr } +}; + +static const mozilla::Module::ContractIDEntry kLocalContracts[] = { + { FACTORY_CONTRACTID, &kFactoryCID2 }, + { nullptr } +}; + +static const mozilla::Module kLocalModule = { + mozilla::Module::kVersion, + kLocalCIDs, + kLocalContracts +}; + +TEST(RacingServiceManager, Test) +{ + nsresult rv; + XRE_AddStaticComponent(&kLocalModule); + + AutoCreateAndDestroyReentrantMonitor mon1(&gReentrantMonitor); + + RefPtr runnable = new TestRunnable(); + ASSERT_TRUE(runnable); + + // Run the classID test + nsCOMPtr newThread; + rv = NS_NewThread(getter_AddRefs(newThread), runnable); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + + { + ReentrantMonitorAutoEnter mon2(*gReentrantMonitor); + + gMainThreadWaiting = true; + mon2.Notify(); + + while (!gCreateInstanceCalled) { + mon2.Wait(); + } + } + + nsCOMPtr component(do_GetService(kFactoryCID1, &rv)); + ASSERT_TRUE(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_TRUE(NS_SUCCEEDED(rv)); + + { + ReentrantMonitorAutoEnter mon3(*gReentrantMonitor); + + gMainThreadWaiting = true; + mon3.Notify(); + + while (!gCreateInstanceCalled) { + mon3.Wait(); + } + } + + component = do_GetService(FACTORY_CONTRACTID, &rv); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + + NS_RELEASE(gFactory); +} + +} // namespace TestRacingServiceManager diff --git a/xpcom/tests/gtest/TestSTLWrappers.cpp b/xpcom/tests/gtest/TestSTLWrappers.cpp new file mode 100644 index 000000000..9559548a3 --- /dev/null +++ b/xpcom/tests/gtest/TestSTLWrappers.cpp @@ -0,0 +1,78 @@ +#include + +#include +#ifndef mozilla_algorithm_h +# error "failed to wrap " +#endif + +#include +#ifndef mozilla_vector_h +# error "failed to wrap " +#endif + +#ifdef MOZ_CRASHREPORTER +#include "nsCOMPtr.h" +#include "nsICrashReporter.h" +#include "nsServiceManagerUtils.h" +#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 + + +#if defined(XP_UNIX) +extern unsigned int _gdb_sleep_duration; +#endif + +void ShouldAbort() +{ +#if defined(XP_UNIX) + _gdb_sleep_duration = 0; +#endif + +#ifdef MOZ_CRASHREPORTER + nsCOMPtr crashreporter = + do_GetService("@mozilla.org/toolkit/crash-reporter;1"); + if (crashreporter) { + crashreporter->SetEnabled(false); + } +#endif + + std::vector v; + int rv = 1; + + TRY { + // v.at(1) on empty v should abort; NOT throw an exception + + // (Do some arithmetic with result of v.at() to avoid + // compiler warnings for unused variable/result.) + rv += v.at(1) ? 1 : 2; + } 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); + return; +} + +#ifdef XP_WIN +TEST(STLWrapper, DISABLED_ShouldAbortDeathTest) +#else +TEST(STLWrapper, ShouldAbortDeathTest) +#endif +{ + ASSERT_DEATH_IF_SUPPORTED(ShouldAbort(), "terminate called after throwing an instance of 'std::out_of_range'|vector::_M_range_check"); +} diff --git a/xpcom/tests/gtest/TestSlicedInputStream.cpp b/xpcom/tests/gtest/TestSlicedInputStream.cpp new file mode 100644 index 000000000..ccad0a6a8 --- /dev/null +++ b/xpcom/tests/gtest/TestSlicedInputStream.cpp @@ -0,0 +1,266 @@ +#include "gtest/gtest.h" + +#include "nsCOMPtr.h" +#include "nsIInputStream.h" +#include "nsStreamUtils.h" +#include "nsString.h" +#include "nsStringStream.h" +#include "SlicedInputStream.h" + +/* 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 nsIInputStream +{ + nsCOMPtr mStream; + +public: + NS_DECL_THREADSAFE_ISUPPORTS + + explicit NonSeekableStringStream(const nsACString& aBuffer) + { + NS_NewCStringInputStream(getter_AddRefs(mStream), aBuffer); + } + + NS_IMETHOD + Available(uint64_t* aLength) override + { + return mStream->Available(aLength); + } + + 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: + ~NonSeekableStringStream() {} +}; + +NS_IMPL_ISUPPORTS(NonSeekableStringStream, nsIInputStream) + +// Helper function for creating a seekable nsIInputStream + a SlicedInputStream. +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, aStart, aLength); +} + +// Helper function for creating a non-seekable nsIInputStream + a +// SlicedInputStream. +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, 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); +} diff --git a/xpcom/tests/gtest/TestSnappyStreams.cpp b/xpcom/tests/gtest/TestSnappyStreams.cpp new file mode 100644 index 000000000..99f41120b --- /dev/null +++ b/xpcom/tests/gtest/TestSnappyStreams.cpp @@ -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/. */ + +#include +#include "gtest/gtest.h" +#include "Helpers.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; + + nsresult rv = NS_NewPipe(aReaderOut, getter_AddRefs(pipeWriter)); + if (NS_FAILED(rv)) { return nullptr; } + + 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_TRUE(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_TRUE(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), aCorruptData, + aCorruptLength); + ASSERT_TRUE(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 000000000..16d06a5ff --- /dev/null +++ b/xpcom/tests/gtest/TestStateWatching.cpp @@ -0,0 +1,46 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. 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 "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() {} +}; + +TEST(WatchManager, Shutdown) +{ + RefPtr queue = new TaskQueue( + GetMediaThreadPool(MediaThreadType::PLAYBACK)); + + RefPtr p = new Foo; + WatchManager manager(p, queue); + Watchable notifier(false, "notifier"); + + queue->Dispatch(NS_NewRunnableFunction([&] () { + 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 000000000..a49d6f6bc --- /dev/null +++ b/xpcom/tests/gtest/TestStorageStream.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 +#include "gtest/gtest.h" +#include "Helpers.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_TRUE(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_TRUE(NS_SUCCEEDED(rv)); + + nsCOMPtr out; + rv = stor->GetOutputStream(0, getter_AddRefs(out)); + EXPECT_TRUE(NS_SUCCEEDED(rv)); + + WriteData(out, kData, kData.Length(), dataWritten); + WriteData(out, kData, kData.Length(), dataWritten); + + rv = out->Close(); + EXPECT_TRUE(NS_SUCCEEDED(rv)); + out = nullptr; + + nsCOMPtr in; + rv = stor->NewInputStream(0, getter_AddRefs(in)); + EXPECT_TRUE(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_TRUE(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_TRUE(NS_SUCCEEDED(rv)); + out = nullptr; + + // now, read all + rv = stor->NewInputStream(0, getter_AddRefs(in)); + EXPECT_TRUE(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_TRUE(NS_SUCCEEDED(rv)); + + // Get input stream before writing data into the output stream + nsCOMPtr in; + rv = stor->NewInputStream(0, getter_AddRefs(in)); + EXPECT_TRUE(NS_SUCCEEDED(rv)); + + // Write data to output stream + nsCOMPtr out; + rv = stor->GetOutputStream(0, getter_AddRefs(out)); + EXPECT_TRUE(NS_SUCCEEDED(rv)); + + WriteData(out, kData, kData.Length(), dataWritten); + WriteData(out, kData, kData.Length(), dataWritten); + + rv = out->Close(); + EXPECT_TRUE(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 000000000..5591ed588 --- /dev/null +++ b/xpcom/tests/gtest/TestStringStream.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 "gtest/gtest.h" +#include "Helpers.h" +#include "nsICloneableInputStream.h" +#include "nsStringStream.h" +#include "nsTArray.h" +#include "nsIInputStream.h" +#include "nsCOMPtr.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_TRUE(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_TRUE(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); +} diff --git a/xpcom/tests/gtest/TestStrings.cpp b/xpcom/tests/gtest/TestStrings.cpp new file mode 100644 index 000000000..285021b8e --- /dev/null +++ b/xpcom/tests/gtest/TestStrings.cpp @@ -0,0 +1,982 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. 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 "nsString.h" +#include "nsStringBuffer.h" +#include "nsReadableUtils.h" +#include "nsCRTGlue.h" +#include "mozilla/RefPtr.h" +#include "nsTArray.h" +#include "gtest/gtest.h" + +namespace TestStrings { + +using mozilla::fallible; + +void test_assign_helper(const nsACString& in, nsACString &_retval) +{ + _retval = in; +} + +TEST(Strings, assign) +{ + nsCString result; + test_assign_helper(NS_LITERAL_CSTRING("a") + NS_LITERAL_CSTRING("b"), result); + EXPECT_STREQ(result.get(), "ab"); +} + +TEST(Strings, assign_c) +{ + nsCString c; c.Assign('c'); + EXPECT_STREQ(c.get(), "c"); +} + +TEST(Strings, test1) +{ + NS_NAMED_LITERAL_STRING(empty, ""); + 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(Strings, test2) +{ + nsCString data("hello world"); + const nsACString& aStr = data; + + nsCString temp(aStr); + temp.Cut(0, 6); + + EXPECT_STREQ(temp.get(), "world"); +} + +TEST(Strings, find) +{ + nsCString src(""); + + int32_t i = src.Find("DOCTYPE", true, 2, 1); + EXPECT_EQ(i, 2); +} + +TEST(Strings, rfind) +{ + const char text[] = ""; + const char term[] = "bLaH"; + nsCString src(text); + int32_t i; + + i = src.RFind(term, true, 3, -1); + EXPECT_EQ(i, kNotFound); + + i = src.RFind(term, true, -1, -1); + EXPECT_EQ(i, 20); + + i = src.RFind(term, true, 13, -1); + EXPECT_EQ(i, 10); + + i = src.RFind(term, true, 22, 3); + EXPECT_EQ(i, 20); +} + +TEST(Strings, rfind_2) +{ + const char text[] = ""; + nsCString src(text); + int32_t i = src.RFind("TYPE", false, 5, -1); + EXPECT_EQ(i, 5); +} + +TEST(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(Strings, rfind_4) +{ + nsCString value("a.msf"); + int32_t i = value.RFind(".msf"); + EXPECT_EQ(i, 1); +} + +TEST(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_LITERAL_CSTRING("!/"), 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(NS_LITERAL_CSTRING("jar:"), 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(NS_LITERAL_CSTRING("jar:"), 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(NS_LITERAL_CSTRING("gecko"), 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(NS_LITERAL_CSTRING("jar:"), 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(NS_LITERAL_CSTRING("classic"), delim_begin, delim_end)); + + // When no match is found, range should be empty + EXPECT_EQ(delim_begin, delim_end); +} + +TEST(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_LITERAL_CSTRING("!/"), 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(NS_LITERAL_CSTRING("jar:"), 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(NS_LITERAL_CSTRING("jar:"), 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(NS_LITERAL_CSTRING("gecko"), 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(NS_LITERAL_CSTRING("jar:"), 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(NS_LITERAL_CSTRING("classic"), delim_begin, delim_end)); + + // When no match is found, range should be empty + EXPECT_EQ(delim_begin, delim_end); +} + +TEST(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(Strings, length) +{ + const char text[] = "abc-xyz"; + nsCString s(text); + size_t d = s.Length(); + EXPECT_EQ(d, sizeof(text) - 1); +} + +TEST(Strings, trim) +{ + const char text[] = " a\t $ "; + const char set[] = " \t$"; + + nsCString s(text); + s.Trim(set); + EXPECT_STREQ(s.get(), "a"); +} + +TEST(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.Assign("foobar"); + s.ReplaceSubstring("foo", "bar"); + s.ReplaceSubstring("bar", ""); + EXPECT_STREQ(s.get(), ""); + + s.Assign("foofoofoo"); + s.ReplaceSubstring("foo", "foo"); + EXPECT_STREQ(s.get(), "foofoofoo"); + + s.Assign("foofoofoo"); + s.ReplaceSubstring("of", "fo"); + EXPECT_STREQ(s.get(), "fofoofooo"); +} + +TEST(Strings, replace_substr_2) +{ + const char *oldName = nullptr; + const char *newName = "user"; + nsString acctName; acctName.AssignLiteral("forums.foo.com"); + nsAutoString newAcctName, oldVal, newVal; + oldVal.AssignWithConversion(oldName); + newVal.AssignWithConversion(newName); + 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(Strings, replace_substr_3) +{ + nsCString s; + s.Assign("abcabcabc"); + s.ReplaceSubstring("ca", "X"); + EXPECT_STREQ(s.get(), "abXbXbc"); + + s.Assign("abcabcabc"); + s.ReplaceSubstring("ca", "XYZ"); + EXPECT_STREQ(s.get(), "abXYZbXYZbc"); + + s.Assign("abcabcabc"); + s.ReplaceSubstring("ca", "XY"); + EXPECT_STREQ(s.get(), "abXYbXYbc"); + + s.Assign("abcabcabc"); + s.ReplaceSubstring("ca", "XYZ!"); + EXPECT_STREQ(s.get(), "abXYZ!bXYZ!bc"); + + s.Assign("abcdabcdabcd"); + s.ReplaceSubstring("bcd", "X"); + EXPECT_STREQ(s.get(), "aXaXaX"); + + s.Assign("abcdabcdabcd"); + s.ReplaceSubstring("bcd", "XYZ!"); + EXPECT_STREQ(s.get(), "aXYZ!aXYZ!aXYZ!"); + + s.Assign("abcdabcdabcd"); + s.ReplaceSubstring("bcd", "XY"); + EXPECT_STREQ(s.get(), "aXYaXYaXY"); + + s.Assign("abcdabcdabcd"); + s.ReplaceSubstring("bcd", "XYZABC"); + EXPECT_STREQ(s.get(), "aXYZABCaXYZABCaXYZABC"); + + s.Assign("abcdabcdabcd"); + s.ReplaceSubstring("bcd", "XYZ"); + EXPECT_STREQ(s.get(), "aXYZaXYZaXYZ"); + + s.Assign("abcdabcdabcd"); + s.ReplaceSubstring("bcd", "XYZ!"); + EXPECT_STREQ(s.get(), "aXYZ!aXYZ!aXYZ!"); + + s.Assign("abcdabcdabcd"); + s.ReplaceSubstring("ab", "X"); + EXPECT_STREQ(s.get(), "XcdXcdXcd"); + + s.Assign("abcdabcdabcd"); + s.ReplaceSubstring("ab", "XYZABC"); + EXPECT_STREQ(s.get(), "XYZABCcdXYZABCcdXYZABCcd"); + + s.Assign("abcdabcdabcd"); + s.ReplaceSubstring("ab", "XY"); + EXPECT_STREQ(s.get(), "XYcdXYcdXYcd"); + + s.Assign("abcdabcdabcd"); + s.ReplaceSubstring("ab", "XYZ!"); + EXPECT_STREQ(s.get(), "XYZ!cdXYZ!cdXYZ!cd"); + + s.Assign("abcdabcdabcd"); + s.ReplaceSubstring("notfound", "X"); + EXPECT_STREQ(s.get(), "abcdabcdabcd"); + + s.Assign("abcdabcdabcd"); + s.ReplaceSubstring("notfound", "longlongstring"); + EXPECT_STREQ(s.get(), "abcdabcdabcd"); +} + +TEST(Strings, strip_ws) +{ + const char text[] = " a $ "; + nsCString s(text); + s.StripWhitespace(); + EXPECT_STREQ(s.get(), "a$"); +} + +TEST(Strings, equals_ic) +{ + nsCString s; + EXPECT_FALSE(s.LowerCaseEqualsLiteral("view-source")); +} + +TEST(Strings, fixed_string) +{ + char buf[256] = "hello world"; + + nsFixedCString s(buf, sizeof(buf)); + + EXPECT_EQ(s.Length(), strlen(buf)); + + EXPECT_STREQ(s.get(), buf); + + s.Assign("foopy doopy doo"); + EXPECT_EQ(s.get(), buf); +} + +TEST(Strings, concat) +{ + nsCString bar("bar"); + const nsACString& barRef = bar; + + const nsPromiseFlatCString& result = + PromiseFlatCString(NS_LITERAL_CSTRING("foo") + + NS_LITERAL_CSTRING(",") + + barRef); + EXPECT_STREQ(result.get(), "foo,bar"); +} + +TEST(Strings, concat_2) +{ + nsCString fieldTextStr("xyz"); + nsCString text("text"); + const nsACString& aText = text; + + nsAutoCString result( fieldTextStr + aText ); + + EXPECT_STREQ(result.get(), "xyztext"); +} + +TEST(Strings, concat_3) +{ + nsCString result; + nsCString ab("ab"), c("c"); + + result = ab + result + c; + EXPECT_STREQ(result.get(), "abc"); +} + +TEST(Strings, xpidl_string) +{ + nsXPIDLCString a, b; + a = b; + EXPECT_TRUE(a == b); + + a.Adopt(0); + EXPECT_TRUE(a == b); + + a.Append("foopy"); + a.Assign(b); + EXPECT_TRUE(a == b); + + a.Insert("", 0); + a.Assign(b); + EXPECT_TRUE(a == b); + + const char text[] = "hello world"; + *getter_Copies(a) = NS_strdup(text); + EXPECT_STREQ(a, text); + + b = a; + EXPECT_STREQ(a, b); + + a.Adopt(0); + nsACString::const_iterator begin, end; + a.BeginReading(begin); + a.EndReading(end); + char *r = ToNewCString(Substring(begin, end)); + EXPECT_STREQ(r, ""); + free(r); + + a.Adopt(0); + EXPECT_TRUE(a.IsVoid()); + + int32_t index = a.FindCharInSet("xyz"); + EXPECT_EQ(index, kNotFound); +} + +TEST(Strings, empty_assign) +{ + nsCString a; + a.AssignLiteral(""); + + a.AppendLiteral(""); + + nsCString b; + b.SetCapacity(0); +} + +TEST(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(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(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(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(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(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(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.Assign("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(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(Strings, voided) +{ + const char kData[] = "hello world"; + + nsXPIDLCString str; + EXPECT_FALSE(str); + EXPECT_TRUE(str.IsVoid()); + EXPECT_TRUE(str.IsEmpty()); + + str.Assign(kData); + EXPECT_STREQ(str, kData); + + str.SetIsVoid(true); + EXPECT_FALSE(str); + EXPECT_TRUE(str.IsVoid()); + EXPECT_TRUE(str.IsEmpty()); + + str.SetIsVoid(false); + EXPECT_STREQ(str, ""); +} + +TEST(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(Strings, voided_assignment) +{ + nsCString a, b; + b.SetIsVoid(true); + a = b; + EXPECT_TRUE(a.IsVoid()); + EXPECT_EQ(a.get(), b.get()); +} + +TEST(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(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; + EXPECT_TRUE(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, uint32_t offset=0) +{ + nsAutoString tmp(str); + nsAString& data = tmp; + data.StripChars(strip, offset); + EXPECT_TRUE(data.Equals(result)); +} + +TEST(String, strip_chars) +{ + test_strip_chars_helper(u"foo \r \nbar", + u" \n\r", + NS_LITERAL_STRING("foobar")); + test_strip_chars_helper(u"\r\nfoo\r\n", + u" \n\r", + NS_LITERAL_STRING("foo")); + test_strip_chars_helper(u"foo", + u" \n\r", + NS_LITERAL_STRING("foo")); + test_strip_chars_helper(u"foo", + u"fo", + NS_LITERAL_STRING("")); + test_strip_chars_helper(u"foo", + u"foo", + NS_LITERAL_STRING("")); + test_strip_chars_helper(u" foo", + u" ", + NS_LITERAL_STRING(" foo"), 1); +} + +TEST(Strings, huge_capacity) +{ + nsString a, b, c, d, e, f, g, h, i, j, k, l, m, n; + nsCString n1; + + // 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(nsString::size_type(-1)/2, fallible)); + EXPECT_TRUE(a.SetCapacity(0, fallible)); // free the allocated memory + + EXPECT_TRUE(b.SetCapacity(1, fallible)); + EXPECT_FALSE(b.SetCapacity(nsString::size_type(-1)/2 - 1, fallible)); + EXPECT_TRUE(b.SetCapacity(0, fallible)); + + EXPECT_TRUE(c.SetCapacity(1, fallible)); + EXPECT_FALSE(c.SetCapacity(nsString::size_type(-1)/2, fallible)); + EXPECT_TRUE(c.SetCapacity(0, fallible)); + + EXPECT_FALSE(d.SetCapacity(nsString::size_type(-1)/2 - 1, fallible)); + EXPECT_FALSE(d.SetCapacity(nsString::size_type(-1)/2, fallible)); + EXPECT_TRUE(d.SetCapacity(0, fallible)); + + EXPECT_FALSE(e.SetCapacity(nsString::size_type(-1)/4, fallible)); + EXPECT_FALSE(e.SetCapacity(nsString::size_type(-1)/4 + 1, fallible)); + EXPECT_TRUE(e.SetCapacity(0, fallible)); + + EXPECT_FALSE(f.SetCapacity(nsString::size_type(-1)/2, fallible)); + EXPECT_TRUE(f.SetCapacity(0, fallible)); + + EXPECT_FALSE(g.SetCapacity(nsString::size_type(-1)/4 + 1000, fallible)); + EXPECT_FALSE(g.SetCapacity(nsString::size_type(-1)/4 + 1001, fallible)); + EXPECT_TRUE(g.SetCapacity(0, fallible)); + + EXPECT_FALSE(h.SetCapacity(nsString::size_type(-1)/4+1, fallible)); + EXPECT_FALSE(h.SetCapacity(nsString::size_type(-1)/2, fallible)); + EXPECT_TRUE(h.SetCapacity(0, fallible)); + + EXPECT_TRUE(i.SetCapacity(1, fallible)); + EXPECT_TRUE(i.SetCapacity(nsString::size_type(-1)/4 - 1000, fallible)); + EXPECT_FALSE(i.SetCapacity(nsString::size_type(-1)/4 + 1, fallible)); + EXPECT_TRUE(i.SetCapacity(0, fallible)); + + EXPECT_TRUE(j.SetCapacity(nsString::size_type(-1)/4 - 1000, fallible)); + EXPECT_FALSE(j.SetCapacity(nsString::size_type(-1)/4 + 1, fallible)); + EXPECT_TRUE(j.SetCapacity(0, fallible)); + + EXPECT_TRUE(k.SetCapacity(nsString::size_type(-1)/8 - 1000, fallible)); + EXPECT_TRUE(k.SetCapacity(nsString::size_type(-1)/4 - 1001, fallible)); + EXPECT_TRUE(k.SetCapacity(nsString::size_type(-1)/4 - 998, fallible)); + EXPECT_FALSE(k.SetCapacity(nsString::size_type(-1)/4 + 1, fallible)); + EXPECT_TRUE(k.SetCapacity(0, fallible)); + + EXPECT_TRUE(l.SetCapacity(nsString::size_type(-1)/8, fallible)); + EXPECT_TRUE(l.SetCapacity(nsString::size_type(-1)/8 + 1, fallible)); + EXPECT_TRUE(l.SetCapacity(nsString::size_type(-1)/8 + 2, fallible)); + EXPECT_TRUE(l.SetCapacity(0, fallible)); + + EXPECT_TRUE(m.SetCapacity(nsString::size_type(-1)/8 + 1000, fallible)); + EXPECT_TRUE(m.SetCapacity(nsString::size_type(-1)/8 + 1001, fallible)); + EXPECT_TRUE(m.SetCapacity(0, fallible)); + + EXPECT_TRUE(n.SetCapacity(nsString::size_type(-1)/8+1, fallible)); + EXPECT_FALSE(n.SetCapacity(nsString::size_type(-1)/4, fallible)); + EXPECT_TRUE(n.SetCapacity(0, fallible)); + + EXPECT_TRUE(n.SetCapacity(0, fallible)); + EXPECT_TRUE(n.SetCapacity((nsString::size_type(-1)/2 - sizeof(nsStringBuffer)) / 2 - 2, fallible)); + EXPECT_TRUE(n.SetCapacity(0, fallible)); + EXPECT_FALSE(n.SetCapacity((nsString::size_type(-1)/2 - sizeof(nsStringBuffer)) / 2 - 1, fallible)); + EXPECT_TRUE(n.SetCapacity(0, fallible)); + EXPECT_TRUE(n1.SetCapacity(0, fallible)); + EXPECT_TRUE(n1.SetCapacity((nsCString::size_type(-1)/2 - sizeof(nsStringBuffer)) / 1 - 2, fallible)); + EXPECT_TRUE(n1.SetCapacity(0, fallible)); + EXPECT_FALSE(n1.SetCapacity((nsCString::size_type(-1)/2 - sizeof(nsStringBuffer)) / 1 - 1, fallible)); + EXPECT_TRUE(n1.SetCapacity(0, fallible)); + } +} + +static void test_tofloat_helper(const nsString& aStr, float aExpected, bool aSuccess) +{ + nsresult result; + EXPECT_EQ(aStr.ToFloat(&result), aExpected); + if (aSuccess) { + EXPECT_EQ(result, NS_OK); + } else { + EXPECT_NE(result, NS_OK); + } +} + +TEST(Strings, tofloat) +{ + test_tofloat_helper(NS_LITERAL_STRING("42"), 42.f, true); + test_tofloat_helper(NS_LITERAL_STRING("42.0"), 42.f, true); + test_tofloat_helper(NS_LITERAL_STRING("-42"), -42.f, true); + test_tofloat_helper(NS_LITERAL_STRING("+42"), 42, true); + test_tofloat_helper(NS_LITERAL_STRING("13.37"), 13.37f, true); + test_tofloat_helper(NS_LITERAL_STRING("1.23456789"), 1.23456789f, true); + test_tofloat_helper(NS_LITERAL_STRING("1.98765432123456"), 1.98765432123456f, true); + test_tofloat_helper(NS_LITERAL_STRING("0"), 0.f, true); + test_tofloat_helper(NS_LITERAL_STRING("1.e5"), 100000, true); + test_tofloat_helper(NS_LITERAL_STRING(""), 0.f, false); + test_tofloat_helper(NS_LITERAL_STRING("42foo"), 42.f, false); + test_tofloat_helper(NS_LITERAL_STRING("foo"), 0.f, false); +} + +static void test_todouble_helper(const nsString& aStr, double aExpected, bool aSuccess) +{ + nsresult result; + EXPECT_EQ(aStr.ToDouble(&result), aExpected); + if (aSuccess) { + EXPECT_EQ(result, NS_OK); + } else { + EXPECT_NE(result, NS_OK); + } +} + +TEST(Strings, todouble) +{ + test_todouble_helper(NS_LITERAL_STRING("42"), 42, true); + test_todouble_helper(NS_LITERAL_STRING("42.0"), 42, true); + test_todouble_helper(NS_LITERAL_STRING("-42"), -42, true); + test_todouble_helper(NS_LITERAL_STRING("+42"), 42, true); + test_todouble_helper(NS_LITERAL_STRING("13.37"), 13.37, true); + test_todouble_helper(NS_LITERAL_STRING("1.23456789"), 1.23456789, true); + test_todouble_helper(NS_LITERAL_STRING("1.98765432123456"), 1.98765432123456, true); + test_todouble_helper(NS_LITERAL_STRING("123456789.98765432123456"), 123456789.98765432123456, true); + test_todouble_helper(NS_LITERAL_STRING("0"), 0, true); + test_todouble_helper(NS_LITERAL_STRING("1.e5"), 100000, true); + test_todouble_helper(NS_LITERAL_STRING(""), 0, false); + test_todouble_helper(NS_LITERAL_STRING("42foo"), 42, false); + test_todouble_helper(NS_LITERAL_STRING("foo"), 0, false); +} + +} // namespace TestStrings diff --git a/xpcom/tests/gtest/TestSynchronization.cpp b/xpcom/tests/gtest/TestSynchronization.cpp new file mode 100644 index 000000000..7ec55545a --- /dev/null +++ b/xpcom/tests/gtest/TestSynchronization.cpp @@ -0,0 +1,313 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. 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) +{ + 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*/) +{ + 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*/) +{ + 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("autolock"); + MutexAutoLock autol1(l1); + + l1.AssertCurrentThreadOwns(); + + { + Mutex l2("autolock2"); + MutexAutoLock autol2(l2); + + l1.AssertCurrentThreadOwns(); + l2.AssertCurrentThreadOwns(); + } + + l1.AssertCurrentThreadOwns(); +} + +//----------------------------------------------------------------------------- +// AutoUnlock tests +// +TEST(Synchronization, AutoUnlock) +{ + Mutex l1("autounlock"); + Mutex l2("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) +{ + 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 000000000..10e33664f --- /dev/null +++ b/xpcom/tests/gtest/TestTArray.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 "nsTArray.h" +#include "gtest/gtest.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_CopyChooser +{ + typedef nsTArray_CopyWithConstructors Type; +}; + +template<> +struct nsTArray_CopyChooser +{ + typedef nsTArray_CopyWithConstructors Type; +}; + +namespace TestTArray { + +const nsTArray& DummyArray() +{ + static nsTArray sArray; + if (sArray.IsEmpty()) { + const int data[] = {4, 1, 2, 8}; + sArray.AppendElements(data, ArrayLength(data)); + } + return sArray; +} + +// This returns an invalid nsTArray with a huge length in order to test that +// fallible operations actually fail. +#ifdef DEBUG +const nsTArray& FakeHugeArray() +{ + static nsTArray sArray; + if (sArray.IsEmpty()) { + sArray.AppendElement(); + ((nsTArrayHeader*)sArray.DebugGetHeader())->mLength = UINT32_MAX; + } + return sArray; +} +#endif + +TEST(TArray, AppendElementsRvalue) +{ + nsTArray array; + + nsTArray temp(DummyArray()); + array.AppendElements(Move(temp)); + ASSERT_EQ(DummyArray(), array); + ASSERT_TRUE(temp.IsEmpty()); + + temp = DummyArray(); + array.AppendElements(Move(temp)); + nsTArray expected; + expected.AppendElements(DummyArray()); + expected.AppendElements(DummyArray()); + ASSERT_EQ(expected, array); + ASSERT_TRUE(temp.IsEmpty()); +} + +TEST(TArray, 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(Move(array)); + ASSERT_TRUE(array.IsEmpty()); + ASSERT_EQ(DummyArray(), array2); +} + +TEST(TArray, AssignmentOperatorSelfAssignment) +{ + nsTArray array; + array = DummyArray(); + + array = array; + ASSERT_EQ(DummyArray(), array); + array = Move(array); + ASSERT_EQ(DummyArray(), array); +} + +TEST(TArray, CopyOverlappingForwards) +{ + nsTArray array; + const size_t rangeLength = 8; + const size_t initialLength = 2 * rangeLength; + array.AppendElements(initialLength); + + uint32_t destructionCounters[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, CopyOverlappingBackwards) +{ + nsTArray array; + const size_t rangeLength = 8; + const size_t initialLength = 2 * rangeLength; + 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. + uint32_t destructionCounters[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]; + } + + array.InsertElementsAt(0, rangeLength); + + for (uint32_t i = 0; i < initialLength; ++i) { + ASSERT_EQ(destructionCounters[i], 1u); + } +} + +} // namespace TestTArray diff --git a/xpcom/tests/gtest/TestTArray2.cpp b/xpcom/tests/gtest/TestTArray2.cpp new file mode 100644 index 000000000..421927104 --- /dev/null +++ b/xpcom/tests/gtest/TestTArray2.cpp @@ -0,0 +1,1033 @@ +/* -*- 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 "nsAutoPtr.h" +#include "nsString.h" +#include "nsDirectoryServiceDefs.h" +#include "nsDirectoryServiceUtils.h" +#include "nsComponentManagerUtils.h" +#include "nsXPCOM.h" +#include "nsIFile.h" + +#include "gtest/gtest.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) { + nsTArray 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; + + size_t index = ary.Length() / 2; + if (!ary.InsertElementAt(index, extra)) + return false; + 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; + + nsTArray copy(ary); + if (!(ary == copy)) + return false; + for (i = 0; i < copy.Length(); ++i) { + if (ary[i] != copy[i]) + return false; + } + if (!ary.AppendElements(copy)) + return false; + 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; + + ary.Clear(); + if (!ary.IsEmpty() || ary.Elements() == nullptr) + 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; + } + + if (!ary.InsertElementsAt(0, copy)) + return false; + 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) : mStr(other.mStr), mNum(other.mNum) { + } + ~Object() {} + + Object& operator=(const Object& other) { + mStr = other.mStr; + mNum = other.mNum; + return *this; + } + + 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 mStr.Compare(other.mStr.get()) < 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'}; + ASSERT_TRUE(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; } +}; + +/* 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) { + ASSERT_TRUE(countableArray.AppendElement(Countable())); + } + + ASSERT_EQ(Countable::Count(), 8); + + const nsTArray& constRefCountableArray = countableArray; + + ASSERT_EQ(Countable::Count(), 8); + + nsTArray copyCountableArray(constRefCountableArray); + + ASSERT_EQ(Countable::Count(), 12); + + nsTArray&& moveRefCountableArray = Move(countableArray); + moveRefCountableArray.Length(); // Make compilers happy. + + ASSERT_EQ(Countable::Count(), 12); + + nsTArray movedCountableArray(Move(countableArray)); + + ASSERT_EQ(Countable::Count(), 12); + + // Test ctor + FallibleTArray differentAllocatorCountableArray(Move(copyCountableArray)); + // operator= + copyCountableArray = Move(differentAllocatorCountableArray); + differentAllocatorCountableArray = Move(copyCountableArray); + // And the other ctor + nsTArray copyCountableArray2(Move(differentAllocatorCountableArray)); + // with auto + AutoTArray autoCountableArray(Move(copyCountableArray2)); + // operator= + copyCountableArray2 = Move(autoCountableArray); + // Mix with FallibleTArray + FallibleTArray differentAllocatorCountableArray2(Move(copyCountableArray2)); + AutoTArray autoCountableArray2(Move(differentAllocatorCountableArray2)); + differentAllocatorCountableArray2 = Move(autoCountableArray2); + + ASSERT_EQ(Countable::Count(), 12); + + nsTArray moveableArray; + for (i = 0; i < 4; ++i) { + ASSERT_TRUE(moveableArray.AppendElement(Moveable())); + } + + ASSERT_EQ(Moveable::Count(), 4); + + const nsTArray& constRefMoveableArray = moveableArray; + + ASSERT_EQ(Moveable::Count(), 4); + + nsTArray copyMoveableArray(constRefMoveableArray); + + ASSERT_EQ(Moveable::Count(), 8); + + nsTArray&& moveRefMoveableArray = Move(moveableArray); + moveRefMoveableArray.Length(); // Make compilers happy. + + ASSERT_EQ(Moveable::Count(), 8); + + nsTArray movedMoveableArray(Move(moveableArray)); + + ASSERT_EQ(Moveable::Count(), 8); + + // Test ctor + FallibleTArray differentAllocatorMoveableArray(Move(copyMoveableArray)); + // operator= + copyMoveableArray = Move(differentAllocatorMoveableArray); + differentAllocatorMoveableArray = Move(copyMoveableArray); + // And the other ctor + nsTArray copyMoveableArray2(Move(differentAllocatorMoveableArray)); + // with auto + AutoTArray autoMoveableArray(Move(copyMoveableArray2)); + // operator= + copyMoveableArray2 = Move(autoMoveableArray); + // Mix with FallibleTArray + FallibleTArray differentAllocatorMoveableArray2(Move(copyMoveableArray2)); + AutoTArray autoMoveableArray2(Move(differentAllocatorMoveableArray2)); + differentAllocatorMoveableArray2 = Move(autoMoveableArray2); + + ASSERT_EQ(Moveable::Count(), 8); +} + +//---- + +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]); + ASSERT_TRUE(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(); + ASSERT_TRUE(strArray.AppendElement(kextra)); + strArray.RemoveElement(kextra); + ASSERT_EQ(oldLen, strArray.Length()); + + ASSERT_EQ(strArray.IndexOf("e"), size_t(1)); + + 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(EmptyCString()), 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_FALSE(NS_FAILED(f->AppendNative(nsDependentCString(kNames[i])))); + fileArray.AppendElement(f); + } + + ASSERT_EQ(fileArray.IndexOf(kNames[1], 0, nsFileNameComparator()), size_t(1)); + + // 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() {} + private: + int32_t rc; +}; + +TEST(TArray, test_refptr_array) { + nsTArray< RefPtr > 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)); + + 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); +} + +//---- + +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)); + } +} + +// 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; + ASSERT_GE(i, size_t(8)) << "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; + + InfallibleTArray i; + const InfallibleTArray iconst; + + 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& z5 = i; \ + ASSERT_EQ((void*)&z5, (void*)&i); \ + const type& z6 = iconst; \ + ASSERT_EQ((void*)&z6, (void*)&iconst); \ + 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(InfallibleTArray); + 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; + + InfallibleTArray i; + + nsTArray t; + AutoTArray tauto; + +#define LPAREN ( +#define RPAREN ) +#define FOR_EACH(pre, post) \ + do { \ + pre f post; \ + pre i 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 >&>(i).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(i[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(i[n], n); + ASSERT_EQ(t[n], n); + ASSERT_EQ(tauto[n], n); + } + + void* current_Hdrs[] = { + static_cast >&>(f).GetHdr(), + static_cast >&>(i).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 +} + +} // namespace TestTArray diff --git a/xpcom/tests/gtest/TestTextFormatter.cpp b/xpcom/tests/gtest/TestTextFormatter.cpp new file mode 100644 index 000000000..c98b766cc --- /dev/null +++ b/xpcom/tests/gtest/TestTextFormatter.cpp @@ -0,0 +1,34 @@ +/* -*- 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(NS_LITERAL_STRING("%3$s %4$S %1$d %2$d %2$d %3$s")); + 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); + ASSERT_STREQ("Hello World", NS_LossyConvertUTF16toASCII(out).get()); + + 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 +#include +#include "nsXPCOM.h" +#include "nsXPCOMCIDInternal.h" +#include "nsIThreadPool.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 Task final : public nsIRunnable +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + + explicit Task(int i) : mIndex(i) {} + + NS_IMETHOD Run() override + { + printf("###(%d) running from thread: %p\n", mIndex, (void *) PR_GetCurrentThread()); + int r = (int) ((float) rand() * 200 / RAND_MAX); + PR_Sleep(PR_MillisecondsToInterval(r)); + printf("###(%d) exiting from thread: %p\n", mIndex, (void *) PR_GetCurrentThread()); + ++sCount; + return NS_OK; + } + + static mozilla::Atomic sCount; + +private: + ~Task() {} + + int mIndex; +}; +NS_IMPL_ISUPPORTS(Task, nsIRunnable) + +mozilla::Atomic Task::sCount; + +TEST(ThreadPool, Main) +{ + nsCOMPtr pool = do_CreateInstance(NS_THREADPOOL_CONTRACTID); + EXPECT_TRUE(pool); + + for (int i = 0; i < 100; ++i) { + nsCOMPtr task = new Task(i); + EXPECT_TRUE(task); + + pool->Dispatch(task, NS_DISPATCH_NORMAL); + } + + pool->Shutdown(); + EXPECT_EQ(Task::sCount, 100); +} + +TEST(ThreadPool, Parallelism) +{ + nsCOMPtr pool = do_CreateInstance(NS_THREADPOOL_CONTRACTID); + EXPECT_TRUE(pool); + + // Dispatch and sleep to ensure we have an idle thread + nsCOMPtr r0 = new Runnable(); + pool->Dispatch(r0, NS_DISPATCH_SYNC); + PR_Sleep(PR_SecondsToInterval(2)); + + class Runnable1 : public Runnable { + public: + Runnable1(Monitor& aMonitor, bool& aDone) + : 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(PR_SecondsToInterval(300)); + } + EXPECT_TRUE(mDone); + return NS_OK; + } + private: + Monitor& mMonitor; + bool& mDone; + }; + + class Runnable2 : public Runnable { + public: + Runnable2(Monitor& aMonitor, bool& aDone) + : 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("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(); +} diff --git a/xpcom/tests/gtest/TestThreadPoolListener.cpp b/xpcom/tests/gtest/TestThreadPoolListener.cpp new file mode 100644 index 000000000..f95106fa8 --- /dev/null +++ b/xpcom/tests/gtest/TestThreadPoolListener.cpp @@ -0,0 +1,209 @@ +/* -*- 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 "nsIThreadPool.h" + +#include "nsComponentManagerUtils.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/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() {} + +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 = + do_CreateInstance(NS_THREADPOOL_CONTRACTID, &rv); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + + rv = pool->SetThreadLimit(NUMBER_OF_THREADS); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + + rv = pool->SetIdleThreadLimit(NUMBER_OF_THREADS); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + + rv = pool->SetIdleThreadTimeout(IDLE_THREAD_TIMEOUT); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + + nsCOMPtr listener = new Listener(); + ASSERT_TRUE(listener); + + rv = pool->SetListener(listener); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + + { + ReentrantMonitorAutoEnter mon(*gReentrantMonitor); + + for (uint32_t i = 0; i < NUMBER_OF_THREADS; i++) { + nsCOMPtr runnable = new Runnable(); + ASSERT_TRUE(runnable); + + rv = pool->Dispatch(runnable, NS_DISPATCH_NORMAL); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + } + + gAllRunnablesPosted = true; + mon.NotifyAll(); + } + + { + ReentrantMonitorAutoEnter mon(*gReentrantMonitor); + while (!gAllThreadsCreated) { + mon.Wait(); + } + } + + rv = pool->Shutdown(); + ASSERT_TRUE(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 000000000..0d5d2f234 --- /dev/null +++ b/xpcom/tests/gtest/TestThreadUtils.cpp @@ -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/. */ + +#include "nsThreadUtils.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() {} +}; + +NS_IMPL_ISUPPORTS0(nsFoo) + +class TestSuicide : public mozilla::Runnable { + 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() {} +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; }; + // 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; }; + // 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; } + ~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; }; + TestCopyMove(TestCopyMove&& a) : mCopyCounter(a.mCopyCounter), mMoveCounter(a.mMoveCounter) { a.mMoveCounter = nullptr; ++mMoveCounter; } + ~TestCopyMove() { mCopyCounter = nullptr; mMoveCounter = nullptr; } + void operator()() { MOZ_RELEASE_ASSERT(mCopyCounter); MOZ_RELEASE_ASSERT(mMoveCounter); } + int* mCopyCounter; + int* mMoveCounter; +}; + +static void Expect(const char* aContext, int aCounter, int aMaxExpected) +{ + EXPECT_LE(aCounter, aMaxExpected) << aContext; +} + +TEST(ThreadUtils, NewRunnableFunction) +{ + // Test NS_NewRunnableFunction with copyable-only function object. + { + int copyCounter = 0; + { + nsCOMPtr trackedRunnable; + { + TestCopyWithNoMove tracker(©Counter); + trackedRunnable = NS_NewRunnableFunction(tracker); + // Original 'tracker' is destroyed here. + } + // Verify that the runnable contains a non-destroyed function object. + trackedRunnable->Run(); + } + Expect("NS_NewRunnableFunction 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 = NS_NewRunnableFunction(TestCopyWithNoMove(©Counter)); + } + trackedRunnable->Run(); + } + Expect("NS_NewRunnableFunction with copyable-only (and no move) function rvalue, copies", + copyCounter, 1); + } + { + int copyCounter = 0; + { + nsCOMPtr trackedRunnable; + { + TestCopyWithDeletedMove tracker(©Counter); + trackedRunnable = NS_NewRunnableFunction(tracker); + } + trackedRunnable->Run(); + } + Expect("NS_NewRunnableFunction with copyable-only (and deleted move) function, copies", + copyCounter, 1); + } + + // Test NS_NewRunnableFunction with movable-only function object. + { + int moveCounter = 0; + { + nsCOMPtr trackedRunnable; + { + TestMove tracker(&moveCounter); + trackedRunnable = NS_NewRunnableFunction(Move(tracker)); + } + trackedRunnable->Run(); + } + Expect("NS_NewRunnableFunction with movable-only function, moves", + moveCounter, 1); + } + { + int moveCounter = 0; + { + nsCOMPtr trackedRunnable; + { + trackedRunnable = NS_NewRunnableFunction(TestMove(&moveCounter)); + } + trackedRunnable->Run(); + } + Expect("NS_NewRunnableFunction with movable-only function rvalue, moves", + moveCounter, 1); + } + + // Test NS_NewRunnableFunction with copyable&movable function object. + { + int copyCounter = 0; + int moveCounter = 0; + { + nsCOMPtr trackedRunnable; + { + TestCopyMove tracker(©Counter, &moveCounter); + trackedRunnable = NS_NewRunnableFunction(Move(tracker)); + } + trackedRunnable->Run(); + } + Expect("NS_NewRunnableFunction with copyable&movable function, copies", + copyCounter, 0); + Expect("NS_NewRunnableFunction with copyable&movable function, moves", + moveCounter, 1); + } + { + int copyCounter = 0; + int moveCounter = 0; + { + nsCOMPtr trackedRunnable; + { + trackedRunnable = + NS_NewRunnableFunction(TestCopyMove(©Counter, &moveCounter)); + } + trackedRunnable->Run(); + } + Expect("NS_NewRunnableFunction with copyable&movable function rvalue, copies", + copyCounter, 0); + Expect("NS_NewRunnableFunction with copyable&movable function rvalue, moves", + moveCounter, 1); + } + + // Test NS_NewRunnableFunction with copyable-only lambda capture. + { + int copyCounter = 0; + { + nsCOMPtr trackedRunnable; + { + TestCopyWithNoMove tracker(©Counter); + // Expect 2 copies (here -> local lambda -> runnable lambda). + trackedRunnable = NS_NewRunnableFunction([tracker]() mutable { tracker(); }); + } + trackedRunnable->Run(); + } + Expect("NS_NewRunnableFunction 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 = NS_NewRunnableFunction([tracker]() mutable { tracker(); }); + } + trackedRunnable->Run(); + } + Expect("NS_NewRunnableFunction 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 NS_NewRunnableFunction with copyable&movable lambda capture. + { + int copyCounter = 0; + int moveCounter = 0; + { + nsCOMPtr trackedRunnable; + { + TestCopyMove tracker(©Counter, &moveCounter); + trackedRunnable = NS_NewRunnableFunction([tracker]() mutable { tracker(); }); + // Expect 1 copy (here -> local lambda) and 1 move (local -> runnable lambda). + } + trackedRunnable->Run(); + } + Expect("NS_NewRunnableFunction with copyable&movable capture, copies", + copyCounter, 1); + Expect("NS_NewRunnableFunction with copyable&movable capture, moves", + moveCounter, 1); + } +} + +TEST(ThreadUtils, RunnableMethod) +{ + 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"; + + NS_DispatchToMainThread(NewRunnableMethod(bar, &nsBar::DoBar1)); + NS_DispatchToMainThread(NewRunnableMethod(constBar, &nsBar::DoBar1Const)); + NS_DispatchToMainThread(NewRunnableMethod(bar, &nsBar::DoBar2)); + NS_DispatchToMainThread(NewRunnableMethod> + (bar, &nsBar::DoBar3, foo)); + NS_DispatchToMainThread(NewRunnableMethod> + (bar, &nsBar::DoBar4, foo)); + NS_DispatchToMainThread(NewRunnableMethod(bar, &nsBar::DoBar5, rawFoo)); + NS_DispatchToMainThread(NewRunnableMethod(bar, &nsBar::DoBar6, message)); +#ifdef HAVE_STDCALL + NS_DispatchToMainThread(NewRunnableMethod(bar, &nsBar::DoBar1std)); + NS_DispatchToMainThread(NewRunnableMethod(bar, &nsBar::DoBar2std)); + NS_DispatchToMainThread(NewRunnableMethod> + (bar, &nsBar::DoBar3std, foo)); + NS_DispatchToMainThread(NewRunnableMethod> + (bar, &nsBar::DoBar4std, foo)); + NS_DispatchToMainThread(NewRunnableMethod(bar, &nsBar::DoBar5std, rawFoo)); + NS_DispatchToMainThread(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; + } +} diff --git a/xpcom/tests/gtest/TestThreads.cpp b/xpcom/tests/gtest/TestThreads.cpp new file mode 100644 index 000000000..4f6055dce --- /dev/null +++ b/xpcom/tests/gtest/TestThreads.cpp @@ -0,0 +1,275 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. 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 "nspr.h" +#include "nsCOMPtr.h" +#include "nsIServiceManager.h" +#include "nsXPCOM.h" +#include "mozilla/Monitor.h" +#include "gtest/gtest.h" + +class nsRunner final : public nsIRunnable { + ~nsRunner() {} +public: + NS_DECL_THREADSAFE_ISUPPORTS + + NS_IMETHOD Run() override { + nsCOMPtr thread; + nsresult rv = NS_GetCurrentThread(getter_AddRefs(thread)); + EXPECT_TRUE(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) : mNum(num) { + } + +protected: + int mNum; +}; + +NS_IMPL_ISUPPORTS(nsRunner, nsIRunnable) + +TEST(Threads, Main) +{ + nsresult rv; + + nsCOMPtr event = new nsRunner(0); + EXPECT_TRUE(event); + + nsCOMPtr runner; + rv = NS_NewThread(getter_AddRefs(runner), event); + EXPECT_TRUE(NS_SUCCEEDED(rv)); + + nsCOMPtr thread; + rv = NS_GetCurrentThread(getter_AddRefs(thread)); + EXPECT_TRUE(NS_SUCCEEDED(rv)); + + rv = runner->Shutdown(); // wait for the runner to die before quitting + EXPECT_TRUE(NS_SUCCEEDED(rv)); + + PR_Sleep(PR_MillisecondsToInterval(100)); // hopefully the runner will quit here +} + +class nsStressRunner final : public nsIRunnable { +public: + NS_DECL_THREADSAFE_ISUPPORTS + + 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) : 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; + +NS_IMPL_ISUPPORTS(nsStressRunner, nsIRunnable) + +TEST(Threads, Stress) +{ + const int loops = 1000; + 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_NewThread(getter_AddRefs(t), new nsStressRunner(k)); + EXPECT_TRUE(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 nsIRunnable { +public: + NS_DECL_THREADSAFE_ISUPPORTS + + NS_IMETHOD Run() override { + EXPECT_FALSE(mWasRun); + mWasRun = true; + + mozilla::MonitorAutoLock lock(*gAsyncShutdownReadyMonitor); + lock.Notify(); + + return NS_OK; + } + + explicit AsyncShutdownPreparer() : mWasRun(false) {} + +private: + virtual ~AsyncShutdownPreparer() { + EXPECT_TRUE(mWasRun); + } + +protected: + bool mWasRun; +}; + +NS_IMPL_ISUPPORTS(AsyncShutdownPreparer, nsIRunnable) + +class AsyncShutdownWaiter : public nsIRunnable { +public: + NS_DECL_THREADSAFE_ISUPPORTS + + NS_IMETHOD Run() override { + EXPECT_FALSE(mWasRun); + mWasRun = true; + + nsCOMPtr t; + nsresult rv; + + { + mozilla::MonitorAutoLock lock(*gBeginAsyncShutdownMonitor); + + rv = NS_NewThread(getter_AddRefs(t), new AsyncShutdownPreparer()); + EXPECT_TRUE(NS_SUCCEEDED(rv)); + + lock.Wait(); + } + + rv = t->AsyncShutdown(); + EXPECT_TRUE(NS_SUCCEEDED(rv)); + + return NS_OK; + } + + explicit AsyncShutdownWaiter() : mWasRun(false) {} + +private: + virtual ~AsyncShutdownWaiter() { + EXPECT_TRUE(mWasRun); + } + +protected: + bool mWasRun; +}; + +NS_IMPL_ISUPPORTS(AsyncShutdownWaiter, nsIRunnable) + +class SameThreadSentinel : public nsIRunnable { +public: + NS_DECL_ISUPPORTS + + NS_IMETHOD Run() override { + mozilla::MonitorAutoLock lock(*gBeginAsyncShutdownMonitor); + lock.Notify(); + return NS_OK; + } + +private: + virtual ~SameThreadSentinel() {} +}; + +NS_IMPL_ISUPPORTS(SameThreadSentinel, nsIRunnable) + +TEST(Threads, AsyncShutdown) +{ + gAsyncShutdownReadyMonitor = new mozilla::Monitor("gAsyncShutdownReady"); + gBeginAsyncShutdownMonitor = new mozilla::Monitor("gBeginAsyncShutdown"); + + nsCOMPtr t; + nsresult rv; + + { + mozilla::MonitorAutoLock lock(*gAsyncShutdownReadyMonitor); + + rv = NS_NewThread(getter_AddRefs(t), new AsyncShutdownWaiter()); + EXPECT_TRUE(NS_SUCCEEDED(rv)); + + lock.Wait(); + } + + NS_DispatchToCurrentThread(new SameThreadSentinel()); + rv = t->Shutdown(); + EXPECT_TRUE(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) +{ + const int loops = 1000; + 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; + } +} diff --git a/xpcom/tests/gtest/TestTimeStamp.cpp b/xpcom/tests/gtest/TestTimeStamp.cpp new file mode 100644 index 000000000..4e85b7e24 --- /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::TimeStamp; +using mozilla::TimeDuration; + +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 000000000..fe7520ab1 --- /dev/null +++ b/xpcom/tests/gtest/TestTimers.cpp @@ -0,0 +1,437 @@ +/* -*- 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 "nsThreadUtils.h" +#include "prinrval.h" +#include "prmon.h" +#include "prthread.h" +#include "mozilla/Attributes.h" + +#include "mozilla/ReentrantMonitor.h" + +#include +#include + +#include "gtest/gtest.h" + +using namespace mozilla; + +typedef nsresult(*TestFuncPtr)(); + +class AutoTestThread +{ +public: + AutoTestThread() { + nsCOMPtr newThread; + nsresult rv = NS_NewThread(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* () { + 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() {} + + nsIThread** mThreadPtr; + ReentrantMonitor* mReentrantMonitor; +}; + +NS_IMPL_ISUPPORTS(TimerCallback, nsITimerCallback) + +TEST(Timers, TargetedTimers) +{ + AutoCreateAndDestroyReentrantMonitor newMon; + ASSERT_TRUE(newMon); + + AutoTestThread testThread; + ASSERT_TRUE(testThread); + + nsresult rv; + nsCOMPtr timer = do_CreateInstance(NS_TIMER_CONTRACTID, &rv); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + + nsIEventTarget* target = static_cast(testThread); + + rv = timer->SetTarget(target); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + + nsIThread* notifiedThread = nullptr; + + nsCOMPtr callback = + new TimerCallback(¬ifiedThread, newMon); + ASSERT_TRUE(callback); + + rv = timer->InitWithCallback(callback, 2000, nsITimer::TYPE_ONE_SHOT); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + + ReentrantMonitorAutoEnter mon(*newMon); + while (!notifiedThread) { + mon.Wait(); + } + ASSERT_EQ(notifiedThread, testThread); +} + +TEST(Timers, TimerWithStoppedTarget) +{ + AutoTestThread testThread; + ASSERT_TRUE(testThread); + + nsresult rv; + nsCOMPtr timer = do_CreateInstance(NS_TIMER_CONTRACTID, &rv); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + + nsIEventTarget* target = static_cast(testThread); + + rv = timer->SetTarget(target); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + + // If this is called, we'll assert + nsCOMPtr callback = + new TimerCallback(nullptr, nullptr); + ASSERT_TRUE(callback); + + rv = timer->InitWithCallback(callback, 100, nsITimer::TYPE_ONE_SHOT); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + + testThread->Shutdown(); + + PR_Sleep(400); +} + +#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) : + 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() + { + nsresult rv; + nsCOMPtr timer = do_CreateInstance(NS_TIMER_CONTRACTID, &rv); + MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv), "Failed to create timer."); + + rv = timer->SetTarget(static_cast(mThread.get())); + MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv), "Failed to set target."); + + 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)); + } + } +} diff --git a/xpcom/tests/gtest/TestTokenizer.cpp b/xpcom/tests/gtest/TestTokenizer.cpp new file mode 100644 index 000000000..283bbd3b8 --- /dev/null +++ b/xpcom/tests/gtest/TestTokenizer.cpp @@ -0,0 +1,1134 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. 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; + +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(NS_LITERAL_CSTRING( + "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(NS_LITERAL_CSTRING("test123 ,15 \t*\r\n%xx,-15\r\r")); + + 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, SingleWord) +{ + // Single word with numbers in it test + + Tokenizer p(NS_LITERAL_CSTRING("test123")); + + EXPECT_TRUE(p.CheckWord("test123")); + EXPECT_TRUE(p.CheckEOF()); +} + +TEST(Tokenizer, EndingAfterNumber) +{ + // An end handling after a number + + Tokenizer p(NS_LITERAL_CSTRING("123")); + + EXPECT_FALSE(p.CheckWord("123")); + EXPECT_TRUE(p.Check(Tokenizer::Token::Number(123))); + EXPECT_TRUE(p.CheckEOF()); +} + +TEST(Tokenizer, BadInteger) +{ + Tokenizer::Token t; + + // A bad integer test + + Tokenizer p(NS_LITERAL_CSTRING("189234891274981758617846178651647620587135")); + + 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(NS_LITERAL_CSTRING("blue velvet")); + + 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(NS_LITERAL_CSTRING("a b")); + + while (p1.Next(t) && t.Type() != Tokenizer::TOKEN_CHAR); + EXPECT_TRUE(p1.HasFailed()); + + + Tokenizer p2(NS_LITERAL_CSTRING("a b ?!c")); + + 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(NS_LITERAL_CSTRING("test")); + 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(NS_LITERAL_CSTRING("test-custom*words and\tdefault-whites"), 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(NS_LITERAL_CSTRING("test, custom,whites"), ", "); + 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(NS_LITERAL_CSTRING("test, custom, whites-and#word-chars"), ",", "-#"); + 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(NS_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(NS_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(NS_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(NS_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(NS_LITERAL_CSTRING("test")), f)); + EXPECT_TRUE(f == "Hello;"); + p.Rollback(); + + EXPECT_TRUE(p.ReadUntil(Tokenizer::Token::Word(NS_LITERAL_CSTRING("test")), 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) +{ + typedef TokenizerBase::Token Token; + + int test = 0; + IncrementalTokenizer i([&](Token const& t, IncrementalTokenizer& i) -> nsresult + { + switch (++test) { + case 1: EXPECT_TRUE(t.Equals(Token::Word(NS_LITERAL_CSTRING("test1")))); break; + case 2: EXPECT_TRUE(t.Equals(Token::Char(','))); break; + case 3: EXPECT_TRUE(t.Equals(Token::Word(NS_LITERAL_CSTRING("test2")))); 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(NS_LITERAL_CSTRING("test3")))); break; + case 8: EXPECT_TRUE(t.Equals(Token::EndOfFile())); break; + } + + return NS_OK; + }); + + NS_NAMED_LITERAL_CSTRING(input, "test1,test2,,,test3"); + auto cur = input.BeginReading(); + 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) +{ + typedef TokenizerBase::Token Token; + + int test = 0; + IncrementalTokenizer i([&](Token const& t, IncrementalTokenizer& i) -> nsresult + { + switch (++test) { + case 1: EXPECT_TRUE(t.Equals(Token::Word(NS_LITERAL_CSTRING("test1")))); break; + case 2: EXPECT_TRUE(t.Equals(Token::Char(','))); break; + case 3: EXPECT_TRUE(t.Equals(Token::Word(NS_LITERAL_CSTRING("test2")))); + i.Rollback(); // so that we get the token again + break; + case 4: EXPECT_TRUE(t.Equals(Token::Word(NS_LITERAL_CSTRING("test2")))); 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(NS_LITERAL_CSTRING("test3")))); break; + case 9: EXPECT_TRUE(t.Equals(Token::EndOfFile())); break; + } + + return NS_OK; + }); + + NS_NAMED_LITERAL_CSTRING(input, "test1,test2,,,test3"); + auto cur = input.BeginReading(); + 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) +{ + typedef TokenizerBase::Token Token; + + int test = 0; + IncrementalTokenizer i([&](Token const& t, IncrementalTokenizer& i) -> nsresult + { + Token t2; + switch (++test) { + case 1: + EXPECT_TRUE(t.Equals(Token::Word(NS_LITERAL_CSTRING("a")))); + 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(NS_LITERAL_CSTRING("bb")))); + } 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(NS_LITERAL_CSTRING("c")))); + return NS_ERROR_FAILURE; + default: + EXPECT_TRUE(false); + break; + } + + return NS_OK; + }); + + NS_NAMED_LITERAL_CSTRING(input, "a bb,c"); + auto cur = input.BeginReading(); + 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) +{ + typedef TokenizerBase::Token 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(NS_LITERAL_CSTRING("bla")))); break; + case 3: EXPECT_TRUE(t.Equals(Token::EndOfFile())); break; + } + + return NS_OK; + }, nullptr, "-"); + + custom = i.AddCustomToken("some-test", Tokenizer::CASE_SENSITIVE); + i.FeedInput(NS_LITERAL_CSTRING("some-")); + EXPECT_TRUE(test == 0); + i.FeedInput(NS_LITERAL_CSTRING("tes")); + EXPECT_TRUE(test == 0); + i.FeedInput(NS_LITERAL_CSTRING("tbla")); + EXPECT_TRUE(test == 1); + i.FinishInput(); + EXPECT_TRUE(test == 3); +} + +TEST(Tokenizer, IncrementalCustomRaw) +{ + typedef TokenizerBase::Token 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); + + NS_NAMED_LITERAL_CSTRING(input, "test1,test2!,,test3test2tes"); + auto cur = input.BeginReading(); + 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) +{ + typedef TokenizerBase::Token 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); + + NS_NAMED_LITERAL_CSTRING(input, "custom1custom1"); + i.FeedInput(input); + EXPECT_TRUE(test == 1); + i.FinishInput(); + EXPECT_TRUE(test == 3); +} + +TEST(Tokenizer, IncrementalBuffering1) +{ + typedef TokenizerBase::Token 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(NS_LITERAL_CSTRING("01234")); + EXPECT_TRUE(test == 1); + EXPECT_TRUE(observedFragment.EqualsLiteral("012")); + + i.FeedInput(NS_LITERAL_CSTRING("5")); + EXPECT_TRUE(test == 1); + i.FeedInput(NS_LITERAL_CSTRING("6789aa")); + EXPECT_TRUE(test == 2); + EXPECT_TRUE(observedFragment.EqualsLiteral("3456789")); + + i.FeedInput(NS_LITERAL_CSTRING("aqwert")); + EXPECT_TRUE(test == 4); + EXPECT_TRUE(observedFragment.EqualsLiteral("qwe")); + + i.FinishInput(); + EXPECT_TRUE(test == 6); +} + +TEST(Tokenizer, IncrementalBuffering2) +{ + typedef TokenizerBase::Token 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(NS_LITERAL_CSTRING("01234")); + EXPECT_TRUE(test == 0); + i.FeedInput(NS_LITERAL_CSTRING("5")); + EXPECT_TRUE(test == 1); + i.FeedInput(NS_LITERAL_CSTRING("6789aa")); + EXPECT_TRUE(test == 2); + i.FeedInput(NS_LITERAL_CSTRING("aqwert")); + EXPECT_TRUE(test == 4); + i.FinishInput(); + EXPECT_TRUE(test == 6); +} diff --git a/xpcom/tests/gtest/TestUTF.cpp b/xpcom/tests/gtest/TestUTF.cpp new file mode 100644 index 000000000..14dc03abe --- /dev/null +++ b/xpcom/tests/gtest/TestUTF.cpp @@ -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/. */ + +#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 "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(NS_LITERAL_CSTRING("string ") + str8)); + + nsString tmp16(NS_LITERAL_STRING("string ")); + AppendUTF8toUTF16(str8, tmp16); + EXPECT_TRUE(tmp16.Equals(NS_LITERAL_STRING("string ") + 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(NS_LITERAL_CSTRING("string ") + 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(NS_LITERAL_STRING("string ")); + AppendUTF8toUTF16(str8, tmp16); + EXPECT_TRUE(tmp16.Equals(NS_LITERAL_STRING("string ") + str16)); + + EXPECT_EQ(CompareUTF8toUTF16(str8, str16), 0); + } +} + +TEST(UTF, Malformed8) +{ +// Don't run this test in debug builds as that intentionally asserts. +#ifndef DEBUG + for (unsigned int i = 0; i < ArrayLength(Malformed8Strings); ++i) { + nsDependentCString str8(Malformed8Strings[i]); + + EXPECT_TRUE(NS_ConvertUTF8toUTF16(str8).IsEmpty()); + + nsString tmp16(NS_LITERAL_STRING("string")); + AppendUTF8toUTF16(str8, tmp16); + EXPECT_TRUE(tmp16.EqualsLiteral("string")); + + EXPECT_NE(CompareUTF8toUTF16(str8, EmptyString()), 0); + } +#endif +} + +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(HashString(Invalid8Strings[i].m16), + HashUTF8AsUTF16(str8.get(), str8.Length(), &err)); + EXPECT_FALSE(err); + } + +// Don't run this test in debug builds as that intentionally asserts. +#ifndef DEBUG + for (unsigned int i = 0; i < ArrayLength(Malformed8Strings); ++i) { + nsDependentCString str8(Malformed8Strings[i]); + bool err; + EXPECT_EQ(HashUTF8AsUTF16(str8.get(), str8.Length(), &err), 0u); + EXPECT_TRUE(err); + } +#endif +} + +/** + * This tests the handling of a non-ascii character at various locations in a + * UTF-16 string that is being converted to UTF-8. + */ +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 surrogate 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); +} + +} // namespace TestUTF diff --git a/xpcom/tests/gtest/TestXPIDLString.cpp b/xpcom/tests/gtest/TestXPIDLString.cpp new file mode 100644 index 000000000..b26cc9aff --- /dev/null +++ b/xpcom/tests/gtest/TestXPIDLString.cpp @@ -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 "nsString.h" +#include "nsReadableUtils.h" +#include "nsXPIDLString.h" +#include "gtest/gtest.h" + +static void +nsXPIDLStringTest_Value(char16_t** aResult) +{ + *aResult = ToNewUnicode(NS_LITERAL_STRING("Hello, World")); +} + +TEST(XPIDLString, Main) +{ + nsXPIDLString s1; + nsXPIDLStringTest_Value(getter_Copies(s1)); + EXPECT_TRUE(s1.EqualsLiteral("Hello, World")); +} + diff --git a/xpcom/tests/gtest/UTFStrings.h b/xpcom/tests/gtest/UTFStrings.h new file mode 100644 index 000000000..ec6bf15d3 --- /dev/null +++ b/xpcom/tests/gtest/UTFStrings.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/. */ + +#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, 'b' }, + { 'a', char(0xC0), char(0x80), 'b' } }, + { { 0xFFFD, 0x80 }, + { char(0xC1), char(0xBF), char(0xC2), char(0x80) } }, + { { 0xFFFD }, + { char(0xC1), char(0xBF) } }, + { { 0xFFFD, 'x', 0x0800 }, + { char(0xE0), char(0x80), char(0x80), 'x', char(0xE0), char(0xA0), char(0x80) } }, + { { 0xFFFD, 'x', 0xFFFD }, + { char(0xF0), char(0x80), char(0x80), char(0x80), 'x', char(0xF0), char(0x80), char(0x8F), char(0x80) } }, + { { 0xFFFD, 0xFFFD }, + { char(0xF4), char(0x90), char(0x80), char(0x80), char(0xF7), char(0xBF), char(0xBF), char(0xBF) } }, + { { 0xFFFD, 'x', 0xD800, 0xDC00, 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, 'x', 0xFFFD }, + { char(0xF8), char(0x80), char(0x80), char(0x80), char(0x80), 'x', char(0xF8), char(0x88), char(0x80), char(0x80), char(0x80) } }, + { { 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 }, + { char(0xFC), char(0x80), char(0x80), char(0x80), char(0x80), char(0x80), char(0xFD), char(0xBF), char(0xBF), char(0xBF), char(0xBF), char(0xBF) } }, + }; + +// Don't use this array in debug builds as that intentionally asserts. +#ifndef DEBUG +static const char Malformed8Strings[][16] = + { + { char(0x80) }, + { 'a', char(0xC8), 'c' }, + { 'a', char(0xC0) }, + { 'a', char(0xE8), 'c' }, + { 'a', char(0xE8), char(0x80), 'c' }, + { 'a', char(0xE8), char(0x80) }, + { char(0xE8), 0x7F, char(0x80) }, + { 'a', char(0xE8), char(0xE8), char(0x80) }, + { 'a', char(0xF4) }, + { 'a', char(0xF4), char(0x80), char(0x80), 'c', 'c' }, + { 'a', char(0xF4), char(0x80), 'x', char(0x80) }, + { char(0xF4), char(0x80), char(0x80), char(0x80), char(0x80) }, + { 'a', char(0xFA), 'c' }, + { 'a', char(0xFA), char(0x80), char(0x80), 0x7F, char(0x80), 'c' }, + { 'a', char(0xFA), char(0x80), char(0x80), char(0x80), char(0x80), char(0x80), 'c' }, + { 'a', char(0xFD) }, + { 'a', char(0xFD), char(0x80), char(0x80), char(0x80), char(0x80), 'c' }, + { 'a', char(0xFD), char(0x80), char(0x80), char(0x80), char(0x80), char(0x80), char(0x80) }, + { 'a', char(0xFC), char(0x80), char(0x80), 0x40, char(0x80), char(0x80), 'c' }, + }; +#endif + +#endif diff --git a/xpcom/tests/gtest/moz.build b/xpcom/tests/gtest/moz.build new file mode 100644 index 000000000..53836eaef --- /dev/null +++ b/xpcom/tests/gtest/moz.build @@ -0,0 +1,77 @@ +# -*- 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', + 'TestAtoms.cpp', + 'TestAutoPtr.cpp', + 'TestAutoRef.cpp', + 'TestBase64.cpp', + 'TestCallTemplates.cpp', + 'TestCloneInputStream.cpp', + 'TestCOMArray.cpp', + 'TestCOMPtrEq.cpp', + 'TestCRT.cpp', + 'TestEncoding.cpp', + 'TestEscapeURL.cpp', + 'TestExpirationTracker.cpp', + 'TestFile.cpp', + 'TestID.cpp', + 'TestNSPRLogModulesParser.cpp', + 'TestObserverArray.cpp', + 'TestObserverService.cpp', + 'TestPipes.cpp', + 'TestPLDHash.cpp', + 'TestPriorityQueue.cpp', + 'TestRacingServiceManager.cpp', + 'TestSlicedInputStream.cpp', + 'TestSnappyStreams.cpp', + 'TestStateWatching.cpp', + 'TestStorageStream.cpp', + 'TestStrings.cpp', + 'TestStringStream.cpp', + 'TestSynchronization.cpp', + 'TestTArray.cpp', + 'TestTArray2.cpp', + 'TestTextFormatter.cpp', + 'TestThreadPool.cpp', + 'TestThreadPoolListener.cpp', + 'TestThreads.cpp', + 'TestThreadUtils.cpp', + 'TestTimers.cpp', + 'TestTimeStamp.cpp', + 'TestTokenizer.cpp', + 'TestUTF.cpp', + 'TestXPIDLString.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['WRAP_STL_INCLUDES'] and not CONFIG['CLANG_CL']: + UNIFIED_SOURCES += [ + 'TestSTLWrappers.cpp', + ] + +# Compile TestAllocReplacement separately so Windows headers don't pollute +# the global namespace for other files. +SOURCES += [ + 'TestAllocReplacement.cpp', + 'TestCOMPtr.cpp', # Redefines IFoo and IBar + 'TestHashtables.cpp', # Redefines IFoo + 'TestNsRefPtr.cpp', # Redefines Foo +] + +LOCAL_INCLUDES += [ + '../../base', +] + +FINAL_LIBRARY = 'xul-gtest' diff --git a/xpcom/tests/moz.build b/xpcom/tests/moz.build new file mode 100644 index 000000000..b5fc138e7 --- /dev/null +++ b/xpcom/tests/moz.build @@ -0,0 +1,51 @@ +# -*- 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 += [ + 'external', + 'component', + 'bug656331_component', + 'component_no_aslr', + 'gtest', +] + +if CONFIG['OS_ARCH'] == 'WINNT': + TEST_DIRS += ['windows'] + +EXPORTS.testing += [ + 'TestHarness.h', +] + +SimplePrograms([ + 'TestArguments', + 'TestBlockingProcess', + 'TestPRIntN', + 'TestQuickReturn', + 'TestUnicodeArguments', +]) + +XPCSHELL_TESTS_MANIFESTS += ['unit/xpcshell.ini'] + +if CONFIG['COMPILE_ENVIRONMENT']: + TEST_HARNESS_FILES.xpcshell.xpcom.tests.unit += [ + '!/dist/bin/components/xpcomtest.xpt', + ] + +XPIDL_MODULE = 'xpcomtest' +XPIDL_SOURCES += [ + 'NotXPCOMTest.idl', +] + +# Don't add our test-only .xpt files to the normal manifests +XPIDL_NO_MANIFEST = True + +LOCAL_INCLUDES += [ + '../ds', +] + +RESOURCE_FILES += [ + 'test.properties', +] diff --git a/xpcom/tests/resources.h b/xpcom/tests/resources.h new file mode 100644 index 000000000..4deea759f --- /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 000000000..19cae9702 --- /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/bug725015.manifest b/xpcom/tests/unit/bug725015.manifest new file mode 100644 index 000000000..2f432f0b2 --- /dev/null +++ b/xpcom/tests/unit/bug725015.manifest @@ -0,0 +1,3 @@ +category bug725015-test-category bug725015-category-entry @bug725015.test.contract +component {05070380-6e6e-42ba-aaa5-3289fc55ca5a} dummyfile.js +contract @bug725015.test.contract {05070380-6e6e-42ba-aaa5-3289fc55ca5a} diff --git a/xpcom/tests/unit/compmgr_warnings.manifest b/xpcom/tests/unit/compmgr_warnings.manifest new file mode 100644 index 000000000..6b60990db --- /dev/null +++ b/xpcom/tests/unit/compmgr_warnings.manifest @@ -0,0 +1,9 @@ +# The following line is malformed (mismatched braces) +component {94b346d7-0cde-4e6e-b819-95d6f200bbf6 MyComponent.js + +component 94b346d7-0cde-4e6e-b819-95d6f200bbf7 MyComponent.js +# The following line re-registers an existing CID +component {94b346d7-0cde-4e6e-b819-95d6f200bbf7} MyOtherComponent.js + +# The following line maps a contractID to a non-existent CID +contract @testing/foo {0c07730f-f875-436b-8deb-90c4251920ec} 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 000000000..8388fa2a5 --- /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 000000000..c821003d3 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 000000000..bd04210fb --- /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 000000000..5e45963c3 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 000000000..59f8803c5 --- /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 000000000..bb27d4a5d 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 000000000..f7885e4fc --- /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 000000000..b45fc9698 --- /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/child_process_directive_service.js b/xpcom/tests/unit/data/child_process_directive_service.js new file mode 100644 index 000000000..9bc1c3206 --- /dev/null +++ b/xpcom/tests/unit/data/child_process_directive_service.js @@ -0,0 +1,21 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +Components.utils.import("resource:///modules/XPCOMUtils.jsm"); + +function TestProcessDirective() {} +TestProcessDirective.prototype = { + + /* Boilerplate */ + QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsISupportsString]), + contractID: "@mozilla.org/xpcom/tests/ChildProcessDirectiveTest;1", + classID: Components.ID("{4bd1ba60-45c4-11e4-916c-0800200c9a66}"), + + type: Components.interfaces.nsISupportsString.TYPE_STRING, + data: "child process", + toString: function() { + return this.data; + } +}; + +this.NSGetFactory = XPCOMUtils.generateNSGetFactory([TestProcessDirective]); diff --git a/xpcom/tests/unit/data/iniparser01-utf16leBOM.ini b/xpcom/tests/unit/data/iniparser01-utf16leBOM.ini new file mode 100644 index 000000000..46b134b19 --- /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 000000000..5f282702b --- /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 000000000..e69de29bb diff --git a/xpcom/tests/unit/data/iniparser02-utf16leBOM.ini b/xpcom/tests/unit/data/iniparser02-utf16leBOM.ini new file mode 100644 index 000000000..49cc8ef0e 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 000000000..e02abfc9b --- /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 000000000..d3f5a12fa --- /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 000000000..05255100a 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 000000000..b76e44e19 --- /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 000000000..60b074253 --- /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 000000000..e95d97113 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 000000000..47ef32c0a --- /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 000000000..23a50d155 --- /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 000000000..a49491816 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 000000000..eb33b5ccf --- /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 000000000..ade137337 --- /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 000000000..e9023ac7c 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 000000000..073d841cf --- /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 000000000..c24821e6e --- /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 000000000..d1c167e6e 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 000000000..38176d944 --- /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 000000000..49816873b --- /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 000000000..e450566a0 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 000000000..5fa7d2495 --- /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 000000000..cfa15c9ff --- /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 000000000..ef1da39e2 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 000000000..e3edce4d4 --- /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 000000000..1c87762ba --- /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 000000000..e5e70b661 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 000000000..bda15fcc7 --- /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 000000000..037fd7930 --- /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 000000000..932d4004b 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 000000000..78caafaba --- /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 000000000..f8d573a28 --- /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 000000000..8a2912722 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 000000000..09ca62779 --- /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 000000000..2727940c0 --- /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 000000000..ebd9a51d3 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 000000000..8c9499b66 --- /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 000000000..21d40b140 --- /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 000000000..bbc3413aa 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 000000000..d109052c8 --- /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 000000000..744af4cb6 --- /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 000000000..e60525dec 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 000000000..172803f90 --- /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 000000000..608a27d8f --- /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 000000000..142b17590 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 000000000..bba1018da --- /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 000000000..b94607d15 --- /dev/null +++ b/xpcom/tests/unit/data/iniparser16.ini @@ -0,0 +1,13 @@ +#á¿»á¹Ò³Ï–·Ì˄ȡǨŅ©& +[☺♫] +#ѼΏá¹Ò³Ï– +♫=☻ +#·Ì˄ȡǨŅ© +♪=♥ +#‽ἧᵿΏá¹Ò³ +#ϖ·Ì˄ȡǨŅ©& +[☼] +♣=â™  +♦=♥ +#‽ἧᵿΏá¹Ò³ +#·Ì˄ȡǨŅ© diff --git a/xpcom/tests/unit/data/main_process_directive_service.js b/xpcom/tests/unit/data/main_process_directive_service.js new file mode 100644 index 000000000..f4eed11c9 --- /dev/null +++ b/xpcom/tests/unit/data/main_process_directive_service.js @@ -0,0 +1,21 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +Components.utils.import("resource:///modules/XPCOMUtils.jsm"); + +function TestProcessDirective() {} +TestProcessDirective.prototype = { + + /* Boilerplate */ + QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsISupportsString]), + contractID: "@mozilla.org/xpcom/tests/MainProcessDirectiveTest;1", + classID: Components.ID("{9b6f4160-45be-11e4-916c-0800200c9a66}"), + + type: Components.interfaces.nsISupportsString.TYPE_STRING, + data: "main process", + toString: function() { + return this.data; + } +}; + +this.NSGetFactory = XPCOMUtils.generateNSGetFactory([TestProcessDirective]); 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 000000000..e69de29bb 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 000000000..b0bc8e076 --- /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 000000000..26178d809 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 000000000..8b49316b4 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 000000000..9cbf7f241 --- /dev/null +++ b/xpcom/tests/unit/data/process_directive.manifest @@ -0,0 +1,5 @@ +component {9b6f4160-45be-11e4-916c-0800200c9a66} main_process_directive_service.js process=main +contract @mozilla.org/xpcom/tests/MainProcessDirectiveTest;1 {9b6f4160-45be-11e4-916c-0800200c9a66} process=main + +component {4bd1ba60-45c4-11e4-916c-0800200c9a66} child_process_directive_service.js process=content +contract @mozilla.org/xpcom/tests/ChildProcessDirectiveTest;1 {4bd1ba60-45c4-11e4-916c-0800200c9a66} process=content diff --git a/xpcom/tests/unit/head_xpcom.js b/xpcom/tests/unit/head_xpcom.js new file mode 100644 index 000000000..eacd5b4e6 --- /dev/null +++ b/xpcom/tests/unit/head_xpcom.js @@ -0,0 +1,21 @@ +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() +{ + var envSvc = Components.classes["@mozilla.org/process/environment;1"]. + getService(Components.interfaces.nsIEnvironment); + var dirSvc = Components.classes["@mozilla.org/file/directory_service;1"]. + getService(Components.interfaces.nsIProperties); + var greBinDir = dirSvc.get("GreBinD", Components.interfaces.nsIFile); + envSvc.set("DYLD_LIBRARY_PATH", greBinDir.path); + // For Linux + envSvc.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 000000000..3aa04186e --- /dev/null +++ b/xpcom/tests/unit/test_bug121341.js @@ -0,0 +1,71 @@ +var Ci = Components.interfaces; +var Cu = Components.utils; +Cu.import("resource://gre/modules/NetUtil.jsm"); + +function run_test() { + var ios = Components.classes["@mozilla.org/network/io-service;1"]. + getService(Components.interfaces.nsIIOService); + + var dataFile = do_get_file("data/bug121341.properties"); + var channel = NetUtil.newChannel({ + uri: ios.newFileURI(dataFile, null, null), + loadUsingSystemPrincipal: true + }); + var inp = channel.open2(); + + var properties = Components.classes["@mozilla.org/persistent-properties;1"]. + createInstance(Components.interfaces.nsIPersistentProperties); + properties.load(inp); + + var value; + + value = properties.getStringProperty("1"); + do_check_eq(value, "abc"); + + value = properties.getStringProperty("2"); + do_check_eq(value, "xy"); + + value = properties.getStringProperty("3"); + do_check_eq(value, "\u1234\t\r\n\u00AB\u0001\n"); + + value = properties.getStringProperty("4"); + do_check_eq(value, "this is multiline property"); + + value = properties.getStringProperty("5"); + do_check_eq(value, "this is another multiline property"); + + value = properties.getStringProperty("6"); + do_check_eq(value, "test\u0036"); + + value = properties.getStringProperty("7"); + do_check_eq(value, "yet another multiline propery"); + + value = properties.getStringProperty("8"); + do_check_eq(value, "\ttest5\u0020"); + + value = properties.getStringProperty("9"); + do_check_eq(value, " test6\t"); + + value = properties.getStringProperty("10a\u1234b"); + do_check_eq(value, "c\uCDEFd"); + + value = properties.getStringProperty("11"); + do_check_eq(value, "\uABCD"); + + dataFile = do_get_file("data/bug121341-2.properties"); + + var channel = NetUtil.newChannel({ + uri: ios.newFileURI(dataFile, null, null), + loadUsingSystemPrincipal: true + }); + inp = channel.open2(); + + var properties2 = Components.classes["@mozilla.org/persistent-properties;1"]. + createInstance(Components.interfaces.nsIPersistentProperties); + try { + properties2.load(inp); + do_throw("load() didn't fail"); + } + catch (e) { + } +} diff --git a/xpcom/tests/unit/test_bug325418.js b/xpcom/tests/unit/test_bug325418.js new file mode 100644 index 000000000..b18866d7e --- /dev/null +++ b/xpcom/tests/unit/test_bug325418.js @@ -0,0 +1,63 @@ +var Cc = Components.classes; +var Ci = Components.interfaces; + +// 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. + do_check_eq(Math.round((Date.now() - gStartTime1) / 1000), + kExpectedDelay1); + + timer = null; + + do_print("1st timer triggered (before being cancelled). Should not have happened!"); + do_check_true(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. + do_check_eq(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 000000000..91f8943e8 --- /dev/null +++ b/xpcom/tests/unit/test_bug332389.js @@ -0,0 +1,19 @@ +var Cc = Components.classes; +var Ci = Components.interfaces; + +function run_test() { + var f = + Cc["@mozilla.org/file/directory_service;1"]. + getService(Ci.nsIProperties).get("CurProcD", Ci.nsIFile); + + var terminated = false; + for (var i = 0; i < 100; i++) { + if (f == null) { + terminated = true; + break; + } + f = f.parent; + } + + do_check_true(terminated); +} diff --git a/xpcom/tests/unit/test_bug333505.js b/xpcom/tests/unit/test_bug333505.js new file mode 100644 index 000000000..856468605 --- /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 000000000..f8bd6ee15 --- /dev/null +++ b/xpcom/tests/unit/test_bug364285-1.js @@ -0,0 +1,51 @@ +var Ci = Components.interfaces; +var Cc = Components.classes; + +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() +{ + var dirService = Cc["@mozilla.org/file/directory_service;1"] + .getService(Ci.nsIProperties); + return dirService.get("TmpD", Ci.nsILocalFile); +} + +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(); + + do_check_eq(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 000000000..9b0ccf26e --- /dev/null +++ b/xpcom/tests/unit/test_bug374754.js @@ -0,0 +1,59 @@ +var Cc = Components.classes; +var Ci = Components.interfaces; + +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: function(iid) { + if (iid.equals(Ci.nsISupports) || iid.equals(Ci.nsIObserver)) + return this; + + throw Components.results.NS_ERROR_NO_INTERFACE; + }, + + observe: function(subject, topic, data) { + if (topic == "timer-callback") { + do_check_eq(result, expected); + + var observerService = Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService); + observerService.removeObserver(this, addedTopic); + observerService.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(); + + var observerService = Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService); + observerService.addObserver(observer, addedTopic, false); + observerService.addObserver(observer, removedTopic, false); + + var categoryManager = Cc["@mozilla.org/categorymanager;1"].getService(Ci.nsICategoryManager); + categoryManager.addCategoryEntry(testCategory, testEntry, testValue, false, true); + categoryManager.addCategoryEntry(testCategory, testEntry, testValue, false, true); + categoryManager.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 000000000..21cd9253e --- /dev/null +++ b/xpcom/tests/unit/test_bug476919.js @@ -0,0 +1,27 @@ +var Cc = Components.classes; +var Ci = Components.interfaces; + +function run_test() { + // skip this test on Windows + if (mozinfo.os != "win") { + 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.nsILocalFile); + 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); + do_check_true(link.isSymlink()); + } +} diff --git a/xpcom/tests/unit/test_bug478086.js b/xpcom/tests/unit/test_bug478086.js new file mode 100644 index 000000000..bd6ea0d08 --- /dev/null +++ b/xpcom/tests/unit/test_bug478086.js @@ -0,0 +1,24 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/licenses/publicdomain/ */ + +function run_test() { + var nsILocalFile = Components.interfaces.nsILocalFile; + var root = Components.classes["@mozilla.org/file/local;1"]. + createInstance(nsILocalFile); + + // 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; + do_check_true(drives.hasMoreElements()); + while (drives.hasMoreElements()) { + var newPath = drives.getNext().QueryInterface(nsILocalFile).path; + do_check_eq(newPath.indexOf("\0"), -1); + } +} diff --git a/xpcom/tests/unit/test_bug656331.js b/xpcom/tests/unit/test_bug656331.js new file mode 100644 index 000000000..3bc1f82c0 --- /dev/null +++ b/xpcom/tests/unit/test_bug656331.js @@ -0,0 +1,39 @@ +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); + +var Cc = Components.classes; +var Ci = Components.interfaces; + +function info(s) { + dump("TEST-INFO | test_bug656331.js | " + s + "\n"); +} + +var gMessageExpected = /Native module.*has version 3.*expected/; +var gFound = false; + +const kConsoleListener = { + QueryInterface: XPCOMUtils.generateQI([Ci.nsIConsoleListener]), + + observe: function listener_observe(message) { + if (gMessageExpected.test(message.message)) + gFound = true; + } +}; + +function run_test() { + let cs = Components.classes["@mozilla.org/consoleservice;1"]. + getService(Ci.nsIConsoleService); + cs.registerListener(kConsoleListener); + + let manifest = do_get_file('components/bug656331.manifest'); + registerAppManifest(manifest); + + do_check_false("{f18fb09b-28b4-4435-bc5b-8027f18df743}" in Components.classesByID); + + do_test_pending(); + Components.classes["@mozilla.org/thread-manager;1"]. + getService(Ci.nsIThreadManager).mainThread.dispatch(function() { + cs.unregisterListener(kConsoleListener); + do_check_true(gFound); + do_test_finished(); + }, 0); +} diff --git a/xpcom/tests/unit/test_bug725015.js b/xpcom/tests/unit/test_bug725015.js new file mode 100644 index 000000000..d6f62c509 --- /dev/null +++ b/xpcom/tests/unit/test_bug725015.js @@ -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/. */ + +var Cc = Components.classes; +var Ci = Components.interfaces; + +Components.utils.import("resource://gre/modules/Services.jsm"); + +const manifest = do_get_file('bug725015.manifest'); +const contract = "@bug725015.test.contract"; +const observerTopic = "xpcom-category-entry-added"; +const category = "bug725015-test-category"; +const entry = "bug725015-category-entry"; +const cid = Components.ID("{05070380-6e6e-42ba-aaa5-3289fc55ca5a}"); + +function observe_category(subj, topic, data) { + try { + do_check_eq(topic, observerTopic); + if (data != category) + return; + + var thisentry = subj.QueryInterface(Ci.nsISupportsCString).data; + do_check_eq(thisentry, entry); + + do_check_eq(Cc["@mozilla.org/categorymanager;1"].getService(Ci.nsICategoryManager).getCategoryEntry(category, entry), contract); + do_check_true(Cc[contract].equals(cid)); + } + catch (e) { + do_throw(e); + } + do_test_finished(); +} + +function run_test() { + do_test_pending(); + Services.obs.addObserver(observe_category, observerTopic, false); + Components.manager.QueryInterface(Ci.nsIComponentRegistrar).autoRegister(manifest); +} diff --git a/xpcom/tests/unit/test_bug745466.js b/xpcom/tests/unit/test_bug745466.js new file mode 100644 index 000000000..22a911ac5 --- /dev/null +++ b/xpcom/tests/unit/test_bug745466.js @@ -0,0 +1,6 @@ +Components.utils.import("resource://gre/modules/FileUtils.jsm"); + +function run_test() +{ + do_check_true(FileUtils.File("~").equals(FileUtils.getDir("Home", []))); +} diff --git a/xpcom/tests/unit/test_comp_no_aslr.js b/xpcom/tests/unit/test_comp_no_aslr.js new file mode 100644 index 000000000..7e15731c6 --- /dev/null +++ b/xpcom/tests/unit/test_comp_no_aslr.js @@ -0,0 +1,18 @@ +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); + +var Cc = Components.classes; +var Ci = Components.interfaces; + +function run_test() { + let manifest = do_get_file('components/testcompnoaslr.manifest'); + registerAppManifest(manifest); + var sysInfo = Cc["@mozilla.org/system-info;1"]. + getService(Ci.nsIPropertyBag2); + var ver = parseFloat(sysInfo.getProperty("version")); + if (ver < 6.0) { + // This is disabled on pre-Vista OSs. + do_check_true("{335fb596-e52d-418f-b01c-1bf16ce5e7e4}" in Components.classesByID); + } else { + do_check_false("{335fb596-e52d-418f-b01c-1bf16ce5e7e4}" in Components.classesByID); + } +} diff --git a/xpcom/tests/unit/test_compmgr_warnings.js b/xpcom/tests/unit/test_compmgr_warnings.js new file mode 100644 index 000000000..be77b0d1b --- /dev/null +++ b/xpcom/tests/unit/test_compmgr_warnings.js @@ -0,0 +1,71 @@ +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); + +var Cc = Components.classes; +var Ci = Components.interfaces; + +function info(s) { + dump("TEST-INFO | test_compmgr_warnings.js | " + s + "\n"); +} + +var gMessagesExpected = [ + { line: 2, message: /Malformed CID/, found: false }, + { line: 6, message: /re-register/, found: false }, + { line: 9, message: /Could not/, found: false }, + { line: 2, message: /binary component twice/, found: false }, + { line: 3, message: /binary component twice/, found: false }, +]; + +const kConsoleListener = { + QueryInterface: XPCOMUtils.generateQI([Ci.nsIConsoleListener]), + + observe: function listener_observe(message) { + if (!(message instanceof Ci.nsIScriptError)) { + info("Not a script error: " + message.message); + return; + } + + info("Script error... " + message.sourceName + ":" + message.lineNumber + ": " + message.errorMessage); + for (let expected of gMessagesExpected) { + if (message.lineNumber != expected.line) + continue; + + if (!expected.message.test(message.errorMessage)) + continue; + + info("Found expected message: " + expected.message); + do_check_false(expected.found); + + expected.found = true; + } + } +}; + +function run_deferred_event(fn) { + do_test_pending(); + Components.classes["@mozilla.org/thread-manager;1"]. + getService(Ci.nsIThreadManager).mainThread.dispatch(function() { + fn(); + do_test_finished(); + }, 0); +} + +function run_test() +{ + let cs = Components.classes["@mozilla.org/consoleservice;1"]. + getService(Ci.nsIConsoleService); + cs.registerListener(kConsoleListener); + + var manifest = do_get_file('compmgr_warnings.manifest'); + registerAppManifest(manifest); + manifest = do_get_file('components/testcomponent.manifest'); + registerAppManifest(manifest); + + run_deferred_event(function() { + cs.unregisterListener(kConsoleListener); + + for (let expected of gMessagesExpected) { + info("checking " + expected.message); + do_check_true(expected.found); + } + }); +} 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 000000000..450d62793 --- /dev/null +++ b/xpcom/tests/unit/test_debugger_malloc_size_of.js @@ -0,0 +1,34 @@ +/* -*- 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. + +var Cu = Components.utils; +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) { + do_print(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 000000000..1ab204bab --- /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/. */ + +var Cc = Components.classes; +var Ci = Components.interfaces; +var Cr = Components.results; + +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 = Cc["@mozilla.org/file/directory_service;1"]. + getService(Ci.nsIProperties).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)) { + // 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 000000000..235792560 --- /dev/null +++ b/xpcom/tests/unit/test_file_equality.js @@ -0,0 +1,43 @@ +/* -*- 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 Cr = Components.results; +var Ci = Components.interfaces; + +var CC = Components.Constructor; +var LocalFile = CC("@mozilla.org/file/local;1", "nsILocalFile", "initWithPath"); + +function run_test() +{ + test_normalized_vs_non_normalized(); +} + +function test_normalized_vs_non_normalized() +{ + // get a directory that exists on all platforms + var dirProvider = Components.classes["@mozilla.org/file/directory_service;1"].getService(Ci.nsIProperties); + var tmp1 = dirProvider.get("TmpD", Ci.nsILocalFile); + var exists = tmp1.exists(); + do_check_true(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); + do_check_true(tmp1.equals(tmp2)); + + // this is a non-normalized version of tmp1, it should not equal tmp1 + tmp2.appendRelativePath("."); + do_check_false(tmp1.equals(tmp2)); + + // normalize and make sure they are equivalent again + tmp2.normalize(); + do_check_true(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 000000000..0a7196fe2 --- /dev/null +++ b/xpcom/tests/unit/test_file_renameTo.js @@ -0,0 +1,61 @@ +/* -*- 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/. */ + +var Cc = Components.classes; +var Ci = Components.interfaces; + +function run_test() +{ + // Create the base directory. + let base = Cc['@mozilla.org/file/directory_service;1'] + .getService(Ci.nsIProperties) + .get('TmpD', Ci.nsILocalFile); + 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'); + do_check_true(exists(subdir, 'file1.txt')); + + // Test moving across directories + tempFile = subdir.clone(); + tempFile.append('file1.txt'); + tempFile.renameTo(base, ''); + do_check_true(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'); + do_check_true(exists(subdir, 'file2.txt')); + + // Test moving a directory + subdir.renameTo(base, 'renamed'); + do_check_true(exists(base, 'renamed')); + let renamed = base.clone(); + renamed.append('renamed'); + do_check_true(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_hidden_files.js b/xpcom/tests/unit/test_hidden_files.js new file mode 100644 index 000000000..3383ba11b --- /dev/null +++ b/xpcom/tests/unit/test_hidden_files.js @@ -0,0 +1,28 @@ +var Ci = Components.interfaces; +var Cc = Components.classes; +const NS_OS_TEMP_DIR = "TmpD"; + +const CWD = do_get_cwd(); + +var hiddenUnixFile; +function createUNIXHiddenFile() { + var dirSvc = Cc["@mozilla.org/file/directory_service;1"].getService(Ci.nsIProperties); + var tmpDir = 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; + + do_check_true(createUNIXHiddenFile()); + do_check_true(hiddenUnixFile.isHidden()); +} + diff --git a/xpcom/tests/unit/test_home.js b/xpcom/tests/unit/test_home.js new file mode 100644 index 000000000..61fa5b344 --- /dev/null +++ b/xpcom/tests/unit/test_home.js @@ -0,0 +1,24 @@ +var Ci = Components.interfaces; +var Cc = Components.classes; + +const CWD = do_get_cwd(); +function checkOS(os) { + const nsILocalFile_ = "nsILocalFile" + os; + return nsILocalFile_ in Components.interfaces && + CWD instanceof Components.interfaces[nsILocalFile_]; +} + +const isWin = checkOS("Win"); + +function run_test() { + var envVar = isWin ? "USERPROFILE" : "HOME"; + + var dirSvc = Cc["@mozilla.org/file/directory_service;1"].getService(Ci.nsIProperties); + var homeDir = dirSvc.get("Home", Ci.nsIFile); + + var env = Cc["@mozilla.org/process/environment;1"].getService(Ci.nsIEnvironment); + var expected = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile); + expected.initWithPath(env.get(envVar)); + + do_check_eq(homeDir.path, expected.path); +} diff --git a/xpcom/tests/unit/test_iniProcessor.js b/xpcom/tests/unit/test_iniProcessor.js new file mode 100644 index 000000000..3d3886d35 --- /dev/null +++ b/xpcom/tests/unit/test_iniProcessor.js @@ -0,0 +1,288 @@ +var Ci = Components.interfaces; +var Cc = Components.classes; +var Cr = Components.results; + +var testnum = 0; +var factory; + +function parserForFile(filename) { + let parser = null; + try { + let file = do_get_file(filename); + do_check_true(!!file); + parser = factory.createINIParser(file); + do_check_true(!!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) { + do_check_eq(parser, null); + do_check_eq(expected, null); + return; + } + + let output = getParserOutput(parser); + for (let section in expected) { + do_check_true(section in output); + for (let key in expected[section]) { + do_check_true(key in output[section]); + do_check_eq(output[section][key], expected[section][key]); + delete output[section][key]; + } + for (let key in output[section]) + do_check_eq(key, "wasn't expecting this key!"); + delete output[section]; + } + for (let section in output) + do_check_eq(section, "wasn't expecting this section!"); +} + +function getParserOutput(parser) { + let output = {}; + + let sections = parser.getSections(); + do_check_true(!!sections); + while (sections.hasMore()) { + let section = sections.getNext(); + do_check_false(section in output); // catch dupes + output[section] = {}; + + let keys = parser.getKeys(section); + do_check_true(!!keys); + while (keys.hasMore()) { + let key = keys.getNext(); + do_check_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: + { "☺♫": { "♫": "☻", "♪": "♥" }, + "☼": { "♣": "â™ ", "♦": "♥" }} }, + + ]; + + 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 } ); + + 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-processor-factory;1"]. + getService(Ci.nsIINIParserFactory); +do_check_true(!!factory); + +// Test reading from a variety of files. 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; + do_check_true(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); +} + +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); +do_check_false(newfile.exists()); + +var parser = factory.createINIParser(newfile); +do_check_true(!!parser); +do_check_true(parser instanceof Ci.nsIINIParserWriter); +checkParserOutput(parser, {}); +parser.writeFile(); +do_check_true(newfile.exists()); + +// test adding a new section and new key +parser.setString("section", "key", "value"); +parser.writeFile(); +do_check_true(newfile.exists()); +checkParserOutput(parser, {section: {key: "value"} }); +// read it in again, check for same data. +parser = parserForFile("data/nonexistent-file.ini"); +checkParserOutput(parser, {section: {key: "value"} }); +// 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"} }); + +do_check_true(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 = false; +checkParserOutput(parser, {section1: {name1: "value2"} }); + +// Bad characters in section name +try { parser.SetString("bad\0", "ok", "ok"); } catch (e) { caughtError = true; } +do_check_true(caughtError); +caughtError = false; +try { parser.SetString("bad\r", "ok", "ok"); } catch (e) { caughtError = true; } +do_check_true(caughtError); +caughtError = false; +try { parser.SetString("bad\n", "ok", "ok"); } catch (e) { caughtError = true; } +do_check_true(caughtError); +caughtError = false; +try { parser.SetString("bad[", "ok", "ok"); } catch (e) { caughtError = true; } +do_check_true(caughtError); +caughtError = false; +try { parser.SetString("bad]", "ok", "ok"); } catch (e) { caughtError = true; } +do_check_true(caughtError); + +// Bad characters in key name +caughtError = false; +try { parser.SetString("ok", "bad\0", "ok"); } catch (e) { caughtError = true; } +do_check_true(caughtError); +caughtError = false; +try { parser.SetString("ok", "bad\r", "ok"); } catch (e) { caughtError = true; } +do_check_true(caughtError); +caughtError = false; +try { parser.SetString("ok", "bad\n", "ok"); } catch (e) { caughtError = true; } +do_check_true(caughtError); +caughtError = false; +try { parser.SetString("ok", "bad=", "ok"); } catch (e) { caughtError = true; } +do_check_true(caughtError); + +// Bad characters in value +caughtError = false; +try { parser.SetString("ok", "ok", "bad\0"); } catch (e) { caughtError = true; } +do_check_true(caughtError); +caughtError = false; +try { parser.SetString("ok", "ok", "bad\r"); } catch (e) { caughtError = true; } +do_check_true(caughtError); +caughtError = false; +try { parser.SetString("ok", "ok", "bad\n"); } catch (e) { caughtError = true; } +do_check_true(caughtError); +caughtError = false; +try { parser.SetString("ok", "ok", "bad="); } catch (e) { caughtError = true; } +do_check_true(caughtError); + +} catch(e) { + throw "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 000000000..ef27f584f --- /dev/null +++ b/xpcom/tests/unit/test_ioutil.js @@ -0,0 +1,33 @@ +/* -*- 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/. */ + +var Cc = Components.classes; +var Ci = Components.interfaces; +var Cr = Components.results; + +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) { + do_check_eq(e.result, Cr.NS_ERROR_INVALID_POINTER); + } + + try { + util.outputStreamIsBuffered(null); + do_throw("outputStreamIsBuffered should have thrown"); + } catch (e) { + do_check_eq(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); + do_check_eq(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 000000000..25f4bf34b --- /dev/null +++ b/xpcom/tests/unit/test_localfile.js @@ -0,0 +1,151 @@ +/* -*- 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 Cr = Components.results; +var CC = Components.Constructor; +var Ci = Components.interfaces; + +const MAX_TIME_DIFFERENCE = 2500; +const MILLIS_PER_DAY = 1000 * 60 * 60 * 24; + +var LocalFile = CC("@mozilla.org/file/local;1", "nsILocalFile", "initWithPath"); + +function run_test() +{ + test_toplevel_parent_is_null(); + test_normalize_crash_if_media_missing(); + test_file_modification_time(); + test_directory_modification_time(); + test_diskSpaceAvailable(); +} + +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 + do_check_true(lf.path.length == 2); + + do_check_true(lf.parent === null); + } + catch (e) + { + // not Windows + do_check_eq(e.result, Cr.NS_ERROR_FILE_UNRECOGNIZED_PATH); + } +} + +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 +function test_file_modification_time() +{ + var file = do_get_profile(); + file.append("testfile"); + + // Should never happen but get rid of it anyway + if (file.exists()) + file.remove(true); + + var now = Date.now(); + file.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0o644); + do_check_true(file.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(file.lastModifiedTime - now); + do_check_true(diff < MAX_TIME_DIFFERENCE); + + var yesterday = now - MILLIS_PER_DAY; + file.lastModifiedTime = yesterday; + + diff = Math.abs(file.lastModifiedTime - yesterday); + do_check_true(diff < MAX_TIME_DIFFERENCE); + + var tomorrow = now - MILLIS_PER_DAY; + file.lastModifiedTime = tomorrow; + + diff = Math.abs(file.lastModifiedTime - tomorrow); + do_check_true(diff < MAX_TIME_DIFFERENCE); + + var bug377307 = 1172950238000; + file.lastModifiedTime = bug377307; + + diff = Math.abs(file.lastModifiedTime - bug377307); + do_check_true(diff < MAX_TIME_DIFFERENCE); + + file.remove(true); +} + +// Tests that changing a directory's modification time is possible +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); + do_check_true(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); + do_check_true(diff < MAX_TIME_DIFFERENCE); + + var yesterday = now - MILLIS_PER_DAY; + dir.lastModifiedTime = yesterday; + + diff = Math.abs(dir.lastModifiedTime - yesterday); + do_check_true(diff < MAX_TIME_DIFFERENCE); + + var tomorrow = now - MILLIS_PER_DAY; + dir.lastModifiedTime = tomorrow; + + diff = Math.abs(dir.lastModifiedTime - tomorrow); + do_check_true(diff < MAX_TIME_DIFFERENCE); + + dir.remove(true); +} + +function test_diskSpaceAvailable() +{ + let file = do_get_profile(); + file.QueryInterface(Ci.nsILocalFile); + + let bytes = file.diskSpaceAvailable; + do_check_true(bytes > 0); + + file.append("testfile"); + if (file.exists()) + file.remove(true); + file.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0o644); + + bytes = file.diskSpaceAvailable; + do_check_true(bytes > 0); + + file.remove(true); +} diff --git a/xpcom/tests/unit/test_mac_bundle.js b/xpcom/tests/unit/test_mac_bundle.js new file mode 100644 index 000000000..550a4abd6 --- /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"); + + do_check_true(keynoteBundle instanceof Components.interfaces.nsILocalFileMac); + do_check_true(appBundle instanceof Components.interfaces.nsILocalFileMac); + + do_check_true(keynoteBundle.isPackage()); + do_check_true(appBundle.isPackage()); +} diff --git a/xpcom/tests/unit/test_notxpcom_scriptable.js b/xpcom/tests/unit/test_notxpcom_scriptable.js new file mode 100644 index 000000000..5d5e24bd9 --- /dev/null +++ b/xpcom/tests/unit/test_notxpcom_scriptable.js @@ -0,0 +1,86 @@ +/* -*- 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/. */ + +var Cc = Components.classes; +var Ci = Components.interfaces; +var Cu = Components.utils; +var Cr = Components.results; + +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); + +const kCID = Components.ID("{1f9f7181-e6c5-4f4c-8f71-08005cec8468}"); +const kContract = "@testing/notxpcomtest"; + +function run_test() +{ + let manifest = do_get_file("xpcomtest.manifest"); + let registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar); + registrar.autoRegister(manifest); + + ok(Ci.ScriptableWithNotXPCOM); + + let method1Called = false; + + let testObject = { + QueryInterface: XPCOMUtils.generateQI([Ci.ScriptableOK, + Ci.ScriptableWithNotXPCOM, + Ci.ScriptableWithNotXPCOMBase]), + + method1: function() { + method1Called = true; + }, + + method2: function() { + ok(false, "method2 should not have been called!"); + }, + + method3: function() { + ok(false, "mehod3 should not have been called!"); + }, + + jsonly: true, + }; + + let factory = { + QueryInterface: XPCOMUtils.generateQI([Ci.nsIFactory]), + + createInstance: function(outer, iid) { + if (outer) { + throw Cr.NS_ERROR_NO_AGGREGATION; + } + return testObject.QueryInterface(iid); + }, + }; + + registrar.registerFactory(kCID, null, kContract, factory); + + let xpcomObject = Cc[kContract].createInstance(); + ok(xpcomObject); + strictEqual(xpcomObject.jsonly, undefined); + + xpcomObject.QueryInterface(Ci.ScriptableOK); + + xpcomObject.method1(); + ok(method1Called); + + try { + xpcomObject.QueryInterface(Ci.ScriptableWithNotXPCOM); + ok(false, "Should not have implemented ScriptableWithNotXPCOM"); + } + catch(e) { + ok(true, "Should not have implemented ScriptableWithNotXPCOM. Correctly threw error: " + e); + } + strictEqual(xpcomObject.method2, undefined); + + try { + xpcomObject.QueryInterface(Ci.ScriptableWithNotXPCOMBase); + ok(false, "Should not have implemented ScriptableWithNotXPCOMBase"); + } + catch (e) { + ok(true, "Should not have implemented ScriptableWithNotXPCOMBase. Correctly threw error: " + e); + } + strictEqual(xpcomObject.method3, undefined); +} + diff --git a/xpcom/tests/unit/test_nsIMutableArray.js b/xpcom/tests/unit/test_nsIMutableArray.js new file mode 100644 index 000000000..b491aee96 --- /dev/null +++ b/xpcom/tests/unit/test_nsIMutableArray.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/. */ + +var Ci = Components.interfaces; +var Cr = Components.results; +var Cc = Components.classes; +var CC = Components.Constructor; + +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 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.getNext().QueryInterface(nsIFile); + const name = file.leafName; + print(name + spaces.substring(name.length) + bools[file.isDirectory()] + + bools[file.isFile()] + bools[file.isSymlink()]); + do_check_eq(file.isDirectory(), dirs.indexOf(name) != -1); + do_check_eq(file.isFile(), files.indexOf(name) != -1); + do_check_eq(file.isSymlink(), links.indexOf(name) != -1); + } +} + +function run_test() { + // Skip this test on Windows + if (mozinfo.os == "win") + return; + + 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 000000000..58fa8042c --- /dev/null +++ b/xpcom/tests/unit/test_systemInfo.js @@ -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/. */ + +function run_test() { + const PROPERTIES = ["name", "host", "arch", "version", "pagesize", + "pageshift", "memmapalign", "cpucount", "memsize"]; + let sysInfo = Components.classes["@mozilla.org/system-info;1"]. + getService(Components.interfaces.nsIPropertyBag2); + + PROPERTIES.forEach(function(aPropertyName) { + print("Testing property: " + aPropertyName); + let value = sysInfo.getProperty(aPropertyName); + do_check_true(!!value); + }); + + // This property must exist, but its value might be zero. + print("Testing property: umask") + do_check_eq(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 000000000..35f8f6eee --- /dev/null +++ b/xpcom/tests/unit/test_versioncomparator.js @@ -0,0 +1,59 @@ +// Versions to test listed in ascending order, none can be equal +var comparisons = [ + "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() +{ + var vc = Components.classes["@mozilla.org/xpcom/version-comparator;1"] + .getService(Components.interfaces.nsIVersionComparator); + + for (var i = 0; i < comparisons.length; i++) { + for (var j = 0; j < comparisons.length; j++) { + var result = 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 (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 000000000..4a3a6cc5f --- /dev/null +++ b/xpcom/tests/unit/test_windows_cmdline_file.js @@ -0,0 +1,21 @@ +let { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components; +Cu.import("resource://gre/modules/Services.jsm"); + +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; + do_print(`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 000000000..9a17678f8 --- /dev/null +++ b/xpcom/tests/unit/test_windows_registry.js @@ -0,0 +1,205 @@ +/* -*- 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 Cr = Components.results; +const Ci = Components.interfaces; +const Cc = Components.classes; +const Cu = Components.utils; +const CC = Components.Constructor; + +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) +{ + do_print("Setup test run"); + try { + testKey.open(nsIWindowsRegKey.ROOT_KEY_CURRENT_USER, keyName, nsIWindowsRegKey.ACCESS_READ); + do_print("Test key exists. Needs cleanup."); + cleanup_test_run(testKey, keyName); + } + catch (e if (e instanceof Ci.nsIException && e.result == Cr.NS_ERROR_FAILURE)) + { + } + + 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)) { + } + + 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)) { + } + + 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)) { + } + + 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)) { + } + +} + +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) +{ + do_print("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 = 9223372036854775807; +const TESTDATA_BINARYNAME = "ABinary"; +const TESTDATA_BINARYVALUE = "She sells seashells by the seashore"; +const TESTDATA_CHILD_KEY = "TestChildKey"; diff --git a/xpcom/tests/unit/test_windows_shortcut.js b/xpcom/tests/unit/test_windows_shortcut.js new file mode 100644 index 000000000..42cb023ff --- /dev/null +++ b/xpcom/tests/unit/test_windows_shortcut.js @@ -0,0 +1,279 @@ +/* -*- 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 Cr = Components.results; +var Ci = Components.interfaces; +var Cc = Components.classes; +var Cu = Components.utils; +var CC = Components.Constructor; + +const LocalFile = CC("@mozilla.org/file/local;1", "nsILocalFile", "initWithPath"); + +Cu.import("resource://gre/modules/Services.jsm"); + +function run_test() +{ + // This test makes sense only on Windows, so skip it on other platforms + if ("nsILocalFileWin" in Ci + && do_get_cwd() instanceof Ci.nsILocalFileWin) { + + let tempDir = Services.dirsvc.get("TmpD", Ci.nsILocalFile); + tempDir.append("shortcutTesting"); + tempDir.createUnique(Ci.nsIFile.DIRECTORY_TYPE, 0o666); + + test_create_noargs(tempDir); + test_create_notarget(tempDir); + test_create_targetonly(tempDir); + test_create_normal(tempDir); + test_create_unicode(tempDir); + + test_update_noargs(tempDir); + test_update_notarget(tempDir); + test_update_targetonly(tempDir); + test_update_normal(tempDir); + test_update_unicode(tempDir); + } +} + +function test_create_noargs(tempDir) +{ + let shortcutFile = tempDir.clone(); + shortcutFile.append("shouldNeverExist.lnk"); + shortcutFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o666); + + let win = shortcutFile.QueryInterface(Ci.nsILocalFileWin); + + try + { + win.setShortcut(); + do_throw("Creating a shortcut with no args (no target) should throw"); + } + catch(e if (e instanceof Ci.nsIException + && e.result == Cr.NS_ERROR_FILE_TARGET_DOES_NOT_EXIST)) + { + + } +} + +function test_create_notarget(tempDir) +{ + let shortcutFile = tempDir.clone(); + shortcutFile.append("shouldNeverExist2.lnk"); + shortcutFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o666); + + let win = shortcutFile.QueryInterface(Ci.nsILocalFileWin); + + try + { + win.setShortcut(null, + do_get_cwd(), + "arg1 arg2", + "Shortcut with no target"); + do_throw("Creating a shortcut with no target should throw"); + } + catch(e if (e instanceof Ci.nsIException + && e.result == Cr.NS_ERROR_FILE_TARGET_DOES_NOT_EXIST)) + { + + } +} + +function test_create_targetonly(tempDir) +{ + let shortcutFile = tempDir.clone(); + shortcutFile.append("createdShortcut.lnk"); + shortcutFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o666); + + let targetFile = tempDir.clone(); + targetFile.append("shortcutTarget.exe"); + targetFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o666); + + let win = shortcutFile.QueryInterface(Ci.nsILocalFileWin); + + win.setShortcut(targetFile); + + let shortcutTarget = LocalFile(shortcutFile.target); + do_check_true(shortcutTarget.equals(targetFile)); +} + +function test_create_normal(tempDir) +{ + let shortcutFile = tempDir.clone(); + shortcutFile.append("createdShortcut.lnk"); + shortcutFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o666); + + let targetFile = tempDir.clone(); + targetFile.append("shortcutTarget.exe"); + targetFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o666); + + let win = shortcutFile.QueryInterface(Ci.nsILocalFileWin); + + win.setShortcut(targetFile, + do_get_cwd(), + "arg1 arg2", + "Ordinary shortcut"); + + let shortcutTarget = LocalFile(shortcutFile.target); + do_check_true(shortcutTarget.equals(targetFile)) +} + +function test_create_unicode(tempDir) +{ + let shortcutFile = tempDir.clone(); + shortcutFile.append("createdShortcut.lnk"); + shortcutFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o666); + + let targetFile = tempDir.clone(); + targetFile.append("ṩhогТϾừ†Target.exe"); + targetFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o666); + + let win = shortcutFile.QueryInterface(Ci.nsILocalFileWin); + + win.setShortcut(targetFile, + do_get_cwd(), // XXX: This should probably be a unicode dir + "ᾶṟǵ1 ᾶṟǵ2", + "ῧṋіḉѻₑ"); + + let shortcutTarget = LocalFile(shortcutFile.target); + do_check_true(shortcutTarget.equals(targetFile)) +} + +function test_update_noargs(tempDir) +{ + let shortcutFile = tempDir.clone(); + shortcutFile.append("createdShortcut.lnk"); + shortcutFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o666); + + let targetFile = tempDir.clone(); + targetFile.append("shortcutTarget.exe"); + targetFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o666); + + let win = shortcutFile.QueryInterface(Ci.nsILocalFileWin); + + win.setShortcut(targetFile, + do_get_cwd(), + "arg1 arg2", + "A sample shortcut"); + + win.setShortcut(); + + let shortcutTarget = LocalFile(shortcutFile.target); + do_check_true(shortcutTarget.equals(targetFile)) +} + +function test_update_notarget(tempDir) +{ + let shortcutFile = tempDir.clone(); + shortcutFile.append("createdShortcut.lnk"); + shortcutFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o666); + + let targetFile = tempDir.clone(); + targetFile.append("shortcutTarget.exe"); + targetFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o666); + + let win = shortcutFile.QueryInterface(Ci.nsILocalFileWin); + + win.setShortcut(targetFile, + do_get_cwd(), + "arg1 arg2", + "A sample shortcut"); + + win.setShortcut(null, + do_get_profile(), + "arg3 arg4", + "An UPDATED shortcut"); + + let shortcutTarget = LocalFile(shortcutFile.target); + do_check_true(shortcutTarget.equals(targetFile)) +} + +function test_update_targetonly(tempDir) +{ + let shortcutFile = tempDir.clone(); + shortcutFile.append("createdShortcut.lnk"); + shortcutFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o666); + + let targetFile = tempDir.clone(); + targetFile.append("shortcutTarget.exe"); + targetFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o666); + + let win = shortcutFile.QueryInterface(Ci.nsILocalFileWin); + + win.setShortcut(targetFile, + do_get_cwd(), + "arg1 arg2", + "A sample shortcut"); + + let newTargetFile = tempDir.clone(); + newTargetFile.append("shortcutTarget.exe"); + shortcutFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o666); + + win.setShortcut(newTargetFile); + + let shortcutTarget = LocalFile(shortcutFile.target); + do_check_true(shortcutTarget.equals(newTargetFile)) +} + +function test_update_normal(tempDir) +{ + let shortcutFile = tempDir.clone(); + shortcutFile.append("createdShortcut.lnk"); + shortcutFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o666); + + let targetFile = tempDir.clone(); + targetFile.append("shortcutTarget.exe"); + targetFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o666); + + let win = shortcutFile.QueryInterface(Ci.nsILocalFileWin); + + win.setShortcut(targetFile, + do_get_cwd(), + "arg1 arg2", + "A sample shortcut"); + + let newTargetFile = tempDir.clone(); + newTargetFile.append("shortcutTarget.exe"); + newTargetFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o666); + + win.setShortcut(newTargetFile, + do_get_profile(), + "arg3 arg4", + "An UPDATED shortcut"); + + let shortcutTarget = LocalFile(shortcutFile.target); + do_check_true(shortcutTarget.equals(newTargetFile)) +} + +function test_update_unicode(tempDir) +{ + let shortcutFile = tempDir.clone(); + shortcutFile.append("createdShortcut.lnk"); + shortcutFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o666); + + let targetFile = tempDir.clone(); + targetFile.append("shortcutTarget.exe"); + targetFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o666); + + let win = shortcutFile.QueryInterface(Ci.nsILocalFileWin); + + win.setShortcut(targetFile, + do_get_cwd(), + "arg1 arg2", + "A sample shortcut"); + + let newTargetFile = tempDir.clone(); + newTargetFile.append("ṩhогТϾừ†Target.exe"); + shortcutFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o666); + + win.setShortcut(newTargetFile, + do_get_profile(), // XXX: This should probably be unicode + "ᾶṟǵ3 ᾶṟǵ4", + "A ῧṋіḉѻₑ shortcut"); + + let shortcutTarget = LocalFile(shortcutFile.target); + do_check_true(shortcutTarget.equals(newTargetFile)) +} diff --git a/xpcom/tests/unit/xpcomtest.manifest b/xpcom/tests/unit/xpcomtest.manifest new file mode 100644 index 000000000..43a4931c6 --- /dev/null +++ b/xpcom/tests/unit/xpcomtest.manifest @@ -0,0 +1 @@ +interfaces xpcomtest.xpt diff --git a/xpcom/tests/unit/xpcshell.ini b/xpcom/tests/unit/xpcshell.ini new file mode 100644 index 000000000..cf8d93627 --- /dev/null +++ b/xpcom/tests/unit/xpcshell.ini @@ -0,0 +1,79 @@ +[DEFAULT] +head = head_xpcom.js +tail = +support-files = + bug725015.manifest + compmgr_warnings.manifest + data/** + xpcomtest.xpt + xpcomtest.manifest +generated-files = + xpcomtest.xpt + +[test_bug121341.js] +[test_bug325418.js] +[test_bug332389.js] +[test_bug333505.js] +[test_bug364285-1.js] +# Bug 902073: test fails consistently on Android x86 +skip-if = os == "android" +[test_bug374754.js] +[test_bug476919.js] +# Bug 676998: test fails consistently on Android +fail-if = os == "android" +[test_bug478086.js] +[test_bug656331.js] +# Bug 676998: test fails consistently on Android +fail-if = os == "android" +[test_bug725015.js] +[test_debugger_malloc_size_of.js] +[test_compmgr_warnings.js] +# Bug 676998: test fails consistently on Android +fail-if = os == "android" +[test_file_createUnique.js] +[test_file_equality.js] +[test_hidden_files.js] +[test_home.js] +# Bug 676998: test fails consistently on Android +fail-if = os == "android" +[test_iniProcessor.js] +[test_ioutil.js] +[test_localfile.js] +[test_mac_bundle.js] +[test_nsIMutableArray.js] +[test_nsIProcess.js] +skip-if = os == "win" || os == "linux" # bug 582821, bug 1325609 +# Bug 676998: test fails consistently on Android +fail-if = os == "android" +[test_nsIProcess_stress.js] +skip-if = os == "win" # bug 676412, test isn't needed on windows and runs really slowly +[test_pipe.js] +[test_process_directives.js] +skip-if = os == "android" +[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] +# Bug 676998: test fails consistently on Android +fail-if = os == "android" +[test_systemInfo.js] +# Bug 902081: test fails consistently on Android 2.2, passes on 4.0 +skip-if = os == "android" +[test_versioncomparator.js] +[test_comp_no_aslr.js] +skip-if = os != "win" +[test_windows_shortcut.js] +skip-if = os != "win" +[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 000000000..9f0b9854c --- /dev/null +++ b/xpcom/tests/windows/TestCOM.cpp @@ -0,0 +1,158 @@ +/* -*- 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 "nsIFactory.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() { + 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; +} + +class nsTestComFactory final : public nsIFactory { + ~nsTestComFactory() { sDestructions++; } + NS_DECL_ISUPPORTS +public: + nsTestComFactory() { + } + + NS_IMETHOD CreateInstance(nsISupports *aOuter, + const nsIID &aIID, + void **aResult); + + NS_IMETHOD LockFactory(bool aLock) { + return NS_OK; + } + + static int sDestructions; +}; + +int nsTestComFactory::sDestructions; + +NS_IMPL_ISUPPORTS(nsTestComFactory, nsIFactory) + +nsresult nsTestComFactory::CreateInstance(nsISupports *aOuter, + const nsIID &aIID, + void **aResult) +{ + if (aOuter != nullptr) { + return NS_ERROR_NO_AGGREGATION; + } + + nsTestCom *t = new nsTestCom(); + + if (t == nullptr) { + return NS_ERROR_OUT_OF_MEMORY; + } + + NS_ADDREF(t); + nsresult res = t->QueryInterface(aIID, aResult); + NS_RELEASE(t); + + return res; +} + +TEST(TestCOM, WindowsInterop) +{ + nsTestComFactory *inst = new nsTestComFactory(); + + // Test we can QI nsIFactory to an IClassFactory. + IClassFactory *iFactory = nullptr; + nsresult rv = inst->QueryInterface(NS_GET_IID(nsIFactory), + (void **) &iFactory); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + ASSERT_TRUE(iFactory); + + // Test we can CreateInstance with an IUnknown. + IUnknown *iUnknown = nullptr; + + HRESULT hr = iFactory->LockServer(TRUE); + ASSERT_TRUE(SUCCEEDED(hr)); + hr = iFactory->CreateInstance(nullptr, IID_IUnknown, (void **) &iUnknown); + ASSERT_TRUE(SUCCEEDED(hr)); + ASSERT_TRUE(iUnknown); + hr = iFactory->LockServer(FALSE); + ASSERT_TRUE(SUCCEEDED(hr)); + + // Test we can QI an IUnknown to nsITestCom. + nsITestCom *iTestCom = nullptr; + GUID testGUID = NS_ITEST_COM_IID; + hr = iUnknown->QueryInterface(testGUID, + (void **) &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_TRUE(NS_SUCCEEDED(rv)); + + iUnknown->Release(); + iTestCom->Release(); + iFactory->Release(); + + ASSERT_EQ(nsTestComFactory::sDestructions, 1); + ASSERT_EQ(nsTestCom::sDestructions, 1); +} diff --git a/xpcom/tests/windows/TestHelloXPLoop.cpp b/xpcom/tests/windows/TestHelloXPLoop.cpp new file mode 100644 index 000000000..ffb4442e0 --- /dev/null +++ b/xpcom/tests/windows/TestHelloXPLoop.cpp @@ -0,0 +1,144 @@ +/* -*- 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 "nsIServiceManager.h" +#include "nsCOMPtr.h" +#include "nsCNativeApp.h" +#include "nsIEventLoop.h" +#include + +static NS_DEFINE_CID(kNativeAppCID, NS_NATIVE_APP_CID); + +LRESULT CALLBACK WndProc(HWND wnd, UINT msg, WPARAM wParam, LPARAM lParam); + +void ErrorBox(LPSTR text) +{ + MessageBox(nullptr, text, "XP Event Loop", MB_OK | MB_ICONSTOP); +} + +void InfoBox(LPSTR text) +{ + MessageBox(nullptr, text, "XP Event Loop", MB_OK | MB_ICONINFORMATION); +} + +int WINAPI WinMain(HINSTANCE inst, + HINSTANCE prevInstance, + LPSTR lpszCmdLine, + int nShowCmd) +{ + char* lpszAppName = "HelloWorld"; + HWND wnd; + WNDCLASSEX wndclass; + int retCode; + + { // Needed to scope all nsCOMPtr within XPCOM Init and Shutdown + nsresult rv; + nsCOMPtr servMan; + rv = NS_InitXPCOM2(getter_AddRefs(servMan), nullptr, nullptr); + if(NS_FAILED(rv)) + { + ErrorBox("Failed to initialize xpcom."); + return -1; + } + + nsCOMPtr registrar = do_QueryInterface(servMan); + NS_ASSERTION(registrar, "Null nsIComponentRegistrar"); + registrar->AutoRegister(nullptr); + + nsCOMPtr nativeAppService(do_GetService(kNativeAppCID, &rv)); + + if(NS_FAILED(rv)) + { + ErrorBox("Failed to get nativeAppService"); + return -1; + } + wndclass.cbSize = sizeof(wndclass); + wndclass.style = CS_HREDRAW | CS_VREDRAW; + wndclass.lpfnWndProc = WndProc; + wndclass.cbClsExtra = 0; + wndclass.cbWndExtra = 0; + wndclass.hInstance = inst; + wndclass.hIcon = LoadIcon(nullptr, IDI_APPLICATION); + wndclass.hCursor = LoadCursor(nullptr, IDC_ARROW); + wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); + wndclass.lpszMenuName = nullptr; + wndclass.lpszClassName = lpszAppName; + wndclass.hIconSm = LoadIcon(nullptr, IDI_APPLICATION); + + RegisterClassEx(&wndclass) ; + + wnd = CreateWindow(lpszAppName, "The Hello World", + WS_OVERLAPPEDWINDOW, + CW_USEDEFAULT, CW_USEDEFAULT, + CW_USEDEFAULT, CW_USEDEFAULT, + nullptr, nullptr, inst, nullptr); + + ShowWindow(wnd, nShowCmd); + UpdateWindow(wnd); + + nsCOMPtr eventLoop; + + if(NS_FAILED(nativeAppService->CreateEventLoop(L"_MainLoop", + nsEventLoopTypes::MainAppLoop, getter_AddRefs(eventLoop)))) + { + ErrorBox("Failed to create event Loop"); + return 0; + } + + eventLoop->Run(nullptr, nullptr, nullptr, &retCode); + eventLoop = nullptr; // Clear out before Shutting down XPCOM + + InfoBox("Hello World app is out of loop"); + } + NS_ShutdownXPCOM(nullptr); + InfoBox("Hello World app is exiting"); + return retCode; +} + +LRESULT CALLBACK WndProc(HWND wnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + HDC hdc; + PAINTSTRUCT ps; + RECT rect; + + switch(msg) + { + case WM_PAINT: + hdc = BeginPaint(wnd, &ps); + + GetClientRect(wnd, &rect); + + DrawText(hdc, "Hello, XP Event Loop!", -1, &rect, + DT_SINGLELINE | DT_CENTER | DT_VCENTER); + + EndPaint(wnd, &ps); + return 0; + + case WM_DESTROY: + { + nsresult rv; + nsCOMPtr nativeAppService = + do_GetService(kNativeAppCID, &rv); + if(NS_FAILED(rv)) + { + ErrorBox("Could not get NativeAppService"); + return 0; + } + nsCOMPtr eventLoop; + + if(NS_FAILED(nativeAppService->FindEventLoop(L"_MainLoop", + getter_AddRefs(eventLoop)))) + { + ErrorBox("Failed to find event Loop"); + return 0; + } + eventLoop->Exit(0); + } + return 0; + } + + return DefWindowProc(wnd, msg, wParam, lParam); +} diff --git a/xpcom/tests/windows/TestNTFSPermissions.cpp b/xpcom/tests/windows/TestNTFSPermissions.cpp new file mode 100644 index 000000000..062a9e650 --- /dev/null +++ b/xpcom/tests/windows/TestNTFSPermissions.cpp @@ -0,0 +1,286 @@ +/* -*- 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 for NTFS File Permissions being correctly changed to match the new + * directory upon moving a file. (Bug 224692.) + */ + +#include "../TestHarness.h" +#include "nsEmbedString.h" +#include "nsIFile.h" +#include +#include + +#define BUFFSIZE 512 + + + +nsresult TestPermissions() +{ + + nsresult rv; // Return value + + // File variables + HANDLE tempFileHandle; + nsCOMPtr tempFile; + nsCOMPtr tempDirectory1; + nsCOMPtr tempDirectory2; + WCHAR filePath[MAX_PATH]; + WCHAR dir1Path[MAX_PATH]; + WCHAR dir2Path[MAX_PATH]; + + // Security variables + DWORD result; + PSID everyoneSID = nullptr, adminSID = nullptr; + PACL dirACL = nullptr, fileACL = nullptr; + PSECURITY_DESCRIPTOR dirSD = nullptr, fileSD = nullptr; + EXPLICIT_ACCESS ea[2]; + SID_IDENTIFIER_AUTHORITY SIDAuthWorld = + SECURITY_WORLD_SID_AUTHORITY; + SID_IDENTIFIER_AUTHORITY SIDAuthNT = SECURITY_NT_AUTHORITY; + SECURITY_ATTRIBUTES sa; + TRUSTEE everyoneTrustee; + ACCESS_MASK everyoneRights; + + // Create a well-known SID for the Everyone group. + if(!AllocateAndInitializeSid(&SIDAuthWorld, 1, + SECURITY_WORLD_RID, + 0, 0, 0, 0, 0, 0, 0, + &everyoneSID)) + { + fail("NTFS Permissions: AllocateAndInitializeSid Error"); + return NS_ERROR_FAILURE; + } + + // Create a SID for the Administrators group. + if(! AllocateAndInitializeSid(&SIDAuthNT, 2, + SECURITY_BUILTIN_DOMAIN_RID, + DOMAIN_ALIAS_RID_ADMINS, + 0, 0, 0, 0, 0, 0, + &adminSID)) + { + fail("NTFS Permissions: AllocateAndInitializeSid Error"); + return NS_ERROR_FAILURE; + } + + // Initialize an EXPLICIT_ACCESS structure for an ACE. + // The ACE will allow Everyone read access to the directory. + ZeroMemory(&ea, 2 * sizeof(EXPLICIT_ACCESS)); + ea[0].grfAccessPermissions = GENERIC_READ; + ea[0].grfAccessMode = SET_ACCESS; + ea[0].grfInheritance= SUB_CONTAINERS_AND_OBJECTS_INHERIT; + ea[0].Trustee.TrusteeForm = TRUSTEE_IS_SID; + ea[0].Trustee.TrusteeType = TRUSTEE_IS_WELL_KNOWN_GROUP; + ea[0].Trustee.ptstrName = (LPTSTR) everyoneSID; + + // Initialize an EXPLICIT_ACCESS structure for an ACE. + // The ACE will allow the Administrators group full access + ea[1].grfAccessPermissions = GENERIC_ALL | STANDARD_RIGHTS_ALL; + ea[1].grfAccessMode = SET_ACCESS; + ea[1].grfInheritance= SUB_CONTAINERS_AND_OBJECTS_INHERIT; + ea[1].Trustee.TrusteeForm = TRUSTEE_IS_SID; + ea[1].Trustee.TrusteeType = TRUSTEE_IS_GROUP; + ea[1].Trustee.ptstrName = (LPTSTR) adminSID; + + // Create a new ACL that contains the new ACEs. + result = SetEntriesInAcl(2, ea, nullptr, &dirACL); + if (ERROR_SUCCESS != result) + { + fail("NTFS Permissions: SetEntriesInAcl Error"); + return NS_ERROR_FAILURE; + } + + // Initialize a security descriptor. + dirSD = (PSECURITY_DESCRIPTOR) LocalAlloc(LPTR, + SECURITY_DESCRIPTOR_MIN_LENGTH); + if (nullptr == dirSD) + { + fail("NTFS Permissions: LocalAlloc Error"); + return NS_ERROR_FAILURE; + } + + if (!InitializeSecurityDescriptor(dirSD, + SECURITY_DESCRIPTOR_REVISION)) + { + fail("NTFS Permissions: InitializeSecurityDescriptor Error"); + return NS_ERROR_FAILURE; + } + + // Add the ACL to the security descriptor. + if (!SetSecurityDescriptorDacl(dirSD, true, dirACL, false)) + { + fail("NTFS Permissions: SetSecurityDescriptorDacl Error"); + return NS_ERROR_FAILURE; + } + + // Initialize a security attributes structure. + sa.nLength = sizeof (SECURITY_ATTRIBUTES); + sa.lpSecurityDescriptor = dirSD; + sa.bInheritHandle = false; + + // Create and open first temporary directory + if(!CreateDirectoryW(L".\\NTFSPERMTEMP1", &sa)) + { + fail("NTFS Permissions: Creating Temporary Directory"); + return NS_ERROR_FAILURE; + } + + GetFullPathNameW((LPCWSTR)L".\\NTFSPERMTEMP1", MAX_PATH, dir1Path, + nullptr); + + rv = NS_NewLocalFile(nsEmbedString(dir1Path), false, + getter_AddRefs(tempDirectory1)); + if (NS_FAILED(rv)) + { + fail("NTFS Permissions: Opening Temporary Directory 1"); + return rv; + } + + + // Create and open temporary file + tempFileHandle = CreateFileW(L".\\NTFSPERMTEMP1\\NTFSPerm.tmp", + GENERIC_READ | GENERIC_WRITE, + 0, + nullptr, //default security + CREATE_ALWAYS, + FILE_ATTRIBUTE_NORMAL, + nullptr); + + if(tempFileHandle == INVALID_HANDLE_VALUE) + { + fail("NTFS Permissions: Creating Temporary File"); + return NS_ERROR_FAILURE; + } + + CloseHandle(tempFileHandle); + + GetFullPathNameW((LPCWSTR)L".\\NTFSPERMTEMP1\\NTFSPerm.tmp", + MAX_PATH, filePath, nullptr); + + rv = NS_NewLocalFile(nsEmbedString(filePath), false, + getter_AddRefs(tempFile)); + if (NS_FAILED(rv)) + { + fail("NTFS Permissions: Opening Temporary File"); + return rv; + } + + // Update Everyone Explict_Acess to full access. + ea[0].grfAccessPermissions = GENERIC_ALL | STANDARD_RIGHTS_ALL; + + // Update the ACL to contain the new ACEs. + result = SetEntriesInAcl(2, ea, nullptr, &dirACL); + if (ERROR_SUCCESS != result) + { + fail("NTFS Permissions: SetEntriesInAcl 2 Error"); + return NS_ERROR_FAILURE; + } + + // Add the new ACL to the security descriptor. + if (!SetSecurityDescriptorDacl(dirSD, true, dirACL, false)) + { + fail("NTFS Permissions: SetSecurityDescriptorDacl 2 Error"); + return NS_ERROR_FAILURE; + } + + // Create and open second temporary directory + if(!CreateDirectoryW(L".\\NTFSPERMTEMP2", &sa)) + { + fail("NTFS Permissions: Creating Temporary Directory 2"); + return NS_ERROR_FAILURE; + } + + GetFullPathNameW((LPCWSTR)L".\\NTFSPERMTEMP2", MAX_PATH, dir2Path, + nullptr); + + rv = NS_NewLocalFile(nsEmbedString(dir2Path), false, + getter_AddRefs(tempDirectory2)); + if (NS_FAILED(rv)) + { + fail("NTFS Permissions: Opening Temporary Directory 2"); + return rv; + } + + // Move the file. + rv = tempFile->MoveTo(tempDirectory2, EmptyString()); + + if (NS_FAILED(rv)) + { + fail("NTFS Permissions: Moving"); + return rv; + } + + // Access the ACL of the file + result = GetNamedSecurityInfoW(L".\\NTFSPERMTEMP2\\NTFSPerm.tmp", + SE_FILE_OBJECT, + DACL_SECURITY_INFORMATION | + UNPROTECTED_DACL_SECURITY_INFORMATION, + nullptr, nullptr, &fileACL, nullptr, + &fileSD); + if (ERROR_SUCCESS != result) + { + fail("NTFS Permissions: GetNamedSecurityDescriptor Error"); + return NS_ERROR_FAILURE; + } + + // Build a trustee representing "Everyone" + BuildTrusteeWithSid(&everyoneTrustee, everyoneSID); + + // Get Everyone's effective rights. + result = GetEffectiveRightsFromAcl(fileACL, &everyoneTrustee, + &everyoneRights); + if (ERROR_SUCCESS != result) + { + fail("NTFS Permissions: GetEffectiveRightsFromAcl Error"); + return NS_ERROR_FAILURE; + } + + // Check for delete access, which we won't have unless permissions have + // updated + if((everyoneRights & DELETE) == (DELETE)) + { + passed("NTFS Permissions Test"); + rv = NS_OK; + } + else + { + fail("NTFS Permissions: Access check."); + rv = NS_ERROR_FAILURE; + } + + // Cleanup + if (everyoneSID) + FreeSid(everyoneSID); + if (adminSID) + FreeSid(adminSID); + if (dirACL) + LocalFree(dirACL); + if (dirSD) + LocalFree(dirSD); + if(fileACL) + LocalFree(fileACL); + + tempDirectory1->Remove(true); + tempDirectory2->Remove(true); + + return rv; +} + +int main(int argc, char** argv) +{ + ScopedXPCOM xpcom("NTFSPermissionsTests"); // name for tests being run + if (xpcom.failed()) + return 1; + + int rv = 0; + + if(NS_FAILED(TestPermissions())) + rv = 1; + + return rv; + +} + diff --git a/xpcom/tests/windows/TestNtPathToDosPath.cpp b/xpcom/tests/windows/TestNtPathToDosPath.cpp new file mode 100644 index 000000000..b826d4f20 --- /dev/null +++ b/xpcom/tests/windows/TestNtPathToDosPath.cpp @@ -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/. */ + +#include +#include + +#include "mozilla/FileUtilsWin.h" +#include "mozilla/DebugOnly.h" +#include "nsCRTGlue.h" + +#include "gtest/gtest.h" + +class DriveMapping +{ +public: + 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(NS_LITERAL_STRING("\\\\127.0.0.1\\C$")); + // 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/TestWinFileAttribs.cpp b/xpcom/tests/windows/TestWinFileAttribs.cpp new file mode 100644 index 000000000..56fbcbdea --- /dev/null +++ b/xpcom/tests/windows/TestWinFileAttribs.cpp @@ -0,0 +1,173 @@ +/* -*- 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/. */ + +/* + * Test: + */ + +#include "../TestHarness.h" +#include "nsEmbedString.h" +#include "nsILocalFileWin.h" +#include + +#define BUFFSIZE 512 + +nsresult TestWinAttribs() +{ + + nsresult rv; + + // File variables + HANDLE hIndexed; + nsCOMPtr localFile; + WCHAR filePath[MAX_PATH]; + + // Create and open temporary file + hIndexed = CreateFileW(L".\\indexbit.txt", + GENERIC_READ | GENERIC_WRITE, + 0, + nullptr, + CREATE_ALWAYS, + FILE_ATTRIBUTE_NORMAL, //FILE_ATTRIBUTE_NOT_CONTENT_INDEXED, not supported by cf + nullptr); + + if(hIndexed == INVALID_HANDLE_VALUE) + { + fail("Test Win Attribs: Creating Test File"); + return NS_ERROR_FAILURE; + } + + CloseHandle(hIndexed); + + GetFullPathNameW((LPCWSTR)L".\\indexbit.txt", + MAX_PATH, filePath, nullptr); + + //wprintf(filePath); + //wprintf(L"\n"); + + rv = NS_NewLocalFile(nsEmbedString(filePath), false, + getter_AddRefs(localFile)); + if (NS_FAILED(rv)) + { + fail("Test Win Attribs: Opening Test File"); + DeleteFileW(filePath); + return rv; + } + + nsCOMPtr localFileWin(do_QueryInterface(localFile)); + + DWORD dwAttrs = GetFileAttributesW(filePath); + if (dwAttrs == INVALID_FILE_ATTRIBUTES) + { + fail("Test Win Attribs: GetFileAttributesW - couldn't find our temp file."); + DeleteFileW(filePath); + return NS_ERROR_FAILURE; + } + + dwAttrs |= FILE_ATTRIBUTE_NOT_CONTENT_INDEXED; + SetFileAttributesW(filePath, dwAttrs); + + uint32_t attribs = 0; + rv = localFileWin->GetFileAttributesWin(&attribs); + + if (NS_FAILED(rv)) + { + fail("Test Win Attribs: GetFileAttributesWin failed to GET attributes. (1)"); + DeleteFileW(filePath); + return NS_ERROR_FAILURE; + } + + if (attribs & nsILocalFileWin::WFA_SEARCH_INDEXED) + { + fail("Test Win Attribs: GetFileAttributesWin attributed did not match. (2)"); + DeleteFileW(filePath); + return NS_ERROR_FAILURE; + } + + dwAttrs &= ~FILE_ATTRIBUTE_NOT_CONTENT_INDEXED; + SetFileAttributesW(filePath, dwAttrs); + + rv = localFileWin->GetFileAttributesWin(&attribs); + + if (NS_FAILED(rv)) + { + fail("Test Win Attribs: GetFileAttributesWin failed to GET attributes. (3)"); + DeleteFileW(filePath); + return NS_ERROR_FAILURE; + } + + if (!(attribs & nsILocalFileWin::WFA_SEARCH_INDEXED)) + { + fail("Test Win Attribs: GetFileAttributesWin attributed did not match. (4)"); + DeleteFileW(filePath); + return NS_ERROR_FAILURE; + } + + dwAttrs &= ~FILE_ATTRIBUTE_NOT_CONTENT_INDEXED; + SetFileAttributesW(filePath, dwAttrs); + + attribs = nsILocalFileWin::WFA_SEARCH_INDEXED; + rv = localFileWin->SetFileAttributesWin(attribs); + + dwAttrs = GetFileAttributesW(filePath); + + if (NS_FAILED(rv)) + { + fail("Test Win Attribs: GetFileAttributesWin failed to SET attributes. (5)"); + DeleteFileW(filePath); + return NS_ERROR_FAILURE; + } + + if (dwAttrs & FILE_ATTRIBUTE_NOT_CONTENT_INDEXED) + { + fail("Test Win Attribs: SetFileAttributesWin attributed did not match. (6)"); + DeleteFileW(filePath); + return NS_ERROR_FAILURE; + } + + dwAttrs |= FILE_ATTRIBUTE_NOT_CONTENT_INDEXED; + SetFileAttributesW(filePath, dwAttrs); + + attribs = 0; + rv = localFileWin->SetFileAttributesWin(attribs); + + dwAttrs = GetFileAttributesW(filePath); + + if (NS_FAILED(rv)) + { + fail("Test Win Attribs: GetFileAttributesWin failed to SET attributes. (7)"); + DeleteFileW(filePath); + return NS_ERROR_FAILURE; + } + + if (!(dwAttrs & FILE_ATTRIBUTE_NOT_CONTENT_INDEXED)) + { + fail("Test Win Attribs: SetFileAttributesWin attributed did not match. (8)"); + DeleteFileW(filePath); + return NS_ERROR_FAILURE; + } + + DeleteFileW(filePath); + + passed("Test Win Attribs: passed tests."); + + return NS_OK; +} + +int main(int argc, char** argv) +{ + ScopedXPCOM xpcom("WinFileAttributes"); + if (xpcom.failed()) + return 1; + + int rv = 0; + + if(NS_FAILED(TestWinAttribs())) + rv = 1; + + return rv; + +} + diff --git a/xpcom/tests/windows/moz.build b/xpcom/tests/windows/moz.build new file mode 100644 index 000000000..21b5eb2f7 --- /dev/null +++ b/xpcom/tests/windows/moz.build @@ -0,0 +1,16 @@ +# -*- 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', +] + +OS_LIBS += [ + 'mpr', +] + +FINAL_LIBRARY = 'xul-gtest' diff --git a/xpcom/threads/AbstractThread.cpp b/xpcom/threads/AbstractThread.cpp new file mode 100644 index 000000000..451b317d8 --- /dev/null +++ b/xpcom/threads/AbstractThread.cpp @@ -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/. */ + +#include "mozilla/AbstractThread.h" + +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/Maybe.h" +#include "mozilla/MozPromise.h" // We initialize the MozPromise logging in this file. +#include "mozilla/StaticPtr.h" +#include "mozilla/StateWatching.h" // We initialize the StateWatching logging in this file. +#include "mozilla/TaskQueue.h" +#include "mozilla/TaskDispatcher.h" +#include "mozilla/Unused.h" + +#include "nsThreadUtils.h" +#include "nsContentUtils.h" +#include "nsServiceManagerUtils.h" + + +namespace mozilla { + +LazyLogModule gMozPromiseLog("MozPromise"); +LazyLogModule gStateWatchingLog("StateWatching"); + +StaticRefPtr sMainThread; +MOZ_THREAD_LOCAL(AbstractThread*) AbstractThread::sCurrentThreadTLS; + +class XPCOMThreadWrapper : public AbstractThread +{ +public: + explicit XPCOMThreadWrapper(nsIThread* aTarget, bool aRequireTailDispatch) + : AbstractThread(aRequireTailDispatch) + , mTarget(aTarget) + { + // Our current mechanism of implementing tail dispatch is appshell-specific. + // This is because a very similar mechanism already exists on the main + // thread, and we want to avoid making event dispatch on the main thread + // more complicated than it already is. + // + // If you need to use tail dispatch on other XPCOM threads, you'll need to + // implement an nsIThreadObserver to fire the tail dispatcher at the + // appropriate times. + MOZ_ASSERT_IF(aRequireTailDispatch, + NS_IsMainThread() && NS_GetCurrentThread() == aTarget); + } + + virtual void Dispatch(already_AddRefed aRunnable, + DispatchFailureHandling aFailureHandling = AssertDispatchSuccess, + DispatchReason aReason = NormalDispatch) override + { + nsCOMPtr r = aRunnable; + AbstractThread* currentThread; + if (aReason != TailDispatch && (currentThread = GetCurrent()) && RequiresTailDispatch(currentThread)) { + currentThread->TailDispatcher().AddTask(this, r.forget(), aFailureHandling); + return; + } + + nsresult rv = mTarget->Dispatch(r, NS_DISPATCH_NORMAL); + MOZ_DIAGNOSTIC_ASSERT(aFailureHandling == DontAssertDispatchSuccess || NS_SUCCEEDED(rv)); + Unused << rv; + } + + virtual bool IsCurrentThreadIn() override + { + // Compare NSPR threads so that this works after shutdown when + // NS_GetCurrentThread starts returning null. + PRThread* thread = nullptr; + mTarget->GetPRThread(&thread); + bool in = PR_GetCurrentThread() == thread; + return in; + } + + void FireTailDispatcher() + { + MOZ_DIAGNOSTIC_ASSERT(mTailDispatcher.isSome()); + mTailDispatcher.ref().DrainDirectTasks(); + mTailDispatcher.reset(); + } + + virtual TaskDispatcher& TailDispatcher() override + { + MOZ_ASSERT(this == sMainThread); // See the comment in the constructor. + MOZ_ASSERT(IsCurrentThreadIn()); + if (!mTailDispatcher.isSome()) { + mTailDispatcher.emplace(/* aIsTailDispatcher = */ true); + + nsCOMPtr event = NewRunnableMethod(this, &XPCOMThreadWrapper::FireTailDispatcher); + nsContentUtils::RunInStableState(event.forget()); + } + + return mTailDispatcher.ref(); + } + + virtual bool MightHaveTailTasks() override + { + return mTailDispatcher.isSome(); + } + + virtual nsIThread* AsXPCOMThread() override { return mTarget; } + +private: + RefPtr mTarget; + Maybe mTailDispatcher; +}; + +void +AbstractThread::TailDispatchTasksFor(AbstractThread* aThread) +{ + if (MightHaveTailTasks()) { + TailDispatcher().DispatchTasksFor(aThread); + } +} + +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::InitStatics() +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(!sMainThread); + nsCOMPtr mainThread; + NS_GetMainThread(getter_AddRefs(mainThread)); + MOZ_DIAGNOSTIC_ASSERT(mainThread); + sMainThread = new XPCOMThreadWrapper(mainThread.get(), /* aRequireTailDispatch = */ true); + ClearOnShutdown(&sMainThread); + + if (!sCurrentThreadTLS.init()) { + MOZ_CRASH(); + } + sCurrentThreadTLS.set(sMainThread); +} + +void +AbstractThread::DispatchStateChange(already_AddRefed aRunnable) +{ + GetCurrent()->TailDispatcher().AddStateChangeTask(this, Move(aRunnable)); +} + +/* static */ void +AbstractThread::DispatchDirectTask(already_AddRefed aRunnable) +{ + GetCurrent()->TailDispatcher().AddDirectTask(Move(aRunnable)); +} + +/* static */ +already_AddRefed +AbstractThread::CreateXPCOMThreadWrapper(nsIThread* aThread, bool aRequireTailDispatch) +{ + RefPtr wrapper = new XPCOMThreadWrapper(aThread, aRequireTailDispatch); + // Set the thread-local sCurrentThreadTLS to point to the wrapper on the + // target thread. This ensures that sCurrentThreadTLS is as expected by + // AbstractThread::GetCurrent() on the target thread. + nsCOMPtr r = + NS_NewRunnableFunction([wrapper]() { sCurrentThreadTLS.set(wrapper); }); + aThread->Dispatch(r.forget(), NS_DISPATCH_NORMAL); + return wrapper.forget(); +} + +} // namespace mozilla diff --git a/xpcom/threads/AbstractThread.h b/xpcom/threads/AbstractThread.h new file mode 100644 index 000000000..ca6ec1b84 --- /dev/null +++ b/xpcom/threads/AbstractThread.h @@ -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/. */ + +#if !defined(AbstractThread_h_) +#define AbstractThread_h_ + +#include "nscore.h" +#include "nsIRunnable.h" +#include "nsISupportsImpl.h" +#include "nsIThread.h" +#include "mozilla/RefPtr.h" + +#include "mozilla/ThreadLocal.h" + +namespace mozilla { + +class TaskQueue; +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 + * and AbstractThread::MainThread. If you add support for another thread that is + * not the MainThread, you'll need to figure out how to make it unique such that + * comparing AbstractThread pointers is equivalent to comparing nsIThread pointers. + */ +class AbstractThread +{ +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) {} + + static already_AddRefed + CreateXPCOMThreadWrapper(nsIThread* aThread, bool aRequireTailDispatch); + + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AbstractThread); + + enum DispatchFailureHandling { AssertDispatchSuccess, DontAssertDispatchSuccess }; + enum DispatchReason { NormalDispatch, TailDispatch }; + virtual void Dispatch(already_AddRefed aRunnable, + DispatchFailureHandling aHandling = AssertDispatchSuccess, + DispatchReason aReason = NormalDispatch) = 0; + + virtual bool IsCurrentThreadIn() = 0; + + // Returns true if dispatch is generally reliable. This is used to guard + // against FlushableTaskQueues, which should go away. + virtual bool IsDispatchReliable() { return true; } + + // 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; } + + // Helper functions for methods on the tail TasklDispatcher. These check + // HasTailTasks to avoid allocating a TailDispatcher if it isn't + // needed. + void 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 TaskQueue* AsTaskQueue() { MOZ_CRASH("Not a task queue!"); } + virtual nsIThread* AsXPCOMThread() { MOZ_CRASH("Not an XPCOM thread!"); } + + // Convenience method for getting an AbstractThread for the main thread. + static AbstractThread* MainThread(); + + // Must be called exactly once during startup. + static void InitStatics(); + + void DispatchStateChange(already_AddRefed aRunnable); + + static void DispatchDirectTask(already_AddRefed aRunnable); + +protected: + virtual ~AbstractThread() {} + 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/BackgroundHangMonitor.cpp b/xpcom/threads/BackgroundHangMonitor.cpp new file mode 100644 index 000000000..ac65d9f37 --- /dev/null +++ b/xpcom/threads/BackgroundHangMonitor.cpp @@ -0,0 +1,734 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. 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/BackgroundHangMonitor.h" +#include "mozilla/LinkedList.h" +#include "mozilla/Monitor.h" +#include "mozilla/Move.h" +#include "mozilla/Preferences.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/Telemetry.h" +#include "mozilla/ThreadHangStats.h" +#include "mozilla/ThreadLocal.h" + +#include "prinrval.h" +#include "prthread.h" +#include "ThreadStackHelper.h" +#include "nsIObserverService.h" +#include "nsIObserver.h" +#include "mozilla/Services.h" +#include "nsXULAppAPI.h" + +#include + +// Activate BHR only for one every BHR_BETA_MOD users. +// This is now 100% of Beta population for the Beta 45/46 e10s A/B trials +// It can be scaled back again in the future +#define BHR_BETA_MOD 1; + +// Maximum depth of the call stack in the reported thread hangs. This value represents +// the 99.9th percentile of the thread hangs stack depths reported by Telemetry. +static const size_t kMaxThreadHangStackDepth = 30; + +// An utility comparator function used by std::unique to collapse "(* script)" entries in +// a vector representing a call stack. +bool StackScriptEntriesCollapser(const char* aStackEntry, const char *aAnotherStackEntry) +{ + return !strcmp(aStackEntry, aAnotherStackEntry) && + (!strcmp(aStackEntry, "(chrome script)") || !strcmp(aStackEntry, "(content script)")); +} + +namespace mozilla { + +/** + * BackgroundHangManager is the global object that + * manages all instances of BackgroundHangThread. + */ +class BackgroundHangManager : public nsIObserver +{ +private: + // Background hang monitor thread function + static void MonitorThread(void* aData) + { + PR_SetCurrentThreadName("BgHangManager"); + + /* We do not hold a reference to BackgroundHangManager here + because the monitor thread only exists as long as the + BackgroundHangManager instance exists. We stop the monitor + thread in the BackgroundHangManager destructor, and we can + only get to the destructor if we don't hold a reference here. */ + static_cast(aData)->RunMonitorThread(); + } + + // Hang monitor thread + PRThread* mHangMonitorThread; + // Stop hang monitoring + bool mShutdown; + + BackgroundHangManager(const BackgroundHangManager&); + BackgroundHangManager& operator=(const BackgroundHangManager&); + void RunMonitorThread(); + +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIOBSERVER + static StaticRefPtr sInstance; + static bool sDisabled; + + // Lock for access to members of this class + Monitor mLock; + // Current time as seen by hang monitors + PRIntervalTime mIntervalNow; + // List of BackgroundHangThread instances associated with each thread + LinkedList mHangThreads; + + void Shutdown() + { + MonitorAutoLock autoLock(mLock); + mShutdown = true; + autoLock.Notify(); + } + + void Wakeup() + { + // PR_CreateThread could have failed earlier + if (mHangMonitorThread) { + // Use PR_Interrupt to avoid potentially taking a lock + PR_Interrupt(mHangMonitorThread); + } + } + + BackgroundHangManager(); +private: + virtual ~BackgroundHangManager(); +}; + +NS_IMPL_ISUPPORTS(BackgroundHangManager, nsIObserver) + +NS_IMETHODIMP +BackgroundHangManager::Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aData) { + NS_ENSURE_TRUE(!strcmp(aTopic, "profile-after-change"), NS_ERROR_UNEXPECTED); + BackgroundHangMonitor::DisableOnBeta(); + + nsCOMPtr observerService = mozilla::services::GetObserverService(); + MOZ_ASSERT(observerService); + observerService->RemoveObserver(this, "profile-after-change"); + + return NS_OK; +} + +/** + * BackgroundHangThread is a per-thread object that is used + * by all instances of BackgroundHangMonitor to monitor hangs. + */ +class BackgroundHangThread : public LinkedListElement +{ +private: + static MOZ_THREAD_LOCAL(BackgroundHangThread*) sTlsKey; + static bool sTlsKeyInitialized; + + BackgroundHangThread(const BackgroundHangThread&); + BackgroundHangThread& operator=(const BackgroundHangThread&); + ~BackgroundHangThread(); + + /* Keep a reference to the manager, so we can keep going even + after BackgroundHangManager::Shutdown is called. */ + const RefPtr mManager; + // Unique thread ID for identification + const PRThread* mThreadID; + + void Update(); + +public: + NS_INLINE_DECL_REFCOUNTING(BackgroundHangThread) + /** + * Returns the BackgroundHangThread associated with the + * running thread. Note that this will not find private + * BackgroundHangThread threads. + * + * @return BackgroundHangThread*, or nullptr if no thread + * is found. + */ + static BackgroundHangThread* FindThread(); + + static void Startup() + { + /* We can tolerate init() failing. */ + sTlsKeyInitialized = sTlsKey.init(); + } + + // Hang timeout in ticks + const PRIntervalTime mTimeout; + // PermaHang timeout in ticks + const PRIntervalTime mMaxTimeout; + // Time at last activity + PRIntervalTime mInterval; + // Time when a hang started + PRIntervalTime mHangStart; + // Is the thread in a hang + bool mHanging; + // Is the thread in a waiting state + bool mWaiting; + // Is the thread dedicated to a single BackgroundHangMonitor + BackgroundHangMonitor::ThreadType mThreadType; + // Platform-specific helper to get hang stacks + ThreadStackHelper mStackHelper; + // Stack of current hang + Telemetry::HangStack mHangStack; + // Statistics for telemetry + Telemetry::ThreadHangStats mStats; + // Annotations for the current hang + UniquePtr mAnnotations; + // Annotators registered for this thread + HangMonitor::Observer::Annotators mAnnotators; + + BackgroundHangThread(const char* aName, + uint32_t aTimeoutMs, + uint32_t aMaxTimeoutMs, + BackgroundHangMonitor::ThreadType aThreadType = BackgroundHangMonitor::THREAD_SHARED); + + // Report a hang; aManager->mLock IS locked + Telemetry::HangHistogram& ReportHang(PRIntervalTime aHangTime); + // Report a permanent hang; aManager->mLock IS locked + void ReportPermaHang(); + // Called by BackgroundHangMonitor::NotifyActivity + void NotifyActivity() + { + MonitorAutoLock autoLock(mManager->mLock); + Update(); + } + // Called by BackgroundHangMonitor::NotifyWait + void NotifyWait() + { + MonitorAutoLock autoLock(mManager->mLock); + + if (mWaiting) { + return; + } + + Update(); + mWaiting = true; + } + + // Returns true if this thread is (or might be) shared between other + // BackgroundHangMonitors for the monitored thread. + bool IsShared() { + return mThreadType == BackgroundHangMonitor::THREAD_SHARED; + } +}; + + +StaticRefPtr BackgroundHangManager::sInstance; +bool BackgroundHangManager::sDisabled = false; + +MOZ_THREAD_LOCAL(BackgroundHangThread*) BackgroundHangThread::sTlsKey; +bool BackgroundHangThread::sTlsKeyInitialized; + +BackgroundHangManager::BackgroundHangManager() + : mShutdown(false) + , mLock("BackgroundHangManager") + , mIntervalNow(0) +{ + // Lock so we don't race against the new monitor thread + MonitorAutoLock autoLock(mLock); + mHangMonitorThread = PR_CreateThread( + PR_USER_THREAD, MonitorThread, this, + PR_PRIORITY_LOW, PR_GLOBAL_THREAD, PR_JOINABLE_THREAD, 0); + + MOZ_ASSERT(mHangMonitorThread, "Failed to create monitor thread"); +} + +BackgroundHangManager::~BackgroundHangManager() +{ + MOZ_ASSERT(mShutdown, "Destruction without Shutdown call"); + MOZ_ASSERT(mHangThreads.isEmpty(), "Destruction with outstanding monitors"); + MOZ_ASSERT(mHangMonitorThread, "No monitor thread"); + + // PR_CreateThread could have failed above due to resource limitation + if (mHangMonitorThread) { + // The monitor thread can only live as long as the instance lives + PR_JoinThread(mHangMonitorThread); + } +} + +void +BackgroundHangManager::RunMonitorThread() +{ + // Keep us locked except when waiting + MonitorAutoLock autoLock(mLock); + + /* mIntervalNow is updated at various intervals determined by waitTime. + However, if an update latency is too long (due to CPU scheduling, system + sleep, etc.), we don't update mIntervalNow at all. This is done so that + long latencies in our timing are not detected as hangs. systemTime is + used to track PR_IntervalNow() and determine our latency. */ + + PRIntervalTime systemTime = PR_IntervalNow(); + // Default values for the first iteration of thread loop + PRIntervalTime waitTime = PR_INTERVAL_NO_WAIT; + PRIntervalTime recheckTimeout = PR_INTERVAL_NO_WAIT; + + while (!mShutdown) { + + PR_ClearInterrupt(); + nsresult rv = autoLock.Wait(waitTime); + + PRIntervalTime newTime = PR_IntervalNow(); + PRIntervalTime systemInterval = newTime - systemTime; + systemTime = newTime; + + /* waitTime is a quarter of the shortest timeout value; If our timing + latency is low enough (less than half the shortest timeout value), + we can update mIntervalNow. */ + if (MOZ_LIKELY(waitTime != PR_INTERVAL_NO_TIMEOUT && + systemInterval < 2 * waitTime)) { + mIntervalNow += systemInterval; + } + + /* If it's before the next recheck timeout, and our wait did not + get interrupted (either through Notify or PR_Interrupt), we can + keep the current waitTime and skip iterating through hang monitors. */ + if (MOZ_LIKELY(systemInterval < recheckTimeout && + systemInterval >= waitTime && + rv == NS_OK)) { + recheckTimeout -= systemInterval; + continue; + } + + /* We are in one of the following scenarios, + - Hang or permahang recheck timeout + - Thread added/removed + - Thread wait or hang ended + In all cases, we want to go through our list of hang + monitors and update waitTime and recheckTimeout. */ + waitTime = PR_INTERVAL_NO_TIMEOUT; + recheckTimeout = PR_INTERVAL_NO_TIMEOUT; + + // Locally hold mIntervalNow + PRIntervalTime intervalNow = mIntervalNow; + + // iterate through hang monitors + for (BackgroundHangThread* currentThread = mHangThreads.getFirst(); + currentThread; currentThread = currentThread->getNext()) { + + if (currentThread->mWaiting) { + // Thread is waiting, not hanging + continue; + } + PRIntervalTime interval = currentThread->mInterval; + PRIntervalTime hangTime = intervalNow - interval; + if (MOZ_UNLIKELY(hangTime >= currentThread->mMaxTimeout)) { + // A permahang started + // Skip subsequent iterations and tolerate a race on mWaiting here + currentThread->mWaiting = true; + currentThread->mHanging = false; + currentThread->ReportPermaHang(); + continue; + } + + if (MOZ_LIKELY(!currentThread->mHanging)) { + if (MOZ_UNLIKELY(hangTime >= currentThread->mTimeout)) { + // A hang started + currentThread->mStackHelper.GetStack(currentThread->mHangStack); + currentThread->mHangStart = interval; + currentThread->mHanging = true; + currentThread->mAnnotations = + currentThread->mAnnotators.GatherAnnotations(); + } + } else { + if (MOZ_LIKELY(interval != currentThread->mHangStart)) { + // A hang ended + currentThread->ReportHang(intervalNow - currentThread->mHangStart); + currentThread->mHanging = false; + } + } + + /* If we are hanging, the next time we check for hang status is when + the hang turns into a permahang. If we're not hanging, the next + recheck timeout is when we may be entering a hang. */ + PRIntervalTime nextRecheck; + if (currentThread->mHanging) { + nextRecheck = currentThread->mMaxTimeout; + } else { + nextRecheck = currentThread->mTimeout; + } + recheckTimeout = std::min(recheckTimeout, nextRecheck - hangTime); + + if (currentThread->mTimeout != PR_INTERVAL_NO_TIMEOUT) { + /* We wait for a quarter of the shortest timeout + value to give mIntervalNow enough granularity. */ + waitTime = std::min(waitTime, currentThread->mTimeout / 4); + } + } + } + + /* We are shutting down now. + Wait for all outstanding monitors to unregister. */ + while (!mHangThreads.isEmpty()) { + autoLock.Wait(PR_INTERVAL_NO_TIMEOUT); + } +} + + +BackgroundHangThread::BackgroundHangThread(const char* aName, + uint32_t aTimeoutMs, + uint32_t aMaxTimeoutMs, + BackgroundHangMonitor::ThreadType aThreadType) + : mManager(BackgroundHangManager::sInstance) + , mThreadID(PR_GetCurrentThread()) + , mTimeout(aTimeoutMs == BackgroundHangMonitor::kNoTimeout + ? PR_INTERVAL_NO_TIMEOUT + : PR_MillisecondsToInterval(aTimeoutMs)) + , mMaxTimeout(aMaxTimeoutMs == BackgroundHangMonitor::kNoTimeout + ? PR_INTERVAL_NO_TIMEOUT + : PR_MillisecondsToInterval(aMaxTimeoutMs)) + , mInterval(mManager->mIntervalNow) + , mHangStart(mInterval) + , mHanging(false) + , mWaiting(true) + , mThreadType(aThreadType) + , mStats(aName) +{ + if (sTlsKeyInitialized && IsShared()) { + sTlsKey.set(this); + } + // Lock here because LinkedList is not thread-safe + MonitorAutoLock autoLock(mManager->mLock); + // Add to thread list + mManager->mHangThreads.insertBack(this); + // Wake up monitor thread to process new thread + autoLock.Notify(); +} + +BackgroundHangThread::~BackgroundHangThread() +{ + // Lock here because LinkedList is not thread-safe + MonitorAutoLock autoLock(mManager->mLock); + // Remove from thread list + remove(); + // Wake up monitor thread to process removed thread + autoLock.Notify(); + + // We no longer have a thread + if (sTlsKeyInitialized && IsShared()) { + sTlsKey.set(nullptr); + } + + // Move our copy of ThreadHangStats to Telemetry storage + Telemetry::RecordThreadHangStats(mStats); +} + +Telemetry::HangHistogram& +BackgroundHangThread::ReportHang(PRIntervalTime aHangTime) +{ + // Recovered from a hang; called on the monitor thread + // mManager->mLock IS locked + + // Remove unwanted "js::RunScript" frame from the stack + for (size_t i = 0; i < mHangStack.length(); ) { + const char** f = mHangStack.begin() + i; + if (!mHangStack.IsInBuffer(*f) && !strcmp(*f, "js::RunScript")) { + mHangStack.erase(f); + } else { + i++; + } + } + + // Collapse duplicated "(chrome script)" and "(content script)" entries in the stack. + auto it = std::unique(mHangStack.begin(), mHangStack.end(), StackScriptEntriesCollapser); + mHangStack.erase(it, mHangStack.end()); + + // Limit the depth of the reported stack if greater than our limit. Only keep its + // last entries, since the most recent frames are at the end of the vector. + if (mHangStack.length() > kMaxThreadHangStackDepth) { + const int elementsToRemove = mHangStack.length() - kMaxThreadHangStackDepth; + // Replace the oldest frame with a known label so that we can tell this stack + // was limited. + mHangStack[0] = "(reduced stack)"; + mHangStack.erase(mHangStack.begin() + 1, mHangStack.begin() + elementsToRemove); + } + + Telemetry::HangHistogram newHistogram(Move(mHangStack)); + for (Telemetry::HangHistogram* oldHistogram = mStats.mHangs.begin(); + oldHistogram != mStats.mHangs.end(); oldHistogram++) { + if (newHistogram == *oldHistogram) { + // New histogram matches old one + oldHistogram->Add(aHangTime, Move(mAnnotations)); + return *oldHistogram; + } + } + // Add new histogram + newHistogram.Add(aHangTime, Move(mAnnotations)); + if (!mStats.mHangs.append(Move(newHistogram))) { + MOZ_CRASH(); + } + return mStats.mHangs.back(); +} + +void +BackgroundHangThread::ReportPermaHang() +{ + // Permanently hanged; called on the monitor thread + // mManager->mLock IS locked + + Telemetry::HangHistogram& hang = ReportHang(mMaxTimeout); + Telemetry::HangStack& stack = hang.GetNativeStack(); + if (stack.empty()) { + mStackHelper.GetNativeStack(stack); + } +} + +MOZ_ALWAYS_INLINE void +BackgroundHangThread::Update() +{ + PRIntervalTime intervalNow = mManager->mIntervalNow; + if (mWaiting) { + mInterval = intervalNow; + mWaiting = false; + /* We have to wake up the manager thread because when all threads + are waiting, the manager thread waits indefinitely as well. */ + mManager->Wakeup(); + } else { + PRIntervalTime duration = intervalNow - mInterval; + mStats.mActivity.Add(duration); + if (MOZ_UNLIKELY(duration >= mTimeout)) { + /* Wake up the manager thread to tell it that a hang ended */ + mManager->Wakeup(); + } + mInterval = intervalNow; + } +} + +BackgroundHangThread* +BackgroundHangThread::FindThread() +{ +#ifdef MOZ_ENABLE_BACKGROUND_HANG_MONITOR + if (BackgroundHangManager::sInstance == nullptr) { + MOZ_ASSERT(BackgroundHangManager::sDisabled, + "BackgroundHandleManager is not initialized"); + return nullptr; + } + + if (sTlsKeyInitialized) { + // Use TLS if available + return sTlsKey.get(); + } + // If TLS is unavailable, we can search through the thread list + RefPtr manager(BackgroundHangManager::sInstance); + MOZ_ASSERT(manager, "Creating BackgroundHangMonitor after shutdown"); + + PRThread* threadID = PR_GetCurrentThread(); + // Lock thread list for traversal + MonitorAutoLock autoLock(manager->mLock); + for (BackgroundHangThread* thread = manager->mHangThreads.getFirst(); + thread; thread = thread->getNext()) { + if (thread->mThreadID == threadID && thread->IsShared()) { + return thread; + } + } +#endif + // Current thread is not initialized + return nullptr; +} + +bool +BackgroundHangMonitor::ShouldDisableOnBeta(const nsCString &clientID) { + MOZ_ASSERT(clientID.Length() == 36, "clientID is invalid"); + const char *suffix = clientID.get() + clientID.Length() - 4; + return strtol(suffix, NULL, 16) % BHR_BETA_MOD; +} + +bool +BackgroundHangMonitor::IsDisabled() { +#ifdef MOZ_ENABLE_BACKGROUND_HANG_MONITOR + return BackgroundHangManager::sDisabled; +#else + return true; +#endif +} + +bool +BackgroundHangMonitor::DisableOnBeta() { + nsAdoptingCString clientID = Preferences::GetCString("toolkit.telemetry.cachedClientID"); + bool telemetryEnabled = Preferences::GetBool("toolkit.telemetry.enabled"); + + if (!telemetryEnabled || !clientID || BackgroundHangMonitor::ShouldDisableOnBeta(clientID)) { + if (XRE_IsParentProcess()) { + BackgroundHangMonitor::Shutdown(); + } else { + BackgroundHangManager::sDisabled = true; + } + return true; + } + + return false; +} + +void +BackgroundHangMonitor::Startup() +{ +#ifdef MOZ_ENABLE_BACKGROUND_HANG_MONITOR + MOZ_ASSERT(!BackgroundHangManager::sInstance, "Already initialized"); + + if (!strcmp(NS_STRINGIFY(MOZ_UPDATE_CHANNEL), "beta")) { + if (XRE_IsParentProcess()) { // cached ClientID hasn't been read yet + ThreadStackHelper::Startup(); + BackgroundHangThread::Startup(); + BackgroundHangManager::sInstance = new BackgroundHangManager(); + + nsCOMPtr observerService = mozilla::services::GetObserverService(); + MOZ_ASSERT(observerService); + + observerService->AddObserver(BackgroundHangManager::sInstance, "profile-after-change", false); + return; + } else if(DisableOnBeta()){ + return; + } + } + + ThreadStackHelper::Startup(); + BackgroundHangThread::Startup(); + BackgroundHangManager::sInstance = new BackgroundHangManager(); +#endif +} + +void +BackgroundHangMonitor::Shutdown() +{ +#ifdef MOZ_ENABLE_BACKGROUND_HANG_MONITOR + if (BackgroundHangManager::sDisabled) { + MOZ_ASSERT(!BackgroundHangManager::sInstance, "Initialized"); + return; + } + + MOZ_ASSERT(BackgroundHangManager::sInstance, "Not initialized"); + /* Scope our lock inside Shutdown() because the sInstance object can + be destroyed as soon as we set sInstance to nullptr below, and + we don't want to hold the lock when it's being destroyed. */ + BackgroundHangManager::sInstance->Shutdown(); + BackgroundHangManager::sInstance = nullptr; + ThreadStackHelper::Shutdown(); + BackgroundHangManager::sDisabled = true; +#endif +} + +BackgroundHangMonitor::BackgroundHangMonitor(const char* aName, + uint32_t aTimeoutMs, + uint32_t aMaxTimeoutMs, + ThreadType aThreadType) + : mThread(aThreadType == THREAD_SHARED ? BackgroundHangThread::FindThread() : nullptr) +{ +#ifdef MOZ_ENABLE_BACKGROUND_HANG_MONITOR + if (!BackgroundHangManager::sDisabled && !mThread) { + mThread = new BackgroundHangThread(aName, aTimeoutMs, aMaxTimeoutMs, + aThreadType); + } +#endif +} + +BackgroundHangMonitor::BackgroundHangMonitor() + : mThread(BackgroundHangThread::FindThread()) +{ +#ifdef MOZ_ENABLE_BACKGROUND_HANG_MONITOR + if (BackgroundHangManager::sDisabled) { + return; + } +#endif +} + +BackgroundHangMonitor::~BackgroundHangMonitor() +{ +} + +void +BackgroundHangMonitor::NotifyActivity() +{ +#ifdef MOZ_ENABLE_BACKGROUND_HANG_MONITOR + if (mThread == nullptr) { + MOZ_ASSERT(BackgroundHangManager::sDisabled, + "This thread is not initialized for hang monitoring"); + return; + } + + if (Telemetry::CanRecordExtended()) { + mThread->NotifyActivity(); + } +#endif +} + +void +BackgroundHangMonitor::NotifyWait() +{ +#ifdef MOZ_ENABLE_BACKGROUND_HANG_MONITOR + if (mThread == nullptr) { + MOZ_ASSERT(BackgroundHangManager::sDisabled, + "This thread is not initialized for hang monitoring"); + return; + } + + if (Telemetry::CanRecordExtended()) { + mThread->NotifyWait(); + } +#endif +} + +bool +BackgroundHangMonitor::RegisterAnnotator(HangMonitor::Annotator& aAnnotator) +{ +#ifdef MOZ_ENABLE_BACKGROUND_HANG_MONITOR + BackgroundHangThread* thisThread = BackgroundHangThread::FindThread(); + if (!thisThread) { + return false; + } + return thisThread->mAnnotators.Register(aAnnotator); +#else + return false; +#endif +} + +bool +BackgroundHangMonitor::UnregisterAnnotator(HangMonitor::Annotator& aAnnotator) +{ +#ifdef MOZ_ENABLE_BACKGROUND_HANG_MONITOR + BackgroundHangThread* thisThread = BackgroundHangThread::FindThread(); + if (!thisThread) { + return false; + } + return thisThread->mAnnotators.Unregister(aAnnotator); +#else + return false; +#endif +} + +/* Because we are iterating through the BackgroundHangThread linked list, + we need to take a lock. Using MonitorAutoLock as a base class makes + sure all of that is taken care of for us. */ +BackgroundHangMonitor::ThreadHangStatsIterator::ThreadHangStatsIterator() + : MonitorAutoLock(BackgroundHangManager::sInstance->mLock) + , mThread(BackgroundHangManager::sInstance ? + BackgroundHangManager::sInstance->mHangThreads.getFirst() : + nullptr) +{ +#ifdef MOZ_ENABLE_BACKGROUND_HANG_MONITOR + MOZ_ASSERT(BackgroundHangManager::sInstance || + BackgroundHangManager::sDisabled, + "Inconsistent state"); +#endif +} + +Telemetry::ThreadHangStats* +BackgroundHangMonitor::ThreadHangStatsIterator::GetNext() +{ + if (!mThread) { + return nullptr; + } + Telemetry::ThreadHangStats* stats = &mThread->mStats; + mThread = mThread->getNext(); + return stats; +} + +} // namespace mozilla diff --git a/xpcom/threads/BackgroundHangMonitor.h b/xpcom/threads/BackgroundHangMonitor.h new file mode 100644 index 000000000..698cf2305 --- /dev/null +++ b/xpcom/threads/BackgroundHangMonitor.h @@ -0,0 +1,246 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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_BackgroundHangMonitor_h +#define mozilla_BackgroundHangMonitor_h + +#include "mozilla/HangAnnotations.h" +#include "mozilla/Monitor.h" +#include "mozilla/RefPtr.h" + +#include "nsString.h" + +#include + +namespace mozilla { + +namespace Telemetry { +class ThreadHangStats; +} // namespace Telemetry + +class BackgroundHangThread; +class BackgroundHangManager; + +/** + * The background hang monitor is responsible for detecting and reporting + * hangs in main and background threads. A thread registers itself using + * the BackgroundHangMonitor object and periodically calls its methods to + * inform the hang monitor of the thread's activity. Each thread is given + * a thread name, a timeout, and a maximum timeout. If one of the thread's + * tasks runs for longer than the timeout duration but shorter than the + * maximum timeout, a (transient) hang is reported. On the other hand, if + * a task runs for longer than the maximum timeout duration or never + * finishes (e.g. in a deadlock), a permahang is reported. + * + * Tasks are defined arbitrarily, but are typically represented by events + * in an event loop -- processing one event is equivalent to running one + * task. To ensure responsiveness, tasks in a thread often have a target + * running time. This is a good starting point for determining the timeout + * and maximum timeout values. For example, the Compositor thread has a + * responsiveness goal of 60Hz or 17ms, so a starting timeout could be + * 100ms. Considering some platforms (e.g. Android) can terminate the app + * when a critical thread hangs for longer than a few seconds, a good + * starting maximum timeout is 4 or 5 seconds. + * + * A thread registers itself through the BackgroundHangMonitor constructor. + * Multiple BackgroundHangMonitor objects can be used in one thread. The + * constructor without arguments can be used when it is known that the thread + * already has a BackgroundHangMonitor registered. When all instances of + * BackgroundHangMonitor are destroyed, the thread is unregistered. + * + * The thread then uses two methods to inform BackgroundHangMonitor of the + * thread's activity: + * + * > BackgroundHangMonitor::NotifyActivity should be called *before* + * starting a task. The task run time is determined by the interval + * between this call and the next NotifyActivity call. + * + * > BackgroundHangMonitor::NotifyWait should be called *before* the + * thread enters a wait state (e.g. to wait for a new event). This + * prevents a waiting thread from being detected as hanging. The wait + * state is automatically cleared at the next NotifyActivity call. + * + * The following example shows hang monitoring in a simple event loop: + * + * void thread_main() + * { + * mozilla::BackgroundHangMonitor hangMonitor("example1", 100, 1000); + * while (!exiting) { + * hangMonitor.NotifyActivity(); + * process_next_event(); + * hangMonitor.NotifyWait(); + * wait_for_next_event(); + * } + * } + * + * The following example shows reentrancy in nested event loops: + * + * void thread_main() + * { + * mozilla::BackgroundHangMonitor hangMonitor("example2", 100, 1000); + * while (!exiting) { + * hangMonitor.NotifyActivity(); + * process_next_event(); + * hangMonitor.NotifyWait(); + * wait_for_next_event(); + * } + * } + * + * void process_next_event() + * { + * mozilla::BackgroundHangMonitor hangMonitor(); + * if (is_sync_event) { + * while (!finished_event) { + * hangMonitor.NotifyActivity(); + * process_next_event(); + * hangMonitor.NotifyWait(); + * wait_for_next_event(); + * } + * } else { + * process_nonsync_event(); + * } + * } + */ +class BackgroundHangMonitor +{ +private: + friend BackgroundHangManager; + + RefPtr mThread; + + static bool ShouldDisableOnBeta(const nsCString &); + static bool DisableOnBeta(); + +public: + static const uint32_t kNoTimeout = 0; + enum ThreadType { + // For a new BackgroundHangMonitor for thread T, only create a new + // monitoring thread for T if one doesn't already exist. If one does, + // share that pre-existing monitoring thread. + THREAD_SHARED, + // For a new BackgroundHangMonitor for thread T, create a new + // monitoring thread for T even if there are other, pre-existing + // monitoring threads for T. + THREAD_PRIVATE + }; + + /** + * ThreadHangStatsIterator is used to iterate through the ThreadHangStats + * associated with each active monitored thread. Because of an internal + * lock while this object is alive, a thread must use only one instance + * of this class at a time and must iterate through the list as fast as + * possible. The following example shows using the iterator: + * + * { + * // Scope the iter variable so it's destroyed as soon as we're done + * BackgroundHangMonitor::ThreadHangStatsIterator iter; + * for (ThreadHangStats* histogram = iter.GetNext(); + * histogram; histogram = iter.GetNext()) { + * // Process histogram + * } + * } + */ + class ThreadHangStatsIterator : public MonitorAutoLock + { + private: + BackgroundHangThread* mThread; + + ThreadHangStatsIterator(const ThreadHangStatsIterator&); + ThreadHangStatsIterator& operator=(const ThreadHangStatsIterator&); + + public: + /** + * Create an ThreadHangStatsIterator instance and take the internal lock. + * Internal lock is released on destruction. + */ + ThreadHangStatsIterator(); + + /** + * Get the next item in the list; the first call returns the first item. + * Returns nullptr at the end of the list. + */ + Telemetry::ThreadHangStats* GetNext(); + }; + + /** + * Enable hang monitoring. + * Must return before using BackgroundHangMonitor. + */ + static void Startup(); + + /** + * Disable hang monitoring. + * Can be called without destroying all BackgroundHangMonitors first. + */ + static void Shutdown(); + + /** + * Returns true if BHR is disabled. + */ + static bool IsDisabled(); + + /** + * Start monitoring hangs for the current thread. + * + * @param aName Name to identify the thread with + * @param aTimeoutMs Amount of time in milliseconds without + * activity before registering a hang + * @param aMaxTimeoutMs Amount of time in milliseconds without + * activity before registering a permanent hang + * @param aThreadType + * The ThreadType type of monitoring thread that should be created + * for this monitor. See the documentation for ThreadType. + */ + BackgroundHangMonitor(const char* aName, + uint32_t aTimeoutMs, + uint32_t aMaxTimeoutMs, + ThreadType aThreadType = THREAD_SHARED); + + /** + * Monitor hangs using an existing monitor + * associated with the current thread. + */ + BackgroundHangMonitor(); + + /** + * Destroys the hang monitor; hang monitoring for a thread stops + * when all monitors associated with the thread are destroyed. + */ + ~BackgroundHangMonitor(); + + /** + * Notify the hang monitor of pending current thread activity. + * Call this method before starting an "activity" or after + * exiting from a wait state. + */ + void NotifyActivity(); + + /** + * Notify the hang monitor of current thread wait. + * Call this method before entering a wait state; call + * NotifyActivity when subsequently exiting the wait state. + */ + void NotifyWait(); + + /** + * Register an annotator with BHR for the current thread. + * @param aAnnotator annotator to register + * @return true if the annotator was registered, otherwise false. + */ + static bool RegisterAnnotator(HangMonitor::Annotator& aAnnotator); + + /** + * Unregister an annotator that was previously registered via + * RegisterAnnotator. + * @param aAnnotator annotator to unregister + * @return true if there are still remaining annotators registered + */ + static bool UnregisterAnnotator(HangMonitor::Annotator& aAnnotator); +}; + +} // namespace mozilla + +#endif // mozilla_BackgroundHangMonitor_h diff --git a/xpcom/threads/HangAnnotations.cpp b/xpcom/threads/HangAnnotations.cpp new file mode 100644 index 000000000..529b57b8e --- /dev/null +++ b/xpcom/threads/HangAnnotations.cpp @@ -0,0 +1,262 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. 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/HangAnnotations.h" + +#include + +#include "MainThreadUtils.h" +#include "mozilla/DebugOnly.h" +#include "nsXULAppAPI.h" + +namespace mozilla { +namespace HangMonitor { + +// Chrome hang annotators. This can go away once BHR has completely replaced +// ChromeHangs. +static StaticAutoPtr gChromehangAnnotators; + +class BrowserHangAnnotations : public HangAnnotations +{ +public: + BrowserHangAnnotations(); + ~BrowserHangAnnotations(); + + void AddAnnotation(const nsAString& aName, const int32_t aData) override; + void AddAnnotation(const nsAString& aName, const double aData) override; + void AddAnnotation(const nsAString& aName, const nsAString& aData) override; + void AddAnnotation(const nsAString& aName, const nsACString& aData) override; + void AddAnnotation(const nsAString& aName, const bool aData) override; + + size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const override; + bool IsEmpty() const override; + UniquePtr GetEnumerator() override; + + typedef std::pair AnnotationType; + typedef std::vector VectorType; + typedef VectorType::const_iterator IteratorType; + +private: + VectorType mAnnotations; +}; + +BrowserHangAnnotations::BrowserHangAnnotations() +{ + MOZ_COUNT_CTOR(BrowserHangAnnotations); +} + +BrowserHangAnnotations::~BrowserHangAnnotations() +{ + MOZ_COUNT_DTOR(BrowserHangAnnotations); +} + +void +BrowserHangAnnotations::AddAnnotation(const nsAString& aName, const int32_t aData) +{ + nsString dataString; + dataString.AppendInt(aData); + AnnotationType annotation = std::make_pair(nsString(aName), dataString); + mAnnotations.push_back(annotation); +} + +void +BrowserHangAnnotations::AddAnnotation(const nsAString& aName, const double aData) +{ + nsString dataString; + dataString.AppendFloat(aData); + AnnotationType annotation = std::make_pair(nsString(aName), dataString); + mAnnotations.push_back(annotation); +} + +void +BrowserHangAnnotations::AddAnnotation(const nsAString& aName, const nsAString& aData) +{ + AnnotationType annotation = std::make_pair(nsString(aName), nsString(aData)); + mAnnotations.push_back(annotation); +} + +void +BrowserHangAnnotations::AddAnnotation(const nsAString& aName, const nsACString& aData) +{ + nsString dataString; + AppendUTF8toUTF16(aData, dataString); + AnnotationType annotation = std::make_pair(nsString(aName), dataString); + mAnnotations.push_back(annotation); +} + +void +BrowserHangAnnotations::AddAnnotation(const nsAString& aName, const bool aData) +{ + nsString dataString; + dataString += aData ? NS_LITERAL_STRING("true") : NS_LITERAL_STRING("false"); + AnnotationType annotation = std::make_pair(nsString(aName), dataString); + mAnnotations.push_back(annotation); +} + +/** + * This class itself does not use synchronization but it (and its parent object) + * should be protected by mutual exclusion in some way. In Telemetry the chrome + * hang data is protected via TelemetryImpl::mHangReportsMutex. + */ +class ChromeHangAnnotationEnumerator : public HangAnnotations::Enumerator +{ +public: + explicit ChromeHangAnnotationEnumerator(const BrowserHangAnnotations::VectorType& aAnnotations); + ~ChromeHangAnnotationEnumerator(); + + virtual bool Next(nsAString& aOutName, nsAString& aOutValue); + +private: + BrowserHangAnnotations::IteratorType mIterator; + BrowserHangAnnotations::IteratorType mEnd; +}; + +ChromeHangAnnotationEnumerator::ChromeHangAnnotationEnumerator( + const BrowserHangAnnotations::VectorType& aAnnotations) + : mIterator(aAnnotations.begin()) + , mEnd(aAnnotations.end()) +{ + MOZ_COUNT_CTOR(ChromeHangAnnotationEnumerator); +} + +ChromeHangAnnotationEnumerator::~ChromeHangAnnotationEnumerator() +{ + MOZ_COUNT_DTOR(ChromeHangAnnotationEnumerator); +} + +bool +ChromeHangAnnotationEnumerator::Next(nsAString& aOutName, nsAString& aOutValue) +{ + aOutName.Truncate(); + aOutValue.Truncate(); + if (mIterator == mEnd) { + return false; + } + aOutName = mIterator->first; + aOutValue = mIterator->second; + ++mIterator; + return true; +} + +bool +BrowserHangAnnotations::IsEmpty() const +{ + return mAnnotations.empty(); +} + +size_t +BrowserHangAnnotations::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const +{ + size_t result = sizeof(mAnnotations) + + mAnnotations.capacity() * sizeof(AnnotationType); + for (IteratorType i = mAnnotations.begin(), e = mAnnotations.end(); i != e; + ++i) { + result += i->first.SizeOfExcludingThisIfUnshared(aMallocSizeOf); + result += i->second.SizeOfExcludingThisIfUnshared(aMallocSizeOf); + } + + return result; +} + +UniquePtr +BrowserHangAnnotations::GetEnumerator() +{ + if (mAnnotations.empty()) { + return nullptr; + } + return MakeUnique(mAnnotations); +} + +namespace Observer { + +Annotators::Annotators() + : mMutex("HangMonitor::Annotators::mMutex") +{ + MOZ_COUNT_CTOR(Annotators); +} + +Annotators::~Annotators() +{ + MOZ_ASSERT(mAnnotators.empty()); + MOZ_COUNT_DTOR(Annotators); +} + +bool +Annotators::Register(Annotator& aAnnotator) +{ + MutexAutoLock lock(mMutex); + auto result = mAnnotators.insert(&aAnnotator); + return result.second; +} + +bool +Annotators::Unregister(Annotator& aAnnotator) +{ + MutexAutoLock lock(mMutex); + DebugOnly::size_type> numErased; + numErased = mAnnotators.erase(&aAnnotator); + MOZ_ASSERT(numErased == 1); + return mAnnotators.empty(); +} + +UniquePtr +Annotators::GatherAnnotations() +{ + auto annotations = MakeUnique(); + { // Scope for lock + MutexAutoLock lock(mMutex); + for (std::set::iterator i = mAnnotators.begin(), + e = mAnnotators.end(); + i != e; ++i) { + (*i)->AnnotateHang(*annotations); + } + } + if (annotations->IsEmpty()) { + return nullptr; + } + return Move(annotations); +} + +} // namespace Observer + +void +RegisterAnnotator(Annotator& aAnnotator) +{ + BackgroundHangMonitor::RegisterAnnotator(aAnnotator); + // We still register annotators for ChromeHangs + if (NS_IsMainThread() && + GeckoProcessType_Default == XRE_GetProcessType()) { + if (!gChromehangAnnotators) { + gChromehangAnnotators = new Observer::Annotators(); + } + gChromehangAnnotators->Register(aAnnotator); + } +} + +void +UnregisterAnnotator(Annotator& aAnnotator) +{ + BackgroundHangMonitor::UnregisterAnnotator(aAnnotator); + // We still register annotators for ChromeHangs + if (NS_IsMainThread() && + GeckoProcessType_Default == XRE_GetProcessType()) { + if (gChromehangAnnotators->Unregister(aAnnotator)) { + gChromehangAnnotators = nullptr; + } + } +} + +UniquePtr +ChromeHangAnnotatorCallout() +{ + if (!gChromehangAnnotators) { + return nullptr; + } + return gChromehangAnnotators->GatherAnnotations(); +} + +} // namespace HangMonitor +} // namespace mozilla diff --git a/xpcom/threads/HangAnnotations.h b/xpcom/threads/HangAnnotations.h new file mode 100644 index 000000000..6dddbf4bb --- /dev/null +++ b/xpcom/threads/HangAnnotations.h @@ -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/. */ + +#ifndef mozilla_HangAnnotations_h +#define mozilla_HangAnnotations_h + +#include + +#include "mozilla/MemoryReporting.h" +#include "mozilla/Mutex.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/Vector.h" +#include "nsString.h" + +namespace mozilla { +namespace HangMonitor { + +/** + * This class declares an abstraction for a data type that encapsulates all + * of the annotations being reported by a registered hang Annotator. + */ +class HangAnnotations +{ +public: + virtual ~HangAnnotations() {} + + virtual void AddAnnotation(const nsAString& aName, const int32_t aData) = 0; + virtual void AddAnnotation(const nsAString& aName, const double aData) = 0; + virtual void AddAnnotation(const nsAString& aName, const nsAString& aData) = 0; + virtual void AddAnnotation(const nsAString& aName, const nsACString& aData) = 0; + virtual void AddAnnotation(const nsAString& aName, const bool aData) = 0; + + class Enumerator + { + public: + virtual ~Enumerator() {} + virtual bool Next(nsAString& aOutName, nsAString& aOutValue) = 0; + }; + + virtual size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const = 0; + virtual bool IsEmpty() const = 0; + virtual UniquePtr GetEnumerator() = 0; +}; + +typedef UniquePtr HangAnnotationsPtr; +typedef Vector HangAnnotationsVector; + +class Annotator +{ +public: + /** + * NB: This function is always called by the HangMonitor thread. + * Plan accordingly. + */ + virtual void AnnotateHang(HangAnnotations& aAnnotations) = 0; +}; + +/** + * Registers an Annotator to be called when a hang is detected. + * @param aAnnotator Reference to an object that implements the + * HangMonitor::Annotator interface. + */ +void RegisterAnnotator(Annotator& aAnnotator); + +/** + * Registers an Annotator that was previously registered via RegisterAnnotator. + * @param aAnnotator Reference to an object that implements the + * HangMonitor::Annotator interface. + */ +void UnregisterAnnotator(Annotator& aAnnotator); + +/** + * Gathers annotations. This function should be called by ChromeHangs. + * @return UniquePtr to HangAnnotations object or nullptr if none. + */ +HangAnnotationsPtr ChromeHangAnnotatorCallout(); + +namespace Observer { + +class Annotators +{ +public: + Annotators(); + ~Annotators(); + + bool Register(Annotator& aAnnotator); + bool Unregister(Annotator& aAnnotator); + + HangAnnotationsPtr GatherAnnotations(); + +private: + Mutex mMutex; + std::set mAnnotators; +}; + +} // namespace Observer + +} // namespace HangMonitor +} // namespace mozilla + +#endif // mozilla_HangAnnotations_h diff --git a/xpcom/threads/HangMonitor.cpp b/xpcom/threads/HangMonitor.cpp new file mode 100644 index 000000000..71cc67ca4 --- /dev/null +++ b/xpcom/threads/HangMonitor.cpp @@ -0,0 +1,434 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. 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/HangMonitor.h" + +#include "mozilla/Atomics.h" +#include "mozilla/BackgroundHangMonitor.h" +#include "mozilla/Monitor.h" +#include "mozilla/Preferences.h" +#include "mozilla/ProcessedStack.h" +#include "mozilla/Telemetry.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/UniquePtr.h" +#include "nsReadableUtils.h" +#include "mozilla/StackWalk.h" +#ifdef _WIN64 +#include "mozilla/StackWalk_windows.h" +#endif +#include "nsThreadUtils.h" +#include "nsXULAppAPI.h" + +#ifdef MOZ_CRASHREPORTER +#include "nsExceptionHandler.h" +#endif + +#ifdef XP_WIN +#include +#endif + +#if defined(MOZ_ENABLE_PROFILER_SPS) && defined(MOZ_PROFILING) && defined(XP_WIN) + #define REPORT_CHROME_HANGS +#endif + +namespace mozilla { +namespace HangMonitor { + +/** + * A flag which may be set from within a debugger to disable the hang + * monitor. + */ +volatile bool gDebugDisableHangMonitor = false; + +const char kHangMonitorPrefName[] = "hangmonitor.timeout"; + +#ifdef REPORT_CHROME_HANGS +const char kTelemetryPrefName[] = "toolkit.telemetry.enabled"; +#endif + +// Monitor protects gShutdown and gTimeout, but not gTimestamp which rely on +// being atomically set by the processor; synchronization doesn't really matter +// in this use case. +Monitor* gMonitor; + +// The timeout preference, in seconds. +int32_t gTimeout; + +PRThread* gThread; + +// Set when shutdown begins to signal the thread to exit immediately. +bool gShutdown; + +// The timestamp of the last event notification, or PR_INTERVAL_NO_WAIT if +// we're currently not processing events. +Atomic gTimestamp(PR_INTERVAL_NO_WAIT); + +#ifdef REPORT_CHROME_HANGS +// Main thread ID used in reporting chrome hangs under Windows +static HANDLE winMainThreadHandle = nullptr; + +// Default timeout for reporting chrome hangs to Telemetry (5 seconds) +static const int32_t DEFAULT_CHROME_HANG_INTERVAL = 5; + +// Maximum number of PCs to gather from the stack +static const int32_t MAX_CALL_STACK_PCS = 400; +#endif + +// PrefChangedFunc +void +PrefChanged(const char*, void*) +{ + int32_t newval = Preferences::GetInt(kHangMonitorPrefName); +#ifdef REPORT_CHROME_HANGS + // Monitor chrome hangs on the profiling branch if Telemetry enabled + if (newval == 0) { + bool telemetryEnabled = Preferences::GetBool(kTelemetryPrefName); + if (telemetryEnabled) { + newval = DEFAULT_CHROME_HANG_INTERVAL; + } + } +#endif + MonitorAutoLock lock(*gMonitor); + if (newval != gTimeout) { + gTimeout = newval; + lock.Notify(); + } +} + +void +Crash() +{ + if (gDebugDisableHangMonitor) { + return; + } + +#ifdef XP_WIN + if (::IsDebuggerPresent()) { + return; + } +#endif + +#ifdef MOZ_CRASHREPORTER + // If you change this, you must also deal with the threadsafety of AnnotateCrashReport in + // non-chrome processes! + if (GeckoProcessType_Default == XRE_GetProcessType()) { + CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("Hang"), + NS_LITERAL_CSTRING("1")); + } +#endif + + NS_RUNTIMEABORT("HangMonitor triggered"); +} + +#ifdef REPORT_CHROME_HANGS + +static void +ChromeStackWalker(uint32_t aFrameNumber, void* aPC, void* aSP, void* aClosure) +{ + MOZ_ASSERT(aClosure); + std::vector* stack = + static_cast*>(aClosure); + if (stack->size() == MAX_CALL_STACK_PCS) { + return; + } + MOZ_ASSERT(stack->size() < MAX_CALL_STACK_PCS); + stack->push_back(reinterpret_cast(aPC)); +} + +static void +GetChromeHangReport(Telemetry::ProcessedStack& aStack, + int32_t& aSystemUptime, + int32_t& aFirefoxUptime) +{ + MOZ_ASSERT(winMainThreadHandle); + + // The thread we're about to suspend might have the alloc lock + // so allocate ahead of time + std::vector rawStack; + rawStack.reserve(MAX_CALL_STACK_PCS); + + // Workaround possible deadlock where the main thread is running a + // long-standing JS job, and happens to be in the JIT allocator when we + // suspend it. Since, on win 64, this requires holding a process lock that + // MozStackWalk requires, take this "workaround lock" to avoid deadlock. +#ifdef _WIN64 + AcquireStackWalkWorkaroundLock(); +#endif + DWORD ret = ::SuspendThread(winMainThreadHandle); + bool suspended = false; + if (ret != -1) { + // SuspendThread is asynchronous, so the thread may still be running. Use + // GetThreadContext to ensure it's really suspended. + // See https://blogs.msdn.microsoft.com/oldnewthing/20150205-00/?p=44743. + CONTEXT context; + context.ContextFlags = CONTEXT_CONTROL; + if (::GetThreadContext(winMainThreadHandle, &context)) { + suspended = true; + } + } + +#ifdef _WIN64 + ReleaseStackWalkWorkaroundLock(); +#endif + + if (!suspended) { + if (ret != -1) { + MOZ_ALWAYS_TRUE(::ResumeThread(winMainThreadHandle) != DWORD(-1)); + } + return; + } + + MozStackWalk(ChromeStackWalker, /* skipFrames */ 0, /* maxFrames */ 0, + reinterpret_cast(&rawStack), + reinterpret_cast(winMainThreadHandle), nullptr); + ret = ::ResumeThread(winMainThreadHandle); + if (ret == -1) { + return; + } + aStack = Telemetry::GetStackAndModules(rawStack); + + // Record system uptime (in minutes) at the time of the hang + aSystemUptime = ((GetTickCount() / 1000) - (gTimeout * 2)) / 60; + + // Record Firefox uptime (in minutes) at the time of the hang + bool error; + TimeStamp processCreation = TimeStamp::ProcessCreation(error); + if (!error) { + TimeDuration td = TimeStamp::Now() - processCreation; + aFirefoxUptime = (static_cast(td.ToSeconds()) - (gTimeout * 2)) / 60; + } else { + aFirefoxUptime = -1; + } +} + +#endif + +void +ThreadMain(void*) +{ + PR_SetCurrentThreadName("Hang Monitor"); + + MonitorAutoLock lock(*gMonitor); + + // In order to avoid issues with the hang monitor incorrectly triggering + // during a general system stop such as sleeping, the monitor thread must + // run twice to trigger hang protection. + PRIntervalTime lastTimestamp = 0; + int waitCount = 0; + +#ifdef REPORT_CHROME_HANGS + Telemetry::ProcessedStack stack; + int32_t systemUptime = -1; + int32_t firefoxUptime = -1; + UniquePtr annotations; +#endif + + while (true) { + if (gShutdown) { + return; // Exit the thread + } + + // avoid rereading the volatile value in this loop + PRIntervalTime timestamp = gTimestamp; + + PRIntervalTime now = PR_IntervalNow(); + + if (timestamp != PR_INTERVAL_NO_WAIT && + now < timestamp) { + // 32-bit overflow, reset for another waiting period + timestamp = 1; // lowest legal PRInterval value + } + + if (timestamp != PR_INTERVAL_NO_WAIT && + timestamp == lastTimestamp && + gTimeout > 0) { + ++waitCount; +#ifdef REPORT_CHROME_HANGS + // Capture the chrome-hang stack + Firefox & system uptimes after + // the minimum hang duration has been reached (not when the hang ends) + if (waitCount == 2) { + GetChromeHangReport(stack, systemUptime, firefoxUptime); + annotations = ChromeHangAnnotatorCallout(); + } +#else + // This is the crash-on-hang feature. + // See bug 867313 for the quirk in the waitCount comparison + if (waitCount >= 2) { + int32_t delay = + int32_t(PR_IntervalToSeconds(now - timestamp)); + if (delay >= gTimeout) { + MonitorAutoUnlock unlock(*gMonitor); + Crash(); + } + } +#endif + } else { +#ifdef REPORT_CHROME_HANGS + if (waitCount >= 2) { + uint32_t hangDuration = PR_IntervalToSeconds(now - lastTimestamp); + Telemetry::RecordChromeHang(hangDuration, stack, systemUptime, + firefoxUptime, Move(annotations)); + stack.Clear(); + } +#endif + lastTimestamp = timestamp; + waitCount = 0; + } + + PRIntervalTime timeout; + if (gTimeout <= 0) { + timeout = PR_INTERVAL_NO_TIMEOUT; + } else { + timeout = PR_MillisecondsToInterval(gTimeout * 500); + } + lock.Wait(timeout); + } +} + +void +Startup() +{ + if (GeckoProcessType_Default != XRE_GetProcessType() && + GeckoProcessType_Content != XRE_GetProcessType()) { + return; + } + + MOZ_ASSERT(!gMonitor, "Hang monitor already initialized"); + gMonitor = new Monitor("HangMonitor"); + + Preferences::RegisterCallback(PrefChanged, kHangMonitorPrefName, nullptr); + PrefChanged(nullptr, nullptr); + +#ifdef REPORT_CHROME_HANGS + Preferences::RegisterCallback(PrefChanged, kTelemetryPrefName, nullptr); + winMainThreadHandle = + OpenThread(THREAD_ALL_ACCESS, FALSE, GetCurrentThreadId()); + if (!winMainThreadHandle) { + return; + } +#endif + + // Don't actually start measuring hangs until we hit the main event loop. + // This potentially misses a small class of really early startup hangs, + // but avoids dealing with some xpcshell tests and other situations which + // start XPCOM but don't ever start the event loop. + Suspend(); + + gThread = PR_CreateThread(PR_USER_THREAD, + ThreadMain, + nullptr, PR_PRIORITY_LOW, PR_GLOBAL_THREAD, + PR_JOINABLE_THREAD, 0); +} + +void +Shutdown() +{ + if (GeckoProcessType_Default != XRE_GetProcessType() && + GeckoProcessType_Content != XRE_GetProcessType()) { + return; + } + + MOZ_ASSERT(gMonitor, "Hang monitor not started"); + + { + // Scope the lock we're going to delete later + MonitorAutoLock lock(*gMonitor); + gShutdown = true; + lock.Notify(); + } + + // thread creation could theoretically fail + if (gThread) { + PR_JoinThread(gThread); + gThread = nullptr; + } + + delete gMonitor; + gMonitor = nullptr; +} + +static bool +IsUIMessageWaiting() +{ +#ifndef XP_WIN + return false; +#else +#define NS_WM_IMEFIRST WM_IME_SETCONTEXT +#define NS_WM_IMELAST WM_IME_KEYUP + BOOL haveUIMessageWaiting = FALSE; + MSG msg; + haveUIMessageWaiting |= ::PeekMessageW(&msg, nullptr, WM_KEYFIRST, + WM_IME_KEYLAST, PM_NOREMOVE); + haveUIMessageWaiting |= ::PeekMessageW(&msg, nullptr, NS_WM_IMEFIRST, + NS_WM_IMELAST, PM_NOREMOVE); + haveUIMessageWaiting |= ::PeekMessageW(&msg, nullptr, WM_MOUSEFIRST, + WM_MOUSELAST, PM_NOREMOVE); + return haveUIMessageWaiting; +#endif +} + +void +NotifyActivity(ActivityType aActivityType) +{ + MOZ_ASSERT(NS_IsMainThread(), + "HangMonitor::Notify called from off the main thread."); + + // Determine the activity type more specifically + if (aActivityType == kGeneralActivity) { + aActivityType = IsUIMessageWaiting() ? kActivityUIAVail : + kActivityNoUIAVail; + } + + // Calculate the cumulative amount of lag time since the last UI message + static uint32_t cumulativeUILagMS = 0; + switch (aActivityType) { + case kActivityNoUIAVail: + cumulativeUILagMS = 0; + break; + case kActivityUIAVail: + case kUIActivity: + if (gTimestamp != PR_INTERVAL_NO_WAIT) { + cumulativeUILagMS += PR_IntervalToMilliseconds(PR_IntervalNow() - + gTimestamp); + } + break; + default: + break; + } + + // This is not a locked activity because PRTimeStamp is a 32-bit quantity + // which can be read/written atomically, and we don't want to pay locking + // penalties here. + gTimestamp = PR_IntervalNow(); + + // If we have UI activity we should reset the timer and report it + if (aActivityType == kUIActivity) { + mozilla::Telemetry::Accumulate(mozilla::Telemetry::EVENTLOOP_UI_ACTIVITY_EXP_MS, + cumulativeUILagMS); + cumulativeUILagMS = 0; + } + + if (gThread && !gShutdown) { + mozilla::BackgroundHangMonitor().NotifyActivity(); + } +} + +void +Suspend() +{ + MOZ_ASSERT(NS_IsMainThread(), + "HangMonitor::Suspend called from off the main thread."); + + // Because gTimestamp changes this resets the wait count. + gTimestamp = PR_INTERVAL_NO_WAIT; + + if (gThread && !gShutdown) { + mozilla::BackgroundHangMonitor().NotifyWait(); + } +} + +} // namespace HangMonitor +} // namespace mozilla diff --git a/xpcom/threads/HangMonitor.h b/xpcom/threads/HangMonitor.h new file mode 100644 index 000000000..fd0e6ff83 --- /dev/null +++ b/xpcom/threads/HangMonitor.h @@ -0,0 +1,58 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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_HangMonitor_h +#define mozilla_HangMonitor_h + +namespace mozilla { +namespace HangMonitor { + +/** + * Signifies the type of activity in question +*/ +enum ActivityType +{ + /* There is activity and it is known to be UI related activity. */ + kUIActivity, + + /* There is non UI activity and no UI activity is pending */ + kActivityNoUIAVail, + + /* There is non UI activity and UI activity is known to be pending */ + kActivityUIAVail, + + /* There is non UI activity and UI activity pending is unknown */ + kGeneralActivity +}; + +/** + * Start monitoring hangs. Should be called by the XPCOM startup process only. + */ +void Startup(); + +/** + * Stop monitoring hangs and join the thread. + */ +void Shutdown(); + +/** + * Notify the hang monitor of activity which will reset its internal timer. + * + * @param activityType The type of activity being reported. + * @see ActivityType + */ +void NotifyActivity(ActivityType activityType = kGeneralActivity); + +/* + * Notify the hang monitor that the browser is now idle and no detection should + * be done. + */ +void Suspend(); + +} // namespace HangMonitor +} // namespace mozilla + +#endif // mozilla_HangMonitor_h diff --git a/xpcom/threads/LazyIdleThread.cpp b/xpcom/threads/LazyIdleThread.cpp new file mode 100644 index 000000000..527cc6819 --- /dev/null +++ b/xpcom/threads/LazyIdleThread.cpp @@ -0,0 +1,624 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. 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 "GeckoProfiler.h" +#include "nsComponentManagerUtils.h" +#include "nsIIdlePeriod.h" +#include "nsServiceManagerUtils.h" +#include "nsThreadUtils.h" +#include "mozilla/Services.h" + +#ifdef DEBUG +#define ASSERT_OWNING_THREAD() \ + PR_BEGIN_MACRO \ + nsIThread* currentThread = NS_GetCurrentThread(); \ + if (currentThread) { \ + nsCOMPtr current(do_QueryInterface(currentThread)); \ + nsCOMPtr test(do_QueryInterface(mOwningThread)); \ + MOZ_ASSERT(current == test, "Wrong thread!"); \ + } \ + PR_END_MACRO +#else +#define ASSERT_OWNING_THREAD() /* nothing */ +#endif + +namespace mozilla { + +LazyIdleThread::LazyIdleThread(uint32_t aIdleTimeoutMS, + const nsCSubstring& aName, + ShutdownMethod aShutdownMethod, + nsIObserver* aIdleObserver) + : mMutex("LazyIdleThread::mMutex") + , mOwningThread(NS_GetCurrentThread()) + , mIdleObserver(aIdleObserver) + , mQueuedRunnables(nullptr) + , mIdleTimeoutMS(aIdleTimeoutMS) + , mPendingEventCount(0) + , mIdleNotificationCount(0) + , mShutdownMethod(aShutdownMethod) + , mShutdown(false) + , mThreadIsShuttingDown(false) + , mIdleTimeoutEnabled(true) + , mName(aName) +{ + MOZ_ASSERT(mOwningThread, "Need owning thread!"); +} + +LazyIdleThread::~LazyIdleThread() +{ + ASSERT_OWNING_THREAD(); + + Shutdown(); +} + +void +LazyIdleThread::SetWeakIdleObserver(nsIObserver* aObserver) +{ + ASSERT_OWNING_THREAD(); + + if (mShutdown) { + NS_WARNING_ASSERTION(!aObserver, + "Setting an observer after Shutdown was called!"); + return; + } + + mIdleObserver = aObserver; +} + +void +LazyIdleThread::DisableIdleTimeout() +{ + ASSERT_OWNING_THREAD(); + if (!mIdleTimeoutEnabled) { + return; + } + mIdleTimeoutEnabled = false; + + if (mIdleTimer && NS_FAILED(mIdleTimer->Cancel())) { + NS_WARNING("Failed to cancel timer!"); + } + + MutexAutoLock lock(mMutex); + + // Pretend we have a pending event to keep the idle timer from firing. + MOZ_ASSERT(mPendingEventCount < UINT32_MAX, "Way too many!"); + mPendingEventCount++; +} + +void +LazyIdleThread::EnableIdleTimeout() +{ + ASSERT_OWNING_THREAD(); + if (mIdleTimeoutEnabled) { + return; + } + mIdleTimeoutEnabled = true; + + { + MutexAutoLock lock(mMutex); + + MOZ_ASSERT(mPendingEventCount, "Mismatched calls to observer methods!"); + --mPendingEventCount; + } + + if (mThread) { + nsCOMPtr runnable(new Runnable()); + if (NS_FAILED(Dispatch(runnable.forget(), NS_DISPATCH_NORMAL))) { + NS_WARNING("Failed to dispatch!"); + } + } +} + +void +LazyIdleThread::PreDispatch() +{ + MutexAutoLock lock(mMutex); + + MOZ_ASSERT(mPendingEventCount < UINT32_MAX, "Way too many!"); + mPendingEventCount++; +} + +nsresult +LazyIdleThread::EnsureThread() +{ + ASSERT_OWNING_THREAD(); + + if (mShutdown) { + return NS_ERROR_UNEXPECTED; + } + + if (mThread) { + return NS_OK; + } + + MOZ_ASSERT(!mPendingEventCount, "Shouldn't have events yet!"); + MOZ_ASSERT(!mIdleNotificationCount, "Shouldn't have idle events yet!"); + MOZ_ASSERT(!mIdleTimer, "Should have killed this long ago!"); + MOZ_ASSERT(!mThreadIsShuttingDown, "Should have cleared that!"); + + nsresult rv; + + if (mShutdownMethod == AutomaticShutdown && NS_IsMainThread()) { + nsCOMPtr obs = + do_GetService(NS_OBSERVERSERVICE_CONTRACTID, &rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = obs->AddObserver(this, "xpcom-shutdown-threads", false); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + + mIdleTimer = do_CreateInstance(NS_TIMER_CONTRACTID, &rv); + if (NS_WARN_IF(!mIdleTimer)) { + return NS_ERROR_UNEXPECTED; + } + + nsCOMPtr runnable = + NewRunnableMethod(this, &LazyIdleThread::InitThread); + if (NS_WARN_IF(!runnable)) { + return NS_ERROR_UNEXPECTED; + } + + rv = NS_NewThread(getter_AddRefs(mThread), runnable); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; +} + +void +LazyIdleThread::InitThread() +{ + char aLocal; + profiler_register_thread(mName.get(), &aLocal); + + PR_SetCurrentThreadName(mName.get()); + + // Happens on mThread but mThread may not be set yet... + + nsCOMPtr thread(do_QueryInterface(NS_GetCurrentThread())); + MOZ_ASSERT(thread, "This should always succeed!"); + + if (NS_FAILED(thread->SetObserver(this))) { + NS_WARNING("Failed to set thread observer!"); + } +} + +void +LazyIdleThread::CleanupThread() +{ + nsCOMPtr thread(do_QueryInterface(NS_GetCurrentThread())); + MOZ_ASSERT(thread, "This should always succeed!"); + + if (NS_FAILED(thread->SetObserver(nullptr))) { + NS_WARNING("Failed to set thread observer!"); + } + + { + MutexAutoLock lock(mMutex); + + MOZ_ASSERT(!mThreadIsShuttingDown, "Shouldn't be true ever!"); + mThreadIsShuttingDown = true; + } + + profiler_unregister_thread(); +} + +void +LazyIdleThread::ScheduleTimer() +{ + ASSERT_OWNING_THREAD(); + + bool shouldSchedule; + { + MutexAutoLock lock(mMutex); + + MOZ_ASSERT(mIdleNotificationCount, "Should have at least one!"); + --mIdleNotificationCount; + + shouldSchedule = !mIdleNotificationCount && !mPendingEventCount; + } + + if (mIdleTimer) { + if (NS_FAILED(mIdleTimer->Cancel())) { + NS_WARNING("Failed to cancel timer!"); + } + + if (shouldSchedule && + NS_FAILED(mIdleTimer->InitWithCallback(this, mIdleTimeoutMS, + nsITimer::TYPE_ONE_SHOT))) { + NS_WARNING("Failed to schedule timer!"); + } + } +} + +nsresult +LazyIdleThread::ShutdownThread() +{ + ASSERT_OWNING_THREAD(); + + // Before calling Shutdown() on the real thread we need to put a queue in + // place in case a runnable is posted to the thread while it's in the + // process of shutting down. This will be our queue. + AutoTArray, 10> queuedRunnables; + + nsresult rv; + + // Make sure to cancel the shutdown timer before spinning the event loop + // during |mThread->Shutdown()| below. Otherwise the timer might fire and we + // could reenter here. + if (mIdleTimer) { + rv = mIdleTimer->Cancel(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + mIdleTimer = nullptr; + } + + if (mThread) { + if (mShutdownMethod == AutomaticShutdown && NS_IsMainThread()) { + nsCOMPtr obs = + mozilla::services::GetObserverService(); + NS_WARNING_ASSERTION(obs, "Failed to get observer service!"); + + if (obs && + NS_FAILED(obs->RemoveObserver(this, "xpcom-shutdown-threads"))) { + NS_WARNING("Failed to remove observer!"); + } + } + + if (mIdleObserver) { + mIdleObserver->Observe(static_cast(this), IDLE_THREAD_TOPIC, + nullptr); + } + +#ifdef DEBUG + { + MutexAutoLock lock(mMutex); + MOZ_ASSERT(!mThreadIsShuttingDown, "Huh?!"); + } +#endif + + nsCOMPtr runnable = + NewRunnableMethod(this, &LazyIdleThread::CleanupThread); + if (NS_WARN_IF(!runnable)) { + return NS_ERROR_UNEXPECTED; + } + + PreDispatch(); + + rv = mThread->Dispatch(runnable.forget(), NS_DISPATCH_NORMAL); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // Put the temporary queue in place before calling Shutdown(). + mQueuedRunnables = &queuedRunnables; + + if (NS_FAILED(mThread->Shutdown())) { + NS_ERROR("Failed to shutdown the thread!"); + } + + // Now unset the queue. + mQueuedRunnables = nullptr; + + mThread = nullptr; + + { + MutexAutoLock lock(mMutex); + + MOZ_ASSERT(!mPendingEventCount, "Huh?!"); + MOZ_ASSERT(!mIdleNotificationCount, "Huh?!"); + MOZ_ASSERT(mThreadIsShuttingDown, "Huh?!"); + mThreadIsShuttingDown = false; + } + } + + // If our temporary queue has any runnables then we need to dispatch them. + if (queuedRunnables.Length()) { + // If the thread manager has gone away then these runnables will never run. + if (mShutdown) { + NS_ERROR("Runnables dispatched to LazyIdleThread will never run!"); + return NS_OK; + } + + // Re-dispatch the queued runnables. + for (uint32_t index = 0; index < queuedRunnables.Length(); index++) { + nsCOMPtr runnable; + runnable.swap(queuedRunnables[index]); + MOZ_ASSERT(runnable, "Null runnable?!"); + + if (NS_FAILED(Dispatch(runnable.forget(), NS_DISPATCH_NORMAL))) { + NS_ERROR("Failed to re-dispatch queued runnable!"); + } + } + } + + return NS_OK; +} + +void +LazyIdleThread::SelfDestruct() +{ + MOZ_ASSERT(mRefCnt == 1, "Bad refcount!"); + delete this; +} + +NS_IMPL_ADDREF(LazyIdleThread) + +NS_IMETHODIMP_(MozExternalRefCountType) +LazyIdleThread::Release() +{ + nsrefcnt count = --mRefCnt; + NS_LOG_RELEASE(this, count, "LazyIdleThread"); + + if (!count) { + // Stabilize refcount. + mRefCnt = 1; + + nsCOMPtr runnable = + NewNonOwningRunnableMethod(this, &LazyIdleThread::SelfDestruct); + NS_WARNING_ASSERTION(runnable, "Couldn't make runnable!"); + + if (NS_FAILED(NS_DispatchToCurrentThread(runnable))) { + MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!"); + // The only way this could fail is if we're in shutdown, and in that case + // threads should have been joined already. Deleting here isn't dangerous + // anymore because we won't spin the event loop waiting to join the + // thread. + SelfDestruct(); + } + } + + return count; +} + +NS_IMPL_QUERY_INTERFACE(LazyIdleThread, nsIThread, + nsIEventTarget, + nsITimerCallback, + nsIThreadObserver, + 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) +{ + ASSERT_OWNING_THREAD(); + nsCOMPtr event(aEvent); // avoid leaks + + // LazyIdleThread can't always support synchronous dispatch currently. + if (NS_WARN_IF(aFlags != NS_DISPATCH_NORMAL)) { + return NS_ERROR_NOT_IMPLEMENTED; + } + + if (NS_WARN_IF(mShutdown)) { + return NS_ERROR_UNEXPECTED; + } + + // If our thread is shutting down then we can't actually dispatch right now. + // Queue this runnable for later. + if (UseRunnableQueue()) { + mQueuedRunnables->AppendElement(event); + return NS_OK; + } + + nsresult rv = EnsureThread(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + PreDispatch(); + + return mThread->Dispatch(event.forget(), aFlags); +} + +NS_IMETHODIMP +LazyIdleThread::DelayedDispatch(already_AddRefed, uint32_t) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +LazyIdleThread::IsOnCurrentThread(bool* aIsOnCurrentThread) +{ + if (mThread) { + return mThread->IsOnCurrentThread(aIsOnCurrentThread); + } + + *aIsOnCurrentThread = false; + return NS_OK; +} + +NS_IMETHODIMP +LazyIdleThread::GetPRThread(PRThread** aPRThread) +{ + if (mThread) { + return mThread->GetPRThread(aPRThread); + } + + *aPRThread = nullptr; + return NS_ERROR_NOT_AVAILABLE; +} + +NS_IMETHODIMP +LazyIdleThread::GetCanInvokeJS(bool* aCanInvokeJS) +{ + *aCanInvokeJS = false; + return NS_OK; +} + +NS_IMETHODIMP +LazyIdleThread::SetCanInvokeJS(bool aCanInvokeJS) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +LazyIdleThread::AsyncShutdown() +{ + ASSERT_OWNING_THREAD(); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +LazyIdleThread::Shutdown() +{ + ASSERT_OWNING_THREAD(); + + mShutdown = true; + + nsresult rv = ShutdownThread(); + MOZ_ASSERT(!mThread, "Should have destroyed this by now!"); + + mIdleObserver = nullptr; + + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; +} + +NS_IMETHODIMP +LazyIdleThread::HasPendingEvents(bool* aHasPendingEvents) +{ + // This is only supposed to be called from the thread itself so it's not + // implemented here. + NS_NOTREACHED("Shouldn't ever call this!"); + return NS_ERROR_UNEXPECTED; +} + +NS_IMETHODIMP +LazyIdleThread::IdleDispatch(already_AddRefed aEvent) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +LazyIdleThread::RegisterIdlePeriod(already_AddRefed aIdlePeriod) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +LazyIdleThread::ProcessNextEvent(bool aMayWait, + bool* aEventWasProcessed) +{ + // This is only supposed to be called from the thread itself so it's not + // implemented here. + NS_NOTREACHED("Shouldn't ever call this!"); + return NS_ERROR_UNEXPECTED; +} + +NS_IMETHODIMP +LazyIdleThread::Notify(nsITimer* aTimer) +{ + ASSERT_OWNING_THREAD(); + + { + MutexAutoLock lock(mMutex); + + if (mPendingEventCount || mIdleNotificationCount) { + // Another event was scheduled since this timer was set. Don't do + // anything and wait for the timer to fire again. + return NS_OK; + } + } + + nsresult rv = ShutdownThread(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; +} + +NS_IMETHODIMP +LazyIdleThread::OnDispatchedEvent(nsIThreadInternal* /*aThread */) +{ + MOZ_ASSERT(NS_GetCurrentThread() == mOwningThread, "Wrong thread!"); + return NS_OK; +} + +NS_IMETHODIMP +LazyIdleThread::OnProcessNextEvent(nsIThreadInternal* /* aThread */, + bool /* aMayWait */) +{ + return NS_OK; +} + +NS_IMETHODIMP +LazyIdleThread::AfterProcessNextEvent(nsIThreadInternal* /* aThread */, + bool aEventWasProcessed) +{ + bool shouldNotifyIdle; + { + MutexAutoLock lock(mMutex); + + if (aEventWasProcessed) { + MOZ_ASSERT(mPendingEventCount, "Mismatched calls to observer methods!"); + --mPendingEventCount; + } + + if (mThreadIsShuttingDown) { + // We're shutting down, no need to fire any timer. + return NS_OK; + } + + shouldNotifyIdle = !mPendingEventCount; + if (shouldNotifyIdle) { + MOZ_ASSERT(mIdleNotificationCount < UINT32_MAX, "Way too many!"); + mIdleNotificationCount++; + } + } + + if (shouldNotifyIdle) { + nsCOMPtr runnable = + NewRunnableMethod(this, &LazyIdleThread::ScheduleTimer); + if (NS_WARN_IF(!runnable)) { + return NS_ERROR_UNEXPECTED; + } + + nsresult rv = mOwningThread->Dispatch(runnable.forget(), NS_DISPATCH_NORMAL); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + + return NS_OK; +} + +NS_IMETHODIMP +LazyIdleThread::Observe(nsISupports* /* aSubject */, + const char* aTopic, + const char16_t* /* aData */) +{ + MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!"); + MOZ_ASSERT(mShutdownMethod == AutomaticShutdown, + "Should not receive notifications if not AutomaticShutdown!"); + 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 000000000..6bf8e8e81 --- /dev/null +++ b/xpcom/threads/LazyIdleThread.h @@ -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/. */ + +#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 "nsIObserver.h" +#include "nsIThreadInternal.h" +#include "nsITimer.h" + +#include "mozilla/Mutex.h" +#include "nsCOMPtr.h" +#include "nsTArray.h" +#include "nsString.h" +#include "mozilla/Attributes.h" + +#define IDLE_THREAD_TOPIC "thread-shutting-down" + +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 on any + * thread but it may only be used from the thread on which it is created. If it + * is created on the main thread then it will automatically join its thread on + * XPCOM shutdown using the Observer Service. + */ +class LazyIdleThread final + : public nsIThread + , public nsITimerCallback + , public nsIThreadObserver + , public nsIObserver +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIEVENTTARGET + NS_DECL_NSITHREAD + NS_DECL_NSITIMERCALLBACK + NS_DECL_NSITHREADOBSERVER + NS_DECL_NSIOBSERVER + using nsIEventTarget::Dispatch; + + 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 nsCSubstring& aName, + ShutdownMethod aShutdownMethod = AutomaticShutdown, + nsIObserver* aIdleObserver = nullptr); + + /** + * Add an observer that will be notified when the thread is idle and about to + * be shut down. The aSubject argument can be QueryInterface'd to an nsIThread + * that can be used to post cleanup events. The aTopic argument will be + * IDLE_THREAD_TOPIC, and aData will be null. The LazyIdleThread does not add + * a reference to the observer to avoid circular references as it is assumed + * to be the owner. It is the caller's responsibility to clear this observer + * if the pointer becomes invalid. + */ + void SetWeakIdleObserver(nsIObserver* aObserver); + + /** + * Disable the idle timeout for this thread. No effect if the timeout is + * already disabled. + */ + void DisableIdleTimeout(); + + /** + * Enable the idle timeout. No effect if the timeout is already enabled. + */ + void EnableIdleTimeout(); + +private: + /** + * Calls Shutdown(). + */ + ~LazyIdleThread(); + + /** + * Called just before dispatching to mThread. + */ + void PreDispatch(); + + /** + * Makes sure a valid thread lives in mThread. + */ + nsresult EnsureThread(); + + /** + * Called on mThread to set up the thread observer. + */ + void InitThread(); + + /** + * Called on mThread to clean up the thread observer. + */ + void CleanupThread(); + + /** + * Called on the main thread when mThread believes itself to be idle. Sets up + * the idle timer. + */ + void ScheduleTimer(); + + /** + * Called when we are shutting down mThread. + */ + nsresult ShutdownThread(); + + /** + * Deletes this object. Used to delay calling mThread->Shutdown() during the + * final release (during a GC, for instance). + */ + void SelfDestruct(); + + /** + * Returns true if events should be queued rather than immediately dispatched + * to mThread. Currently only happens when the thread is shutting down. + */ + bool UseRunnableQueue() + { + return !!mQueuedRunnables; + } + + /** + * Protects data that is accessed on both threads. + */ + mozilla::Mutex mMutex; + + /** + * Touched on both threads but set before mThread is created. Used to direct + * timer events to the owning thread. + */ + nsCOMPtr mOwningThread; + + /** + * Only accessed on the owning thread. Set by EnsureThread(). + */ + nsCOMPtr mThread; + + /** + * Protected by mMutex. Created when mThread has no pending events and fired + * at mOwningThread. Any thread that dispatches to mThread will take ownership + * of the timer and fire a separate cancel event to the owning thread. + */ + nsCOMPtr mIdleTimer; + + /** + * Idle observer. Called when the thread is about to be shut down. Released + * only when Shutdown() is called. + */ + nsIObserver* MOZ_UNSAFE_REF("See the documentation for SetWeakIdleObserver for " + "how the owner of LazyIdleThread should manage the " + "lifetime information of this field") mIdleObserver; + + /** + * Temporary storage for events that happen to be dispatched while we're in + * the process of shutting down our real thread. + */ + nsTArray>* mQueuedRunnables; + + /** + * The number of milliseconds a thread should be idle before dying. + */ + const uint32_t mIdleTimeoutMS; + + /** + * The number of events that are pending on mThread. A nonzero value means + * that the thread cannot be cleaned up. + */ + uint32_t mPendingEventCount; + + /** + * The number of times that mThread has dispatched an idle notification. Any + * timer that fires while this count is nonzero can safely be ignored as + * another timer will be on the way. + */ + uint32_t mIdleNotificationCount; + + /** + * Whether or not the thread should automatically shutdown. If the owner + * specified ManualShutdown at construction time then the owner should take + * care to call Shutdown() manually when appropriate. + */ + ShutdownMethod mShutdownMethod; + + /** + * Only accessed on the owning thread. Set to true when Shutdown() has been + * called and prevents EnsureThread() from recreating mThread. + */ + bool mShutdown; + + /** + * Set from CleanupThread and lasting until the thread has shut down. Prevents + * further idle notifications during the shutdown process. + */ + bool mThreadIsShuttingDown; + + /** + * Whether or not the idle timeout is enabled. + */ + bool mIdleTimeoutEnabled; + + /** + * Name of the thread, set on the actual thread after it gets created. + */ + nsCString mName; +}; + +} // namespace mozilla + +#endif // mozilla_lazyidlethread_h__ diff --git a/xpcom/threads/LeakRefPtr.h b/xpcom/threads/LeakRefPtr.h new file mode 100644 index 000000000..56f5d90af --- /dev/null +++ b/xpcom/threads/LeakRefPtr.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/. */ + +/* 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 000000000..4a5f99dd7 --- /dev/null +++ b/xpcom/threads/MainThreadIdlePeriod.cpp @@ -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/. */ + +#include "MainThreadIdlePeriod.h" + +#include "mozilla/Maybe.h" +#include "mozilla/Preferences.h" +#include "nsRefreshDriver.h" + +#define DEFAULT_LONG_IDLE_PERIOD 50.0f +#define DEFAULT_MIN_IDLE_PERIOD 3.0f + +namespace mozilla { + +NS_IMETHODIMP +MainThreadIdlePeriod::GetIdlePeriodHint(TimeStamp* aIdleDeadline) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aIdleDeadline); + + Maybe deadline = nsRefreshDriver::GetIdleDeadlineHint(); + + if (deadline.isSome()) { + // If the idle period is too small, then just return a null time + // to indicate we are busy. Otherwise return the actual deadline. + TimeDuration minIdlePeriod = + TimeDuration::FromMilliseconds(GetMinIdlePeriod()); + bool busySoon = deadline.value().IsNull() || + (TimeStamp::Now() >= (deadline.value() - minIdlePeriod)); + *aIdleDeadline = busySoon ? TimeStamp() : deadline.value(); + } else { + *aIdleDeadline = + TimeStamp::Now() + TimeDuration::FromMilliseconds(GetLongIdlePeriod()); + } + + return NS_OK; +} + +/* static */ float +MainThreadIdlePeriod::GetLongIdlePeriod() +{ + MOZ_ASSERT(NS_IsMainThread()); + + static float sLongIdlePeriod = DEFAULT_LONG_IDLE_PERIOD; + static bool sInitialized = false; + + if (!sInitialized && Preferences::IsServiceAvailable()) { + sInitialized = true; + Preferences::AddFloatVarCache(&sLongIdlePeriod, "idle_queue.long_period", + DEFAULT_LONG_IDLE_PERIOD); + } + + return sLongIdlePeriod; +} + +/* static */ float +MainThreadIdlePeriod::GetMinIdlePeriod() +{ + MOZ_ASSERT(NS_IsMainThread()); + + static float sMinIdlePeriod = DEFAULT_MIN_IDLE_PERIOD; + static bool sInitialized = false; + + if (!sInitialized && Preferences::IsServiceAvailable()) { + sInitialized = true; + Preferences::AddFloatVarCache(&sMinIdlePeriod, "idle_queue.min_period", + DEFAULT_MIN_IDLE_PERIOD); + } + + return sMinIdlePeriod; +} + +} // namespace mozilla diff --git a/xpcom/threads/MainThreadIdlePeriod.h b/xpcom/threads/MainThreadIdlePeriod.h new file mode 100644 index 000000000..2b773551c --- /dev/null +++ b/xpcom/threads/MainThreadIdlePeriod.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 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 + + static float GetLongIdlePeriod(); + static float GetMinIdlePeriod(); +private: + virtual ~MainThreadIdlePeriod() {} +}; + +} // namespace mozilla + +#endif // mozilla_dom_mainthreadidleperiod_h diff --git a/xpcom/threads/MozPromise.h b/xpcom/threads/MozPromise.h new file mode 100644 index 000000000..7a2921d2a --- /dev/null +++ b/xpcom/threads/MozPromise.h @@ -0,0 +1,1067 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 "mozilla/AbstractThread.h" +#include "mozilla/IndexSequence.h" +#include "mozilla/Logging.h" +#include "mozilla/Maybe.h" +#include "mozilla/Mutex.h" +#include "mozilla/Monitor.h" +#include "mozilla/Tuple.h" +#include "mozilla/TypeTraits.h" + +#include "nsTArray.h" +#include "nsThreadUtils.h" + +#if defined(DEBUG) || !defined(RELEASE_OR_BETA) +#define PROMISE_DEBUG +#endif + +#ifdef PROMISE_DEBUG +#define PROMISE_ASSERT MOZ_RELEASE_ASSERT +#else +#define PROMISE_ASSERT(...) do { } while (0) +#endif + +namespace mozilla { + +extern LazyLogModule gMozPromiseLog; + +#define PROMISE_LOG(x, ...) \ + MOZ_LOG(gMozPromiseLog, mozilla::LogLevel::Debug, (x, ##__VA_ARGS__)) + +namespace detail { +template +static TrueType TakesArgumentHelper(Ret (ThisType::*)(ArgType)); +template +static TrueType TakesArgumentHelper(Ret (ThisType::*)(ArgType) const); +template +static FalseType TakesArgumentHelper(Ret (ThisType::*)()); +template +static FalseType TakesArgumentHelper(Ret (ThisType::*)() const); + +template +static Ret ReturnTypeHelper(Ret (ThisType::*)(ArgType)); +template +static Ret ReturnTypeHelper(Ret (ThisType::*)(ArgType) const); +template +static Ret ReturnTypeHelper(Ret (ThisType::*)()); +template +static Ret ReturnTypeHelper(Ret (ThisType::*)() const); + +template +struct ReturnType { + typedef decltype(detail::ReturnTypeHelper(DeclVal())) Type; +}; + +} // namespace detail + +template +struct TakesArgument { + static const bool value = decltype(detail::TakesArgumentHelper(DeclVal()))::value; +}; + +template +struct ReturnTypeIs { + static const bool value = IsConvertible::Type, TargetType>::value; +}; + +/* + * 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 MozPromise::Request. + * The Request object serves several purposes for the consumer. + * + * (1) 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) It provides access to a "Completion Promise", which is roughly analagous + * to the Promise returned directly by ->then() calls on JS promises. 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() {} +}; + +template class MozPromiseHolder; +template +class MozPromise : public MozPromiseRefcountable +{ + static const uint32_t sMagic = 0xcecace11; + +public: + typedef ResolveValueT ResolveValueType; + typedef RejectValueT RejectValueType; + class ResolveOrRejectValue + { + public: + template + void SetResolve(ResolveValueType_&& aResolveValue) + { + MOZ_ASSERT(IsNothing()); + mResolveValue.emplace(Forward(aResolveValue)); + } + + template + void SetReject(RejectValueType_&& aRejectValue) + { + MOZ_ASSERT(IsNothing()); + mRejectValue.emplace(Forward(aRejectValue)); + } + + template + static ResolveOrRejectValue MakeResolve(ResolveValueType_&& aResolveValue) + { + ResolveOrRejectValue val; + val.SetResolve(Forward(aResolveValue)); + return val; + } + + template + static ResolveOrRejectValue MakeReject(RejectValueType_&& aRejectValue) + { + ResolveOrRejectValue val; + val.SetReject(Forward(aRejectValue)); + return val; + } + + bool IsResolve() const { return mResolveValue.isSome(); } + bool IsReject() const { return mRejectValue.isSome(); } + bool IsNothing() const { return mResolveValue.isNothing() && mRejectValue.isNothing(); } + + const ResolveValueType& ResolveValue() const { return mResolveValue.ref(); } + const RejectValueType& RejectValue() const { return mRejectValue.ref(); } + + private: + Maybe mResolveValue; + Maybe mRejectValue; + }; + +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.mLock) +#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 + static RefPtr + CreateAndResolve(ResolveValueType_&& aResolveValue, const char* aResolveSite) + { + RefPtr p = new MozPromise::Private(aResolveSite); + p->Resolve(Forward(aResolveValue), aResolveSite); + return p.forget(); + } + + template + static RefPtr + CreateAndReject(RejectValueType_&& aRejectValue, const char* aRejectSite) + { + RefPtr p = new MozPromise::Private(aRejectSite); + p->Reject(Forward(aRejectValue), aRejectSite); + return p.forget(); + } + + typedef MozPromise, RejectValueType, IsExclusive> AllPromiseType; +private: + class AllPromiseHolder : public MozPromiseRefcountable + { + public: + explicit AllPromiseHolder(size_t aDependentPromises) + : mPromise(new typename AllPromiseType::Private(__func__)) + , mOutstandingPromises(aDependentPromises) + { + mResolveValues.SetLength(aDependentPromises); + } + + void Resolve(size_t aIndex, const ResolveValueType& aResolveValue) + { + if (!mPromise) { + // Already rejected. + return; + } + + mResolveValues[aIndex].emplace(aResolveValue); + if (--mOutstandingPromises == 0) { + nsTArray resolveValues; + resolveValues.SetCapacity(mResolveValues.Length()); + for (size_t i = 0; i < mResolveValues.Length(); ++i) { + resolveValues.AppendElement(mResolveValues[i].ref()); + } + + mPromise->Resolve(resolveValues, __func__); + mPromise = nullptr; + mResolveValues.Clear(); + } + } + + void Reject(const RejectValueType& aRejectValue) + { + if (!mPromise) { + // Already rejected. + return; + } + + mPromise->Reject(aRejectValue, __func__); + mPromise = nullptr; + mResolveValues.Clear(); + } + + AllPromiseType* Promise() { return mPromise; } + + private: + nsTArray> mResolveValues; + RefPtr mPromise; + size_t mOutstandingPromises; + }; +public: + + static RefPtr All(AbstractThread* aProcessingThread, nsTArray>& aPromises) + { + RefPtr holder = new AllPromiseHolder(aPromises.Length()); + for (size_t i = 0; i < aPromises.Length(); ++i) { + aPromises[i]->Then(aProcessingThread, __func__, + [holder, i] (ResolveValueType aResolveValue) -> void { holder->Resolve(i, aResolveValue); }, + [holder] (RejectValueType aRejectValue) -> void { holder->Reject(aRejectValue); } + ); + } + return holder->Promise(); + } + + class Request : public MozPromiseRefcountable + { + public: + virtual void Disconnect() = 0; + + virtual MozPromise* CompletionPromise() = 0; + + virtual void AssertIsDead() = 0; + + protected: + Request() : mComplete(false), mDisconnected(false) {} + virtual ~Request() {} + + 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 + { + static const uint32_t sMagic = 0xfadece11; + + public: + class ResolveOrRejectRunnable : public Runnable + { + public: + ResolveOrRejectRunnable(ThenValueBase* aThenValue, MozPromise* aPromise) + : 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; + } + + private: + RefPtr mThenValue; + RefPtr mPromise; + }; + + explicit ThenValueBase(AbstractThread* aResponseTarget, const char* aCallSite) + : mResponseTarget(aResponseTarget), mCallSite(aCallSite) {} + +#ifdef PROMISE_DEBUG + ~ThenValueBase() + { + mMagic1 = 0; + mMagic2 = 0; + } +#endif + + MozPromise* CompletionPromise() override + { + MOZ_DIAGNOSTIC_ASSERT(mResponseTarget->IsCurrentThreadIn()); + MOZ_DIAGNOSTIC_ASSERT(!Request::mComplete); + if (!mCompletionPromise) { + mCompletionPromise = new MozPromise::Private( + "", true /* aIsCompletionPromise */); + } + return mCompletionPromise; + } + + void AssertIsDead() override + { + 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 (mCompletionPromise) { + mCompletionPromise->AssertIsDead(); + } else { + MOZ_DIAGNOSTIC_ASSERT(Request::mDisconnected); + } + } + + void Dispatch(MozPromise *aPromise) + { + PROMISE_ASSERT(mMagic1 == sMagic && mMagic2 == sMagic); + aPromise->mMutex.AssertCurrentThreadOwns(); + MOZ_ASSERT(!aPromise->IsPending()); + + RefPtr runnable = + static_cast(new (typename ThenValueBase::ResolveOrRejectRunnable)(this, aPromise)); + PROMISE_LOG("%s Then() call made from %s [Runnable=%p, Promise=%p, ThenValue=%p]", + aPromise->mValue.IsResolve() ? "Resolving" : "Rejecting", ThenValueBase::mCallSite, + runnable.get(), aPromise, this); + + // 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. :-( + mResponseTarget->Dispatch(runnable.forget(), AbstractThread::DontAssertDispatchSuccess); + } + + virtual void Disconnect() override + { + MOZ_DIAGNOSTIC_ASSERT(ThenValueBase::mResponseTarget->IsCurrentThreadIn()); + 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(!mCompletionPromise); + } + + protected: + virtual already_AddRefed DoResolveOrRejectInternal(const ResolveOrRejectValue& aValue) = 0; + + void DoResolveOrReject(const ResolveOrRejectValue& aValue) + { + PROMISE_ASSERT(mMagic1 == sMagic && mMagic2 == sMagic); + MOZ_DIAGNOSTIC_ASSERT(mResponseTarget->IsCurrentThreadIn()); + Request::mComplete = true; + if (Request::mDisconnected) { + PROMISE_LOG("ThenValue::DoResolveOrReject disconnected - bailing out [this=%p]", this); + return; + } + + // Invoke the resolve or reject method. + RefPtr p = DoResolveOrRejectInternal(aValue); + + // If there's a completion promise, resolve it appropriately with the + // result of the method. + // + // We jump through some hoops to cast to MozPromise::Private here. This + // can go away when we can just declare mCompletionPromise as + // MozPromise::Private. See the declaration below. + RefPtr completionPromise = + dont_AddRef(static_cast(mCompletionPromise.forget().take())); + if (completionPromise) { + if (p) { + p->ChainTo(completionPromise.forget(), ""); + } else { + completionPromise->ResolveOrReject(aValue, ""); + } + } + } + + RefPtr mResponseTarget; // May be released on any thread. +#ifdef PROMISE_DEBUG + uint32_t mMagic1 = sMagic; +#endif + // Declaring RefPtr here causes build failures + // on MSVC because MozPromise::Private is only forward-declared at this + // point. This hack can go away when we inline-declare MozPromise::Private, + // which is blocked on the B2G ICS compiler being too old. + RefPtr mCompletionPromise; +#ifdef PROMISE_DEBUG + uint32_t mMagic2 = sMagic; +#endif + const char* mCallSite; + }; + + /* + * We create two overloads for invoking Resolve/Reject Methods so as to + * make the resolve/reject value argument "optional". + */ + + template + static typename EnableIf>::value && + TakesArgument::value, + already_AddRefed>::Type + InvokeCallbackMethod(ThisType* aThisVal, MethodType aMethod, ValueType&& aValue) + { + return ((*aThisVal).*aMethod)(Forward(aValue)).forget(); + } + + template + static typename EnableIf::value && + TakesArgument::value, + already_AddRefed>::Type + InvokeCallbackMethod(ThisType* aThisVal, MethodType aMethod, ValueType&& aValue) + { + ((*aThisVal).*aMethod)(Forward(aValue)); + return nullptr; + } + + template + static typename EnableIf>::value && + !TakesArgument::value, + already_AddRefed>::Type + InvokeCallbackMethod(ThisType* aThisVal, MethodType aMethod, ValueType&& aValue) + { + return ((*aThisVal).*aMethod)().forget(); + } + + template + static typename EnableIf::value && + !TakesArgument::value, + already_AddRefed>::Type + InvokeCallbackMethod(ThisType* aThisVal, MethodType aMethod, ValueType&& aValue) + { + ((*aThisVal).*aMethod)(); + return nullptr; + } + + template + class MethodThenValue : public ThenValueBase + { + public: + MethodThenValue(AbstractThread* aResponseTarget, ThisType* aThisVal, + ResolveMethodType aResolveMethod, RejectMethodType aRejectMethod, + const char* aCallSite) + : ThenValueBase(aResponseTarget, aCallSite) + , mThisVal(aThisVal) + , mResolveMethod(aResolveMethod) + , mRejectMethod(aRejectMethod) {} + + virtual 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: + virtual already_AddRefed DoResolveOrRejectInternal(const ResolveOrRejectValue& aValue) override + { + RefPtr completion; + if (aValue.IsResolve()) { + completion = InvokeCallbackMethod(mThisVal.get(), mResolveMethod, aValue.ResolveValue()); + } else { + completion = InvokeCallbackMethod(mThisVal.get(), mRejectMethod, aValue.RejectValue()); + } + + // 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; + + return completion.forget(); + } + + private: + RefPtr mThisVal; // Only accessed and refcounted on dispatch thread. + ResolveMethodType mResolveMethod; + RejectMethodType mRejectMethod; + }; + + // NB: We could use std::function here instead of a template if it were supported. :-( + template + class FunctionThenValue : public ThenValueBase + { + public: + FunctionThenValue(AbstractThread* aResponseTarget, + ResolveFunction&& aResolveFunction, + RejectFunction&& aRejectFunction, + const char* aCallSite) + : ThenValueBase(aResponseTarget, aCallSite) + { + mResolveFunction.emplace(Move(aResolveFunction)); + mRejectFunction.emplace(Move(aRejectFunction)); + } + + virtual 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: + virtual already_AddRefed DoResolveOrRejectInternal(const 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. + RefPtr completion; + if (aValue.IsResolve()) { + completion = InvokeCallbackMethod(mResolveFunction.ptr(), &ResolveFunction::operator(), aValue.ResolveValue()); + } else { + completion = InvokeCallbackMethod(mRejectFunction.ptr(), &RejectFunction::operator(), aValue.RejectValue()); + } + + // 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(); + + return completion.forget(); + } + + private: + Maybe mResolveFunction; // Only accessed and deleted on dispatch thread. + Maybe mRejectFunction; // Only accessed and deleted on dispatch thread. + }; + +public: + void ThenInternal(AbstractThread* aResponseThread, ThenValueBase* aThenValue, + const char* aCallSite) + { + PROMISE_ASSERT(mMagic1 == sMagic && mMagic2 == sMagic && mMagic3 == sMagic && mMagic4 == mMutex.mLock); + MutexAutoLock lock(mMutex); + MOZ_ASSERT(aResponseThread->IsDispatchReliable()); + MOZ_DIAGNOSTIC_ASSERT(!IsExclusive || !mHaveRequest); + mHaveRequest = true; + PROMISE_LOG("%s invoking Then() [this=%p, aThenValue=%p, isPending=%d]", + aCallSite, this, aThenValue, (int) IsPending()); + if (!IsPending()) { + aThenValue->Dispatch(this); + } else { + mThenValues.AppendElement(aThenValue); + } + } + +public: + + template + RefPtr Then(AbstractThread* aResponseThread, const char* aCallSite, ThisType* aThisVal, + ResolveMethodType aResolveMethod, RejectMethodType aRejectMethod) + { + RefPtr thenValue = new MethodThenValue( + aResponseThread, aThisVal, aResolveMethod, aRejectMethod, aCallSite); + ThenInternal(aResponseThread, thenValue, aCallSite); + return thenValue.forget(); // Implicit conversion from already_AddRefed to RefPtr. + } + + template + RefPtr Then(AbstractThread* aResponseThread, const char* aCallSite, + ResolveFunction&& aResolveFunction, RejectFunction&& aRejectFunction) + { + RefPtr thenValue = new FunctionThenValue(aResponseThread, + Move(aResolveFunction), Move(aRejectFunction), aCallSite); + ThenInternal(aResponseThread, thenValue, aCallSite); + return thenValue.forget(); // Implicit conversion from already_AddRefed to RefPtr. + } + + void ChainTo(already_AddRefed aChainedPromise, const char* aCallSite) + { + MutexAutoLock lock(mMutex); + MOZ_DIAGNOSTIC_ASSERT(!IsExclusive || !mHaveRequest); + mHaveRequest = true; + RefPtr chainedPromise = aChainedPromise; + PROMISE_LOG("%s invoking Chain() [this=%p, chainedPromise=%p, isPending=%d]", + aCallSite, this, chainedPromise.get(), (int) IsPending()); + if (!IsPending()) { + ForwardTo(chainedPromise); + } else { + mChainedPromises.AppendElement(chainedPromise); + } + } + + // 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() + { + PROMISE_ASSERT(mMagic1 == sMagic && mMagic2 == sMagic && mMagic3 == sMagic && mMagic4 == mMutex.mLock); + MutexAutoLock lock(mMutex); + for (auto&& then : mThenValues) { + then->AssertIsDead(); + } + for (auto&& chained : mChainedPromises) { + chained->AssertIsDead(); + } + } + +protected: + bool IsPending() const { return mValue.IsNothing(); } + const ResolveOrRejectValue& Value() const + { + // 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 (size_t i = 0; i < mThenValues.Length(); ++i) { + mThenValues[i]->Dispatch(this); + } + mThenValues.Clear(); + + for (size_t i = 0; i < mChainedPromises.Length(); ++i) { + ForwardTo(mChainedPromises[i]); + } + mChainedPromises.Clear(); + } + + void ForwardTo(Private* aOther) + { + MOZ_ASSERT(!IsPending()); + if (mValue.IsResolve()) { + aOther->Resolve(mValue.ResolveValue(), ""); + } else { + aOther->Reject(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; + ResolveOrRejectValue mValue; +#ifdef PROMISE_DEBUG + uint32_t mMagic1 = sMagic; +#endif + nsTArray> 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.mLock); + MutexAutoLock lock(mMutex); + MOZ_ASSERT(IsPending()); + PROMISE_LOG("%s resolving MozPromise (%p created at %s)", aResolveSite, this, mCreationSite); + mValue.SetResolve(Forward(aResolveValue)); + DispatchAll(); + } + + template + void Reject(RejectValueT_&& aRejectValue, const char* aRejectSite) + { + PROMISE_ASSERT(mMagic1 == sMagic && mMagic2 == sMagic && mMagic3 == sMagic && mMagic4 == mMutex.mLock); + MutexAutoLock lock(mMutex); + MOZ_ASSERT(IsPending()); + PROMISE_LOG("%s rejecting MozPromise (%p created at %s)", aRejectSite, this, mCreationSite); + mValue.SetReject(Forward(aRejectValue)); + DispatchAll(); + } + + template + void ResolveOrReject(ResolveOrRejectValue_&& aValue, const char* aSite) + { + PROMISE_ASSERT(mMagic1 == sMagic && mMagic2 == sMagic && mMagic3 == sMagic && mMagic4 == mMutex.mLock); + MutexAutoLock lock(mMutex); + MOZ_ASSERT(IsPending()); + PROMISE_LOG("%s resolveOrRejecting MozPromise (%p created at %s)", aSite, this, mCreationSite); + mValue = Forward(aValue); + DispatchAll(); + } +}; + +// A generic promise type that does the trick for simple use cases. +typedef MozPromise GenericPromise; + +/* + * 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 MozPromiseHolder +{ +public: + MozPromiseHolder() + : mMonitor(nullptr) {} + + // Move semantics. + MozPromiseHolder& operator=(MozPromiseHolder&& aOther) + { + MOZ_ASSERT(!mMonitor && !aOther.mMonitor); + MOZ_DIAGNOSTIC_ASSERT(!mPromise); + mPromise = aOther.mPromise; + aOther.mPromise = nullptr; + return *this; + } + + ~MozPromiseHolder() { MOZ_ASSERT(!mPromise); } + + already_AddRefed Ensure(const char* aMethodName) { + if (mMonitor) { + mMonitor->AssertCurrentThreadOwns(); + } + if (!mPromise) { + mPromise = new (typename PromiseType::Private)(aMethodName); + } + RefPtr p = mPromise.get(); + return p.forget(); + } + + // Provide a Monitor that should always be held when accessing this instance. + void SetMonitor(Monitor* aMonitor) { mMonitor = aMonitor; } + + bool IsEmpty() const + { + if (mMonitor) { + mMonitor->AssertCurrentThreadOwns(); + } + return !mPromise; + } + + already_AddRefed Steal() + { + if (mMonitor) { + mMonitor->AssertCurrentThreadOwns(); + } + + RefPtr p = mPromise; + mPromise = nullptr; + return p.forget(); + } + + void Resolve(typename PromiseType::ResolveValueType aResolveValue, + const char* aMethodName) + { + if (mMonitor) { + mMonitor->AssertCurrentThreadOwns(); + } + MOZ_ASSERT(mPromise); + mPromise->Resolve(aResolveValue, aMethodName); + mPromise = nullptr; + } + + + void ResolveIfExists(typename PromiseType::ResolveValueType aResolveValue, + const char* aMethodName) + { + if (!IsEmpty()) { + Resolve(aResolveValue, aMethodName); + } + } + + void Reject(typename PromiseType::RejectValueType aRejectValue, + const char* aMethodName) + { + if (mMonitor) { + mMonitor->AssertCurrentThreadOwns(); + } + MOZ_ASSERT(mPromise); + mPromise->Reject(aRejectValue, aMethodName); + mPromise = nullptr; + } + + + void RejectIfExists(typename PromiseType::RejectValueType aRejectValue, + const char* aMethodName) + { + if (!IsEmpty()) { + Reject(aRejectValue, aMethodName); + } + } + +private: + Monitor* mMonitor; + RefPtr mPromise; +}; + +/* + * 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() {} + ~MozPromiseRequestHolder() { MOZ_ASSERT(!mRequest); } + + void Begin(RefPtr&& aRequest) + { + MOZ_DIAGNOSTIC_ASSERT(!Exists()); + mRequest = Move(aRequest); + } + + void Begin(typename PromiseType::Request* 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 method 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 method 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 { + +template +ReturnType +MethodCallInvokeHelper(ReturnType(ThisType::*aMethod)(ArgTypes...), ThisType* aThisVal, + Tuple& aArgs, IndexSequence) +{ + return ((*aThisVal).*aMethod)(Get(aArgs)...); +} + +// 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: + MethodCallBase() { MOZ_COUNT_CTOR(MethodCallBase); } + virtual ~MethodCallBase() { MOZ_COUNT_DTOR(MethodCallBase); } +}; + +template +class MethodCall : public MethodCallBase +{ +public: + typedef RefPtr(ThisType::*MethodType)(ArgTypes...); + MethodCall(MethodType aMethod, ThisType* aThisVal, ArgTypes... aArgs) + : mMethod(aMethod) + , mThisVal(aThisVal) + , mArgs(Forward(aArgs)...) + {} + + RefPtr Invoke() + { + return MethodCallInvokeHelper(mMethod, mThisVal.get(), mArgs, typename IndexSequenceFor::Type()); + } + +private: + MethodType mMethod; + RefPtr mThisVal; + Tuple mArgs; +}; + +template +class ProxyRunnable : public Runnable +{ +public: + ProxyRunnable(typename PromiseType::Private* aProxyPromise, MethodCall* aMethodCall) + : mProxyPromise(aProxyPromise), mMethodCall(aMethodCall) {} + + NS_IMETHOD Run() override + { + RefPtr p = mMethodCall->Invoke(); + mMethodCall = nullptr; + p->ChainTo(mProxyPromise.forget(), ""); + return NS_OK; + } + +private: + RefPtr mProxyPromise; + nsAutoPtr> mMethodCall; +}; + +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 + +template +static RefPtr +InvokeAsync(AbstractThread* aTarget, ThisType* aThisVal, const char* aCallerName, + RefPtr(ThisType::*aMethod)(ArgTypes...), ActualArgTypes&&... aArgs) +{ + static_assert(!detail::Any(IsReference::value...), + "Cannot pass reference types through InvokeAsync, see bug 1313497 if you require it"); + typedef detail::MethodCall MethodCallType; + typedef detail::ProxyRunnable ProxyRunnableType; + + MethodCallType* methodCall = new MethodCallType(aMethod, aThisVal, Forward(aArgs)...); + RefPtr p = new (typename PromiseType::Private)(aCallerName); + RefPtr r = new ProxyRunnableType(p, methodCall); + MOZ_ASSERT(aTarget->IsDispatchReliable()); + aTarget->Dispatch(r.forget()); + return p.forget(); +} + +#undef PROMISE_LOG +#undef PROMISE_ASSERT +#undef PROMISE_DEBUG + +} // namespace mozilla + +#endif diff --git a/xpcom/threads/SharedThreadPool.cpp b/xpcom/threads/SharedThreadPool.cpp new file mode 100644 index 000000000..9adf6449e --- /dev/null +++ b/xpcom/threads/SharedThreadPool.cpp @@ -0,0 +1,224 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. 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/StaticPtr.h" +#include "nsDataHashtable.h" +#include "nsXPCOMCIDInternal.h" +#include "nsComponentManagerUtils.h" +#include "nsIObserver.h" +#include "nsIObserverService.h" +#ifdef XP_WIN +#include "ThreadPoolCOMListener.h" +#endif + +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() {} +}; + +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")); + 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 nsDataHashtable(); + 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()); + while (!IsEmpty()) { + sMonitor->AssertNotCurrentThreadIn(); + NS_ProcessNextEvent(NS_GetCurrentThread(), true); + } +} + +already_AddRefed +SharedThreadPool::Get(const nsCString& aName, uint32_t aThreadLimit) +{ + MOZ_ASSERT(sMonitor && sPools); + ReentrantMonitorAutoEnter mon(*sMonitor); + SharedThreadPool* pool = nullptr; + nsresult rv; + if (!sPools->Get(aName, &pool)) { + nsCOMPtr threadPool(CreateThreadPool(aName)); + NS_ENSURE_TRUE(threadPool, 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. + rv = pool->SetThreadLimit(aThreadLimit); + NS_ENSURE_SUCCESS(rv, nullptr); + + rv = pool->SetIdleThreadLimit(aThreadLimit); + NS_ENSURE_SUCCESS(rv, nullptr); + + sPools->Put(aName, pool); + } else if (NS_FAILED(pool->EnsureThreadLimitIsAtLeast(aThreadLimit))) { + NS_WARNING("Failed to set limits on thread pool"); + } + + MOZ_ASSERT(pool); + RefPtr instance(pool); + return instance.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(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) +{ + MOZ_COUNT_CTOR(SharedThreadPool); + mEventTarget = do_QueryInterface(aPool); +} + +SharedThreadPool::~SharedThreadPool() +{ + MOZ_COUNT_DTOR(SharedThreadPool); +} + +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) +{ + nsresult rv; + nsCOMPtr pool = do_CreateInstance(NS_THREADPOOL_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, nullptr); + + rv = pool->SetName(aName); + NS_ENSURE_SUCCESS(rv, nullptr); + + rv = pool->SetThreadStackSize(SharedThreadPool::kStackSize); + NS_ENSURE_SUCCESS(rv, nullptr); + +#ifdef XP_WIN + // Ensure MSCOM is initialized on the thread pools threads. + nsCOMPtr listener = new MSCOMInitThreadPoolListener(); + rv = pool->SetListener(listener); + NS_ENSURE_SUCCESS(rv, nullptr); +#endif + + return pool.forget(); +} + +} // namespace mozilla diff --git a/xpcom/threads/SharedThreadPool.h b/xpcom/threads/SharedThreadPool.h new file mode 100644 index 000000000..185b9e76f --- /dev/null +++ b/xpcom/threads/SharedThreadPool.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/. */ + +#ifndef SharedThreadPool_h_ +#define SharedThreadPool_h_ + +#include +#include "mozilla/RefPtr.h" +#include "nsThreadUtils.h" +#include "nsIThreadManager.h" +#include "nsIThreadPool.h" +#include "nsISupports.h" +#include "nsISupportsImpl.h" +#include "nsCOMPtr.h" + +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://dxr.mozilla.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) override + { return !mEventTarget ? NS_ERROR_NULL_POINTER : mEventTarget->Dispatch(Move(event), flags); } + + NS_IMETHOD DelayedDispatch(already_AddRefed, uint32_t) override + { return NS_ERROR_NOT_IMPLEMENTED; } + + using nsIEventTarget::Dispatch; + + NS_IMETHOD IsOnCurrentThread(bool *_retval) override { return !mEventTarget ? NS_ERROR_NULL_POINTER : mEventTarget->IsOnCurrentThread(_retval); } + + // 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(); + +#if defined(MOZ_ASAN) + // Use the system default in ASAN builds, because the default is assumed to be + // larger than the size we want to use and is hopefully sufficient for ASAN. + static const uint32_t kStackSize = nsIThreadManager::DEFAULT_STACK_SIZE; +#elif defined(XP_WIN) || defined(XP_MACOSX) || defined(LINUX) + static const uint32_t kStackSize = (256 * 1024); +#else + // All other platforms use their system defaults. + static const uint32_t kStackSize = nsIThreadManager::DEFAULT_STACK_SIZE; +#endif + +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; + + // mPool QI'd to nsIEventTarget. We cache this, so that we can use + // NS_FORWARD_SAFE_NSIEVENTTARGET above. + nsCOMPtr mEventTarget; +}; + +} // namespace mozilla + +#endif // SharedThreadPool_h_ diff --git a/xpcom/threads/StateMirroring.h b/xpcom/threads/StateMirroring.h new file mode 100644 index 000000000..87d94ba74 --- /dev/null +++ b/xpcom/threads/StateMirroring.h @@ -0,0 +1,378 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 "mozilla/Maybe.h" +#include "mozilla/MozPromise.h" +#include "mozilla/StateWatching.h" +#include "mozilla/TaskDispatcher.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/Unused.h" + +#include "mozilla/Logging.h" +#include "nsISupportsImpl.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(mMirrors[i], + &AbstractMirror::NotifyDisconnected), + AbstractThread::DontAssertDispatchSuccess); + } + 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(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(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>> + (aCanonical, &AbstractCanonical::AddMirror, this); + aCanonical->OwnerThread()->Dispatch(r.forget(), AbstractThread::DontAssertDispatchSuccess); + 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>> + (mCanonical, &AbstractCanonical::RemoveMirror, this); + mCanonical->OwnerThread()->Dispatch(r.forget(), AbstractThread::DontAssertDispatchSuccess); + 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 000000000..99d521603 --- /dev/null +++ b/xpcom/threads/StateWatching.h @@ -0,0 +1,317 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 "mozilla/AbstractThread.h" +#include "mozilla/Logging.h" +#include "mozilla/TaskDispatcher.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/Unused.h" + +#include "nsISupportsImpl.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() + { + for (int i = mWatchers.Length() - 1; i >= 0; --i) { + if (mWatchers[i]->IsDestroyed()) { + mWatchers.RemoveElementAt(i); + } + } + } + + 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); // Not implemented + Watchable& operator=(const Watchable& aOther); // Not implemented + + 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 (size_t i = 0; i < mWatchers.Length(); ++i) { + mWatchers[i]->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 (mStrongRef) { + // We've already got a notification job in the pipe. + return; + } + mStrongRef = mOwner; // Hold the owner alive while notifying. + + // Queue up our notification jobs to run in a stable state. + mOwnerThread->TailDispatcher().AddDirectTask(NewRunnableMethod(this, &PerCallbackWatcher::DoNotify)); + } + + bool CallbackMethodIs(CallbackMethod aMethod) const + { + return mCallbackMethod == aMethod; + } + + private: + ~PerCallbackWatcher() {} + + void DoNotify() + { + MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); + MOZ_ASSERT(mStrongRef); + RefPtr ref = mStrongRef.forget(); + if (!mDestroyed) { + ((*ref).*mCallbackMethod)(); + } + } + + OwnerType* mOwner; // Never null. + RefPtr mStrongRef; // Only non-null when notifying. + RefPtr mOwnerThread; + CallbackMethod mCallbackMethod; + }; + + PerCallbackWatcher* GetWatcher(CallbackMethod aMethod) + { + MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); + for (size_t i = 0; i < mWatchers.Length(); ++i) { + if (mWatchers[i]->CallbackMethodIs(aMethod)) { + return mWatchers[i]; + } + } + return nullptr; + } + + PerCallbackWatcher& EnsureWatcher(CallbackMethod aMethod) + { + MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); + PerCallbackWatcher* watcher = GetWatcher(aMethod); + if (watcher) { + return *watcher; + } + watcher = mWatchers.AppendElement(new PerCallbackWatcher(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 000000000..d96bac7ba --- /dev/null +++ b/xpcom/threads/SyncRunnable.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/. */ + +#ifndef mozilla_SyncRunnable_h +#define mozilla_SyncRunnable_h + +#include "nsThreadUtils.h" +#include "mozilla/AbstractThread.h" +#include "mozilla/Monitor.h" +#include "mozilla/Move.h" + +namespace mozilla { + +/** + * This class will wrap a nsIRunnable and dispatch it to the main thread + * synchronously. This is different from nsIEventTarget.DISPATCH_SYNC: + * 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 a convenience wrapper: + * SyncRunnable::DispatchToThread(new myrunnable...()); + * + */ +class SyncRunnable : public Runnable +{ +public: + explicit SyncRunnable(nsIRunnable* aRunnable) + : mRunnable(aRunnable) + , mMonitor("SyncRunnable") + , mDone(false) + { + } + + explicit SyncRunnable(already_AddRefed aRunnable) + : mRunnable(Move(aRunnable)) + , mMonitor("SyncRunnable") + , mDone(false) + { + } + + void 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; + } + } + + rv = aThread->Dispatch(this, NS_DISPATCH_NORMAL); + if (NS_SUCCEEDED(rv)) { + mozilla::MonitorAutoLock lock(mMonitor); + while (!mDone) { + lock.Wait(); + } + } + } + + void DispatchToThread(AbstractThread* aThread, bool aForceDispatch = false) + { + if (!aForceDispatch && aThread->IsCurrentThreadIn()) { + mRunnable->Run(); + return; + } + + // Check we don't have tail dispatching here. Otherwise we will deadlock + // ourself when spinning the loop below. + MOZ_ASSERT(!aThread->RequiresTailDispatchFromCurrentThread()); + + aThread->Dispatch(RefPtr(this).forget()); + mozilla::MonitorAutoLock lock(mMonitor); + while (!mDone) { + lock.Wait(); + } + } + + static void DispatchToThread(nsIEventTarget* aThread, + nsIRunnable* aRunnable, + bool aForceDispatch = false) + { + RefPtr s(new SyncRunnable(aRunnable)); + s->DispatchToThread(aThread, aForceDispatch); + } + + static void DispatchToThread(AbstractThread* aThread, + nsIRunnable* aRunnable, + bool aForceDispatch = false) + { + RefPtr s(new SyncRunnable(aRunnable)); + s->DispatchToThread(aThread, aForceDispatch); + } + +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; +}; + +} // namespace mozilla + +#endif // mozilla_SyncRunnable_h diff --git a/xpcom/threads/TaskDispatcher.h b/xpcom/threads/TaskDispatcher.h new file mode 100644 index 000000000..405c3acfe --- /dev/null +++ b/xpcom/threads/TaskDispatcher.h @@ -0,0 +1,276 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 "mozilla/AbstractThread.h" +#include "mozilla/Maybe.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/Unused.h" + +#include "nsISupportsImpl.h" +#include "nsTArray.h" +#include "nsThreadUtils.h" + +#include + +namespace mozilla { + +/* + * 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() {} + virtual ~TaskDispatcher() {} + + // 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 void AddTask(AbstractThread* aThread, + already_AddRefed aRunnable, + AbstractThread::DispatchFailureHandling aFailureHandling = AbstractThread::AssertDispatchSuccess) = 0; + + virtual void 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(bool aIsTailDispatcher = false) + : 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(Move(mTaskGroups[i])); + } + } + + bool HaveDirectTasks() const + { + return mDirectTasks.isSome() && !mDirectTasks->empty(); + } + + void DrainDirectTasks() override + { + while (HaveDirectTasks()) { + nsCOMPtr r = mDirectTasks->front(); + mDirectTasks->pop(); + r->Run(); + } + } + + void AddDirectTask(already_AddRefed aRunnable) override + { + if (mDirectTasks.isNothing()) { + mDirectTasks.emplace(); + } + mDirectTasks->push(Move(aRunnable)); + } + + void AddStateChangeTask(AbstractThread* aThread, + already_AddRefed aRunnable) override + { + EnsureTaskGroup(aThread).mStateChangeTasks.AppendElement(aRunnable); + } + + void AddTask(AbstractThread* aThread, + already_AddRefed aRunnable, + AbstractThread::DispatchFailureHandling aFailureHandling) override + { + PerThreadTaskGroup& group = EnsureTaskGroup(aThread); + group.mRegularTasks.AppendElement(aRunnable); + + // The task group needs to assert dispatch success if any of the runnables + // it's dispatching want to assert it. + if (aFailureHandling == AbstractThread::AssertDispatchSuccess) { + group.mFailureHandling = AbstractThread::AssertDispatchSuccess; + } + } + + bool HasTasksFor(AbstractThread* aThread) override + { + return !!GetTaskGroup(aThread) || + (aThread == AbstractThread::GetCurrent() && HaveDirectTasks()); + } + + void DispatchTasksFor(AbstractThread* aThread) override + { + for (size_t i = 0; i < mTaskGroups.Length(); ++i) { + if (mTaskGroups[i]->mThread == aThread) { + DispatchTaskGroup(Move(mTaskGroups[i])); + mTaskGroups.RemoveElementAt(i); + return; + } + } + } + +private: + + struct PerThreadTaskGroup + { + public: + explicit PerThreadTaskGroup(AbstractThread* aThread) + : mThread(aThread), mFailureHandling(AbstractThread::DontAssertDispatchSuccess) + { + MOZ_COUNT_CTOR(PerThreadTaskGroup); + } + + ~PerThreadTaskGroup() { MOZ_COUNT_DTOR(PerThreadTaskGroup); } + + RefPtr mThread; + nsTArray> mStateChangeTasks; + nsTArray> mRegularTasks; + AbstractThread::DispatchFailureHandling mFailureHandling; + }; + + class TaskGroupRunnable : public Runnable + { + public: + explicit TaskGroupRunnable(UniquePtr&& aTasks) : mTasks(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) { + 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->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; + } + + void DispatchTaskGroup(UniquePtr aGroup) + { + RefPtr thread = aGroup->mThread; + + AbstractThread::DispatchFailureHandling failureHandling = aGroup->mFailureHandling; + AbstractThread::DispatchReason reason = mIsTailDispatcher ? AbstractThread::TailDispatch + : AbstractThread::NormalDispatch; + nsCOMPtr r = new TaskGroupRunnable(Move(aGroup)); + thread->Dispatch(r.forget(), failureHandling, reason); + } + + // Direct tasks. We use a Maybe<> because (a) this class is hot, (b) + // mDirectTasks often doesn't get anything put into it, and (c) the + // std::queue implementation in GNU libstdc++ does two largish heap + // allocations when creating a new std::queue. + mozilla::Maybe>> mDirectTasks; + + // Task groups, organized by thread. + nsTArray> mTaskGroups; + + // 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() {} + 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 000000000..2e593a773 --- /dev/null +++ b/xpcom/threads/TaskQueue.cpp @@ -0,0 +1,271 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. 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 "nsIEventTarget.h" +#include "nsThreadUtils.h" + +namespace mozilla { + +class TaskQueue::EventTargetWrapper final : public nsIEventTarget +{ + RefPtr mTaskQueue; + + ~EventTargetWrapper() + { + } + +public: + explicit EventTargetWrapper(TaskQueue* aTaskQueue) + : mTaskQueue(aTaskQueue) + { + MOZ_ASSERT(mTaskQueue); + } + + NS_IMETHOD + DispatchFromScript(nsIRunnable* aEvent, uint32_t aFlags) override + { + nsCOMPtr ref = aEvent; + return Dispatch(ref.forget(), aFlags); + } + + NS_IMETHOD + Dispatch(already_AddRefed aEvent, uint32_t aFlags) override + { + nsCOMPtr runnable = aEvent; + MonitorAutoLock mon(mTaskQueue->mQueueMonitor); + return mTaskQueue->DispatchLocked(/* passed by ref */runnable, + DontAssertDispatchSuccess, + NormalDispatch); + } + + NS_IMETHOD + DelayedDispatch(already_AddRefed, uint32_t aFlags) override + { + return NS_ERROR_NOT_IMPLEMENTED; + } + + NS_IMETHOD + IsOnCurrentThread(bool* aResult) override + { + *aResult = mTaskQueue->IsCurrentThreadIn(); + return NS_OK; + } + + NS_DECL_THREADSAFE_ISUPPORTS +}; + +NS_IMPL_ISUPPORTS(TaskQueue::EventTargetWrapper, nsIEventTarget) + +TaskQueue::TaskQueue(already_AddRefed aTarget, + bool aRequireTailDispatch) + : AbstractThread(aRequireTailDispatch) + , mTarget(aTarget) + , mQueueMonitor("TaskQueue::Queue") + , mTailDispatcher(nullptr) + , mIsRunning(false) + , mIsShutdown(false) +{ + MOZ_COUNT_CTOR(TaskQueue); +} + +TaskQueue::~TaskQueue() +{ + MonitorAutoLock mon(mQueueMonitor); + MOZ_ASSERT(mIsShutdown); + MOZ_COUNT_DTOR(TaskQueue); +} + +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, + DispatchFailureHandling aFailureHandling, + DispatchReason aReason) +{ + mQueueMonitor.AssertCurrentThreadOwns(); + if (mIsShutdown) { + return NS_ERROR_FAILURE; + } + + AbstractThread* currentThread; + if (aReason != TailDispatch && (currentThread = GetCurrent()) && RequiresTailDispatch(currentThread)) { + currentThread->TailDispatcher().AddTask(this, aRunnable.forget(), aFailureHandling); + return NS_OK; + } + + mTasks.push(aRunnable.forget()); + if (mIsRunning) { + return NS_OK; + } + RefPtr runner(new Runner(this)); + nsresult rv = mTarget->Dispatch(runner.forget(), NS_DISPATCH_NORMAL); + if (NS_FAILED(rv)) { + NS_WARNING("Failed to dispatch runnable to run TaskQueue"); + return rv; + } + mIsRunning = true; + + return NS_OK; +} + +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.empty()); + 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); + mIsShutdown = true; + RefPtr p = mShutdownPromise.Ensure(__func__); + MaybeResolveShutdown(); + mon.NotifyAll(); + return p; +} + +bool +TaskQueue::IsEmpty() +{ + MonitorAutoLock mon(mQueueMonitor); + return mTasks.empty(); +} + +uint32_t +TaskQueue::ImpreciseLengthForHeuristics() +{ + MonitorAutoLock mon(mQueueMonitor); + return mTasks.size(); +} + +bool +TaskQueue::IsCurrentThreadIn() +{ + bool in = NS_GetCurrentThread() == mRunningThread; + return in; +} + +already_AddRefed +TaskQueue::WrapAsEventTarget() +{ + nsCOMPtr ref = new EventTargetWrapper(this); + return ref.forget(); +} + +nsresult +TaskQueue::Runner::Run() +{ + RefPtr event; + { + MonitorAutoLock mon(mQueue->mQueueMonitor); + MOZ_ASSERT(mQueue->mIsRunning); + if (mQueue->mTasks.size() == 0) { + mQueue->mIsRunning = false; + mQueue->MaybeResolveShutdown(); + mon.NotifyAll(); + return NS_OK; + } + event = mQueue->mTasks.front().forget(); + mQueue->mTasks.pop(); + } + MOZ_ASSERT(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); + 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 = nullptr; + + { + MonitorAutoLock mon(mQueue->mQueueMonitor); + if (mQueue->mTasks.size() == 0) { + // 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 = mQueue->mTarget->Dispatch(this, 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; +} + +} // namespace mozilla diff --git a/xpcom/threads/TaskQueue.h b/xpcom/threads/TaskQueue.h new file mode 100644 index 000000000..aafd206a7 --- /dev/null +++ b/xpcom/threads/TaskQueue.h @@ -0,0 +1,203 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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/Monitor.h" +#include "mozilla/MozPromise.h" +#include "mozilla/RefPtr.h" +#include "mozilla/TaskDispatcher.h" +#include "mozilla/Unused.h" + +#include + +#include "nsThreadUtils.h" + +class nsIEventTarget; +class nsIRunnable; + +namespace mozilla { + +typedef MozPromise ShutdownPromise; + +// 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. +class TaskQueue : public AbstractThread +{ + class EventTargetWrapper; + +public: + explicit TaskQueue(already_AddRefed aTarget, + bool aSupportsTailDispatch = false); + + TaskDispatcher& TailDispatcher() override; + + TaskQueue* AsTaskQueue() override { return this; } + + void Dispatch(already_AddRefed aRunnable, + DispatchFailureHandling aFailureHandling = AssertDispatchSuccess, + DispatchReason aReason = NormalDispatch) override + { + nsCOMPtr r = aRunnable; + { + MonitorAutoLock mon(mQueueMonitor); + nsresult rv = DispatchLocked(/* passed by ref */r, aFailureHandling, aReason); + MOZ_DIAGNOSTIC_ASSERT(aFailureHandling == DontAssertDispatchSuccess || NS_SUCCEEDED(rv)); + Unused << rv; + } + // 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. + } + + // 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(); + uint32_t ImpreciseLengthForHeuristics(); + + // Returns true if the current thread is currently running a Runnable in + // the task queue. + bool IsCurrentThreadIn() override; + + // Create a new nsIEventTarget wrapper object that dispatches to this + // TaskQueue. + already_AddRefed WrapAsEventTarget(); + +protected: + 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, + DispatchFailureHandling aFailureHandling, + DispatchReason aReason = NormalDispatch); + + void MaybeResolveShutdown() + { + mQueueMonitor.AssertCurrentThreadOwns(); + if (mIsShutdown && !mIsRunning) { + mShutdownPromise.ResolveIfExists(true, __func__); + mTarget = nullptr; + } + } + + nsCOMPtr mTarget; + + // Monitor that protects the queue and mIsRunning; + Monitor mQueueMonitor; + + // Queue of tasks to run. + std::queue> mTasks; + + // 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 AutoTaskDispatcher + { + public: + explicit AutoTaskGuard(TaskQueue* aQueue) + : AutoTaskDispatcher(/* aIsTailDispatcher = */ true), 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); + mQueue->mTailDispatcher = this; + + mLastCurrentThread = sCurrentThreadTLS.get(); + sCurrentThreadTLS.set(aQueue); + + MOZ_ASSERT(mQueue->mRunningThread == nullptr); + mQueue->mRunningThread = NS_GetCurrentThread(); + } + + ~AutoTaskGuard() + { + DrainDirectTasks(); + + MOZ_ASSERT(mQueue->mRunningThread == NS_GetCurrentThread()); + mQueue->mRunningThread = nullptr; + + sCurrentThreadTLS.set(mLastCurrentThread); + mQueue->mTailDispatcher = nullptr; + } + + private: + TaskQueue* mQueue; + AbstractThread* mLastCurrentThread; + }; + + TaskDispatcher* mTailDispatcher; + + // True if we've dispatched an event to the target to execute events from + // the queue. + bool mIsRunning; + + // True if we've started our shutdown process. + bool mIsShutdown; + MozPromiseHolder mShutdownPromise; + + class Runner : public Runnable { + public: + explicit Runner(TaskQueue* aQueue) + : mQueue(aQueue) + { + } + NS_IMETHOD Run() override; + private: + RefPtr mQueue; + }; +}; + +} // namespace mozilla + +#endif // TaskQueue_h_ diff --git a/xpcom/threads/ThreadStackHelper.cpp b/xpcom/threads/ThreadStackHelper.cpp new file mode 100644 index 000000000..d31bf6359 --- /dev/null +++ b/xpcom/threads/ThreadStackHelper.cpp @@ -0,0 +1,726 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ThreadStackHelper.h" +#include "MainThreadUtils.h" +#include "nsJSPrincipals.h" +#include "nsScriptSecurityManager.h" +#include "jsfriendapi.h" +#ifdef MOZ_THREADSTACKHELPER_NATIVE +#include "shared-libraries.h" +#endif + +#include "mozilla/Assertions.h" +#include "mozilla/Attributes.h" +#include "mozilla/IntegerPrintfMacros.h" +#include "mozilla/Move.h" +#include "mozilla/Scoped.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/MemoryChecking.h" +#include "mozilla/Sprintf.h" + +#ifdef __GNUC__ +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wshadow" +#endif + +#if defined(MOZ_VALGRIND) +# include +#endif + +#include +#include +#include + +#ifdef XP_LINUX +#include +#include +#include +#endif + +#ifdef __GNUC__ +# pragma GCC diagnostic pop // -Wshadow +#endif + +#if defined(XP_LINUX) || defined(XP_MACOSX) +#include +#endif + +#ifdef ANDROID +#ifndef SYS_gettid +#define SYS_gettid __NR_gettid +#endif +#if defined(__arm__) && !defined(__NR_rt_tgsigqueueinfo) +// Some NDKs don't define this constant even though the kernel supports it. +#define __NR_rt_tgsigqueueinfo (__NR_SYSCALL_BASE+363) +#endif +#ifndef SYS_rt_tgsigqueueinfo +#define SYS_rt_tgsigqueueinfo __NR_rt_tgsigqueueinfo +#endif +#endif + +#ifdef MOZ_THREADSTACKHELPER_NATIVE +#if defined(MOZ_THREADSTACKHELPER_X86) || \ + defined(MOZ_THREADSTACKHELPER_X64) || \ + defined(MOZ_THREADSTACKHELPER_ARM) +// On these architectures, the stack grows downwards (toward lower addresses). +#define MOZ_THREADSTACKHELPER_STACK_GROWS_DOWN +#else +#error "Unsupported architecture" +#endif +#endif // MOZ_THREADSTACKHELPER_NATIVE + +namespace mozilla { + +void +ThreadStackHelper::Startup() +{ +#if defined(XP_LINUX) + MOZ_ASSERT(NS_IsMainThread()); + if (!sInitialized) { + // TODO: centralize signal number allocation + sFillStackSignum = SIGRTMIN + 4; + if (sFillStackSignum > SIGRTMAX) { + // Leave uninitialized + MOZ_ASSERT(false); + return; + } + struct sigaction sigact = {}; + sigact.sa_sigaction = FillStackHandler; + sigemptyset(&sigact.sa_mask); + sigact.sa_flags = SA_SIGINFO | SA_RESTART; + MOZ_ALWAYS_TRUE(!::sigaction(sFillStackSignum, &sigact, nullptr)); + } + sInitialized++; +#endif +} + +void +ThreadStackHelper::Shutdown() +{ +#if defined(XP_LINUX) + MOZ_ASSERT(NS_IsMainThread()); + if (sInitialized == 1) { + struct sigaction sigact = {}; + sigact.sa_handler = SIG_DFL; + MOZ_ALWAYS_TRUE(!::sigaction(sFillStackSignum, &sigact, nullptr)); + } + sInitialized--; +#endif +} + +ThreadStackHelper::ThreadStackHelper() + : mStackToFill(nullptr) +#ifdef MOZ_THREADSTACKHELPER_PSEUDO + , mPseudoStack(mozilla_get_pseudo_stack()) +#ifdef MOZ_THREADSTACKHELPER_NATIVE + , mContextToFill(nullptr) +#endif + , mMaxStackSize(Stack::sMaxInlineStorage) + , mMaxBufferSize(512) +#endif +{ +#if defined(XP_LINUX) + MOZ_ALWAYS_TRUE(!::sem_init(&mSem, 0, 0)); + mThreadID = ::syscall(SYS_gettid); +#elif defined(XP_WIN) + mInitialized = !!::DuplicateHandle( + ::GetCurrentProcess(), ::GetCurrentThread(), + ::GetCurrentProcess(), &mThreadID, + THREAD_SUSPEND_RESUME +#ifdef MOZ_THREADSTACKHELPER_NATIVE + | THREAD_GET_CONTEXT | THREAD_QUERY_INFORMATION +#endif + , FALSE, 0); + MOZ_ASSERT(mInitialized); +#elif defined(XP_MACOSX) + mThreadID = mach_thread_self(); +#endif + +#ifdef MOZ_THREADSTACKHELPER_NATIVE + GetThreadStackBase(); +#endif +} + +ThreadStackHelper::~ThreadStackHelper() +{ +#if defined(XP_LINUX) + MOZ_ALWAYS_TRUE(!::sem_destroy(&mSem)); +#elif defined(XP_WIN) + if (mInitialized) { + MOZ_ALWAYS_TRUE(!!::CloseHandle(mThreadID)); + } +#endif +} + +#ifdef MOZ_THREADSTACKHELPER_NATIVE +void ThreadStackHelper::GetThreadStackBase() +{ + mThreadStackBase = 0; + +#if defined(XP_LINUX) + void* stackAddr; + size_t stackSize; + ::pthread_t pthr = ::pthread_self(); + ::pthread_attr_t pthr_attr; + NS_ENSURE_TRUE_VOID(!::pthread_getattr_np(pthr, &pthr_attr)); + if (!::pthread_attr_getstack(&pthr_attr, &stackAddr, &stackSize)) { +#ifdef MOZ_THREADSTACKHELPER_STACK_GROWS_DOWN + mThreadStackBase = intptr_t(stackAddr) + stackSize; +#else + mThreadStackBase = intptr_t(stackAddr); +#endif + } + MOZ_ALWAYS_TRUE(!::pthread_attr_destroy(&pthr_attr)); + +#elif defined(XP_WIN) + ::MEMORY_BASIC_INFORMATION meminfo = {}; + NS_ENSURE_TRUE_VOID(::VirtualQuery(&meminfo, &meminfo, sizeof(meminfo))); +#ifdef MOZ_THREADSTACKHELPER_STACK_GROWS_DOWN + mThreadStackBase = intptr_t(meminfo.BaseAddress) + meminfo.RegionSize; +#else + mThreadStackBase = intptr_t(meminfo.AllocationBase); +#endif + +#elif defined(XP_MACOSX) + ::pthread_t pthr = ::pthread_self(); + mThreadStackBase = intptr_t(::pthread_get_stackaddr_np(pthr)); + +#else + #error "Unsupported platform" +#endif // platform +} +#endif // MOZ_THREADSTACKHELPER_NATIVE + +namespace { +template +class ScopedSetPtr +{ +private: + T*& mPtr; +public: + ScopedSetPtr(T*& p, T* val) : mPtr(p) { mPtr = val; } + ~ScopedSetPtr() { mPtr = nullptr; } +}; +} // namespace + +void +ThreadStackHelper::GetStack(Stack& aStack) +{ + // Always run PrepareStackBuffer first to clear aStack + if (!PrepareStackBuffer(aStack)) { + // Skip and return empty aStack + return; + } + + ScopedSetPtr stackPtr(mStackToFill, &aStack); + +#if defined(XP_LINUX) + if (!sInitialized) { + MOZ_ASSERT(false); + return; + } + siginfo_t uinfo = {}; + uinfo.si_signo = sFillStackSignum; + uinfo.si_code = SI_QUEUE; + uinfo.si_pid = getpid(); + uinfo.si_uid = getuid(); + uinfo.si_value.sival_ptr = this; + if (::syscall(SYS_rt_tgsigqueueinfo, uinfo.si_pid, + mThreadID, sFillStackSignum, &uinfo)) { + // rt_tgsigqueueinfo was added in Linux 2.6.31. + // Could have failed because the syscall did not exist. + return; + } + MOZ_ALWAYS_TRUE(!::sem_wait(&mSem)); + +#elif defined(XP_WIN) + if (!mInitialized) { + MOZ_ASSERT(false); + return; + } + if (::SuspendThread(mThreadID) == DWORD(-1)) { + MOZ_ASSERT(false); + return; + } + + // SuspendThread is asynchronous, so the thread may still be running. Use + // GetThreadContext to ensure it's really suspended. + // See https://blogs.msdn.microsoft.com/oldnewthing/20150205-00/?p=44743. + CONTEXT context; + context.ContextFlags = CONTEXT_CONTROL; + if (::GetThreadContext(mThreadID, &context)) { + FillStackBuffer(); + FillThreadContext(); + } + + MOZ_ALWAYS_TRUE(::ResumeThread(mThreadID) != DWORD(-1)); + +#elif defined(XP_MACOSX) +# if defined(MOZ_VALGRIND) && defined(RUNNING_ON_VALGRIND) + if (RUNNING_ON_VALGRIND) { + /* thread_suspend and thread_resume sometimes hang runs on Valgrind, + for unknown reasons. So, just avoid them. See bug 1100911. */ + return; + } +# endif + + if (::thread_suspend(mThreadID) != KERN_SUCCESS) { + MOZ_ASSERT(false); + return; + } + + FillStackBuffer(); + FillThreadContext(); + + MOZ_ALWAYS_TRUE(::thread_resume(mThreadID) == KERN_SUCCESS); + +#endif +} + +#ifdef MOZ_THREADSTACKHELPER_NATIVE +class ThreadStackHelper::ThreadContext final +{ +public: + // TODO: provide per-platform definition of Context. + typedef struct {} Context; + + // Limit copied stack to 4kB + static const size_t kMaxStackSize = 0x1000; + // Limit unwound stack to 32 frames + static const unsigned int kMaxStackFrames = 32; + // Whether this structure contains valid data + bool mValid; + // Processor context + Context mContext; + // Stack area + UniquePtr mStack; + // Start of stack area + uintptr_t mStackBase; + // Size of stack area + size_t mStackSize; + // End of stack area + const void* mStackEnd; + + ThreadContext() + : mValid(false) + , mStackBase(0) + , mStackSize(0) + , mStackEnd(nullptr) {} +}; +#endif // MOZ_THREADSTACKHELPER_NATIVE + +void +ThreadStackHelper::GetNativeStack(Stack& aStack) +{ +#ifdef MOZ_THREADSTACKHELPER_NATIVE + ThreadContext context; + context.mStack = MakeUnique(ThreadContext::kMaxStackSize); + + ScopedSetPtr contextPtr(mContextToFill, &context); + + // Get pseudostack first and fill the thread context. + GetStack(aStack); + NS_ENSURE_TRUE_VOID(context.mValid); + + // TODO: walk the saved stack frames. +#endif // MOZ_THREADSTACKHELPER_NATIVE +} + +#ifdef XP_LINUX + +int ThreadStackHelper::sInitialized; +int ThreadStackHelper::sFillStackSignum; + +void +ThreadStackHelper::FillStackHandler(int aSignal, siginfo_t* aInfo, + void* aContext) +{ + ThreadStackHelper* const helper = + reinterpret_cast(aInfo->si_value.sival_ptr); + helper->FillStackBuffer(); + helper->FillThreadContext(aContext); + ::sem_post(&helper->mSem); +} + +#endif // XP_LINUX + +bool +ThreadStackHelper::PrepareStackBuffer(Stack& aStack) +{ + // Return false to skip getting the stack and return an empty stack + aStack.clear(); +#ifdef MOZ_THREADSTACKHELPER_PSEUDO + /* Normally, provided the profiler is enabled, it would be an error if we + don't have a pseudostack here (the thread probably forgot to call + profiler_register_thread). However, on B2G, profiling secondary threads + may be disabled despite profiler being enabled. This is by-design and + is not an error. */ +#ifdef MOZ_WIDGET_GONK + if (!mPseudoStack) { + return false; + } +#endif + MOZ_ASSERT(mPseudoStack); + if (!aStack.reserve(mMaxStackSize) || + !aStack.reserve(aStack.capacity()) || // reserve up to the capacity + !aStack.EnsureBufferCapacity(mMaxBufferSize)) { + return false; + } + return true; +#else + return false; +#endif +} + +#ifdef MOZ_THREADSTACKHELPER_PSEUDO + +namespace { + +bool +IsChromeJSScript(JSScript* aScript) +{ + // May be called from another thread or inside a signal handler. + // We assume querying the script is safe but we must not manipulate it. + + nsIScriptSecurityManager* const secman = + nsScriptSecurityManager::GetScriptSecurityManager(); + NS_ENSURE_TRUE(secman, false); + + JSPrincipals* const principals = JS_GetScriptPrincipals(aScript); + return secman->IsSystemPrincipal(nsJSPrincipals::get(principals)); +} + +// Get the full path after the URI scheme, if the URI matches the scheme. +// For example, GetFullPathForScheme("a://b/c/d/e", "a://") returns "b/c/d/e". +template +const char* +GetFullPathForScheme(const char* filename, const char (&scheme)[LEN]) { + // Account for the null terminator included in LEN. + if (!strncmp(filename, scheme, LEN - 1)) { + return filename + LEN - 1; + } + return nullptr; +} + +// Get the full path after a URI component, if the URI contains the component. +// For example, GetPathAfterComponent("a://b/c/d/e", "/c/") returns "d/e". +template +const char* +GetPathAfterComponent(const char* filename, const char (&component)[LEN]) { + const char* found = nullptr; + const char* next = strstr(filename, component); + while (next) { + // Move 'found' to end of the component, after the separator '/'. + // 'LEN - 1' accounts for the null terminator included in LEN, + found = next + LEN - 1; + // Resume searching before the separator '/'. + next = strstr(found - 1, component); + } + return found; +} + +} // namespace + +const char* +ThreadStackHelper::AppendJSEntry(const volatile StackEntry* aEntry, + intptr_t& aAvailableBufferSize, + const char* aPrevLabel) +{ + // May be called from another thread or inside a signal handler. + // We assume querying the script is safe but we must not manupulate it. + // Also we must not allocate any memory from heap. + MOZ_ASSERT(aEntry->isJs()); + + const char* label; + JSScript* script = aEntry->script(); + if (!script) { + label = "(profiling suppressed)"; + } else if (IsChromeJSScript(aEntry->script())) { + const char* filename = JS_GetScriptFilename(aEntry->script()); + const unsigned lineno = JS_PCToLineNumber(aEntry->script(), aEntry->pc()); + MOZ_ASSERT(filename); + + char buffer[128]; // Enough to fit longest js file name from the tree + + // Some script names are in the form "foo -> bar -> baz". + // Here we find the origin of these redirected scripts. + const char* basename = GetPathAfterComponent(filename, " -> "); + if (basename) { + filename = basename; + } + + basename = GetFullPathForScheme(filename, "chrome://"); + if (!basename) { + basename = GetFullPathForScheme(filename, "resource://"); + } + if (!basename) { + // If the (add-on) script is located under the {profile}/extensions + // directory, extract the path after the /extensions/ part. + basename = GetPathAfterComponent(filename, "/extensions/"); + } + if (!basename) { + // Only keep the file base name for paths outside the above formats. + basename = strrchr(filename, '/'); + basename = basename ? basename + 1 : filename; + // Look for Windows path separator as well. + filename = strrchr(basename, '\\'); + if (filename) { + basename = filename + 1; + } + } + + size_t len = SprintfLiteral(buffer, "%s:%u", basename, lineno); + if (len < sizeof(buffer)) { + if (mStackToFill->IsSameAsEntry(aPrevLabel, buffer)) { + return aPrevLabel; + } + + // Keep track of the required buffer size + aAvailableBufferSize -= (len + 1); + if (aAvailableBufferSize >= 0) { + // Buffer is big enough. + return mStackToFill->InfallibleAppendViaBuffer(buffer, len); + } + // Buffer is not big enough; fall through to using static label below. + } + // snprintf failed or buffer is not big enough. + label = "(chrome script)"; + } else { + label = "(content script)"; + } + + if (mStackToFill->IsSameAsEntry(aPrevLabel, label)) { + return aPrevLabel; + } + mStackToFill->infallibleAppend(label); + return label; +} + +#endif // MOZ_THREADSTACKHELPER_PSEUDO + +void +ThreadStackHelper::FillStackBuffer() +{ + MOZ_ASSERT(mStackToFill->empty()); + +#ifdef MOZ_THREADSTACKHELPER_PSEUDO + size_t reservedSize = mStackToFill->capacity(); + size_t reservedBufferSize = mStackToFill->AvailableBufferSize(); + intptr_t availableBufferSize = intptr_t(reservedBufferSize); + + // Go from front to back + const volatile StackEntry* entry = mPseudoStack->mStack; + const volatile StackEntry* end = entry + mPseudoStack->stackSize(); + // Deduplicate identical, consecutive frames + const char* prevLabel = nullptr; + for (; reservedSize-- && entry != end; entry++) { + /* We only accept non-copy labels, including js::RunScript, + because we only want static labels in the hang stack. */ + if (entry->isCopyLabel()) { + continue; + } + if (entry->isJs()) { + prevLabel = AppendJSEntry(entry, availableBufferSize, prevLabel); + continue; + } +#ifdef MOZ_THREADSTACKHELPER_NATIVE + if (mContextToFill) { + mContextToFill->mStackEnd = entry->stackAddress(); + } +#endif + const char* const label = entry->label(); + if (mStackToFill->IsSameAsEntry(prevLabel, label)) { + // Avoid duplicate labels to save space in the stack. + continue; + } + mStackToFill->infallibleAppend(label); + prevLabel = label; + } + + // end != entry if we exited early due to not enough reserved frames. + // Expand the number of reserved frames for next time. + mMaxStackSize = mStackToFill->capacity() + (end - entry); + + // availableBufferSize < 0 if we needed a larger buffer than we reserved. + // Calculate a new reserve size for next time. + if (availableBufferSize < 0) { + mMaxBufferSize = reservedBufferSize - availableBufferSize; + } +#endif +} + +MOZ_ASAN_BLACKLIST void +ThreadStackHelper::FillThreadContext(void* aContext) +{ +#ifdef MOZ_THREADSTACKHELPER_NATIVE + if (!mContextToFill) { + return; + } + +#if 0 // TODO: remove dependency on Breakpad structs. +#if defined(XP_LINUX) + const ucontext_t& context = *reinterpret_cast(aContext); +#if defined(MOZ_THREADSTACKHELPER_X86) + mContextToFill->mContext.context_flags = MD_CONTEXT_X86_FULL; + mContextToFill->mContext.edi = context.uc_mcontext.gregs[REG_EDI]; + mContextToFill->mContext.esi = context.uc_mcontext.gregs[REG_ESI]; + mContextToFill->mContext.ebx = context.uc_mcontext.gregs[REG_EBX]; + mContextToFill->mContext.edx = context.uc_mcontext.gregs[REG_EDX]; + mContextToFill->mContext.ecx = context.uc_mcontext.gregs[REG_ECX]; + mContextToFill->mContext.eax = context.uc_mcontext.gregs[REG_EAX]; + mContextToFill->mContext.ebp = context.uc_mcontext.gregs[REG_EBP]; + mContextToFill->mContext.eip = context.uc_mcontext.gregs[REG_EIP]; + mContextToFill->mContext.eflags = context.uc_mcontext.gregs[REG_EFL]; + mContextToFill->mContext.esp = context.uc_mcontext.gregs[REG_ESP]; +#elif defined(MOZ_THREADSTACKHELPER_X64) + mContextToFill->mContext.context_flags = MD_CONTEXT_AMD64_FULL; + mContextToFill->mContext.eflags = uint32_t(context.uc_mcontext.gregs[REG_EFL]); + mContextToFill->mContext.rax = context.uc_mcontext.gregs[REG_RAX]; + mContextToFill->mContext.rcx = context.uc_mcontext.gregs[REG_RCX]; + mContextToFill->mContext.rdx = context.uc_mcontext.gregs[REG_RDX]; + mContextToFill->mContext.rbx = context.uc_mcontext.gregs[REG_RBX]; + mContextToFill->mContext.rsp = context.uc_mcontext.gregs[REG_RSP]; + mContextToFill->mContext.rbp = context.uc_mcontext.gregs[REG_RBP]; + mContextToFill->mContext.rsi = context.uc_mcontext.gregs[REG_RSI]; + mContextToFill->mContext.rdi = context.uc_mcontext.gregs[REG_RDI]; + memcpy(&mContextToFill->mContext.r8, + &context.uc_mcontext.gregs[REG_R8], 8 * sizeof(int64_t)); + mContextToFill->mContext.rip = context.uc_mcontext.gregs[REG_RIP]; +#elif defined(MOZ_THREADSTACKHELPER_ARM) + mContextToFill->mContext.context_flags = MD_CONTEXT_ARM_FULL; + memcpy(&mContextToFill->mContext.iregs[0], + &context.uc_mcontext.arm_r0, 17 * sizeof(int32_t)); +#else + #error "Unsupported architecture" +#endif // architecture + +#elif defined(XP_WIN) + // Breakpad context struct is based off of the Windows CONTEXT struct, + // so we assume they are the same; do some sanity checks to make sure. + static_assert(sizeof(ThreadContext::Context) == sizeof(::CONTEXT), + "Context struct mismatch"); + static_assert(offsetof(ThreadContext::Context, context_flags) == + offsetof(::CONTEXT, ContextFlags), + "Context struct mismatch"); + mContextToFill->mContext.context_flags = CONTEXT_FULL; + NS_ENSURE_TRUE_VOID(::GetThreadContext(mThreadID, + reinterpret_cast<::CONTEXT*>(&mContextToFill->mContext))); + +#elif defined(XP_MACOSX) +#if defined(MOZ_THREADSTACKHELPER_X86) + const thread_state_flavor_t flavor = x86_THREAD_STATE32; + x86_thread_state32_t state = {}; + mach_msg_type_number_t count = x86_THREAD_STATE32_COUNT; +#elif defined(MOZ_THREADSTACKHELPER_X64) + const thread_state_flavor_t flavor = x86_THREAD_STATE64; + x86_thread_state64_t state = {}; + mach_msg_type_number_t count = x86_THREAD_STATE64_COUNT; +#elif defined(MOZ_THREADSTACKHELPER_ARM) + const thread_state_flavor_t flavor = ARM_THREAD_STATE; + arm_thread_state_t state = {}; + mach_msg_type_number_t count = ARM_THREAD_STATE_COUNT; +#endif + NS_ENSURE_TRUE_VOID(KERN_SUCCESS == ::thread_get_state( + mThreadID, flavor, reinterpret_cast(&state), &count)); +#if __DARWIN_UNIX03 +#define GET_REGISTER(s, r) ((s).__##r) +#else +#define GET_REGISTER(s, r) ((s).r) +#endif +#if defined(MOZ_THREADSTACKHELPER_X86) + mContextToFill->mContext.context_flags = MD_CONTEXT_X86_FULL; + mContextToFill->mContext.edi = GET_REGISTER(state, edi); + mContextToFill->mContext.esi = GET_REGISTER(state, esi); + mContextToFill->mContext.ebx = GET_REGISTER(state, ebx); + mContextToFill->mContext.edx = GET_REGISTER(state, edx); + mContextToFill->mContext.ecx = GET_REGISTER(state, ecx); + mContextToFill->mContext.eax = GET_REGISTER(state, eax); + mContextToFill->mContext.ebp = GET_REGISTER(state, ebp); + mContextToFill->mContext.eip = GET_REGISTER(state, eip); + mContextToFill->mContext.eflags = GET_REGISTER(state, eflags); + mContextToFill->mContext.esp = GET_REGISTER(state, esp); +#elif defined(MOZ_THREADSTACKHELPER_X64) + mContextToFill->mContext.context_flags = MD_CONTEXT_AMD64_FULL; + mContextToFill->mContext.eflags = uint32_t(GET_REGISTER(state, rflags)); + mContextToFill->mContext.rax = GET_REGISTER(state, rax); + mContextToFill->mContext.rcx = GET_REGISTER(state, rcx); + mContextToFill->mContext.rdx = GET_REGISTER(state, rdx); + mContextToFill->mContext.rbx = GET_REGISTER(state, rbx); + mContextToFill->mContext.rsp = GET_REGISTER(state, rsp); + mContextToFill->mContext.rbp = GET_REGISTER(state, rbp); + mContextToFill->mContext.rsi = GET_REGISTER(state, rsi); + mContextToFill->mContext.rdi = GET_REGISTER(state, rdi); + memcpy(&mContextToFill->mContext.r8, + &GET_REGISTER(state, r8), 8 * sizeof(int64_t)); + mContextToFill->mContext.rip = GET_REGISTER(state, rip); +#elif defined(MOZ_THREADSTACKHELPER_ARM) + mContextToFill->mContext.context_flags = MD_CONTEXT_ARM_FULL; + memcpy(mContextToFill->mContext.iregs, + GET_REGISTER(state, r), 17 * sizeof(int32_t)); +#else + #error "Unsupported architecture" +#endif // architecture +#undef GET_REGISTER + +#else + #error "Unsupported platform" +#endif // platform + + intptr_t sp = 0; +#if defined(MOZ_THREADSTACKHELPER_X86) + sp = mContextToFill->mContext.esp; +#elif defined(MOZ_THREADSTACKHELPER_X64) + sp = mContextToFill->mContext.rsp; +#elif defined(MOZ_THREADSTACKHELPER_ARM) + sp = mContextToFill->mContext.iregs[13]; +#else + #error "Unsupported architecture" +#endif // architecture + NS_ENSURE_TRUE_VOID(sp); + NS_ENSURE_TRUE_VOID(mThreadStackBase); + + size_t stackSize = std::min(intptr_t(ThreadContext::kMaxStackSize), + std::abs(sp - mThreadStackBase)); + + if (mContextToFill->mStackEnd) { + // Limit the start of stack to a certain location if specified. + stackSize = std::min(intptr_t(stackSize), + std::abs(sp - intptr_t(mContextToFill->mStackEnd))); + } + +#ifndef MOZ_THREADSTACKHELPER_STACK_GROWS_DOWN + // If if the stack grows upwards, and we need to recalculate our + // stack copy's base address. Subtract sizeof(void*) so that the + // location pointed to by sp is included. + sp -= stackSize - sizeof(void*); +#endif + +#ifndef MOZ_ASAN + memcpy(mContextToFill->mStack.get(), reinterpret_cast(sp), stackSize); + // Valgrind doesn't care about the access outside the stack frame, but + // the presence of uninitialised values on the stack does cause it to + // later report a lot of false errors when Breakpad comes to unwind it. + // So mark the extracted data as defined. + MOZ_MAKE_MEM_DEFINED(mContextToFill->mStack.get(), stackSize); +#else + // ASan will flag memcpy for access outside of stack frames, + // so roll our own memcpy here. + intptr_t* dst = reinterpret_cast(&mContextToFill->mStack[0]); + const intptr_t* src = reinterpret_cast(sp); + for (intptr_t len = stackSize; len > 0; len -= sizeof(*src)) { + *(dst++) = *(src++); + } +#endif + + mContextToFill->mStackBase = uintptr_t(sp); + mContextToFill->mStackSize = stackSize; + mContextToFill->mValid = true; +#endif +#endif // MOZ_THREADSTACKHELPER_NATIVE +} + +} // namespace mozilla diff --git a/xpcom/threads/ThreadStackHelper.h b/xpcom/threads/ThreadStackHelper.h new file mode 100644 index 000000000..9c40ad5e2 --- /dev/null +++ b/xpcom/threads/ThreadStackHelper.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_ThreadStackHelper_h +#define mozilla_ThreadStackHelper_h + +#include "mozilla/ThreadHangStats.h" + +#include "GeckoProfiler.h" + +#include + +#if defined(XP_LINUX) +#include +#include +#include +#elif defined(XP_WIN) +#include +#elif defined(XP_MACOSX) +#include +#endif + +// Support pseudostack on these platforms. +#if defined(XP_LINUX) || defined(XP_WIN) || defined(XP_MACOSX) +# ifdef MOZ_ENABLE_PROFILER_SPS +# define MOZ_THREADSTACKHELPER_PSEUDO +# endif +#endif + +#ifdef MOZ_THREADSTACKHELPER_PSEUDO +# define MOZ_THREADSTACKHELPER_NATIVE +# if defined(__i386__) || defined(_M_IX86) +# define MOZ_THREADSTACKHELPER_X86 +# elif defined(__x86_64__) || defined(_M_X64) +# define MOZ_THREADSTACKHELPER_X64 +# elif defined(__arm__) || defined(_M_ARM) +# define MOZ_THREADSTACKHELPER_ARM +# else + // Unsupported architecture +# undef MOZ_THREADSTACKHELPER_NATIVE +# endif +#endif + +namespace mozilla { + +/** + * ThreadStackHelper is used to retrieve the profiler pseudo-stack of a + * thread, as an alternative of using the profiler to take a profile. + * The target thread first declares an ThreadStackHelper instance; + * then another thread can call ThreadStackHelper::GetStack to retrieve + * the pseudo-stack of the target thread at that instant. + * + * Only non-copying labels are included in the stack, which means labels + * with custom text and markers are not included. + */ +class ThreadStackHelper +{ +public: + typedef Telemetry::HangStack Stack; + +private: + Stack* mStackToFill; +#ifdef MOZ_THREADSTACKHELPER_PSEUDO + const PseudoStack* const mPseudoStack; +#ifdef MOZ_THREADSTACKHELPER_NATIVE + class ThreadContext; + // Set to non-null if GetStack should get the thread context. + ThreadContext* mContextToFill; + intptr_t mThreadStackBase; +#endif + size_t mMaxStackSize; + size_t mMaxBufferSize; +#endif + + bool PrepareStackBuffer(Stack& aStack); + void FillStackBuffer(); + void FillThreadContext(void* aContext = nullptr); +#ifdef MOZ_THREADSTACKHELPER_PSEUDO + const char* AppendJSEntry(const volatile StackEntry* aEntry, + intptr_t& aAvailableBufferSize, + const char* aPrevLabel); +#endif +#ifdef MOZ_THREADSTACKHELPER_NATIVE + void GetThreadStackBase(); +#endif + +public: + /** + * Initialize ThreadStackHelper. Must be called from main thread. + */ + static void Startup(); + /** + * Uninitialize ThreadStackHelper. Must be called from main thread. + */ + static void Shutdown(); + + /** + * Create a ThreadStackHelper instance targeting the current thread. + */ + ThreadStackHelper(); + + ~ThreadStackHelper(); + + /** + * Retrieve the current pseudostack of the thread associated + * with this ThreadStackHelper. + * + * @param aStack Stack instance to be filled. + */ + void GetStack(Stack& aStack); + + /** + * Retrieve the current native stack of the thread associated + * with this ThreadStackHelper. + * + * @param aNativeStack Stack instance to be filled. + */ + void GetNativeStack(Stack& aStack); + +#if defined(XP_LINUX) +private: + static int sInitialized; + static int sFillStackSignum; + + static void FillStackHandler(int aSignal, siginfo_t* aInfo, void* aContext); + + sem_t mSem; + pid_t mThreadID; + +#elif defined(XP_WIN) +private: + bool mInitialized; + HANDLE mThreadID; + +#elif defined(XP_MACOSX) +private: + thread_act_t mThreadID; + +#endif +}; + +} // namespace mozilla + +#endif // mozilla_ThreadStackHelper_h diff --git a/xpcom/threads/ThrottledEventQueue.cpp b/xpcom/threads/ThrottledEventQueue.cpp new file mode 100644 index 000000000..941566ef2 --- /dev/null +++ b/xpcom/threads/ThrottledEventQueue.cpp @@ -0,0 +1,446 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. 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/Mutex.h" +#include "mozilla/Unused.h" +#include "nsEventQueue.h" + +namespace mozilla { + +using mozilla::services::GetObserverService; + +namespace { + +static const char kShutdownTopic[] = "xpcom-shutdown"; + +} // anonymous namespace + +// The ThrottledEventQueue is designed with inner and outer objects: +// +// XPCOM code nsObserverService +// | | +// | | +// v | +// +-------+ | +// | Outer | | +// +-------+ | +// | | +// | +-------+ | +// +-->| Inner |<--+ +// +-------+ +// +// Client code references the outer nsIEventTarget which in turn references +// an inner object. The inner object is also held alive by the observer +// service. +// +// If the outer object is dereferenced and destroyed, it will trigger a +// shutdown operation on the inner object. Similarly if the observer +// service notifies that the browser is shutting down, then the inner +// object also starts shutting down. +// +// Once the queue has drained we unregister from the observer service. If +// the outer object is already gone, then the inner object is free'd at this +// point. If the outer object still exists then calls fall back to the +// ThrottledEventQueue's base target. We just don't queue things +// any more. The inner is then released once the outer object is released. +// +// Note, we must keep the inner object alive and attached to the observer +// service until the TaskQueue is fully shutdown and idle. We must delay +// xpcom shutdown if the TaskQueue is in the middle of draining. +class ThrottledEventQueue::Inner final : public nsIObserver +{ + // 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 + { + RefPtr mInner; + + public: + explicit Executor(Inner* aInner) + : mInner(aInner) + { } + + NS_IMETHODIMP + Run() + { + mInner->ExecuteRunnable(); + return NS_OK; + } + }; + + mutable Mutex mMutex; + mutable CondVar mIdleCondVar; + + mozilla::CondVar mEventsAvailable; + + // any thread, protected by mutex + nsEventQueue mEventQueue; + + // written on main thread, read on any thread + nsCOMPtr mBaseTarget; + + // any thread, protected by mutex + nsCOMPtr mExecutor; + + // any thread, atomic + Atomic mExecutionDepth; + + // any thread, protected by mutex + bool mShutdownStarted; + + explicit Inner(nsIEventTarget* aBaseTarget) + : mMutex("ThrottledEventQueue") + , mIdleCondVar(mMutex, "ThrottledEventQueue:Idle") + , mEventsAvailable(mMutex, "[ThrottledEventQueue::Inner.mEventsAvailable]") + , mEventQueue(mEventsAvailable, nsEventQueue::eNormalQueue) + , mBaseTarget(aBaseTarget) + , mExecutionDepth(0) + , mShutdownStarted(false) + { + } + + ~Inner() + { + MOZ_ASSERT(!mExecutor); + MOZ_ASSERT(mShutdownStarted); + } + + void + ExecuteRunnable() + { + // Any thread + nsCOMPtr event; + bool shouldShutdown = false; + +#ifdef DEBUG + bool currentThread = false; + mBaseTarget->IsOnCurrentThread(¤tThread); + MOZ_ASSERT(currentThread); +#endif + + { + MutexAutoLock lock(mMutex); + + // We only dispatch an executor runnable when we know there is something + // in the queue, so this should never fail. + MOZ_ALWAYS_TRUE(mEventQueue.GetPendingEvent(getter_AddRefs(event), lock)); + + // 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.HasPendingEvent(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. We might also need to shutdown after running the + // last event. + else { + shouldShutdown = mShutdownStarted; + // Note, this breaks a ref cycle. + mExecutor = nullptr; + mIdleCondVar.NotifyAll(); + } + } + + // Execute the event now that we have unlocked. + ++mExecutionDepth; + Unused << event->Run(); + --mExecutionDepth; + + // If shutdown was started and the queue is now empty we can now + // finalize the shutdown. This is performed separately at the end + // of the method in order to wait for the event to finish running. + if (shouldShutdown) { + MOZ_ASSERT(IsEmpty()); + NS_DispatchToMainThread(NewRunnableMethod(this, &Inner::ShutdownComplete)); + } + } + + void + ShutdownComplete() + { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(IsEmpty()); + nsCOMPtr obs = GetObserverService(); + obs->RemoveObserver(this, kShutdownTopic); + } + +public: + static already_AddRefed + Create(nsIEventTarget* aBaseTarget) + { + MOZ_ASSERT(NS_IsMainThread()); + + if (ClearOnShutdown_Internal::sCurrentShutdownPhase != ShutdownPhase::NotInShutdown) { + return nullptr; + } + + nsCOMPtr obs = GetObserverService(); + if (NS_WARN_IF(!obs)) { + return nullptr; + } + + RefPtr ref = new Inner(aBaseTarget); + + nsresult rv = obs->AddObserver(ref, kShutdownTopic, + false /* means OS will hold a strong ref */); + if (NS_WARN_IF(NS_FAILED(rv))) { + ref->MaybeStartShutdown(); + MOZ_ASSERT(ref->IsEmpty()); + return nullptr; + } + + return ref.forget(); + } + + NS_IMETHOD + Observe(nsISupports*, const char* aTopic, const char16_t*) override + { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(!strcmp(aTopic, kShutdownTopic)); + + MaybeStartShutdown(); + + // Once shutdown begins we set the Atomic mShutdownStarted flag. + // This prevents any new runnables from being dispatched into the + // TaskQueue. Therefore this loop should be finite. + while (!IsEmpty()) { + MOZ_ALWAYS_TRUE(NS_ProcessNextEvent()); + } + + return NS_OK; + } + + void + MaybeStartShutdown() + { + // Any thread + MutexAutoLock lock(mMutex); + + if (mShutdownStarted) { + return; + } + mShutdownStarted = true; + + // We are marked for shutdown now, but we are still processing runnables. + // Return for now. The shutdown will be completed once the queue is + // drained. + if (mExecutor) { + return; + } + + // The queue is empty, so we can complete immediately. + NS_DispatchToMainThread(NewRunnableMethod(this, &Inner::ShutdownComplete)); + } + + bool + IsEmpty() const + { + // Any thread + return Length() == 0; + } + + uint32_t + Length() const + { + // Any thread + MutexAutoLock lock(mMutex); + return mEventQueue.Count(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) { + mIdleCondVar.Wait(); + } + } + + 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 we are shutting down, just fall back to our base target + // directly. + if (mShutdownStarted) { + return mBaseTarget->Dispatch(Move(aEvent), aFlags); + } + + // We are not currently processing events, so we must start + // operating on our base target. This is fallible, so do + // it first. Our lock will prevent the executor from accessing + // the event queue before we add the event below. + if (!mExecutor) { + // 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; + } + } + + // Only add the event to the underlying queue if are able to + // dispatch to our base target. + mEventQueue.PutEvent(Move(aEvent), 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 + IsOnCurrentThread(bool* aResult) + { + // Any thread + + bool shutdownAndIdle = false; + { + MutexAutoLock lock(mMutex); + shutdownAndIdle = mShutdownStarted && mEventQueue.Count(lock) == 0; + } + + bool onBaseTarget = false; + nsresult rv = mBaseTarget->IsOnCurrentThread(&onBaseTarget); + if (NS_FAILED(rv)) { + return rv; + } + + // We consider the current stack on this event target if are on + // the base target and one of the following is true + // 1) We are currently running an event OR + // 2) We are both shutting down and the queue is idle + *aResult = onBaseTarget && (mExecutionDepth || shutdownAndIdle); + + return NS_OK; + } + + NS_DECL_THREADSAFE_ISUPPORTS +}; + +NS_IMPL_ISUPPORTS(ThrottledEventQueue::Inner, nsIObserver); + +NS_IMPL_ISUPPORTS(ThrottledEventQueue, nsIEventTarget); + +ThrottledEventQueue::ThrottledEventQueue(already_AddRefed aInner) + : mInner(aInner) +{ + MOZ_ASSERT(mInner); +} + +ThrottledEventQueue::~ThrottledEventQueue() +{ + mInner->MaybeStartShutdown(); +} + +void +ThrottledEventQueue::MaybeStartShutdown() +{ + return mInner->MaybeStartShutdown(); +} + +already_AddRefed +ThrottledEventQueue::Create(nsIEventTarget* aBaseTarget) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aBaseTarget); + + RefPtr inner = Inner::Create(aBaseTarget); + if (NS_WARN_IF(!inner)) { + return nullptr; + } + + RefPtr ref = + new ThrottledEventQueue(inner.forget()); + return ref.forget(); +} + +bool +ThrottledEventQueue::IsEmpty() const +{ + return mInner->IsEmpty(); +} + +uint32_t +ThrottledEventQueue::Length() const +{ + return mInner->Length(); +} + +void +ThrottledEventQueue::AwaitIdle() const +{ + return mInner->AwaitIdle(); +} + +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(Move(aEvent), aFlags); +} + +NS_IMETHODIMP +ThrottledEventQueue::DelayedDispatch(already_AddRefed aEvent, + uint32_t aFlags) +{ + return mInner->DelayedDispatch(Move(aEvent), aFlags); +} + +NS_IMETHODIMP +ThrottledEventQueue::IsOnCurrentThread(bool* aResult) +{ + return mInner->IsOnCurrentThread(aResult); +} + +} // namespace mozilla diff --git a/xpcom/threads/ThrottledEventQueue.h b/xpcom/threads/ThrottledEventQueue.h new file mode 100644 index 000000000..e0762bcce --- /dev/null +++ b/xpcom/threads/ThrottledEventQueue.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/. */ + +// nsIEventTarget wrapper for throttling event dispatch. + +#ifndef mozilla_ThrottledEventQueue_h +#define mozilla_ThrottledEventQueue_h + +#include "nsIEventTarget.h" + +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 simply principal. 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. +// +// ThrottledEventQueue also implements an automatic shutdown mechanism. +// De-referencing the queue or browser shutdown will automatically begin +// shutdown. +// +// Once shutdown begins all events will bypass the queue and be dispatched +// straight to the underlying base target. +class ThrottledEventQueue final : public nsIEventTarget +{ + class Inner; + RefPtr mInner; + + explicit ThrottledEventQueue(already_AddRefed aInner); + ~ThrottledEventQueue(); + + // Begin shutdown of the event queue. This has no effect if shutdown + // is already in process. After this is called nsIEventTarget methods + // will bypass the queue and operate directly on the base target. + // Note, this could be made public if code needs to explicitly shutdown + // for some reason. + void MaybeStartShutdown(); + +public: + // Attempt to create a ThrottledEventQueue for the given target. This + // may return nullptr if the browser is already shutting down. + static already_AddRefed + Create(nsIEventTarget* aBaseTarget); + + // 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; + + // Block the current thread until the queue is empty. This may not + // be called on the main thread or the base target. + void AwaitIdle() const; + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIEVENTTARGET +}; + +} // namespace mozilla + +#endif // mozilla_ThrottledEventQueue_h diff --git a/xpcom/threads/TimerThread.cpp b/xpcom/threads/TimerThread.cpp new file mode 100644 index 000000000..0127e2dd1 --- /dev/null +++ b/xpcom/threads/TimerThread.cpp @@ -0,0 +1,752 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. 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 "nsThreadUtils.h" +#include "plarena.h" +#include "pratom.h" + +#include "nsIObserverService.h" +#include "nsIServiceManager.h" +#include "mozilla/Services.h" +#include "mozilla/ChaosMode.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/BinarySearch.h" + +#include + +using namespace mozilla; +#ifdef MOZ_TASK_TRACER +#include "GeckoTaskTracerImpl.h" +using namespace mozilla::tasktracer; +#endif + +NS_IMPL_ISUPPORTS(TimerThread, nsIRunnable, nsIObserver) + +TimerThread::TimerThread() : + mInitInProgress(false), + mInitialized(false), + mMonitor("TimerThread.mMonitor"), + mShutdown(false), + mWaiting(false), + mNotified(false), + mSleeping(false) +{ +} + +TimerThread::~TimerThread() +{ + mThread = nullptr; + + NS_ASSERTION(mTimers.IsEmpty(), "Timers remain in TimerThread::~TimerThread"); +} + +nsresult +TimerThread::InitLocks() +{ + return NS_OK; +} + +namespace { + +class TimerObserverRunnable : public Runnable +{ +public: + explicit TimerObserverRunnable(nsIObserver* aObserver) + : 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 allocator is layered over PLArenaPool, 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; + }; + + PLArenaPool mPool; + FreeEntry* mFirstFree; + mozilla::Monitor mMonitor; + +public: + TimerEventAllocator() + : mFirstFree(nullptr) + , mMonitor("TimerEventAllocator") + { + PL_InitArenaPool(&mPool, "TimerEventPool", 4096, /* align = */ 0); + } + + ~TimerEventAllocator() + { + PL_FinishArenaPool(&mPool); + } + + 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 + { + // Since nsTimerImpl is not thread-safe, we should release |mTimer| + // here in the target thread to avoid race condition. Otherwise, + // ~nsTimerEvent() which calls nsTimerImpl::Release() could run in the + // timer thread and result in race condition. + mTimer = nullptr; + return NS_OK; + } + + nsTimerEvent() + : mTimer() + , mGeneration(0) + { + // Note: We override operator new for this class, and the override is + // fallible! + sAllocatorUsers++; + } + + TimeStamp mInitTime; + + static void Init(); + static void Shutdown(); + static void DeleteAllocatorIfNeeded(); + + static void* operator new(size_t aSize) CPP_THROW_NEW + { + return sAllocator->Alloc(aSize); + } + void operator delete(void* aPtr) + { + sAllocator->Free(aPtr); + DeleteAllocatorIfNeeded(); + } + + already_AddRefed ForgetTimer() + { + return mTimer.forget(); + } + + void SetTimer(already_AddRefed aTimer) + { + mTimer = aTimer; + mGeneration = mTimer->GetGeneration(); + } + +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"); + sAllocatorUsers--; + } + + RefPtr mTimer; + int32_t mGeneration; + + static TimerEventAllocator* sAllocator; + static Atomic sAllocatorUsers; + static bool sCanDeleteAllocator; +}; + +TimerEventAllocator* nsTimerEvent::sAllocator = nullptr; +Atomic nsTimerEvent::sAllocatorUsers; +bool nsTimerEvent::sCanDeleteAllocator = false; + +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 { + PL_ARENA_ALLOCATE(p, &mPool, aSize); + if (!p) { + return nullptr; + } + } + + return p; +} + +void +TimerEventAllocator::Free(void* aPtr) +{ + mozilla::MonitorAutoLock lock(mMonitor); + + FreeEntry* entry = reinterpret_cast(aPtr); + + entry->mNext = mFirstFree; + mFirstFree = entry; +} + +} // namespace + +void +nsTimerEvent::Init() +{ + sAllocator = new TimerEventAllocator(); +} + +void +nsTimerEvent::Shutdown() +{ + sCanDeleteAllocator = true; + DeleteAllocatorIfNeeded(); +} + +void +nsTimerEvent::DeleteAllocatorIfNeeded() +{ + if (sCanDeleteAllocator && sAllocatorUsers == 0) { + delete sAllocator; + sAllocator = nullptr; + } +} + +NS_IMETHODIMP +nsTimerEvent::Run() +{ + if (!mTimer) { + MOZ_ASSERT(false); + return NS_OK; + } + + 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())); + } + + mTimer->Fire(mGeneration); + + // We call Cancel() to correctly release mTimer. + // Read more in the Cancel() implementation. + return Cancel(); +} + +nsresult +TimerThread::Init() +{ + MOZ_LOG(GetTimerLog(), LogLevel::Debug, + ("TimerThread::Init [%d]\n", mInitialized)); + + if (mInitialized) { + if (!mThread) { + return NS_ERROR_FAILURE; + } + + return NS_OK; + } + + nsTimerEvent::Init(); + + if (mInitInProgress.exchange(true) == false) { + // We hold on to mThread to keep the thread alive. + nsresult rv = NS_NewThread(getter_AddRefs(mThread), this); + if (NS_FAILED(rv)) { + mThread = nullptr; + } else { + RefPtr r = new TimerObserverRunnable(this); + if (NS_IsMainThread()) { + r->Run(); + } else { + NS_DispatchToMainThread(r); + } + } + + { + MonitorAutoLock lock(mMonitor); + mInitialized = true; + mMonitor.NotifyAll(); + } + } else { + MonitorAutoLock lock(mMonitor); + while (!mInitialized) { + mMonitor.Wait(); + } + } + + 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.AppendElements(mTimers); + mTimers.Clear(); + } + + uint32_t timersCount = timers.Length(); + for (uint32_t i = 0; i < timersCount; i++) { + nsTimerImpl* timer = timers[i]; + timer->Cancel(); + ReleaseTimerInternal(timer); + } + + 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 + +NS_IMETHODIMP +TimerThread::Run() +{ + PR_SetCurrentThreadName("Timer"); + + MonitorAutoLock lock(mMonitor); + + // 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 + int32_t halfMicrosecondsIntervalResolution = usIntervalResolution / 2; + bool forceRunNextTimer = false; + + while (!mShutdown) { + // Have to use PRIntervalTime here, since PR_WaitCondVar takes it + PRIntervalTime waitFor; + bool forceRunThisTimer = forceRunNextTimer; + forceRunNextTimer = false; + + 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 = PR_MillisecondsToInterval(milliseconds); + } else { + waitFor = PR_INTERVAL_NO_TIMEOUT; + TimeStamp now = TimeStamp::Now(); + nsTimerImpl* timer = nullptr; + + if (!mTimers.IsEmpty()) { + timer = mTimers[0]; + + if (now >= timer->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(timer); + RemoveTimerInternal(timer); + timer = nullptr; + + 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. + timerRef = PostTimerEvent(timerRef.forget()); + + if (timerRef) { + // We got our reference back due to an error. + // Unhook the nsRefPtr, and release manually so we can get the + // refcount. + nsrefcnt rc = timerRef.forget().take()->Release(); + (void)rc; + + // 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 ever happens, it is a bug in the + // code that created and used the timer. + // + // Further, note that this should never happen even with a + // misbehaving user, because nsTimerImpl::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(rc != 0, "destroyed timer off its target thread!"); + } + + 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(); + } + } + + if (!mTimers.IsEmpty()) { + timer = mTimers[0]; + + TimeStamp timeout = timer->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 halfMicrosecondsIntervalResolution, 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).ToMilliseconds() * 1000; + + if (ChaosMode::isActive(ChaosFeature::TimerScheduling)) { + // 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 const float sFractions[] = { + 0.0f, 0.25f, 0.5f, 0.75f, 1.0f, 1.75f, 2.75f + }; + microseconds *= + sFractions[ChaosMode::randomUint32LessThan(ArrayLength(sFractions))]; + forceRunNextTimer = true; + } + + if (microseconds < halfMicrosecondsIntervalResolution) { + forceRunNextTimer = false; + goto next; // round down; execute event now + } + waitFor = PR_MicrosecondsToInterval( + static_cast(microseconds)); // Floor is accurate enough. + if (waitFor == 0) { + waitFor = 1; // round up, wait the minimum time we can wait + } + } + + if (MOZ_LOG_TEST(GetTimerLog(), LogLevel::Debug)) { + if (waitFor == PR_INTERVAL_NO_TIMEOUT) + MOZ_LOG(GetTimerLog(), LogLevel::Debug, + ("waiting for PR_INTERVAL_NO_TIMEOUT\n")); + else + MOZ_LOG(GetTimerLog(), LogLevel::Debug, + ("waiting for %u\n", PR_IntervalToMilliseconds(waitFor))); + } + } + + mWaiting = true; + mNotified = false; + mMonitor.Wait(waitFor); + if (mNotified) { + forceRunNextTimer = false; + } + mWaiting = false; + } + + return NS_OK; +} + +nsresult +TimerThread::AddTimer(nsTimerImpl* aTimer) +{ + MonitorAutoLock lock(mMonitor); + + if (!aTimer->mEventTarget) { + return NS_ERROR_NOT_INITIALIZED; + } + + // Add the timer to our list. + int32_t i = AddTimerInternal(aTimer); + if (i < 0) { + return NS_ERROR_OUT_OF_MEMORY; + } + + // Awaken the timer thread. + if (mWaiting && i == 0) { + mNotified = true; + mMonitor.Notify(); + } + + return NS_OK; +} + +nsresult +TimerThread::RemoveTimer(nsTimerImpl* aTimer) +{ + MonitorAutoLock lock(mMonitor); + + // 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; + } + + // Awaken the timer thread. + if (mWaiting) { + mNotified = true; + mMonitor.Notify(); + } + + return NS_OK; +} + +// This function must be called from within a lock +int32_t +TimerThread::AddTimerInternal(nsTimerImpl* aTimer) +{ + mMonitor.AssertCurrentThreadOwns(); + if (mShutdown) { + return -1; + } + + TimeStamp now = TimeStamp::Now(); + + TimerAdditionComparator c(now, aTimer); + nsTimerImpl** insertSlot = mTimers.InsertElementSorted(aTimer, c); + + if (!insertSlot) { + return -1; + } + + NS_ADDREF(aTimer); + +#ifdef MOZ_TASK_TRACER + // Caller of AddTimer is the parent task of its timer event, so we store the + // TraceInfo here for later used. + aTimer->GetTLSTraceInfo(); +#endif + + return insertSlot - mTimers.Elements(); +} + +bool +TimerThread::RemoveTimerInternal(nsTimerImpl* aTimer) +{ + mMonitor.AssertCurrentThreadOwns(); + if (!mTimers.RemoveElement(aTimer)) { + return false; + } + + ReleaseTimerInternal(aTimer); + return true; +} + +void +TimerThread::ReleaseTimerInternal(nsTimerImpl* aTimer) +{ + if (!mShutdown) { + // copied to a local array before releasing in shutdown + mMonitor.AssertCurrentThreadOwns(); + } + NS_RELEASE(aTimer); +} + +already_AddRefed +TimerThread::PostTimerEvent(already_AddRefed aTimerRef) +{ + mMonitor.AssertCurrentThreadOwns(); + + RefPtr timer(aTimerRef); + if (!timer->mEventTarget) { + NS_ERROR("Attempt to post timer event to NULL event target"); + return timer.forget(); + } + + // 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. + + RefPtr event = new nsTimerEvent; + if (!event) { + return timer.forget(); + } + + if (MOZ_LOG_TEST(GetTimerLog(), LogLevel::Debug)) { + event->mInitTime = TimeStamp::Now(); + } + +#ifdef MOZ_TASK_TRACER + // During the dispatch of TimerEvent, we overwrite the current TraceInfo + // partially with the info saved in timer earlier, and restore it back by + // AutoSaveCurTraceInfo. + AutoSaveCurTraceInfo saveCurTraceInfo; + (timer->GetTracedTask()).SetTLSTraceInfo(); +#endif + + nsCOMPtr target = timer->mEventTarget; + event->SetTimer(timer.forget()); + + nsresult rv; + { + // We release mMonitor around the Dispatch because if this timer is targeted + // at the TimerThread we'll deadlock. + MonitorAutoUnlock unlock(mMonitor); + rv = target->Dispatch(event, NS_DISPATCH_NORMAL); + } + + if (NS_FAILED(rv)) { + timer = event->ForgetTimer(); + RemoveTimerInternal(timer); + return timer.forget(); + } + + return nullptr; +} + +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; + mMonitor.Notify(); +} + + +NS_IMETHODIMP +TimerThread::Observe(nsISupports* /* aSubject */, const char* aTopic, + const char16_t* /* aData */) +{ + 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; +} diff --git a/xpcom/threads/TimerThread.h b/xpcom/threads/TimerThread.h new file mode 100644 index 000000000..a7204810a --- /dev/null +++ b/xpcom/threads/TimerThread.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 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" + +namespace mozilla { +class TimeStamp; +} // namespace mozilla + +class TimerThread final + : public nsIRunnable + , public nsIObserver +{ +public: + typedef mozilla::Monitor Monitor; + typedef mozilla::TimeStamp TimeStamp; + typedef mozilla::TimeDuration TimeDuration; + + TimerThread(); + nsresult InitLocks(); + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIRUNNABLE + NS_DECL_NSIOBSERVER + + nsresult Init(); + nsresult Shutdown(); + + nsresult AddTimer(nsTimerImpl* aTimer); + nsresult RemoveTimer(nsTimerImpl* aTimer); + + void DoBeforeSleep(); + void DoAfterSleep(); + + bool IsOnTimerThread() const + { + return mThread == NS_GetCurrentThread(); + } + +private: + ~TimerThread(); + + mozilla::Atomic mInitInProgress; + bool mInitialized; + + // These two internal helper methods must be called while mMonitor is held. + // AddTimerInternal returns the position where the timer was added in the + // list, or -1 if it failed. + int32_t AddTimerInternal(nsTimerImpl* aTimer); + bool RemoveTimerInternal(nsTimerImpl* aTimer); + void ReleaseTimerInternal(nsTimerImpl* aTimer); + + already_AddRefed PostTimerEvent(already_AddRefed aTimerRef); + + nsCOMPtr mThread; + Monitor mMonitor; + + bool mShutdown; + bool mWaiting; + bool mNotified; + bool mSleeping; + + nsTArray mTimers; +}; + +struct TimerAdditionComparator +{ + TimerAdditionComparator(const mozilla::TimeStamp& aNow, + nsTimerImpl* aTimerToInsert) : + now(aNow) +#ifdef DEBUG + , timerToInsert(aTimerToInsert) +#endif + { + } + + bool LessThan(nsTimerImpl* aFromArray, nsTimerImpl* aNewTimer) const + { + MOZ_ASSERT(aNewTimer == timerToInsert, "Unexpected timer ordering"); + + // Skip any overdue timers. + return aFromArray->mTimeout <= now || + aFromArray->mTimeout <= aNewTimer->mTimeout; + } + + bool Equals(nsTimerImpl* aFromArray, nsTimerImpl* aNewTimer) const + { + return false; + } + +private: + const mozilla::TimeStamp& now; +#ifdef DEBUG + const nsTimerImpl* const timerToInsert; +#endif +}; + +#endif /* TimerThread_h___ */ diff --git a/xpcom/threads/moz.build b/xpcom/threads/moz.build new file mode 100644 index 000000000..5d54a4bf4 --- /dev/null +++ b/xpcom/threads/moz.build @@ -0,0 +1,89 @@ +# -*- 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 += [ + 'nsIEnvironment.idl', + 'nsIEventTarget.idl', + 'nsIIdlePeriod.idl', + 'nsIProcess.idl', + 'nsIRunnable.idl', + 'nsISupportsPriority.idl', + 'nsIThread.idl', + 'nsIThreadInternal.idl', + 'nsIThreadManager.idl', + 'nsIThreadPool.idl', + 'nsITimer.idl', +] + +XPIDL_MODULE = 'xpcom_threads' + +EXPORTS += [ + 'nsEventQueue.h', + 'nsICancelableRunnable.h', + 'nsIIncrementalRunnable.h', + 'nsMemoryPressure.h', + 'nsProcess.h', + 'nsThread.h', +] + +EXPORTS.mozilla += [ + 'AbstractThread.h', + 'BackgroundHangMonitor.h', + 'HangAnnotations.h', + 'HangMonitor.h', + 'LazyIdleThread.h', + 'MainThreadIdlePeriod.h', + 'MozPromise.h', + 'SharedThreadPool.h', + 'StateMirroring.h', + 'StateWatching.h', + 'SyncRunnable.h', + 'TaskDispatcher.h', + 'TaskQueue.h', + 'ThrottledEventQueue.h', +] + +UNIFIED_SOURCES += [ + 'AbstractThread.cpp', + 'BackgroundHangMonitor.cpp', + 'HangAnnotations.cpp', + 'HangMonitor.cpp', + 'LazyIdleThread.cpp', + 'MainThreadIdlePeriod.cpp', + 'nsEnvironment.cpp', + 'nsEventQueue.cpp', + 'nsMemoryPressure.cpp', + 'nsProcessCommon.cpp', + 'nsThread.cpp', + 'nsThreadManager.cpp', + 'nsThreadPool.cpp', + 'nsTimerImpl.cpp', + 'SharedThreadPool.cpp', + 'TaskQueue.cpp', + 'ThreadStackHelper.cpp', + 'ThrottledEventQueue.cpp', + 'TimerThread.cpp', +] + +LOCAL_INCLUDES += [ + '../build', + '/caps', + '/tools/profiler', +] + +# BHR disabled for Release builds because of bug 965392. +# BHR disabled for debug builds because of bug 979069. +# BHR disabled on gonk because of bug 1180533 +# BHR disabled for TSan builds because of bug 1121216. +if CONFIG['MOZ_UPDATE_CHANNEL'] not in ('release') and \ + not CONFIG['MOZ_DEBUG'] and \ + not CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk' and \ + not CONFIG['MOZ_TSAN']: + DEFINES['MOZ_ENABLE_BACKGROUND_HANG_MONITOR'] = 1 + +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 000000000..0de56675e --- /dev/null +++ b/xpcom/threads/nsEnvironment.cpp @@ -0,0 +1,163 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. 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 "prprf.h" +#include "nsBaseHashtable.h" +#include "nsHashKeys.h" +#include "nsPromiseFlatString.h" +#include "nsDependentString.h" +#include "nsNativeCharsetUtils.h" + +using namespace mozilla; + +NS_IMPL_ISUPPORTS(nsEnvironment, nsIEnvironment) + +nsresult +nsEnvironment::Create(nsISupports* aOuter, REFNSIID aIID, void** aResult) +{ + nsresult rv; + *aResult = nullptr; + + if (aOuter) { + return NS_ERROR_NO_AGGREGATION; + } + + nsEnvironment* obj = new nsEnvironment(); + + rv = obj->QueryInterface(aIID, aResult); + if (NS_FAILED(rv)) { + delete obj; + } + return rv; +} + +nsEnvironment::~nsEnvironment() +{ +} + +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 EnvHashType* gEnvHash = nullptr; + +static bool +EnsureEnvHash() +{ + if (gEnvHash) { + return true; + } + + gEnvHash = new EnvHashType; + if (!gEnvHash) { + return false; + } + + return true; +} + +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; + } + + MutexAutoLock lock(mLock); + + if (!EnsureEnvHash()) { + return NS_ERROR_UNEXPECTED; + } + + EnvEntryType* entry = gEnvHash->PutEntry(nativeName.get()); + if (!entry) { + return NS_ERROR_OUT_OF_MEMORY; + } + + char* newData = PR_smprintf("%s=%s", + nativeName.get(), + nativeVal.get()); + if (!newData) { + return NS_ERROR_OUT_OF_MEMORY; + } + + PR_SetEnv(newData); + if (entry->mData) { + PR_smprintf_free(entry->mData); + } + entry->mData = newData; + return NS_OK; +} + + diff --git a/xpcom/threads/nsEnvironment.h b/xpcom/threads/nsEnvironment.h new file mode 100644 index 000000000..234055a07 --- /dev/null +++ b/xpcom/threads/nsEnvironment.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 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(nsISupports* aOuter, REFNSIID aIID, void** aResult); + +private: + nsEnvironment() : mLock("nsEnvironment.mLock") + { + } + ~nsEnvironment(); + + mozilla::Mutex mLock; +}; + +#endif /* !nsEnvironment_h__ */ diff --git a/xpcom/threads/nsEventQueue.cpp b/xpcom/threads/nsEventQueue.cpp new file mode 100644 index 000000000..4ca2f11ea --- /dev/null +++ b/xpcom/threads/nsEventQueue.cpp @@ -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/. */ + +#include "nsEventQueue.h" +#include "nsAutoPtr.h" +#include "mozilla/Logging.h" +#include "nsThreadUtils.h" +#include "prthread.h" +#include "mozilla/ChaosMode.h" + +using namespace mozilla; + +static LazyLogModule sEventQueueLog("nsEventQueue"); +#ifdef LOG +#undef LOG +#endif +#define LOG(args) MOZ_LOG(sEventQueueLog, mozilla::LogLevel::Debug, args) + +nsEventQueue::nsEventQueue(mozilla::CondVar& aCondVar, EventQueueType aType) + : mHead(nullptr) + , mTail(nullptr) + , mOffsetHead(0) + , mOffsetTail(0) + , mEventsAvailable(aCondVar) + , mType(aType) +{ +} + +nsEventQueue::~nsEventQueue() +{ + // It'd be nice to be able to assert that no one else is holding the lock, + // but NSPR doesn't really expose APIs for it. + NS_ASSERTION(IsEmpty(), + "Non-empty event queue being destroyed; events being leaked."); + + if (mHead) { + FreePage(mHead); + } +} + +bool +nsEventQueue::GetEvent(bool aMayWait, nsIRunnable** aResult, + MutexAutoLock& aProofOfLock) +{ + if (aResult) { + *aResult = nullptr; + } + + while (IsEmpty()) { + if (!aMayWait) { + return false; + } + LOG(("EVENTQ(%p): wait begin\n", this)); + mEventsAvailable.Wait(); + LOG(("EVENTQ(%p): wait end\n", this)); + + if (mType == eSharedCondVarQueue) { + if (IsEmpty()) { + return false; + } + break; + } + } + + if (aResult) { + MOZ_ASSERT(mOffsetHead < EVENTS_PER_PAGE); + MOZ_ASSERT_IF(mHead == mTail, mOffsetHead <= mOffsetTail); + *aResult = mHead->mEvents[mOffsetHead++]; + + MOZ_ASSERT(*aResult); + MOZ_ASSERT(mOffsetHead <= EVENTS_PER_PAGE); + + // Check if mHead points to empty Page + if (mOffsetHead == EVENTS_PER_PAGE) { + Page* dead = mHead; + mHead = mHead->mNext; + FreePage(dead); + mOffsetHead = 0; + } + } + + return true; +} + +void +nsEventQueue::PutEvent(already_AddRefed&& aRunnable, + MutexAutoLock& aProofOfLock) +{ + if (!mHead) { + mHead = NewPage(); + MOZ_ASSERT(mHead); + + mTail = mHead; + mOffsetHead = 0; + mOffsetTail = 0; + } else if (mOffsetTail == EVENTS_PER_PAGE) { + Page* page = NewPage(); + MOZ_ASSERT(page); + + mTail->mNext = page; + mTail = page; + mOffsetTail = 0; + } + + nsIRunnable*& queueLocation = mTail->mEvents[mOffsetTail]; + MOZ_ASSERT(!queueLocation); + queueLocation = aRunnable.take(); + ++mOffsetTail; + LOG(("EVENTQ(%p): notify\n", this)); + mEventsAvailable.Notify(); +} + +void +nsEventQueue::PutEvent(nsIRunnable* aRunnable, MutexAutoLock& aProofOfLock) +{ + nsCOMPtr event(aRunnable); + PutEvent(event.forget(), aProofOfLock); +} + +size_t +nsEventQueue::Count(MutexAutoLock& aProofOfLock) const +{ + // It is obvious count is 0 when the queue is empty. + if (!mHead) { + return 0; + } + + /* How we count the number of events in the queue: + * 1. Let pageCount(x, y) denote the number of pages excluding the tail page + * where x is the index of head page and y is the index of the tail page. + * 2. Then we have pageCount(x, y) = y - x. + * + * Ex: pageCount(0, 0) = 0 where both head and tail pages point to page 0. + * pageCount(0, 1) = 1 where head points to page 0 and tail points page 1. + * + * 3. number of events = (EVENTS_PER_PAGE * pageCount(x, y)) + * - (empty slots in head page) + (non-empty slots in tail page) + * = (EVENTS_PER_PAGE * pageCount(x, y)) - mOffsetHead + mOffsetTail + */ + + int count = -mOffsetHead; + + // Compute (EVENTS_PER_PAGE * pageCount(x, y)) + for (Page* page = mHead; page != mTail; page = page->mNext) { + count += EVENTS_PER_PAGE; + } + + count += mOffsetTail; + MOZ_ASSERT(count >= 0); + + return count; +} diff --git a/xpcom/threads/nsEventQueue.h b/xpcom/threads/nsEventQueue.h new file mode 100644 index 000000000..23b55e63d --- /dev/null +++ b/xpcom/threads/nsEventQueue.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 nsEventQueue_h__ +#define nsEventQueue_h__ + +#include +#include "mozilla/CondVar.h" +#include "mozilla/Mutex.h" +#include "nsIRunnable.h" +#include "nsCOMPtr.h" +#include "mozilla/AlreadyAddRefed.h" +#include "mozilla/UniquePtr.h" + +class nsThreadPool; + +// A threadsafe FIFO event queue... +class nsEventQueue +{ +public: + typedef mozilla::MutexAutoLock MutexAutoLock; + + enum EventQueueType + { + eNormalQueue, + eSharedCondVarQueue + }; + + nsEventQueue(mozilla::CondVar& aCondVar, EventQueueType aType); + ~nsEventQueue(); + + // This method adds a new event to the pending event queue. The queue holds + // a strong reference to the event after this method returns. This method + // cannot fail. + void PutEvent(nsIRunnable* aEvent, MutexAutoLock& aProofOfLock); + void PutEvent(already_AddRefed&& aEvent, + MutexAutoLock& aProofOfLock); + + // This method gets an event from the event queue. If mayWait is true, then + // the method will block the calling thread until an event is available. If + // the event is null, then the method returns immediately indicating whether + // or not an event is pending. When the resulting event is non-null, the + // caller is responsible for releasing the event object. This method does + // not alter the reference count of the resulting event. + bool GetEvent(bool aMayWait, nsIRunnable** aEvent, + MutexAutoLock& aProofOfLock); + + // This method returns true if there is a pending event. + bool HasPendingEvent(MutexAutoLock& aProofOfLock) + { + return GetEvent(false, nullptr, aProofOfLock); + } + + // This method returns the next pending event or null. + bool GetPendingEvent(nsIRunnable** aRunnable, MutexAutoLock& aProofOfLock) + { + return GetEvent(false, aRunnable, aProofOfLock); + } + + size_t Count(MutexAutoLock&) const; + +private: + bool IsEmpty() + { + return !mHead || (mHead == mTail && mOffsetHead == mOffsetTail); + } + + enum + { + EVENTS_PER_PAGE = 255 + }; + + // Page objects are linked together to form a simple deque. + + struct Page + { + struct Page* mNext; + nsIRunnable* mEvents[EVENTS_PER_PAGE]; + }; + + static_assert((sizeof(Page) & (sizeof(Page) - 1)) == 0, + "sizeof(Page) should be a power of two to avoid heap slop."); + + static Page* NewPage() + { + return static_cast(moz_xcalloc(1, sizeof(Page))); + } + + static void FreePage(Page* aPage) + { + free(aPage); + } + + Page* mHead; + Page* mTail; + + uint16_t mOffsetHead; // offset into mHead where next item is removed + uint16_t mOffsetTail; // offset into mTail where next item is added + mozilla::CondVar& mEventsAvailable; + + EventQueueType mType; + + // These methods are made available to nsThreadPool as a hack, since + // nsThreadPool needs to have its threads sleep for fixed amounts of + // time as well as being able to wake up all threads when thread + // limits change. + friend class nsThreadPool; + void Wait(PRIntervalTime aInterval) + { + MOZ_ASSERT(mType == eNormalQueue); + mEventsAvailable.Wait(aInterval); + } + void NotifyAll() + { + MOZ_ASSERT(mType == eNormalQueue); + mEventsAvailable.NotifyAll(); + } +}; + +#endif // nsEventQueue_h__ diff --git a/xpcom/threads/nsICancelableRunnable.h b/xpcom/threads/nsICancelableRunnable.h new file mode 100644 index 000000000..5ae9f5b14 --- /dev/null +++ b/xpcom/threads/nsICancelableRunnable.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 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. If the task has already been executed this 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() { } + virtual ~nsICancelableRunnable() {} +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(nsICancelableRunnable, + NS_ICANCELABLERUNNABLE_IID) + +#endif // nsICancelableRunnable_h__ diff --git a/xpcom/threads/nsIEnvironment.idl b/xpcom/threads/nsIEnvironment.idl new file mode 100644 index 000000000..afbc3eb7c --- /dev/null +++ b/xpcom/threads/nsIEnvironment.idl @@ -0,0 +1,55 @@ +/* -*- 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 000000000..a6f9068dc --- /dev/null +++ b/xpcom/threads/nsIEventTarget.idl @@ -0,0 +1,127 @@ +/* -*- 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" +%} + +native alreadyAddRefed_nsIRunnable(already_AddRefed); + +[scriptable, uuid(88145945-3278-424e-9f37-d874cbdd9f6f)] +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; + + /** + * This flag specifies the synchronous mode of event dispatch, in which the + * dispatch method does not return until the event has been processed. + * + * NOTE: passing this flag to dispatch may have the side-effect of causing + * other events on the current thread to be processed while waiting for the + * given event to be processed. + */ + const unsigned long DISPATCH_SYNC = 1; + + /** + * 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; + + /** + * Check to see if this event target is associated with the current thread. + * + * @returns + * A boolean value that if "true" indicates that events dispatched to this + * event target will run on the current thread (i.e., the thread calling + * this method). + */ + 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, 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); +}; + +%{C++ +// convenient aliases: +#define NS_DISPATCH_NORMAL nsIEventTarget::DISPATCH_NORMAL +#define NS_DISPATCH_SYNC nsIEventTarget::DISPATCH_SYNC +#define NS_DISPATCH_AT_END nsIEventTarget::DISPATCH_AT_END +%} diff --git a/xpcom/threads/nsIIdlePeriod.idl b/xpcom/threads/nsIIdlePeriod.idl new file mode 100644 index 000000000..aa72b2171 --- /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. + */ +[builtinclass, 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/nsIIncrementalRunnable.h b/xpcom/threads/nsIIncrementalRunnable.h new file mode 100644 index 000000000..526bc165e --- /dev/null +++ b/xpcom/threads/nsIIncrementalRunnable.h @@ -0,0 +1,41 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 nsIIncrementalRunnable_h__ +#define nsIIncrementalRunnable_h__ + +#include "nsISupports.h" +#include "mozilla/TimeStamp.h" + +#define NS_IINCREMENTALRUNNABLE_IID \ +{ 0x688be92e, 0x7ade, 0x4fdc, \ +{ 0x9d, 0x83, 0x74, 0xcb, 0xef, 0xf4, 0xa5, 0x2c } } + + +/** + * A task interface for tasks that can schedule their work to happen + * in increments bounded by a deadline. + */ +class nsIIncrementalRunnable : public nsISupports +{ +public: + NS_DECLARE_STATIC_IID_ACCESSOR(NS_IINCREMENTALRUNNABLE_IID) + + /** + * Notify the task of a point in time in the future when the task + * should stop executing. + */ + virtual void SetDeadline(mozilla::TimeStamp aDeadline) = 0; + +protected: + nsIIncrementalRunnable() { } + virtual ~nsIIncrementalRunnable() {} +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(nsIIncrementalRunnable, + NS_IINCREMENTALRUNNABLE_IID) + +#endif // nsIIncrementalRunnable_h__ diff --git a/xpcom/threads/nsIProcess.idl b/xpcom/threads/nsIProcess.idl new file mode 100644 index 000000000..2c7dcd55e --- /dev/null +++ b/xpcom/threads/nsIProcess.idl @@ -0,0 +1,99 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. 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); + + /** + * 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 000000000..4d26f72d9 --- /dev/null +++ b/xpcom/threads/nsIRunnable.idl @@ -0,0 +1,27 @@ +/* -*- 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(); +}; + +[uuid(e75aa42a-80a9-11e6-afb5-e89d87348e2c)] +interface nsIRunnablePriority : nsISupports +{ + const unsigned short PRIORITY_NORMAL = 0; + const unsigned short PRIORITY_HIGH = 1; + readonly attribute unsigned long priority; +}; diff --git a/xpcom/threads/nsISupportsPriority.idl b/xpcom/threads/nsISupportsPriority.idl new file mode 100644 index 000000000..579c280cf --- /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/nsIThread.idl b/xpcom/threads/nsIThread.idl new file mode 100644 index 000000000..fbfc8d4fb --- /dev/null +++ b/xpcom/threads/nsIThread.idl @@ -0,0 +1,149 @@ +/* -*- 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 "nsIIdlePeriod.idl" + +%{C++ +#include "mozilla/AlreadyAddRefed.h" +%} + +[ptr] native PRThread(PRThread); + +native alreadyAddRefed_nsIIdlePeriod(already_AddRefed); + +/** + * 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. + */ +[scriptable, uuid(5801d193-29d1-4964-a6b7-70eb697ddf2b)] +interface nsIThread : nsIEventTarget +{ + /** + * @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; + + + /** + * 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(); + + /** + * 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::NewThread, or if this method was called more than once + * on the thread object. + */ + void asyncShutdown(); + + /** + * Register an instance of nsIIdlePeriod which works as a facade of + * the abstract notion of a "current idle period". The + * nsIIdlePeriod should always represent the "current" idle period + * with regard to expected work for the thread. The thread is free + * to use this when there are no higher prioritized tasks to process + * to determine if it is reasonable to schedule tasks that could run + * when the thread is idle. The responsibility of the registered + * nsIIdlePeriod is to answer with an estimated deadline at which + * the thread should expect that it could receive new higher + * priority tasks. + */ + [noscript] void registerIdlePeriod(in alreadyAddRefed_nsIIdlePeriod aIdlePeriod); + + /** + * Dispatch an event to the thread's idle queue. 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. + * + * @throws NS_ERROR_INVALID_ARG + * Indicates that event is null. + */ + [noscript] void idleDispatch(in alreadyAddRefed_nsIRunnable event); +}; diff --git a/xpcom/threads/nsIThreadInternal.idl b/xpcom/threads/nsIThreadInternal.idl new file mode 100644 index 000000000..e5d7cc83f --- /dev/null +++ b/xpcom/threads/nsIThreadInternal.idl @@ -0,0 +1,135 @@ +/* -*- 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. + */ +[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 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. + */ + [noscript] nsIEventTarget pushEventQueue(); + + /** + * 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. + */ + [noscript] void popEventQueue(in nsIEventTarget aInnermostTarget); +}; + +/** + * 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(thread) { + * 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. + * + * @param thread + * The thread where the event is being dispatched. + */ + void onDispatchedEvent(in nsIThreadInternal thread); + + /** + * 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 000000000..9b4fc126f --- /dev/null +++ b/xpcom/threads/nsIThreadManager.idl @@ -0,0 +1,68 @@ +/* -*- 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); + +interface nsIThread; + +/** + * 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(). 0 means use platform default. + */ + const unsigned long DEFAULT_STACK_SIZE = 0; + + /** + * Create a new thread (a global, user PRThread). + * + * @param creationFlags + * Reserved for future use. Pass 0. + * @param stackSize + * Number of bytes to reserve for the thread's stack. + * + * @returns + * The newly created nsIThread object. + */ + nsIThread newThread(in unsigned long creationFlags, [optional] in unsigned long stackSize); + + /** + * Get the nsIThread object (if any) corresponding to the given PRThread. + * This method returns null if there is no corresponding nsIThread. + * + * @param prthread + * The PRThread of the nsIThread being requested. + * + * @returns + * The nsIThread object corresponding to the given PRThread or null if no + * such nsIThread exists. + */ + [noscript] nsIThread getThreadFromPRThread(in PRThread prthread); + + /** + * 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 attribute is true if the calling thread is the main thread of the + * application process. + */ + readonly attribute boolean isMainThread; +}; diff --git a/xpcom/threads/nsIThreadPool.idl b/xpcom/threads/nsIThreadPool.idl new file mode 100644 index 000000000..d04e8504a --- /dev/null +++ b/xpcom/threads/nsIThreadPool.idl @@ -0,0 +1,88 @@ +/* -*- 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" + +[scriptable, 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. + */ +[scriptable, uuid(76ce99c9-8e43-489a-9789-f27cc4424965)] +interface nsIThreadPool : nsIEventTarget +{ + /** + * 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(); + + /** + * 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; + + /** + * 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/nsITimer.idl b/xpcom/threads/nsITimer.idl new file mode 100644 index 000000000..ade2168f2 --- /dev/null +++ b/xpcom/threads/nsITimer.idl @@ -0,0 +1,244 @@ +/* -*- 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 nsIObserver; +interface nsIEventTarget; + +%{C++ +#include "mozilla/MemoryReporting.h" + +/** + * 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); + +/** + * The signature of the timer name callback function passed to + * initWithNameableFuncCallback. + * This is the function that will get called when timer profiling is enabled + * via the "TimerFirings" log module. + * + * @param aTimer the timer which has expired + * @param aClosure opaque parameter passed to initWithFuncCallback + * @param aBuf a buffer in which to put the name + * @param aLen the length of the buffer + */ +typedef void (*nsTimerNameCallbackFunc) (nsITimer *aTimer, void *aClosure, + char *aBuf, size_t aLen); +%} + +native nsTimerCallbackFunc(nsTimerCallbackFunc); +native nsTimerNameCallbackFunc(nsTimerNameCallbackFunc); + +/** + * 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, 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 + * should not influence the timer period. However this timer type + * guarantees that it will not queue up new events to fire the callback + * until the previous callback event finishes firing. If the callback + * takes a long time, then the next callback will be scheduled immediately + * afterward, but only once. This is the only non-slack timer available. + */ + const short TYPE_REPEATING_PRECISE_CAN_SKIP = 3; + + /** + * 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 aDelay delay in milliseconds for timer to fire + * @param aType timer type per TYPE* consts defined above + */ + void init(in nsIObserver aObserver, in unsigned long aDelay, + in unsigned long aType); + + + /** + * Initialize a timer to fire after the given millisecond interval. + * This version takes a function to call and a closure to pass to + * that function. + * + * @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 + */ + [noscript] void initWithFuncCallback(in nsTimerCallbackFunc aCallback, + in voidPtr aClosure, + in unsigned long aDelay, + in unsigned long aType); + + /** + * Initialize a timer to fire after the given millisecond interval. + * This version takes a function to call. + * + * @param aFunc nsITimerCallback interface to call when timer expires + * @param aDelay The millisecond interval + * @param aType Timer type per TYPE* consts defined above + */ + void initWithCallback(in nsITimerCallback aCallback, + in unsigned long 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); + + /** + * Like initWithNamedFuncCallback, but instead of a timer name it takes a + * callback that will provide a name when the timer fires. + * + * @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 aNameCallback The callback function + */ + [noscript] void initWithNameableFuncCallback( + in nsTimerCallbackFunc aCallback, + in voidPtr aClosure, + in unsigned long aDelay, + in unsigned long aType, + in nsTimerNameCallbackFunc aNameCallback); + + /** + * 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; + +%{C++ + virtual size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const = 0; +%} +}; + +%{C++ +#define NS_TIMER_CONTRACTID "@mozilla.org/timer;1" +#define NS_TIMER_CALLBACK_TOPIC "timer-callback" +%} + diff --git a/xpcom/threads/nsMemoryPressure.cpp b/xpcom/threads/nsMemoryPressure.cpp new file mode 100644 index 000000000..fea9b0437 --- /dev/null +++ b/xpcom/threads/nsMemoryPressure.cpp @@ -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/. */ + +#include "nsMemoryPressure.h" +#include "mozilla/Assertions.h" +#include "mozilla/Atomics.h" + +#include "nsThreadUtils.h" + +using namespace mozilla; + +static Atomic sMemoryPressurePending; +static_assert(MemPressure_None == 0, + "Bad static initialization with the default constructor."); + +MemoryPressureState +NS_GetPendingMemoryPressure() +{ + int32_t value = sMemoryPressurePending.exchange(MemPressure_None); + return MemoryPressureState(value); +} + +void +NS_DispatchEventualMemoryPressure(MemoryPressureState aState) +{ + /* + * A new memory pressure event erases an ongoing memory pressure, but an + * existing "new" memory pressure event takes precedence over a new "ongoing" + * memory pressure event. + */ + switch (aState) { + case MemPressure_None: + sMemoryPressurePending = MemPressure_None; + break; + case MemPressure_New: + sMemoryPressurePending = MemPressure_New; + break; + case MemPressure_Ongoing: + sMemoryPressurePending.compareExchange(MemPressure_None, + MemPressure_Ongoing); + break; + } +} + +nsresult +NS_DispatchMemoryPressure(MemoryPressureState aState) +{ + NS_DispatchEventualMemoryPressure(aState); + nsCOMPtr event = new Runnable; + return NS_DispatchToMainThread(event); +} diff --git a/xpcom/threads/nsMemoryPressure.h b/xpcom/threads/nsMemoryPressure.h new file mode 100644 index 000000000..a05728c61 --- /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" + +enum MemoryPressureState +{ + /* + * No memory pressure. + */ + MemPressure_None = 0, + + /* + * 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. + */ + MemPressure_New, + + /* + * 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. + */ + MemPressure_Ongoing +}; + +/** + * Return and erase the latest state of the memory pressure event set by any of + * the corresponding dispatch function. + */ +MemoryPressureState +NS_GetPendingMemoryPressure(); + +/** + * 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_DispatchEventualMemoryPressure(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_DispatchEventualMemoryPressure, 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_DispatchMemoryPressure(MemoryPressureState aState); + +#endif // nsMemoryPressure_h__ diff --git a/xpcom/threads/nsProcess.h b/xpcom/threads/nsProcess.h new file mode 100644 index 000000000..140944415 --- /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 "nsIFile.h" +#include "nsIThread.h" +#include "nsIObserver.h" +#include "nsIWeakReferenceUtils.h" +#include "nsIObserver.h" +#include "nsString.h" +#ifndef XP_MACOSX +#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 nsProcess final + : public nsIProcess + , public nsIObserver +{ +public: + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIPROCESS + NS_DECL_NSIOBSERVER + + nsProcess(); + +private: + ~nsProcess(); + 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; + bool mBlocking; + + nsCOMPtr mExecutable; + nsString mTargetPath; + int32_t mPid; + nsCOMPtr mObserver; + nsWeakPtr mWeakObserver; + + // These members are modified by multiple threads, any accesses should be + // protected with mLock. + int32_t mExitValue; +#if defined(PROCESSMODEL_WINAPI) + HANDLE mProcess; +#elif !defined(XP_MACOSX) + PRProcess* mProcess; +#endif +}; + +#endif diff --git a/xpcom/threads/nsProcessCommon.cpp b/xpcom/threads/nsProcessCommon.cpp new file mode 100644 index 000000000..709865a09 --- /dev/null +++ b/xpcom/threads/nsProcessCommon.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/. */ + +/***************************************************************************** + * + * 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 "nsAutoPtr.h" +#include "nsMemory.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 "prmem.h" +#include "nsString.h" +#include "nsLiteralString.h" +#include "nsReadableUtils.h" +#else +#ifdef XP_MACOSX +#include +#include +#include +#include +#endif +#include +#include +#endif + +using namespace mozilla; + +#ifdef XP_MACOSX +cpu_type_t pref_cpu_types[2] = { +#if defined(__i386__) + CPU_TYPE_X86, +#elif defined(__x86_64__) + CPU_TYPE_X86_64, +#elif defined(__ppc__) + CPU_TYPE_POWERPC, +#endif + CPU_TYPE_ANY +}; +#endif + +//-------------------------------------------------------------------// +// nsIProcess implementation +//-------------------------------------------------------------------// +NS_IMPL_ISUPPORTS(nsProcess, nsIProcess, + nsIObserver) + +//Constructor +nsProcess::nsProcess() + : mThread(nullptr) + , mLock("nsProcess.mLock") + , mShutdown(false) + , mBlocking(false) + , mPid(-1) + , mObserver(nullptr) + , mWeakObserver(nullptr) + , mExitValue(-1) +#if !defined(XP_MACOSX) + , mProcess(nullptr) +#endif +{ +} + +//Destructor +nsProcess::~nsProcess() +{ +} + +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; +} + + +#if defined(XP_WIN) +// Out param `aWideCmdLine` must be PR_Freed by the caller. +static int +assembleCmdLine(char* const* aArgv, wchar_t** aWideCmdLine, UINT aCodePage) +{ + char* const* arg; + char* p; + char* q; + char* cmdLine; + int cmdLineSize; + int numBackslashes; + int i; + int argNeedQuotes; + + /* + * Find out how large the command line buffer should be. + */ + cmdLineSize = 0; + for (arg = aArgv; *arg; ++arg) { + /* + * \ and " need to be escaped by a \. In the worst case, + * every character is a \ or ", so the string of length + * may double. If we quote an argument, that needs two ". + * Finally, we need a space between arguments, and + * a null byte at the end of command line. + */ + cmdLineSize += 2 * strlen(*arg) /* \ and " need to be escaped */ + + 2 /* we quote every argument */ + + 1; /* space in between, or final null */ + } + p = cmdLine = (char*)PR_MALLOC(cmdLineSize * sizeof(char)); + if (!p) { + return -1; + } + + for (arg = aArgv; *arg; ++arg) { + /* Add a space to separates the arguments */ + if (arg != aArgv) { + *p++ = ' '; + } + q = *arg; + numBackslashes = 0; + argNeedQuotes = 0; + + /* If the argument contains white space, it needs to be quoted. */ + if (strpbrk(*arg, " \f\n\r\t\v")) { + argNeedQuotes = 1; + } + + if (argNeedQuotes) { + *p++ = '"'; + } + while (*q) { + if (*q == '\\') { + numBackslashes++; + q++; + } else if (*q == '"') { + if (numBackslashes) { + /* + * Double the backslashes since they are followed + * by a quote + */ + for (i = 0; i < 2 * numBackslashes; i++) { + *p++ = '\\'; + } + numBackslashes = 0; + } + /* To escape the quote */ + *p++ = '\\'; + *p++ = *q++; + } else { + if (numBackslashes) { + /* + * Backslashes are not followed by a quote, so + * don't need to double the backslashes. + */ + for (i = 0; i < numBackslashes; i++) { + *p++ = '\\'; + } + numBackslashes = 0; + } + *p++ = *q++; + } + } + + /* Now we are at the end of this argument */ + if (numBackslashes) { + /* + * Double the backslashes if we have a quote string + * delimiter at the end. + */ + if (argNeedQuotes) { + numBackslashes *= 2; + } + for (i = 0; i < numBackslashes; i++) { + *p++ = '\\'; + } + } + if (argNeedQuotes) { + *p++ = '"'; + } + } + + *p = '\0'; + int32_t numChars = MultiByteToWideChar(aCodePage, 0, cmdLine, -1, nullptr, 0); + *aWideCmdLine = (wchar_t*)PR_MALLOC(numChars * sizeof(wchar_t)); + MultiByteToWideChar(aCodePage, 0, cmdLine, -1, *aWideCmdLine, numChars); + PR_Free(cmdLine); + return 0; +} +#endif + +void +nsProcess::Monitor(void* aArg) +{ + RefPtr process = dont_AddRef(static_cast(aArg)); + + if (!process->mBlocking) { + PR_SetCurrentThreadName("RunProcess"); + } + +#if defined(PROCESSMODEL_WINAPI) + DWORD dwRetVal; + unsigned long exitCode = -1; + + dwRetVal = WaitForSingleObject(process->mProcess, INFINITE); + if (dwRetVal != WAIT_FAILED) { + if (GetExitCodeProcess(process->mProcess, &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_MACOSX + 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; + if (PR_WaitProcess(process->mProcess, &exitCode) != PR_SUCCESS) { + exitCode = -1; + } +#endif + + // Lock in case Kill or GetExitCode are called during this + { + MutexAutoLock lock(process->mLock); +#if !defined(XP_MACOSX) + 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(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; + if (mExitValue < 0) { + topic = "process-failed"; + } else { + topic = "process-finished"; + } + + mPid = -1; + nsCOMPtr observer; + if (mWeakObserver) { + observer = do_QueryReferent(mWeakObserver); + } else if (mObserver) { + observer = mObserver; + } + mObserver = nullptr; + mWeakObserver = 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)); + if (!my_argv) { + return NS_ERROR_OUT_OF_MEMORY; + } + + 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)); + if (!my_argv) { + return NS_ERROR_OUT_OF_MEMORY; + } + + 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) { + mWeakObserver = do_GetWeakReference(aObserver); + if (!mWeakObserver) { + return NS_NOINTERFACE; + } + } else { + mObserver = aObserver; + } + } + + mExitValue = -1; + mPid = -1; + +#if defined(PROCESSMODEL_WINAPI) + BOOL retVal; + wchar_t* cmdLine = nullptr; + + // |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] && assembleCmdLine(aMyArgv + 1, &cmdLine, + aArgsUTF8 ? CP_UTF8 : CP_ACP) == -1) { + return NS_ERROR_FILE_EXECUTION_FAILED; + } + + /* 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. + */ + + // The program name in aMyArgv[0] is always UTF-8 + NS_ConvertUTF8toUTF16 wideFile(aMyArgv[0]); + + SHELLEXECUTEINFOW sinfo; + memset(&sinfo, 0, sizeof(SHELLEXECUTEINFOW)); + sinfo.cbSize = sizeof(SHELLEXECUTEINFOW); + sinfo.hwnd = nullptr; + sinfo.lpFile = wideFile.get(); + sinfo.nShow = SW_SHOWNORMAL; + sinfo.fMask = SEE_MASK_FLAG_DDEWAIT | + SEE_MASK_NO_CONSOLE | + SEE_MASK_NOCLOSEPROCESS; + + if (cmdLine) { + sinfo.lpParameters = cmdLine; + } + + retVal = ShellExecuteExW(&sinfo); + if (!retVal) { + return NS_ERROR_FILE_EXECUTION_FAILED; + } + + mProcess = sinfo.hProcess; + + if (cmdLine) { + PR_Free(cmdLine); + } + + mPid = GetProcessId(mProcess); +#elif defined(XP_MACOSX) + // Initialize spawn attributes. + posix_spawnattr_t spawnattr; + if (posix_spawnattr_init(&spawnattr) != 0) { + return NS_ERROR_FAILURE; + } + + // Set spawn attributes. + size_t attr_count = ArrayLength(pref_cpu_types); + size_t attr_ocount = 0; + if (posix_spawnattr_setbinpref_np(&spawnattr, attr_count, pref_cpu_types, + &attr_ocount) != 0 || + attr_ocount != attr_count) { + posix_spawnattr_destroy(&spawnattr); + return NS_ERROR_FAILURE; + } + + // Note: |aMyArgv| is already null-terminated as required by posix_spawnp. + pid_t newPid = 0; + int result = posix_spawnp(&newPid, aMyArgv[0], nullptr, &spawnattr, aMyArgv, + *_NSGetEnviron()); + mPid = static_cast(newPid); + + posix_spawnattr_destroy(&spawnattr); + + if (result != 0) { + return NS_ERROR_FAILURE; + } +#else + mProcess = PR_CreateProcess(aMyArgv[0], aMyArgv, nullptr, nullptr); + if (!mProcess) { + return NS_ERROR_FAILURE; + } + struct MYProcess + { + uint32_t pid; + }; + MYProcess* ptrProc = (MYProcess*)mProcess; + mPid = ptrProc->pid; +#endif + + NS_ADDREF_THIS(); + mBlocking = aBlocking; + if (aBlocking) { + Monitor(this); + if (mExitValue < 0) { + return NS_ERROR_FILE_EXECUTION_FAILED; + } + } else { + mThread = PR_CreateThread(PR_SYSTEM_THREAD, Monitor, this, + PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD, + PR_JOINABLE_THREAD, 0); + 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; +} + +NS_IMETHODIMP +nsProcess::GetIsRunning(bool* aIsRunning) +{ + if (mThread) { + *aIsRunning = true; + } else { + *aIsRunning = false; + } + + 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_MACOSX) + 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; + mWeakObserver = nullptr; + + MutexAutoLock lock(mLock); + mShutdown = true; + + return NS_OK; +} diff --git a/xpcom/threads/nsThread.cpp b/xpcom/threads/nsThread.cpp new file mode 100644 index 000000000..63bd28ca3 --- /dev/null +++ b/xpcom/threads/nsThread.cpp @@ -0,0 +1,1500 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. 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" + +// 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 "nsAutoPtr.h" +#include "nsCOMPtr.h" +#include "nsQueryObject.h" +#include "pratom.h" +#include "mozilla/CycleCollectedJSContext.h" +#include "mozilla/Logging.h" +#include "nsIObserverService.h" +#include "mozilla/HangMonitor.h" +#include "mozilla/IOInterposer.h" +#include "mozilla/ipc/MessageChannel.h" +#include "mozilla/ipc/BackgroundChild.h" +#include "mozilla/Services.h" +#include "nsXPCOMPrivate.h" +#include "mozilla/ChaosMode.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/Unused.h" +#include "mozilla/dom/ScriptSettings.h" +#include "nsIIdlePeriod.h" +#include "nsIIncrementalRunnable.h" +#include "nsThreadSyncDispatch.h" +#include "LeakRefPtr.h" + +#ifdef MOZ_CRASHREPORTER +#include "nsServiceManagerUtils.h" +#include "nsICrashReporter.h" +#include "mozilla/dom/ContentChild.h" +#endif + +#ifdef XP_LINUX +#include +#include +#include +#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 +#endif + +#ifdef MOZ_CANARY +# include +# include +# include +# include +# include "nsXULAppAPI.h" +#endif + +#if defined(NS_FUNCTION_TIMER) && defined(_MSC_VER) +#include "nsTimerImpl.h" +#include "mozilla/StackWalk.h" +#endif +#ifdef NS_FUNCTION_TIMER +#include "nsCRT.h" +#endif + +#ifdef MOZ_TASK_TRACER +#include "GeckoTaskTracer.h" +#include "TracedTaskCommon.h" +using namespace mozilla::tasktracer; +#endif + +using namespace mozilla; + +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) + +//----------------------------------------------------------------------------- +// 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() + { + } +}; + +NS_IMETHODIMP_(MozExternalRefCountType) +nsThreadClassInfo::AddRef() +{ + return 2; +} +NS_IMETHODIMP_(MozExternalRefCountType) +nsThreadClassInfo::Release() +{ + return 1; +} +NS_IMPL_QUERY_INTERFACE(nsThreadClassInfo, nsIClassInfo) + +NS_IMETHODIMP +nsThreadClassInfo::GetInterfaces(uint32_t* aCount, nsIID*** aArray) +{ + return NS_CI_INTERFACE_GETTER_NAME(nsThread)(aCount, aArray); +} + +NS_IMETHODIMP +nsThreadClassInfo::GetScriptableHelper(nsIXPCScriptable** aResult) +{ + *aResult = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +nsThreadClassInfo::GetContractID(char** aResult) +{ + *aResult = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +nsThreadClassInfo::GetClassDescription(char** aResult) +{ + *aResult = nullptr; + 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(nsISupportsPriority) + 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, nsISupportsPriority) + +//----------------------------------------------------------------------------- + +class nsThreadStartupEvent : public Runnable +{ +public: + nsThreadStartupEvent() + : mMon("nsThreadStartupEvent.mMon") + , mInitialized(false) + { + } + + // This method does not return until the thread startup object is in the + // completion state. + void Wait() + { + ReentrantMonitorAutoEnter mon(mMon); + while (!mInitialized) { + mon.Wait(); + } + } + + // This method needs to be public to support older compilers (xlC_r on AIX). + // It should be called directly as this class type is reference counted. + virtual ~nsThreadStartupEvent() {} + +private: + NS_IMETHOD Run() override + { + ReentrantMonitorAutoEnter mon(mMon); + mInitialized = true; + mon.Notify(); + return NS_OK; + } + + ReentrantMonitor mMon; + bool mInitialized; +}; +//----------------------------------------------------------------------------- + +namespace { +class DelayedRunnable : public Runnable, + public nsITimerCallback +{ +public: + DelayedRunnable(already_AddRefed aTargetThread, + already_AddRefed aRunnable, + uint32_t aDelay) + : mTargetThread(aTargetThread), + mWrappedRunnable(aRunnable), + mDelayedFrom(TimeStamp::NowLoRes()), + mDelay(aDelay) + { } + + NS_DECL_ISUPPORTS_INHERITED + + nsresult Init() + { + nsresult rv; + mTimer = do_CreateInstance(NS_TIMER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + MOZ_ASSERT(mTimer); + rv = mTimer->SetTarget(mTargetThread); + + NS_ENSURE_SUCCESS(rv, rv); + return mTimer->InitWithCallback(this, mDelay, nsITimer::TYPE_ONE_SHOT); + } + + nsresult DoRun() + { + nsCOMPtr r = mWrappedRunnable.forget(); + return r->Run(); + } + + NS_IMETHOD Run() override + { + // Already ran? + if (!mWrappedRunnable) { + return NS_OK; + } + + // Are we too early? + if ((TimeStamp::NowLoRes() - mDelayedFrom).ToMilliseconds() < mDelay) { + return NS_OK; // Let the nsITimer run us. + } + + mTimer->Cancel(); + return DoRun(); + } + + NS_IMETHOD Notify(nsITimer* aTimer) override + { + // If we already ran, the timer should have been canceled. + MOZ_ASSERT(mWrappedRunnable); + MOZ_ASSERT(aTimer == mTimer); + + return DoRun(); + } + +private: + ~DelayedRunnable() {} + + nsCOMPtr mTargetThread; + nsCOMPtr mWrappedRunnable; + nsCOMPtr mTimer; + TimeStamp mDelayedFrom; + uint32_t mDelay; +}; + +NS_IMPL_ISUPPORTS_INHERITED(DelayedRunnable, Runnable, nsITimerCallback) + +} // anonymous namespace + +//----------------------------------------------------------------------------- + +struct nsThreadShutdownContext +{ + nsThreadShutdownContext(NotNull aTerminatingThread, + NotNull aJoiningThread, + bool aAwaitingShutdownAck) + : mTerminatingThread(aTerminatingThread) + , mJoiningThread(aJoiningThread) + , mAwaitingShutdownAck(aAwaitingShutdownAck) + { + MOZ_COUNT_CTOR(nsThreadShutdownContext); + } + ~nsThreadShutdownContext() + { + MOZ_COUNT_DTOR(nsThreadShutdownContext); + } + + // NB: This will be the last reference. + NotNull> mTerminatingThread; + NotNull mJoiningThread; + bool mAwaitingShutdownAck; +}; + +// 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) + : mShutdownContext(aCtx) + { + } + NS_IMETHOD Run() override + { + mShutdownContext->mTerminatingThread->ShutdownComplete(mShutdownContext); + return NS_OK; + } + nsresult Cancel() override + { + return Run(); + } +private: + virtual ~nsThreadShutdownAckEvent() { } + + NotNull mShutdownContext; +}; + +// This event is responsible for setting mShutdownContext +class nsThreadShutdownEvent : public Runnable +{ +public: + nsThreadShutdownEvent(NotNull aThr, + NotNull aCtx) + : mThread(aThr) + , mShutdownContext(aCtx) + { + } + NS_IMETHOD Run() override + { + mThread->mShutdownContext = mShutdownContext; + MessageLoop::current()->Quit(); + 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; + MOZ_ALWAYS_TRUE(thread_policy_set(mach_thread_self(), THREAD_AFFINITY_POLICY, + &policy.affinity_tag, 1) == KERN_SUCCESS); +#elif defined(XP_WIN) + MOZ_ALWAYS_TRUE(SetThreadIdealProcessor(GetCurrentThread(), cpu) != -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); + } +} + +/*static*/ void +nsThread::ThreadFunc(void* aArg) +{ + using mozilla::ipc::BackgroundChild; + + nsThread* self = static_cast(aArg); // strong reference + self->mThread = PR_GetCurrentThread(); + SetupCurrentThreadForChaosMode(); + + // Inform the ThreadManager + nsThreadManager::get().RegisterCurrentThread(*self); + + mozilla::IOInterposer::RegisterCurrentThread(); + + // Wait for and process startup event + nsCOMPtr event; + { + MutexAutoLock lock(self->mLock); + if (!self->mEvents->GetEvent(true, getter_AddRefs(event), lock)) { + NS_WARNING("failed waiting for thread startup event"); + return; + } + } + event->Run(); // unblocks nsThread::Init + event = nullptr; + + { + // Scope for MessageLoop. + nsAutoPtr loop( + new MessageLoop(MessageLoop::TYPE_MOZILLA_NONMAINTHREAD, self)); + + // Now, process incoming events... + loop->Run(); + + 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(); + + { + MutexAutoLock lock(self->mLock); + if (!self->mEvents->HasPendingEvent(lock)) { + // No events in the queue, so we will stop now. Don't let any more + // events be added, since they won't be processed. It is critical + // that no PutEvent can occur between testing that the event queue is + // empty and setting mEventsAreDoomed! + self->mEventsAreDoomed = true; + break; + } + } + NS_ProcessPendingEvents(self); + } + } + + mozilla::IOInterposer::UnregisterCurrentThread(); + + // Inform the threadmanager that this thread is going away + nsThreadManager::get().UnregisterCurrentThread(*self); + + // Dispatch shutdown ACK + NotNull context = + WrapNotNull(self->mShutdownContext); + MOZ_ASSERT(context->mTerminatingThread == self); + event = do_QueryObject(new nsThreadShutdownAckEvent(context)); + context->mJoiningThread->Dispatch(event, NS_DISPATCH_NORMAL); + + // Release any observer of the thread here. + self->SetObserver(nullptr); + +#ifdef MOZ_TASK_TRACER + FreeTraceInfo(); +#endif + + NS_RELEASE(self); +} + +//----------------------------------------------------------------------------- + +#ifdef MOZ_CRASHREPORTER +// Tell the crash reporter to save a memory report if our heuristics determine +// that an OOM failure is likely to occur soon. +// Memory usage will not be checked more than every 30 seconds or saved more +// than every 3 minutes +// If |aShouldSave == kForceReport|, a report will be saved regardless of +// whether the process is low on memory or not. However, it will still not be +// saved if a report was saved less than 3 minutes ago. +bool +nsThread::SaveMemoryReportNearOOM(ShouldSaveMemoryReport aShouldSave) +{ + // Keep an eye on memory usage (cheap, ~7ms) somewhat frequently, + // but save memory reports (expensive, ~75ms) less frequently. + const size_t kLowMemoryCheckSeconds = 30; + const size_t kLowMemorySaveSeconds = 3 * 60; + + static TimeStamp nextCheck = TimeStamp::NowLoRes() + + TimeDuration::FromSeconds(kLowMemoryCheckSeconds); + static bool recentlySavedReport = false; // Keeps track of whether a report + // was saved last time we checked + + // Are we checking again too soon? + TimeStamp now = TimeStamp::NowLoRes(); + if ((aShouldSave == ShouldSaveMemoryReport::kMaybeReport || + recentlySavedReport) && now < nextCheck) { + return false; + } + + bool needMemoryReport = (aShouldSave == ShouldSaveMemoryReport::kForceReport); +#ifdef XP_WIN // XXX implement on other platforms as needed + // If the report is forced there is no need to check whether it is necessary + if (aShouldSave != ShouldSaveMemoryReport::kForceReport) { + const size_t LOWMEM_THRESHOLD_VIRTUAL = 200 * 1024 * 1024; + MEMORYSTATUSEX statex; + statex.dwLength = sizeof(statex); + if (GlobalMemoryStatusEx(&statex)) { + if (statex.ullAvailVirtual < LOWMEM_THRESHOLD_VIRTUAL) { + needMemoryReport = true; + } + } + } +#endif + + if (needMemoryReport) { + if (XRE_IsContentProcess()) { + dom::ContentChild* cc = dom::ContentChild::GetSingleton(); + if (cc) { + cc->SendNotifyLowMemory(); + } + } else { + nsCOMPtr cr = + do_GetService("@mozilla.org/toolkit/crash-reporter;1"); + if (cr) { + cr->SaveMemoryReport(); + } + } + recentlySavedReport = true; + nextCheck = now + TimeDuration::FromSeconds(kLowMemorySaveSeconds); + } else { + recentlySavedReport = false; + nextCheck = now + TimeDuration::FromSeconds(kLowMemoryCheckSeconds); + } + + return recentlySavedReport; +} +#endif + +#ifdef MOZ_CANARY +int sCanaryOutputFD = -1; +#endif + +nsThread::nsThread(MainThreadFlag aMainThread, uint32_t aStackSize) + : mLock("nsThread.mLock") + , mScriptObserver(nullptr) + , mEvents(WrapNotNull(&mEventsRoot)) + , mEventsRoot(mLock) + , mIdleEventsAvailable(mLock, "[nsThread.mEventsAvailable]") + , mIdleEvents(mIdleEventsAvailable, nsEventQueue::eNormalQueue) + , mPriority(PRIORITY_NORMAL) + , mThread(nullptr) + , mNestedEventLoopDepth(0) + , mStackSize(aStackSize) + , mShutdownContext(nullptr) + , mShutdownRequired(false) + , mEventsAreDoomed(false) + , mIsMainThread(aMainThread) + , mCanInvokeJS(false) +{ +} + +nsThread::~nsThread() +{ + NS_ASSERTION(mRequestedShutdownContexts.IsEmpty(), + "shouldn't be waiting on other threads to shutdown"); +#ifdef DEBUG + // We deliberately leak these so they can be tracked by the leak checker. + // If you're having nsThreadShutdownContext leaks, you can set: + // XPCOM_MEM_LOG_CLASSES=nsThreadShutdownContext + // during a test run and that will at least tell you what thread is + // requesting shutdown on another, which can be helpful for diagnosing + // the leak. + for (size_t i = 0; i < mRequestedShutdownContexts.Length(); ++i) { + Unused << mRequestedShutdownContexts[i].forget(); + } +#endif +} + +nsresult +nsThread::Init() +{ + // spawn thread and wait until it is fully setup + RefPtr startup = new nsThreadStartupEvent(); + + NS_ADDREF_THIS(); + + mIdlePeriod = new IdlePeriod(); + + mShutdownRequired = true; + + // ThreadFunc is responsible for setting mThread + if (!PR_CreateThread(PR_USER_THREAD, ThreadFunc, this, + PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD, + PR_JOINABLE_THREAD, mStackSize)) { + NS_RELEASE_THIS(); + return NS_ERROR_OUT_OF_MEMORY; + } + + // ThreadFunc will wait for this event to be run before it tries to access + // mThread. By delaying insertion of this event into the queue, we ensure + // that mThread is set properly. + { + MutexAutoLock lock(mLock); + mEventsRoot.PutEvent(startup, lock); // retain a reference + } + + // Wait for thread to call ThreadManager::SetupCurrentThread, which completes + // initialization of ThreadFunc. + startup->Wait(); + return NS_OK; +} + +nsresult +nsThread::InitCurrentThread() +{ + mThread = PR_GetCurrentThread(); + SetupCurrentThreadForChaosMode(); + + mIdlePeriod = new IdlePeriod(); + + nsThreadManager::get().RegisterCurrentThread(*this); + return NS_OK; +} + +nsresult +nsThread::PutEvent(nsIRunnable* aEvent, nsNestedEventTarget* aTarget) +{ + nsCOMPtr event(aEvent); + return PutEvent(event.forget(), aTarget); +} + +nsresult +nsThread::PutEvent(already_AddRefed aEvent, nsNestedEventTarget* aTarget) +{ + // 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(Move(aEvent)); + nsCOMPtr obs; + + { + MutexAutoLock lock(mLock); + nsChainedEventQueue* queue = aTarget ? aTarget->mQueue : &mEventsRoot; + if (!queue || (queue == &mEventsRoot && mEventsAreDoomed)) { + NS_WARNING("An event was posted to a thread that will never run it (rejected)"); + return NS_ERROR_UNEXPECTED; + } + queue->PutEvent(event.take(), lock); + + // 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(this); + } + + return NS_OK; +} + +nsresult +nsThread::DispatchInternal(already_AddRefed aEvent, uint32_t aFlags, + nsNestedEventTarget* aTarget) +{ + // 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(Move(aEvent)); + if (NS_WARN_IF(!event)) { + return NS_ERROR_INVALID_ARG; + } + + if (gXPCOMThreadsShutDown && MAIN_THREAD != mIsMainThread && !aTarget) { + NS_ASSERTION(false, "Failed Dispatch after xpcom-shutdown-threads"); + return NS_ERROR_ILLEGAL_DURING_SHUTDOWN; + } + +#ifdef MOZ_TASK_TRACER + nsCOMPtr tracedRunnable = CreateTracedRunnable(event.take()); + (static_cast(tracedRunnable.get()))->DispatchTask(); + // XXX tracedRunnable will always leaked when we fail to disptch. + event = tracedRunnable.forget(); +#endif + + if (aFlags & DISPATCH_SYNC) { + nsThread* thread = nsThreadManager::get().GetCurrentThread(); + if (NS_WARN_IF(!thread)) { + return NS_ERROR_NOT_AVAILABLE; + } + + // XXX we should be able to do something better here... we should + // be able to monitor the slot occupied by this event and use + // that to tell us when the event has been processed. + + RefPtr wrapper = + new nsThreadSyncDispatch(thread, event.take()); + nsresult rv = PutEvent(wrapper, aTarget); // hold a ref + // Don't wait for the event to finish if we didn't dispatch it... + if (NS_FAILED(rv)) { + // PutEvent leaked the wrapper runnable object on failure, so we + // explicitly release this object once for that. Note that this + // object will be released again soon because it exits the scope. + wrapper.get()->Release(); + return rv; + } + + // Allows waiting; ensure no locks are held that would deadlock us! + while (wrapper->IsPending()) { + NS_ProcessNextEvent(thread, true); + } + return NS_OK; + } + + NS_ASSERTION(aFlags == NS_DISPATCH_NORMAL || + aFlags == NS_DISPATCH_AT_END, "unexpected dispatch flags"); + return PutEvent(event.take(), aTarget); +} + +bool +nsThread::nsChainedEventQueue::GetEvent(bool aMayWait, nsIRunnable** aEvent, + mozilla::MutexAutoLock& aProofOfLock) +{ + bool retVal = false; + do { + if (mProcessSecondaryQueueRunnable) { + MOZ_ASSERT(mSecondaryQueue->HasPendingEvent(aProofOfLock)); + retVal = mSecondaryQueue->GetEvent(aMayWait, aEvent, aProofOfLock); + MOZ_ASSERT(*aEvent); + mProcessSecondaryQueueRunnable = false; + return retVal; + } + + // We don't want to wait if mSecondaryQueue has some events. + bool reallyMayWait = + aMayWait && !mSecondaryQueue->HasPendingEvent(aProofOfLock); + retVal = + mNormalQueue->GetEvent(reallyMayWait, aEvent, aProofOfLock); + + // Let's see if we should next time process an event from the secondary + // queue. + mProcessSecondaryQueueRunnable = + mSecondaryQueue->HasPendingEvent(aProofOfLock); + + if (*aEvent) { + // We got an event, return early. + return retVal; + } + } while(aMayWait || mProcessSecondaryQueueRunnable); + + return retVal; +} + +//----------------------------------------------------------------------------- +// nsIEventTarget + +NS_IMETHODIMP +nsThread::DispatchFromScript(nsIRunnable* aEvent, uint32_t aFlags) +{ + nsCOMPtr event(aEvent); + return Dispatch(event.forget(), aFlags); +} + +NS_IMETHODIMP +nsThread::Dispatch(already_AddRefed aEvent, uint32_t aFlags) +{ + LOG(("THRD(%p) Dispatch [%p %x]\n", this, /* XXX aEvent */nullptr, aFlags)); + + return DispatchInternal(Move(aEvent), aFlags, nullptr); +} + +NS_IMETHODIMP +nsThread::DelayedDispatch(already_AddRefed aEvent, uint32_t aDelayMs) +{ + NS_ENSURE_TRUE(!!aDelayMs, NS_ERROR_UNEXPECTED); + + RefPtr r = new DelayedRunnable(Move(do_AddRef(this)), + Move(aEvent), + aDelayMs); + nsresult rv = r->Init(); + NS_ENSURE_SUCCESS(rv, rv); + + return DispatchInternal(r.forget(), 0, nullptr); +} + +NS_IMETHODIMP +nsThread::IsOnCurrentThread(bool* aResult) +{ + *aResult = (PR_GetCurrentThread() == mThread); + return NS_OK; +} + +//----------------------------------------------------------------------------- +// nsIThread + +NS_IMETHODIMP +nsThread::GetPRThread(PRThread** aResult) +{ + *aResult = mThread; + return NS_OK; +} + +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::AsyncShutdown() +{ + LOG(("THRD(%p) async shutdown\n", this)); + + // XXX If we make this warn, then we hit that warning at xpcom shutdown while + // shutting down a thread in a thread pool. That happens b/c the thread + // in the thread pool is already shutdown by the thread manager. + if (!mThread) { + return NS_OK; + } + + return !!ShutdownInternal(/* aSync = */ false) ? NS_OK : NS_ERROR_UNEXPECTED; +} + +nsThreadShutdownContext* +nsThread::ShutdownInternal(bool aSync) +{ + MOZ_ASSERT(mThread); + MOZ_ASSERT(mThread != PR_GetCurrentThread()); + if (NS_WARN_IF(mThread == PR_GetCurrentThread())) { + return nullptr; + } + + // Prevent multiple calls to this method + { + MutexAutoLock lock(mLock); + if (!mShutdownRequired) { + return nullptr; + } + mShutdownRequired = false; + } + + NotNull currentThread = + WrapNotNull(nsThreadManager::get().GetCurrentThread()); + + nsAutoPtr& context = + *currentThread->mRequestedShutdownContexts.AppendElement(); + context = new nsThreadShutdownContext(WrapNotNull(this), currentThread, aSync); + + // Set mShutdownContext and wake up the thread in case it is waiting for + // events to process. + nsCOMPtr event = + new nsThreadShutdownEvent(WrapNotNull(this), WrapNotNull(context.get())); + // XXXroc What if posting the event fails due to OOM? + PutEvent(event.forget(), nullptr); + + // 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. + return context; +} + +void +nsThread::ShutdownComplete(NotNull aContext) +{ + MOZ_ASSERT(mThread); + MOZ_ASSERT(aContext->mTerminatingThread == this); + + if (aContext->mAwaitingShutdownAck) { + // We're in a synchronous shutdown, so tell whatever is up the stack that + // we're done and unwind the stack so it can call us again. + aContext->mAwaitingShutdownAck = false; + return; + } + + // Now, it should be safe to join without fear of dead-locking. + + PR_JoinThread(mThread); + mThread = nullptr; + + // We hold strong references to our event observers, and once the thread is + // shut down the observers can't easily unregister themselves. Do it here + // to avoid leaking. + ClearObservers(); + +#ifdef DEBUG + { + MutexAutoLock lock(mLock); + MOZ_ASSERT(!mObserver, "Should have been cleared at shutdown!"); + } +#endif + + // Delete aContext. + MOZ_ALWAYS_TRUE( + aContext->mJoiningThread->mRequestedShutdownContexts.RemoveElement(aContext)); +} + +void +nsThread::WaitForAllAsynchronousShutdowns() +{ + while (mRequestedShutdownContexts.Length()) { + NS_ProcessNextEvent(this, true); + } +} + +NS_IMETHODIMP +nsThread::Shutdown() +{ + LOG(("THRD(%p) sync shutdown\n", this)); + + // XXX If we make this warn, then we hit that warning at xpcom shutdown while + // shutting down a thread in a thread pool. That happens b/c the thread + // in the thread pool is already shutdown by the thread manager. + if (!mThread) { + return NS_OK; + } + + nsThreadShutdownContext* maybeContext = ShutdownInternal(/* aSync = */ true); + NS_ENSURE_TRUE(maybeContext, NS_ERROR_UNEXPECTED); + NotNull context = WrapNotNull(maybeContext); + + // Process events on the current thread until we receive a shutdown ACK. + // Allows waiting; ensure no locks are held that would deadlock us! + while (context->mAwaitingShutdownAck) { + NS_ProcessNextEvent(context->mJoiningThread, true); + } + + ShutdownComplete(context); + + return NS_OK; +} + +NS_IMETHODIMP +nsThread::HasPendingEvents(bool* aResult) +{ + if (NS_WARN_IF(PR_GetCurrentThread() != mThread)) { + return NS_ERROR_NOT_SAME_THREAD; + } + + { + MutexAutoLock lock(mLock); + *aResult = mEvents->HasPendingEvent(lock); + } + return NS_OK; +} + +NS_IMETHODIMP +nsThread::RegisterIdlePeriod(already_AddRefed aIdlePeriod) +{ + if (NS_WARN_IF(PR_GetCurrentThread() != mThread)) { + return NS_ERROR_NOT_SAME_THREAD; + } + + MutexAutoLock lock(mLock); + mIdlePeriod = aIdlePeriod; + return NS_OK; +} + +NS_IMETHODIMP +nsThread::IdleDispatch(already_AddRefed aEvent) +{ + // Currently the only supported idle dispatch is from the same + // thread. To support idle dispatch from another thread we need to + // support waking threads that are waiting for an event queue that + // isn't mIdleEvents. + MOZ_ASSERT(PR_GetCurrentThread() == mThread); + + MutexAutoLock lock(mLock); + LeakRefPtr event(Move(aEvent)); + + if (NS_WARN_IF(!event)) { + return NS_ERROR_INVALID_ARG; + } + + if (mEventsAreDoomed) { + NS_WARNING("An idle event was posted to a thread that will never run it (rejected)"); + return NS_ERROR_UNEXPECTED; + } + + mIdleEvents.PutEvent(event.take(), lock); + 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(func_, params_) \ + PR_BEGIN_MACRO \ + if (!mEventObservers.IsEmpty()) { \ + nsAutoTObserverArray>, 2>::ForwardIterator \ + iter_(mEventObservers); \ + nsCOMPtr obs_; \ + while (iter_.HasMore()) { \ + obs_ = iter_.GetNext(); \ + obs_ -> func_ params_ ; \ + } \ + } \ + PR_END_MACRO + +void +nsThread::GetIdleEvent(nsIRunnable** aEvent, MutexAutoLock& aProofOfLock) +{ + MOZ_ASSERT(PR_GetCurrentThread() == mThread); + MOZ_ASSERT(aEvent); + + TimeStamp idleDeadline; + { + MutexAutoUnlock unlock(mLock); + mIdlePeriod->GetIdlePeriodHint(&idleDeadline); + } + + if (!idleDeadline || idleDeadline < TimeStamp::Now()) { + aEvent = nullptr; + return; + } + + mIdleEvents.GetEvent(false, aEvent, aProofOfLock); + + if (*aEvent) { + nsCOMPtr incrementalEvent(do_QueryInterface(*aEvent)); + if (incrementalEvent) { + incrementalEvent->SetDeadline(idleDeadline); + } + } +} + +void +nsThread::GetEvent(bool aWait, nsIRunnable** aEvent, MutexAutoLock& aProofOfLock) +{ + MOZ_ASSERT(PR_GetCurrentThread() == mThread); + MOZ_ASSERT(aEvent); + + // We'll try to get an event to execute in three stages. + // [1] First we just try to get it from the regular queue without waiting. + mEvents->GetEvent(false, aEvent, aProofOfLock); + + // [2] If we didn't get an event from the regular queue, try to + // get one from the idle queue + if (!*aEvent) { + // Since events in mEvents have higher priority than idle + // events, we will only consider idle events when there are no + // pending events in mEvents. We will for the same reason never + // wait for an idle event, since a higher priority event might + // appear at any time. + GetIdleEvent(aEvent, aProofOfLock); + } + + // [3] If we neither got an event from the regular queue nor the + // idle queue, then if we should wait for events we block on the + // main queue until an event is available. + // If we are shutting down, then do not wait for new events. + if (!*aEvent && aWait) { + mEvents->GetEvent(aWait, aEvent, aProofOfLock); + } +} + +NS_IMETHODIMP +nsThread::ProcessNextEvent(bool aMayWait, bool* aResult) +{ + 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()); + + if (mIsMainThread == MAIN_THREAD) { + DoMainThreadSpecificProcessing(reallyWait); + } + + ++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. + Maybe noJSAPI; + bool callScriptObserver = !!mScriptObserver; + if (callScriptObserver) { + noJSAPI.emplace(); + mScriptObserver->BeforeProcessTask(reallyWait); + } + + nsCOMPtr obs = mObserver; + if (obs) { + obs->OnProcessNextEvent(this, reallyWait); + } + + NOTIFY_EVENT_OBSERVERS(OnProcessNextEvent, (this, reallyWait)); + +#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; + { + MutexAutoLock lock(mLock); + GetEvent(reallyWait, getter_AddRefs(event), lock); + } + + *aResult = (event.get() != nullptr); + + if (event) { + LOG(("THRD(%p) running [%p]\n", this, event.get())); + if (MAIN_THREAD == mIsMainThread) { + HangMonitor::NotifyActivity(); + } + event->Run(); + } else if (aMayWait) { + MOZ_ASSERT(ShuttingDown(), + "This should only happen when shutting down"); + rv = NS_ERROR_UNEXPECTED; + } + } + + NOTIFY_EVENT_OBSERVERS(AfterProcessNextEvent, (this, *aResult)); + + if (obs) { + obs->AfterProcessNextEvent(this, *aResult); + } + + 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) +{ + MutexAutoLock lock(mLock); + NS_IF_ADDREF(*aObs = mObserver); + return NS_OK; +} + +NS_IMETHODIMP +nsThread::SetObserver(nsIThreadObserver* aObs) +{ + if (NS_WARN_IF(PR_GetCurrentThread() != mThread)) { + return NS_ERROR_NOT_SAME_THREAD; + } + + MutexAutoLock lock(mLock); + mObserver = aObs; + return NS_OK; +} + +uint32_t +nsThread::RecursionDepth() const +{ + MOZ_ASSERT(PR_GetCurrentThread() == mThread); + return mNestedEventLoopDepth; +} + +NS_IMETHODIMP +nsThread::AddObserver(nsIThreadObserver* aObserver) +{ + if (NS_WARN_IF(!aObserver)) { + return NS_ERROR_INVALID_ARG; + } + if (NS_WARN_IF(PR_GetCurrentThread() != mThread)) { + return NS_ERROR_NOT_SAME_THREAD; + } + + NS_WARNING_ASSERTION(!mEventObservers.Contains(aObserver), + "Adding an observer twice!"); + + if (!mEventObservers.AppendElement(WrapNotNull(aObserver))) { + NS_WARNING("Out of memory!"); + return NS_ERROR_OUT_OF_MEMORY; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsThread::RemoveObserver(nsIThreadObserver* aObserver) +{ + if (NS_WARN_IF(PR_GetCurrentThread() != mThread)) { + return NS_ERROR_NOT_SAME_THREAD; + } + + if (aObserver && !mEventObservers.RemoveElement(aObserver)) { + NS_WARNING("Removing an observer that was never added!"); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsThread::PushEventQueue(nsIEventTarget** aResult) +{ + if (NS_WARN_IF(PR_GetCurrentThread() != mThread)) { + return NS_ERROR_NOT_SAME_THREAD; + } + + NotNull queue = + WrapNotNull(new nsChainedEventQueue(mLock)); + queue->mEventTarget = new nsNestedEventTarget(WrapNotNull(this), queue); + + { + MutexAutoLock lock(mLock); + queue->mNext = mEvents; + mEvents = queue; + } + + NS_ADDREF(*aResult = queue->mEventTarget); + return NS_OK; +} + +NS_IMETHODIMP +nsThread::PopEventQueue(nsIEventTarget* aInnermostTarget) +{ + if (NS_WARN_IF(PR_GetCurrentThread() != mThread)) { + return NS_ERROR_NOT_SAME_THREAD; + } + + if (NS_WARN_IF(!aInnermostTarget)) { + return NS_ERROR_NULL_POINTER; + } + + // Don't delete or release anything while holding the lock. + nsAutoPtr queue; + RefPtr target; + + { + MutexAutoLock lock(mLock); + + // Make sure we're popping the innermost event target. + if (NS_WARN_IF(mEvents->mEventTarget != aInnermostTarget)) { + return NS_ERROR_UNEXPECTED; + } + + MOZ_ASSERT(mEvents != &mEventsRoot); + + queue = mEvents; + mEvents = WrapNotNull(mEvents->mNext); + + nsCOMPtr event; + while (queue->GetEvent(false, getter_AddRefs(event), lock)) { + mEvents->PutEvent(event.forget(), lock); + } + + // Don't let the event target post any more events. + queue->mEventTarget.swap(target); + target->mQueue = nullptr; + } + + return NS_OK; +} + +void +nsThread::SetScriptObserver(mozilla::CycleCollectedJSContext* aScriptObserver) +{ + if (!aScriptObserver) { + mScriptObserver = nullptr; + return; + } + + MOZ_ASSERT(!mScriptObserver); + mScriptObserver = aScriptObserver; +} + +void +nsThread::DoMainThreadSpecificProcessing(bool aReallyWait) +{ + MOZ_ASSERT(mIsMainThread == MAIN_THREAD); + + ipc::CancelCPOWs(); + + if (aReallyWait) { + HangMonitor::Suspend(); + } + + // Fire a memory pressure notification, if one is pending. + if (!ShuttingDown()) { + MemoryPressureState mpPending = NS_GetPendingMemoryPressure(); + if (mpPending != MemPressure_None) { + nsCOMPtr os = services::GetObserverService(); + + // Use no-forward to prevent the notifications from being transferred to + // the children of this process. + NS_NAMED_LITERAL_STRING(lowMem, "low-memory-no-forward"); + NS_NAMED_LITERAL_STRING(lowMemOngoing, "low-memory-ongoing-no-forward"); + + if (os) { + os->NotifyObservers(nullptr, "memory-pressure", + mpPending == MemPressure_New ? lowMem.get() : + lowMemOngoing.get()); + } else { + NS_WARNING("Can't get observer service!"); + } + } + } + +#ifdef MOZ_CRASHREPORTER + if (!ShuttingDown()) { + SaveMemoryReportNearOOM(ShouldSaveMemoryReport::kMaybeReport); + } +#endif +} + +//----------------------------------------------------------------------------- + +NS_IMPL_ISUPPORTS(nsThread::nsNestedEventTarget, nsIEventTarget) + +NS_IMETHODIMP +nsThread::nsNestedEventTarget::DispatchFromScript(nsIRunnable* aEvent, uint32_t aFlags) +{ + nsCOMPtr event(aEvent); + return Dispatch(event.forget(), aFlags); +} + +NS_IMETHODIMP +nsThread::nsNestedEventTarget::Dispatch(already_AddRefed aEvent, uint32_t aFlags) +{ + LOG(("THRD(%p) Dispatch [%p %x] to nested loop %p\n", mThread.get().get(), + /*XXX aEvent*/ nullptr, aFlags, this)); + + return mThread->DispatchInternal(Move(aEvent), aFlags, this); +} + +NS_IMETHODIMP +nsThread::nsNestedEventTarget::DelayedDispatch(already_AddRefed, uint32_t) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsThread::nsNestedEventTarget::IsOnCurrentThread(bool* aResult) +{ + return mThread->IsOnCurrentThread(aResult); +} diff --git a/xpcom/threads/nsThread.h b/xpcom/threads/nsThread.h new file mode 100644 index 000000000..836123747 --- /dev/null +++ b/xpcom/threads/nsThread.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 nsThread_h__ +#define nsThread_h__ + +#include "mozilla/Mutex.h" +#include "nsIIdlePeriod.h" +#include "nsIThreadInternal.h" +#include "nsISupportsPriority.h" +#include "nsEventQueue.h" +#include "nsThreadUtils.h" +#include "nsString.h" +#include "nsTObserverArray.h" +#include "mozilla/Attributes.h" +#include "mozilla/NotNull.h" +#include "nsAutoPtr.h" +#include "mozilla/AlreadyAddRefed.h" +#include "mozilla/UniquePtr.h" + +namespace mozilla { +class CycleCollectedJSContext; +} + +using mozilla::NotNull; + +// A native thread +class nsThread + : public nsIThreadInternal + , public nsISupportsPriority +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIEVENTTARGET + NS_DECL_NSITHREAD + NS_DECL_NSITHREADINTERNAL + NS_DECL_NSISUPPORTSPRIORITY + using nsIEventTarget::Dispatch; + + enum MainThreadFlag + { + MAIN_THREAD, + NOT_MAIN_THREAD + }; + + nsThread(MainThreadFlag aMainThread, uint32_t aStackSize); + + // Initialize this as a wrapper for a new PRThread. + nsresult Init(); + + // Initialize this as a wrapper for the current PRThread. + nsresult InitCurrentThread(); + + // The PRThread corresponding to this thread. + PRThread* GetPRThread() + { + return mThread; + } + + // If this flag is true, then the nsThread was created using + // nsIThreadManager::NewThread. + bool ShutdownRequired() + { + return mShutdownRequired; + } + + // Clear the observer list. + void ClearObservers() + { + mEventObservers.Clear(); + } + + void + SetScriptObserver(mozilla::CycleCollectedJSContext* aScriptObserver); + + uint32_t + RecursionDepth() const; + + void ShutdownComplete(NotNull aContext); + + void WaitForAllAsynchronousShutdowns(); + +#ifdef MOZ_CRASHREPORTER + enum class ShouldSaveMemoryReport + { + kMaybeReport, + kForceReport + }; + + static bool SaveMemoryReportNearOOM(ShouldSaveMemoryReport aShouldSave); +#endif + +private: + void DoMainThreadSpecificProcessing(bool aReallyWait); + + void GetIdleEvent(nsIRunnable** aEvent, mozilla::MutexAutoLock& aProofOfLock); + void GetEvent(bool aWait, nsIRunnable** aEvent, + mozilla::MutexAutoLock& aProofOfLock); + +protected: + class nsChainedEventQueue; + + class nsNestedEventTarget; + friend class nsNestedEventTarget; + + friend class nsThreadShutdownEvent; + + virtual ~nsThread(); + + bool ShuttingDown() + { + return mShutdownContext != nullptr; + } + + static void ThreadFunc(void* aArg); + + // Helper + already_AddRefed GetObserver() + { + nsIThreadObserver* obs; + nsThread::GetObserver(&obs); + return already_AddRefed(obs); + } + + // Wrappers for event queue methods: + nsresult PutEvent(nsIRunnable* aEvent, nsNestedEventTarget* aTarget); + nsresult PutEvent(already_AddRefed aEvent, + nsNestedEventTarget* aTarget); + + nsresult DispatchInternal(already_AddRefed aEvent, + uint32_t aFlags, nsNestedEventTarget* aTarget); + + struct nsThreadShutdownContext* ShutdownInternal(bool aSync); + + // Wrapper for nsEventQueue that supports chaining. + class nsChainedEventQueue + { + public: + explicit nsChainedEventQueue(mozilla::Mutex& aLock) + : mNext(nullptr) + , mEventsAvailable(aLock, "[nsChainedEventQueue.mEventsAvailable]") + , mProcessSecondaryQueueRunnable(false) + { + mNormalQueue = + mozilla::MakeUnique(mEventsAvailable, + nsEventQueue::eSharedCondVarQueue); + // Both queues need to use the same CondVar! + mSecondaryQueue = + mozilla::MakeUnique(mEventsAvailable, + nsEventQueue::eSharedCondVarQueue); + } + + bool GetEvent(bool aMayWait, nsIRunnable** aEvent, + mozilla::MutexAutoLock& aProofOfLock); + + void PutEvent(nsIRunnable* aEvent, mozilla::MutexAutoLock& aProofOfLock) + { + RefPtr event(aEvent); + PutEvent(event.forget(), aProofOfLock); + } + + void PutEvent(already_AddRefed aEvent, + mozilla::MutexAutoLock& aProofOfLock) + { + RefPtr event(aEvent); + nsCOMPtr runnablePrio = + do_QueryInterface(event); + uint32_t prio = nsIRunnablePriority::PRIORITY_NORMAL; + if (runnablePrio) { + runnablePrio->GetPriority(&prio); + } + MOZ_ASSERT(prio == nsIRunnablePriority::PRIORITY_NORMAL || + prio == nsIRunnablePriority::PRIORITY_HIGH); + if (prio == nsIRunnablePriority::PRIORITY_NORMAL) { + mNormalQueue->PutEvent(event.forget(), aProofOfLock); + } else { + mSecondaryQueue->PutEvent(event.forget(), aProofOfLock); + } + } + + bool HasPendingEvent(mozilla::MutexAutoLock& aProofOfLock) + { + return mNormalQueue->HasPendingEvent(aProofOfLock) || + mSecondaryQueue->HasPendingEvent(aProofOfLock); + } + + nsChainedEventQueue* mNext; + RefPtr mEventTarget; + + private: + mozilla::CondVar mEventsAvailable; + mozilla::UniquePtr mNormalQueue; + mozilla::UniquePtr mSecondaryQueue; + + // Try to process one high priority runnable after each normal + // priority runnable. This gives the processing model HTML spec has for + // 'Update the rendering' in the case only vsync messages are in the + // secondary queue and prevents starving the normal queue. + bool mProcessSecondaryQueueRunnable; + }; + + class nsNestedEventTarget final : public nsIEventTarget + { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIEVENTTARGET + + nsNestedEventTarget(NotNull aThread, + NotNull aQueue) + : mThread(aThread) + , mQueue(aQueue) + + + + { + } + + NotNull> mThread; + + // This is protected by mThread->mLock. + nsChainedEventQueue* mQueue; + + private: + ~nsNestedEventTarget() + { + } + }; + + // This lock protects access to mObserver, mEvents, mIdleEvents, + // mIdlePeriod and mEventsAreDoomed. All of those fields are only + // modified on the thread itself (never from another thread). This + // means that we can avoid holding the lock while using mObserver + // and mEvents on the thread itself. When calling PutEvent on + // mEvents, we have to hold the lock to synchronize with + // PopEventQueue. + mozilla::Mutex mLock; + + nsCOMPtr mObserver; + mozilla::CycleCollectedJSContext* mScriptObserver; + + // Only accessed on the target thread. + nsAutoTObserverArray>, 2> mEventObservers; + + NotNull mEvents; // never null + nsChainedEventQueue mEventsRoot; + + // mIdlePeriod keeps track of the current idle period. If at any + // time the main event queue is empty, calling + // mIdlePeriod->GetIdlePeriodHint() will give an estimate of when + // the current idle period will end. + nsCOMPtr mIdlePeriod; + mozilla::CondVar mIdleEventsAvailable; + nsEventQueue mIdleEvents; + + int32_t mPriority; + PRThread* mThread; + uint32_t mNestedEventLoopDepth; + uint32_t mStackSize; + + // The shutdown context for ourselves. + struct nsThreadShutdownContext* mShutdownContext; + // The shutdown contexts for any other threads we've asked to shut down. + nsTArray> mRequestedShutdownContexts; + + bool mShutdownRequired; + // Set to true when events posted to this thread will never run. + bool mEventsAreDoomed; + MainThreadFlag mIsMainThread; + + // Set to true if this thread creates a JSRuntime. + bool mCanInvokeJS; +}; + +#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 000000000..d1eb84b8f --- /dev/null +++ b/xpcom/threads/nsThreadManager.cpp @@ -0,0 +1,342 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. 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 "nsThreadUtils.h" +#include "nsIClassInfoImpl.h" +#include "nsTArray.h" +#include "nsAutoPtr.h" +#include "mozilla/ThreadLocal.h" +#ifdef MOZ_CANARY +#include +#include +#endif + +#include "MainThreadIdlePeriod.h" + +using namespace mozilla; + +static MOZ_THREAD_LOCAL(bool) sTLSIsMainThread; + +bool +NS_IsMainThread() +{ + return sTLSIsMainThread.get(); +} + +void +NS_SetMainThread() +{ + if (!sTLSIsMainThread.init()) { + MOZ_CRASH(); + } + sTLSIsMainThread.set(true); + MOZ_ASSERT(NS_IsMainThread()); +} + +typedef nsTArray>> nsThreadArray; + +//----------------------------------------------------------------------------- + +static void +ReleaseObject(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) + +//----------------------------------------------------------------------------- + +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, ReleaseObject) == 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 + + // Setup "main" thread + mMainThread = new nsThread(nsThread::MAIN_THREAD, 0); + + nsresult rv = mMainThread->InitCurrentThread(); + if (NS_FAILED(rv)) { + mMainThread = nullptr; + return rv; + } + + { + nsCOMPtr idlePeriod = new MainThreadIdlePeriod(); + mMainThread->RegisterIdlePeriod(idlePeriod.forget()); + } + + // We need to keep a pointer to the current thread, so we can satisfy + // GetIsMainThread calls that occur post-Shutdown. + mMainThread->GetPRThread(&mMainPRThread); + + mInitialized = true; + return NS_OK; +} + +void +nsThreadManager::Shutdown() +{ + 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); + + // We gather the threads from the hashtable into a list, so that we avoid + // holding the hashtable lock while calling nsIThread::Shutdown. + nsThreadArray threads; + { + OffTheBooksMutexAutoLock lock(mLock); + for (auto iter = mThreadsByPRThread.Iter(); !iter.Done(); iter.Next()) { + RefPtr& thread = iter.Data(); + threads.AppendElement(WrapNotNull(thread)); + iter.Remove(); + } + } + + // 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 (uint32_t i = 0; i < threads.Length(); ++i) { + NotNull thread = threads[i]; + if (thread->ShutdownRequired()) { + 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(); + + // In case there are any more events somehow... + NS_ProcessPendingEvents(mMainThread); + + // There are no more background threads at this point. + + // Clear the table of threads. + { + OffTheBooksMutexAutoLock lock(mLock); + mThreadsByPRThread.Clear(); + } + + // 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); + mMainThread->ClearObservers(); + + // 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"); + + OffTheBooksMutexAutoLock lock(mLock); + + ++mCurrentNumberOfThreads; + if (mCurrentNumberOfThreads > mHighestNumberOfThreads) { + mHighestNumberOfThreads = mCurrentNumberOfThreads; + } + + mThreadsByPRThread.Put(aThread.GetPRThread(), &aThread); // XXX check OOM? + + aThread.AddRef(); // for TLS entry + PR_SetThreadPrivate(mCurThreadIndex, &aThread); +} + +void +nsThreadManager::UnregisterCurrentThread(nsThread& aThread) +{ + MOZ_ASSERT(aThread.GetPRThread() == PR_GetCurrentThread(), "bad aThread"); + + OffTheBooksMutexAutoLock lock(mLock); + + --mCurrentNumberOfThreads; + mThreadsByPRThread.Remove(aThread.GetPRThread()); + + PR_SetThreadPrivate(mCurThreadIndex, nullptr); + // Ref-count balanced via ReleaseObject +} + +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 :-) + RefPtr thread = new nsThread(nsThread::NOT_MAIN_THREAD, 0); + if (!thread || NS_FAILED(thread->InitCurrentThread())) { + return nullptr; + } + + return thread.get(); // reference held in TLS +} + +NS_IMETHODIMP +nsThreadManager::NewThread(uint32_t aCreationFlags, + uint32_t aStackSize, + nsIThread** aResult) +{ + // Note: can be called from arbitrary threads + + // No new threads during Shutdown + if (NS_WARN_IF(!mInitialized)) { + return NS_ERROR_NOT_INITIALIZED; + } + + RefPtr thr = new nsThread(nsThread::NOT_MAIN_THREAD, aStackSize); + nsresult rv = thr->Init(); // 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; + } + + thr.forget(aResult); + return NS_OK; +} + +NS_IMETHODIMP +nsThreadManager::GetThreadFromPRThread(PRThread* aThread, nsIThread** aResult) +{ + // Keep this functioning during Shutdown + if (NS_WARN_IF(!mMainThread)) { + return NS_ERROR_NOT_INITIALIZED; + } + if (NS_WARN_IF(!aThread)) { + return NS_ERROR_INVALID_ARG; + } + + RefPtr temp; + { + OffTheBooksMutexAutoLock lock(mLock); + mThreadsByPRThread.Get(aThread, getter_AddRefs(temp)); + } + + NS_IF_ADDREF(*aResult = temp); + return NS_OK; +} + +NS_IMETHODIMP +nsThreadManager::GetMainThread(nsIThread** aResult) +{ + // Keep this functioning during Shutdown + if (NS_WARN_IF(!mMainThread)) { + return NS_ERROR_NOT_INITIALIZED; + } + NS_ADDREF(*aResult = mMainThread); + return NS_OK; +} + +NS_IMETHODIMP +nsThreadManager::GetCurrentThread(nsIThread** aResult) +{ + // Keep this functioning during Shutdown + if (NS_WARN_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::GetIsMainThread(bool* aResult) +{ + // This method may be called post-Shutdown + + *aResult = (PR_GetCurrentThread() == mMainPRThread); + return NS_OK; +} + +uint32_t +nsThreadManager::GetHighestNumberOfThreads() +{ + OffTheBooksMutexAutoLock lock(mLock); + return mHighestNumberOfThreads; +} diff --git a/xpcom/threads/nsThreadManager.h b/xpcom/threads/nsThreadManager.h new file mode 100644 index 000000000..64ccc9bc9 --- /dev/null +++ b/xpcom/threads/nsThreadManager.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/. */ + +#ifndef nsThreadManager_h__ +#define nsThreadManager_h__ + +#include "mozilla/Mutex.h" +#include "nsIThreadManager.h" +#include "nsRefPtrHashtable.h" +#include "nsThread.h" + +class nsIRunnable; + +class nsThreadManager : public nsIThreadManager +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSITHREADMANAGER + + static nsThreadManager& get() + { + static nsThreadManager sInstance; + return sInstance; + } + + nsresult Init(); + + // Shutdown all threads. This function should only be called on the main + // thread of the application process. + void Shutdown(); + + // 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. + nsThread* GetCurrentThread(); + + // Returns the maximal number of threads that have been in existence + // simultaneously during the execution of the thread manager. + uint32_t GetHighestNumberOfThreads(); + + // This needs to be public in order to support static instantiation of this + // class with older compilers (e.g., egcs-2.91.66). + ~nsThreadManager() + { + } + +private: + nsThreadManager() + : mCurThreadIndex(0) + , mMainPRThread(nullptr) + , mLock("nsThreadManager.mLock") + , mInitialized(false) + , mCurrentNumberOfThreads(1) + , mHighestNumberOfThreads(1) + { + } + + nsRefPtrHashtable, nsThread> mThreadsByPRThread; + unsigned mCurThreadIndex; // thread-local-storage index + RefPtr mMainThread; + PRThread* mMainPRThread; + mozilla::OffTheBooksMutex mLock; // protects tables + mozilla::Atomic mInitialized; + + // The current number of threads + uint32_t mCurrentNumberOfThreads; + // The highest number of threads encountered so far during the session + uint32_t mHighestNumberOfThreads; +}; + +#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 000000000..241fad39d --- /dev/null +++ b/xpcom/threads/nsThreadPool.cpp @@ -0,0 +1,449 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. 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 "nsThreadPool.h" +#include "nsThreadManager.h" +#include "nsThread.h" +#include "nsMemory.h" +#include "nsAutoPtr.h" +#include "prinrval.h" +#include "mozilla/Logging.h" +#include "nsThreadSyncDispatch.h" + +using namespace mozilla; + +static LazyLogModule sThreadPoolLog("nsThreadPool"); +#ifdef LOG +#undef LOG +#endif +#define LOG(args) MOZ_LOG(sThreadPoolLog, mozilla::LogLevel::Debug, args) + +// 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_ADDREF(nsThreadPool) +NS_IMPL_RELEASE(nsThreadPool) +NS_IMPL_CLASSINFO(nsThreadPool, nullptr, nsIClassInfo::THREADSAFE, + NS_THREADPOOL_CID) +NS_IMPL_QUERY_INTERFACE_CI(nsThreadPool, nsIThreadPool, nsIEventTarget, + nsIRunnable) +NS_IMPL_CI_INTERFACE_GETTER(nsThreadPool, nsIThreadPool, nsIEventTarget) + +nsThreadPool::nsThreadPool() + : mMutex("[nsThreadPool.mMutex]") + , mEventsAvailable(mMutex, "[nsThreadPool.mEventsAvailable]") + , mEvents(mEventsAvailable, nsEventQueue::eNormalQueue) + , mThreadLimit(DEFAULT_THREAD_LIMIT) + , mIdleThreadLimit(DEFAULT_IDLE_THREAD_LIMIT) + , mIdleThreadTimeout(DEFAULT_IDLE_THREAD_TIMEOUT) + , mIdleCount(0) + , mStackSize(nsIThreadManager::DEFAULT_STACK_SIZE) + , mShutdown(false) +{ + 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; + { + 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; + } + + mEvents.PutEvent(Move(aEvent), lock); + stackSize = mStackSize; + } + + LOG(("THRD-P(%p) put [spawn=%d]\n", this, spawnThread)); + if (!spawnThread) { + return NS_OK; + } + + nsCOMPtr thread; + nsThreadManager::get().NewThread(0, stackSize, getter_AddRefs(thread)); + if (NS_WARN_IF(!thread)) { + return NS_ERROR_UNEXPECTED; + } + + bool killThread = false; + { + MutexAutoLock lock(mMutex); + if (mThreads.Count() < (int32_t)mThreadLimit) { + mThreads.AppendObject(thread); + } else { + 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_NORMAL); + } + + 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. + NS_DispatchToMainThread(NewRunnableMethod(aThread, + &nsIThread::AsyncShutdown)); +} + +NS_IMETHODIMP +nsThreadPool::Run() +{ + mThreadNaming.SetThreadPoolName(mName); + + LOG(("THRD-P(%p) enter %s\n", this, mName.BeginReading())); + + nsCOMPtr current; + nsThreadManager::get().GetCurrentThread(getter_AddRefs(current)); + + bool shutdownThreadOnExit = false; + bool exitThread = false; + bool wasIdle = false; + PRIntervalTime idleSince; + + nsCOMPtr listener; + { + MutexAutoLock lock(mMutex); + listener = mListener; + } + + if (listener) { + listener->OnThreadCreated(); + } + + do { + nsCOMPtr event; + { + MutexAutoLock lock(mMutex); + + if (!mEvents.GetPendingEvent(getter_AddRefs(event), lock)) { + PRIntervalTime now = PR_IntervalNow(); + PRIntervalTime timeout = PR_MillisecondsToInterval(mIdleThreadTimeout); + + // 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); + } else { + PRIntervalTime delta = timeout - (now - idleSince); + LOG(("THRD-P(%p) %s waiting [%d]\n", this, mName.BeginReading(), delta)); + mEvents.Wait(delta); + LOG(("THRD-P(%p) done waiting\n", this)); + } + } else if (wasIdle) { + wasIdle = false; + --mIdleCount; + } + } + if (event) { + LOG(("THRD-P(%p) %s running [%p]\n", this, mName.BeginReading(), event.get())); + event->Run(); + } + } while (!exitThread); + + if (listener) { + listener->OnThreadShuttingDown(); + } + + 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; + } + + if (aFlags & DISPATCH_SYNC) { + nsCOMPtr thread; + nsThreadManager::get().GetCurrentThread(getter_AddRefs(thread)); + if (NS_WARN_IF(!thread)) { + return NS_ERROR_NOT_AVAILABLE; + } + + RefPtr wrapper = + new nsThreadSyncDispatch(thread, Move(aEvent)); + PutEvent(wrapper); + + while (wrapper->IsPending()) { + NS_ProcessNextEvent(thread); + } + } else { + NS_ASSERTION(aFlags == NS_DISPATCH_NORMAL || + aFlags == NS_DISPATCH_AT_END, "unexpected dispatch flags"); + PutEvent(Move(aEvent), aFlags); + } + return NS_OK; +} + +NS_IMETHODIMP +nsThreadPool::DelayedDispatch(already_AddRefed, uint32_t) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsThreadPool::IsOnCurrentThread(bool* aResult) +{ + MutexAutoLock lock(mMutex); + if (NS_WARN_IF(mShutdown)) { + return NS_ERROR_NOT_AVAILABLE; + } + + nsIThread* thread = NS_GetCurrentThread(); + for (uint32_t i = 0; i < static_cast(mThreads.Count()); ++i) { + if (mThreads[i] == thread) { + *aResult = true; + return NS_OK; + } + } + *aResult = false; + return NS_OK; +} + +NS_IMETHODIMP +nsThreadPool::Shutdown() +{ + nsCOMArray threads; + nsCOMPtr listener; + { + MutexAutoLock lock(mMutex); + mShutdown = true; + mEvents.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); + } + + // It's important that we shutdown the threads while outside the event queue + // monitor. Otherwise, we could end up dead-locking. + + for (int32_t i = 0; i < threads.Count(); ++i) { + threads[i]->Shutdown(); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsThreadPool::GetThreadLimit(uint32_t* aValue) +{ + *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) { + mEvents.NotifyAll(); // wake up threads so they observe this change + } + return NS_OK; +} + +NS_IMETHODIMP +nsThreadPool::GetIdleThreadLimit(uint32_t* aValue) +{ + *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) { + mEvents.NotifyAll(); // wake up threads so they observe this change + } + return NS_OK; +} + +NS_IMETHODIMP +nsThreadPool::GetIdleThreadTimeout(uint32_t* aValue) +{ + *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) { + mEvents.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 000000000..47a4bd1ff --- /dev/null +++ b/xpcom/threads/nsThreadPool.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 nsThreadPool_h__ +#define nsThreadPool_h__ + +#include "nsIThreadPool.h" +#include "nsIThread.h" +#include "nsIRunnable.h" +#include "nsEventQueue.h" +#include "nsCOMArray.h" +#include "nsCOMPtr.h" +#include "nsThreadUtils.h" +#include "mozilla/Attributes.h" +#include "mozilla/AlreadyAddRefed.h" +#include "mozilla/Mutex.h" +#include "mozilla/Monitor.h" + +class nsThreadPool final + : public nsIThreadPool + , public nsIRunnable +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIEVENTTARGET + NS_DECL_NSITHREADPOOL + NS_DECL_NSIRUNNABLE + using nsIEventTarget::Dispatch; + + nsThreadPool(); + +private: + ~nsThreadPool(); + + void ShutdownThread(nsIThread* aThread); + nsresult PutEvent(nsIRunnable* aEvent); + nsresult PutEvent(already_AddRefed aEvent, uint32_t aFlags); + + nsCOMArray mThreads; + mozilla::Mutex mMutex; + mozilla::CondVar mEventsAvailable; + nsEventQueue mEvents; + uint32_t mThreadLimit; + uint32_t mIdleThreadLimit; + uint32_t mIdleThreadTimeout; + uint32_t mIdleCount; + uint32_t mStackSize; + nsCOMPtr mListener; + bool mShutdown; + nsCString mName; + nsThreadPoolNaming mThreadNaming; +}; + +#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 000000000..ae5e85464 --- /dev/null +++ b/xpcom/threads/nsThreadSyncDispatch.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/. */ + +#ifndef nsThreadSyncDispatch_h_ +#define nsThreadSyncDispatch_h_ + +#include "nsThreadUtils.h" +#include "LeakRefPtr.h" +#include "mozilla/DebugOnly.h" + +class nsThreadSyncDispatch : public mozilla::Runnable +{ +public: + nsThreadSyncDispatch(nsIThread* aOrigin, already_AddRefed&& aTask) + : mOrigin(aOrigin) + , mSyncTask(mozilla::Move(aTask)) + { + } + + bool IsPending() + { + return !!mSyncTask; + } + +private: + NS_IMETHOD Run() override + { + if (nsIRunnable* task = mSyncTask.get()) { + 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. + mSyncTask.release(); + // unblock the origin thread + mOrigin->Dispatch(this, NS_DISPATCH_NORMAL); + } + 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; +}; + +#endif // nsThreadSyncDispatch_h_ diff --git a/xpcom/threads/nsTimerImpl.cpp b/xpcom/threads/nsTimerImpl.cpp new file mode 100644 index 000000000..bc2d338e0 --- /dev/null +++ b/xpcom/threads/nsTimerImpl.cpp @@ -0,0 +1,658 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. 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 "nsAutoPtr.h" +#include "nsThreadManager.h" +#include "nsThreadUtils.h" +#include "pratom.h" +#include "GeckoProfiler.h" +#include "mozilla/Atomics.h" +#include "mozilla/IntegerPrintfMacros.h" +#include "mozilla/Logging.h" +#ifdef MOZ_TASK_TRACER +#include "GeckoTaskTracerImpl.h" +using namespace mozilla::tasktracer; +#endif + +#ifdef XP_WIN +#include +#ifndef getpid +#define getpid _getpid +#endif +#else +#include +#endif + +using mozilla::Atomic; +using mozilla::LogLevel; +using mozilla::TimeDuration; +using mozilla::TimeStamp; + +static TimerThread* gThread = nullptr; + +// This module prints info about the precision of timers. +static mozilla::LazyLogModule sTimerLog("nsTimerImpl"); + +mozilla::LogModule* +GetTimerLog() +{ + return sTimerLog; +} + +// 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. The name used for a +// |Callback::Type::Function| timer depends on the circumstances. +// +// - If it was explicitly named (e.g. it was initialized with +// InitWithNamedFuncCallback()) then that explicit name will be shown. +// +// - Otherwise, if we are on a platform that supports function name lookup +// (Mac or Linux) then the looked-up name will be shown with a +// "[from dladdr]" annotation. On Mac the looked-up name will be immediately +// useful. On Linux it'll need post-processing with +// tools/rb/fix_linux_stack.py. +// +// - Otherwise, no name will be printed. If many timers hit this case then +// you'll need to re-run the workload on a Mac to find out which timers they +// are, and then give them explicit names. +// +// 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"); + +mozilla::LogModule* +GetTimerFiringsLog() +{ + return sTimerFiringsLog; +} + +#include + +double nsTimerImpl::sDeltaSumSquared = 0; +double nsTimerImpl::sDeltaSum = 0; +double nsTimerImpl::sDeltaNum = 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_IMETHODIMP_(MozExternalRefCountType) +nsTimer::Release(void) +{ + nsrefcnt count = --mRefCnt; + NS_LOG_RELEASE(this, count, "nsTimer"); + + if (count == 1) { + // Last ref, held by nsTimerImpl. Make sure the cycle is broken. + // If there is a nsTimerEvent in a queue for this timer, the nsTimer will + // live until that event pops, otherwise the nsTimerImpl will go away and + // the nsTimer along with it. + mImpl->Cancel(); + mImpl = nullptr; + } else if (count == 0) { + delete this; + } + + return count; +} + +nsTimerImpl::nsTimerImpl(nsITimer* aTimer) : + mGeneration(0), + mDelay(0), + mITimer(aTimer), + mMutex("nsTimerImpl::mMutex") +{ + // XXXbsmedberg: shouldn't this be in Init()? + mEventTarget = static_cast(NS_GetCurrentThread()); +} + +//static +nsresult +nsTimerImpl::Startup() +{ + nsresult rv; + + gThread = new TimerThread(); + + NS_ADDREF(gThread); + rv = gThread->InitLocks(); + + if (NS_FAILED(rv)) { + NS_RELEASE(gThread); + } + + return rv; +} + +void +nsTimerImpl::Shutdown() +{ + if (MOZ_LOG_TEST(GetTimerLog(), LogLevel::Debug)) { + 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)); + } + + if (!gThread) { + return; + } + + gThread->Shutdown(); + NS_RELEASE(gThread); +} + + +nsresult +nsTimerImpl::InitCommon(uint32_t aDelay, uint32_t aType) +{ + mMutex.AssertCurrentThreadOwns(); + nsresult rv; + + if (NS_WARN_IF(!gThread)) { + return NS_ERROR_NOT_INITIALIZED; + } + if (!mEventTarget) { + NS_ERROR("mEventTarget is NULL"); + return NS_ERROR_NOT_INITIALIZED; + } + + rv = gThread->Init(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + gThread->RemoveTimer(this); + ++mGeneration; + + mType = (uint8_t)aType; + mDelay = aDelay; + mTimeout = TimeStamp::Now() + TimeDuration::FromMilliseconds(mDelay); + + return gThread->AddTimer(this); +} + +nsresult +nsTimerImpl::InitWithFuncCallbackCommon(nsTimerCallbackFunc aFunc, + void* aClosure, + uint32_t aDelay, + uint32_t aType, + Callback::Name aName) +{ + if (NS_WARN_IF(!aFunc)) { + return NS_ERROR_INVALID_ARG; + } + + Callback cb; // Goes out of scope after the unlock, prevents deadlock + cb.mType = Callback::Type::Function; + cb.mCallback.c = aFunc; + cb.mClosure = aClosure; + cb.mName = aName; + + MutexAutoLock lock(mMutex); + cb.swap(mCallback); + + return InitCommon(aDelay, aType); +} + +NS_IMETHODIMP +nsTimerImpl::InitWithFuncCallback(nsTimerCallbackFunc aFunc, + void* aClosure, + uint32_t aDelay, + uint32_t aType) +{ + Callback::Name name(Callback::Nothing); + return InitWithFuncCallbackCommon(aFunc, aClosure, aDelay, aType, name); +} + +NS_IMETHODIMP +nsTimerImpl::InitWithNamedFuncCallback(nsTimerCallbackFunc aFunc, + void* aClosure, + uint32_t aDelay, + uint32_t aType, + const char* aNameString) +{ + Callback::Name name(aNameString); + return InitWithFuncCallbackCommon(aFunc, aClosure, aDelay, aType, name); +} + +NS_IMETHODIMP +nsTimerImpl::InitWithNameableFuncCallback(nsTimerCallbackFunc aFunc, + void* aClosure, + uint32_t aDelay, + uint32_t aType, + nsTimerNameCallbackFunc aNameFunc) +{ + Callback::Name name(aNameFunc); + return InitWithFuncCallbackCommon(aFunc, aClosure, aDelay, aType, name); +} + +NS_IMETHODIMP +nsTimerImpl::InitWithCallback(nsITimerCallback* aCallback, + uint32_t aDelay, + uint32_t aType) +{ + if (NS_WARN_IF(!aCallback)) { + return NS_ERROR_INVALID_ARG; + } + + Callback cb; // Goes out of scope after the unlock, prevents deadlock + cb.mType = Callback::Type::Interface; + cb.mCallback.i = aCallback; + NS_ADDREF(cb.mCallback.i); + + MutexAutoLock lock(mMutex); + cb.swap(mCallback); + + return InitCommon(aDelay, aType); +} + +NS_IMETHODIMP +nsTimerImpl::Init(nsIObserver* aObserver, uint32_t aDelay, uint32_t aType) +{ + if (NS_WARN_IF(!aObserver)) { + return NS_ERROR_INVALID_ARG; + } + + Callback cb; // Goes out of scope after the unlock, prevents deadlock + cb.mType = Callback::Type::Observer; + cb.mCallback.o = aObserver; + NS_ADDREF(cb.mCallback.o); + + MutexAutoLock lock(mMutex); + cb.swap(mCallback); + + return InitCommon(aDelay, aType); +} + +NS_IMETHODIMP +nsTimerImpl::Cancel() +{ + Callback cb; + + MutexAutoLock lock(mMutex); + + if (gThread) { + gThread->RemoveTimer(this); + } + + cb.swap(mCallback); + ++mGeneration; + + return NS_OK; +} + +NS_IMETHODIMP +nsTimerImpl::SetDelay(uint32_t aDelay) +{ + MutexAutoLock lock(mMutex); + if (GetCallback().mType == Callback::Type::Unknown && !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; + if (gThread) { + reAdd = NS_SUCCEEDED(gThread->RemoveTimer(this)); + } + + mDelay = aDelay; + mTimeout = TimeStamp::Now() + TimeDuration::FromMilliseconds(mDelay); + + if (reAdd) { + gThread->AddTimer(this); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsTimerImpl::GetDelay(uint32_t* aDelay) +{ + MutexAutoLock lock(mMutex); + *aDelay = mDelay; + return NS_OK; +} + +NS_IMETHODIMP +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; +} + +NS_IMETHODIMP +nsTimerImpl::GetType(uint32_t* aType) +{ + MutexAutoLock lock(mMutex); + *aType = mType; + return NS_OK; +} + + +NS_IMETHODIMP +nsTimerImpl::GetClosure(void** aClosure) +{ + MutexAutoLock lock(mMutex); + *aClosure = GetCallback().mClosure; + return NS_OK; +} + + +NS_IMETHODIMP +nsTimerImpl::GetCallback(nsITimerCallback** aCallback) +{ + MutexAutoLock lock(mMutex); + if (GetCallback().mType == Callback::Type::Interface) { + NS_IF_ADDREF(*aCallback = GetCallback().mCallback.i); + } else { + *aCallback = nullptr; + } + + return NS_OK; +} + + +NS_IMETHODIMP +nsTimerImpl::GetTarget(nsIEventTarget** aTarget) +{ + MutexAutoLock lock(mMutex); + NS_IF_ADDREF(*aTarget = mEventTarget); + return NS_OK; +} + + +NS_IMETHODIMP +nsTimerImpl::SetTarget(nsIEventTarget* aTarget) +{ + MutexAutoLock lock(mMutex); + if (NS_WARN_IF(mCallback.mType != Callback::Type::Unknown)) { + return NS_ERROR_ALREADY_INITIALIZED; + } + + if (aTarget) { + mEventTarget = aTarget; + } else { + mEventTarget = static_cast(NS_GetCurrentThread()); + } + return NS_OK; +} + + +void +nsTimerImpl::Fire(int32_t aGeneration) +{ + uint8_t oldType; + uint32_t oldDelay; + TimeStamp oldTimeout; + + { + // 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) { + return; + } + + mCallbackDuringFire.swap(mCallback); + oldType = mType; + oldDelay = mDelay; + oldTimeout = mTimeout; + } + + PROFILER_LABEL("Timer", "Fire", + js::ProfileEntry::Category::OTHER); + + TimeStamp now = TimeStamp::Now(); + if (MOZ_LOG_TEST(GetTimerLog(), LogLevel::Debug)) { + TimeDuration delta = now - oldTimeout; + int32_t d = delta.ToMilliseconds(); // delta in ms + 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(mCallbackDuringFire, oldType, oldDelay); + } + + switch (mCallbackDuringFire.mType) { + case Callback::Type::Function: + mCallbackDuringFire.mCallback.c(mITimer, mCallbackDuringFire.mClosure); + break; + case Callback::Type::Interface: + mCallbackDuringFire.mCallback.i->Notify(mITimer); + break; + case Callback::Type::Observer: + mCallbackDuringFire.mCallback.o->Observe(mITimer, NS_TIMER_CALLBACK_TOPIC, + nullptr); + break; + default: + ; + } + + Callback trash; // Swap into here to dispose of callback after the unlock + MutexAutoLock lock(mMutex); + if (aGeneration == mGeneration && IsRepeating()) { + // Repeating timer has not been re-init or canceled; reschedule + mCallbackDuringFire.swap(mCallback); + TimeDuration delay = TimeDuration::FromMilliseconds(mDelay); + if (mType == nsITimer::TYPE_REPEATING_SLACK) { + mTimeout = TimeStamp::Now() + delay; + } else { + mTimeout = mTimeout + delay; + } + if (gThread) { + gThread->AddTimer(this); + } + } + + mCallbackDuringFire.swap(trash); + + MOZ_LOG(GetTimerLog(), LogLevel::Debug, + ("[this=%p] Took %fms to fire timer callback\n", + this, (TimeStamp::Now() - now).ToMilliseconds())); +} + +#if defined(HAVE_DLADDR) && defined(HAVE___CXA_DEMANGLE) +#define USE_DLADDR 1 +#endif + +#ifdef USE_DLADDR +#include +#include +#endif + +// 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_REPEATING_SLACK: typeStr = "SLACK "; break; + case nsITimer::TYPE_REPEATING_PRECISE: /* fall through */ + case nsITimer::TYPE_REPEATING_PRECISE_CAN_SKIP: typeStr = "PRECISE "; break; + default: MOZ_CRASH("bad type"); + } + + switch (aCallback.mType) { + case Callback::Type::Function: { + bool needToFreeName = false; + const char* annotation = ""; + const char* name; + static const size_t buflen = 1024; + char buf[buflen]; + + if (aCallback.mName.is()) { + name = aCallback.mName.as(); + + } else if (aCallback.mName.is()) { + aCallback.mName.as()( + mITimer, aCallback.mClosure, buf, buflen); + name = buf; + + } else { + MOZ_ASSERT(aCallback.mName.is()); +#ifdef USE_DLADDR + annotation = "[from dladdr] "; + + Dl_info info; + void* addr = reinterpret_cast(aCallback.mCallback.c); + if (dladdr(addr, &info) == 0) { + name = "???[dladdr: failed]"; + + } else if (info.dli_sname) { + int status; + name = abi::__cxa_demangle(info.dli_sname, nullptr, nullptr, &status); + if (status == 0) { + // Success. Because we didn't pass in a buffer to __cxa_demangle it + // allocates its own one with malloc() which we must free() later. + MOZ_ASSERT(name); + needToFreeName = true; + } else if (status == -1) { + name = "???[__cxa_demangle: OOM]"; + } else if (status == -2) { + name = "???[__cxa_demangle: invalid mangled name]"; + } else if (status == -3) { + name = "???[__cxa_demangle: invalid argument]"; + } else { + name = "???[__cxa_demangle: unexpected status value]"; + } + + } else if (info.dli_fname) { + // The "#0: " prefix is necessary for fix_linux_stack.py to interpret + // this string as something to convert. + snprintf(buf, buflen, "#0: ???[%s +0x%" PRIxPTR "]\n", + info.dli_fname, uintptr_t(addr) - uintptr_t(info.dli_fbase)); + name = buf; + + } else { + name = "???[dladdr: no symbol or shared object obtained]"; + } +#else + name = "???[dladdr is unimplemented or doesn't work well on this OS]"; +#endif + } + + MOZ_LOG(GetTimerFiringsLog(), LogLevel::Debug, + ("[%d] fn timer (%s %5d ms): %s%s\n", + getpid(), typeStr, aDelay, annotation, name)); + + if (needToFreeName) { + free(const_cast(name)); + } + + break; + } + + case Callback::Type::Interface: { + MOZ_LOG(GetTimerFiringsLog(), LogLevel::Debug, + ("[%d] iface timer (%s %5d ms): %p\n", + getpid(), typeStr, aDelay, aCallback.mCallback.i)); + break; + } + + case Callback::Type::Observer: { + MOZ_LOG(GetTimerFiringsLog(), LogLevel::Debug, + ("[%d] obs timer (%s %5d ms): %p\n", + getpid(), typeStr, aDelay, aCallback.mCallback.o)); + break; + } + + case Callback::Type::Unknown: + default: { + MOZ_LOG(GetTimerFiringsLog(), LogLevel::Debug, + ("[%d] ??? timer (%s, %5d ms)\n", + getpid(), typeStr, aDelay)); + break; + } + } +} + +nsTimer::~nsTimer() +{ +} + +size_t +nsTimer::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const +{ + return aMallocSizeOf(this); +} + +/* static */ +const nsTimerImpl::Callback::NameNothing nsTimerImpl::Callback::Nothing = 0; + +#ifdef MOZ_TASK_TRACER +void +nsTimerImpl::GetTLSTraceInfo() +{ + mTracedTask.GetTLSTraceInfo(); +} + +TracedTaskCommon +nsTimerImpl::GetTracedTask() +{ + return mTracedTask; +} + +#endif + diff --git a/xpcom/threads/nsTimerImpl.h b/xpcom/threads/nsTimerImpl.h new file mode 100644 index 000000000..5c731fbb4 --- /dev/null +++ b/xpcom/threads/nsTimerImpl.h @@ -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/. */ + +#ifndef nsTimerImpl_h___ +#define nsTimerImpl_h___ + +#include "nsITimer.h" +#include "nsIEventTarget.h" +#include "nsIObserver.h" + +#include "nsCOMPtr.h" + +#include "mozilla/Attributes.h" +#include "mozilla/Logging.h" +#include "mozilla/Mutex.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/Variant.h" + +#ifdef MOZ_TASK_TRACER +#include "TracedTaskCommon.h" +#endif + +extern mozilla::LogModule* GetTimerLog(); + +#define NS_TIMER_CID \ +{ /* 5ff24248-1dd2-11b2-8427-fbab44f29bc8 */ \ + 0x5ff24248, \ + 0x1dd2, \ + 0x11b2, \ + {0x84, 0x27, 0xfb, 0xab, 0x44, 0xf2, 0x9b, 0xc8} \ +} + +// 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() {} +public: + typedef mozilla::TimeStamp TimeStamp; + + explicit nsTimerImpl(nsITimer* aTimer); + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(nsTimerImpl) + NS_DECL_NON_VIRTUAL_NSITIMER + + static nsresult Startup(); + static void Shutdown(); + + void Fire(int32_t aGeneration); + +#ifdef MOZ_TASK_TRACER + void GetTLSTraceInfo(); + mozilla::tasktracer::TracedTaskCommon GetTracedTask(); +#endif + + int32_t GetGeneration() + { + return mGeneration; + } + + nsresult InitCommon(uint32_t aDelay, uint32_t aType); + + struct Callback { + Callback() : + mType(Type::Unknown), + mName(Nothing), + mClosure(nullptr) + { + mCallback.c = nullptr; + } + + Callback(const Callback& other) = delete; + Callback& operator=(const Callback& other) = delete; + + ~Callback() + { + if (mType == Type::Interface) { + NS_RELEASE(mCallback.i); + } else if (mType == Type::Observer) { + NS_RELEASE(mCallback.o); + } + } + + void swap(Callback& other) + { + std::swap(mType, other.mType); + std::swap(mCallback, other.mCallback); + std::swap(mName, other.mName); + std::swap(mClosure, other.mClosure); + } + + enum class Type : uint8_t { + Unknown = 0, + Interface = 1, + Function = 2, + Observer = 3, + }; + Type mType; + + union CallbackUnion + { + nsTimerCallbackFunc c; + // These refcounted references are managed manually, as they are in a union + nsITimerCallback* MOZ_OWNING_REF i; + nsIObserver* MOZ_OWNING_REF o; + } mCallback; + + // |Name| is a tagged union type representing one of (a) nothing, (b) a + // string, or (c) a function. mozilla::Variant doesn't naturally handle the + // "nothing" case, so we define a dummy type and value (which is unused and + // so the exact value doesn't matter) for it. + typedef const int NameNothing; + typedef const char* NameString; + typedef nsTimerNameCallbackFunc NameFunc; + typedef mozilla::Variant Name; + static const NameNothing Nothing; + Name mName; + + void* mClosure; + }; + + Callback& GetCallback() + { + mMutex.AssertCurrentThreadOwns(); + if (mCallback.mType == Callback::Type::Unknown) { + return mCallbackDuringFire; + } + + 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; + } + + nsCOMPtr mEventTarget; + + void LogFiring(const Callback& aCallback, uint8_t aType, uint32_t aDelay); + + nsresult InitWithFuncCallbackCommon(nsTimerCallbackFunc aFunc, + void* aClosure, + uint32_t aDelay, + uint32_t aType, + Callback::Name aName); + + // 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; + + uint32_t mDelay; + // Updated only after this timer has been removed from the timer thread. + TimeStamp mTimeout; + +#ifdef MOZ_TASK_TRACER + mozilla::tasktracer::TracedTaskCommon mTracedTask; +#endif + + static double sDeltaSum; + static double sDeltaSumSquared; + static double sDeltaNum; + const RefPtr mITimer; + mozilla::Mutex mMutex; + Callback mCallback; + Callback mCallbackDuringFire; +}; + +class nsTimer final : public nsITimer +{ + virtual ~nsTimer(); +public: + nsTimer() : mImpl(new nsTimerImpl(this)) {} + + friend class TimerThread; + friend class nsTimerEvent; + friend struct TimerAdditionComparator; + + NS_DECL_THREADSAFE_ISUPPORTS + NS_FORWARD_SAFE_NSITIMER(mImpl); + + virtual size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const override; + +private: + // nsTimerImpl holds a strong ref to us. When our refcount goes to 1, we will + // null this to break the cycle. + RefPtr mImpl; +}; + +#endif /* nsTimerImpl_h___ */ diff --git a/xpcom/typelib/moz.build b/xpcom/typelib/moz.build new file mode 100644 index 000000000..3d82a998d --- /dev/null +++ b/xpcom/typelib/moz.build @@ -0,0 +1,8 @@ +# -*- 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/. + +DIRS += ['xpt'] + diff --git a/xpcom/typelib/xpt/moz.build b/xpcom/typelib/xpt/moz.build new file mode 100644 index 000000000..80b9160b0 --- /dev/null +++ b/xpcom/typelib/xpt/moz.build @@ -0,0 +1,40 @@ +# -*- 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/. + +Library('xpt') + +DIRS += ['tools'] + +UNIFIED_SOURCES += [ + 'xpt_arena.cpp', + 'xpt_struct.cpp', + 'xpt_xdr.cpp', +] + +EXPORTS += [ + 'xpt_arena.h', + 'xpt_struct.h', + 'xpt_xdr.h', +] + +FINAL_LIBRARY = 'xul' + +LOCAL_INCLUDES += [ + '!/xpcom/base', + '/xpcom/base', +] + +if CONFIG['_MSC_VER']: + CFLAGS += ['-Zl'] + +# Code with FINAL_LIBRARY = 'xul' shouldn't do this, but the code +# here doesn't use malloc functions anyways, while not setting +# MOZ_NO_MOZALLOC makes the code include mozalloc.h, which includes +# inline operator new definitions that MSVC linker doesn't strip +# when linking the xpt tests. +DEFINES['MOZ_NO_MOZALLOC'] = True + +DIST_INSTALL = True diff --git a/xpcom/typelib/xpt/tools/moz.build b/xpcom/typelib/xpt/tools/moz.build new file mode 100644 index 000000000..bd4a2836b --- /dev/null +++ b/xpcom/typelib/xpt/tools/moz.build @@ -0,0 +1,13 @@ +# -*- 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/. + +PYTHON_UNIT_TESTS += [ + 'runtests.py', +] + +SDK_FILES.bin += [ + 'xpt.py', +] diff --git a/xpcom/typelib/xpt/tools/runtests.py b/xpcom/typelib/xpt/tools/runtests.py new file mode 100644 index 000000000..a86e6625d --- /dev/null +++ b/xpcom/typelib/xpt/tools/runtests.py @@ -0,0 +1,770 @@ +#!/usr/bin/env python +# Copyright 2010,2011 Mozilla Foundation. 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. +# +# THIS SOFTWARE IS PROVIDED BY THE MOZILLA FOUNDATION ``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 MOZILLA FOUNDATION 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. +# +# The views and conclusions contained in the software and documentation +# are those of the authors and should not be interpreted as representing +# official policies, either expressed or implied, of the Mozilla +# Foundation. + +import difflib +import os +from StringIO import StringIO +import subprocess +import tempfile +import mozunit +import unittest +import xpt + + +def get_output(bin, file): + p = subprocess.Popen([bin, file], stdout=subprocess.PIPE) + stdout, _ = p.communicate() + return stdout + +if "MOZILLA_OBJDIR" in os.environ: + class CheckXPTDump(unittest.TestCase): + def test_xpt_dump_diffs(self): + MOZILLA_OBJDIR = os.environ["MOZILLA_OBJDIR"] + xptdump = os.path.abspath(os.path.join(MOZILLA_OBJDIR, + "dist", "bin", "xpt_dump")) + components = os.path.abspath(os.path.join(MOZILLA_OBJDIR, + "dist", "bin", "components")) + for f in os.listdir(components): + if not f.endswith(".xpt"): + continue + fullpath = os.path.join(components, f) + # read a Typelib and dump it to a string + t = xpt.Typelib.read(fullpath) + self.assert_(t is not None) + outf = StringIO() + t.dump(outf) + out = outf.getvalue() + # now run xpt_dump on it + out2 = get_output(xptdump, fullpath) + if out != out2: + print "diff %s" % f + for line in difflib.unified_diff(out2.split("\n"), out.split("\n"), lineterm=""): + print line + self.assert_(out == out2, "xpt_dump output should be identical for %s" % f) + + +class TestIIDString(unittest.TestCase): + def test_iid_str_roundtrip(self): + iid_str = "11223344-5566-7788-9900-aabbccddeeff" + iid = xpt.Typelib.string_to_iid(iid_str) + self.assertEqual(iid_str, xpt.Typelib.iid_to_string(iid)) + + def test_iid_roundtrip(self): + iid = "\x11\x22\x33\x44\x55\x66\x77\x88\x99\x00\xaa\xbb\xcc\xdd\xee\xff" + iid_str = xpt.Typelib.iid_to_string(iid) + self.assertEqual(iid, xpt.Typelib.string_to_iid(iid_str)) + + +class TypelibCompareMixin: + def assertEqualTypelibs(self, t1, t2): + self.assert_(t1 is not None, "Should not be None") + self.assert_(t2 is not None, "Should not be None") + self.assertEqual(t1.version, t2.version, "Versions should be equal") + self.assertEqual(len(t1.interfaces), len(t2.interfaces), + "Number of interfaces should be equal") + for i, j in zip(t1.interfaces, t2.interfaces): + self.assertEqualInterfaces(i, j) + + def assertEqualInterfaces(self, i1, i2): + self.assert_(i1 is not None, "Should not be None") + self.assert_(i2 is not None, "Should not be None") + self.assertEqual(i1.name, i2.name, "Names should be equal") + self.assertEqual(i1.iid, i2.iid, "IIDs should be equal") + self.assertEqual(i1.namespace, i2.namespace, + "Namespaces should be equal") + self.assertEqual(i1.resolved, i2.resolved, + "Resolved status should be equal") + if i1.resolved: + if i1.parent or i2.parent: + # Can't test exact equality, probably different objects + self.assertEqualInterfaces(i1.parent, i2.parent) + self.assertEqual(len(i1.methods), len(i2.methods)) + for m, n in zip(i1.methods, i2.methods): + self.assertEqualMethods(m, n) + self.assertEqual(len(i1.constants), len(i2.constants)) + for c, d in zip(i1.constants, i2.constants): + self.assertEqualConstants(c, d) + self.assertEqual(i1.scriptable, i2.scriptable, + "Scriptable status should be equal") + self.assertEqual(i1.function, i2.function, + "Function status should be equal") + + def assertEqualMethods(self, m1, m2): + self.assert_(m1 is not None, "Should not be None") + self.assert_(m2 is not None, "Should not be None") + self.assertEqual(m1.name, m2.name, "Names should be equal") + self.assertEqual(m1.getter, m2.getter, "Getter flag should be equal") + self.assertEqual(m1.setter, m2.setter, "Setter flag should be equal") + self.assertEqual(m1.notxpcom, m2.notxpcom, + "notxpcom flag should be equal") + self.assertEqual(m1.constructor, m2.constructor, + "constructor flag should be equal") + self.assertEqual(m1.hidden, m2.hidden, "hidden flag should be equal") + self.assertEqual(m1.optargc, m2.optargc, "optargc flag should be equal") + self.assertEqual(m1.implicit_jscontext, m2.implicit_jscontext, + "implicit_jscontext flag should be equal") + for p1, p2 in zip(m1.params, m2.params): + self.assertEqualParams(p1, p2) + self.assertEqualParams(m1.result, m2.result) + + def assertEqualConstants(self, c1, c2): + self.assert_(c1 is not None, "Should not be None") + self.assert_(c2 is not None, "Should not be None") + self.assertEqual(c1.name, c2.name) + self.assertEqual(c1.value, c2.value) + self.assertEqualTypes(c1.type, c2.type) + + def assertEqualParams(self, p1, p2): + self.assert_(p1 is not None, "Should not be None") + self.assert_(p2 is not None, "Should not be None") + self.assertEqualTypes(p1.type, p2.type) + self.assertEqual(p1.in_, p2.in_) + self.assertEqual(p1.out, p2.out) + self.assertEqual(p1.retval, p2.retval) + self.assertEqual(p1.shared, p2.shared) + self.assertEqual(p1.dipper, p2.dipper) + self.assertEqual(p1.optional, p2.optional) + + def assertEqualTypes(self, t1, t2): + self.assert_(t1 is not None, "Should not be None") + self.assert_(t2 is not None, "Should not be None") + self.assertEqual(type(t1), type(t2), "type types should be equal") + self.assertEqual(t1.pointer, t2.pointer, + "pointer flag should be equal for %s and %s" % (t1, t2)) + self.assertEqual(t1.reference, t2.reference) + if isinstance(t1, xpt.SimpleType): + self.assertEqual(t1.tag, t2.tag) + elif isinstance(t1, xpt.InterfaceType): + self.assertEqualInterfaces(t1.iface, t2.iface) + elif isinstance(t1, xpt.InterfaceIsType): + self.assertEqual(t1.param_index, t2.param_index) + elif isinstance(t1, xpt.ArrayType): + self.assertEqualTypes(t1.element_type, t2.element_type) + self.assertEqual(t1.size_is_arg_num, t2.size_is_arg_num) + self.assertEqual(t1.length_is_arg_num, t2.length_is_arg_num) + elif isinstance(t1, xpt.StringWithSizeType) or isinstance(t1, xpt.WideStringWithSizeType): + self.assertEqual(t1.size_is_arg_num, t2.size_is_arg_num) + self.assertEqual(t1.length_is_arg_num, t2.length_is_arg_num) + + +class TestTypelibReadWrite(unittest.TestCase, TypelibCompareMixin): + def test_read_file(self): + """ + Test that a Typelib can be read/written from/to a file. + """ + t = xpt.Typelib() + # add an unresolved interface + t.interfaces.append(xpt.Interface("IFoo")) + fd, f = tempfile.mkstemp() + os.close(fd) + t.write(f) + t2 = xpt.Typelib.read(f) + os.remove(f) + self.assert_(t2 is not None) + self.assertEqualTypelibs(t, t2) + + +# TODO: test flags in various combinations +class TestTypelibRoundtrip(unittest.TestCase, TypelibCompareMixin): + def checkRoundtrip(self, t): + s = StringIO() + t.write(s) + s.seek(0) + t2 = xpt.Typelib.read(s) + self.assert_(t2 is not None) + self.assertEqualTypelibs(t, t2) + + def test_simple(self): + t = xpt.Typelib() + # add an unresolved interface + t.interfaces.append(xpt.Interface("IFoo")) + self.checkRoundtrip(t) + + t = xpt.Typelib() + # add an unresolved interface with an IID + t.interfaces.append(xpt.Interface("IBar", "11223344-5566-7788-9900-aabbccddeeff")) + self.checkRoundtrip(t) + + def test_parent(self): + """ + Test that an interface's parent property is correctly serialized + and deserialized. + + """ + t = xpt.Typelib() + pi = xpt.Interface("IParent") + t.interfaces.append(pi) + t.interfaces.append(xpt.Interface("IChild", iid="11223344-5566-7788-9900-aabbccddeeff", + parent=pi, resolved=True)) + self.checkRoundtrip(t) + + def test_ifaceFlags(self): + """ + Test that an interface's flags are correctly serialized + and deserialized. + + """ + t = xpt.Typelib() + t.interfaces.append(xpt.Interface("IFlags", iid="11223344-5566-7788-9900-aabbccddeeff", + resolved=True, + scriptable=True, + function=True)) + self.checkRoundtrip(t) + + def test_constants(self): + c = xpt.Constant("X", xpt.SimpleType(xpt.Type.Tags.uint32), + 0xF000F000) + i = xpt.Interface("IFoo", iid="11223344-5566-7788-9900-aabbccddeeff", + constants=[c]) + t = xpt.Typelib(interfaces=[i]) + self.checkRoundtrip(t) + # tack on some more constants + i.constants.append(xpt.Constant("Y", + xpt.SimpleType(xpt.Type.Tags.int16), + -30000)) + i.constants.append(xpt.Constant("Z", + xpt.SimpleType(xpt.Type.Tags.uint16), + 0xB0B0)) + i.constants.append(xpt.Constant("A", + xpt.SimpleType(xpt.Type.Tags.int32), + -1000000)) + self.checkRoundtrip(t) + + def test_methods(self): + p = xpt.Param(xpt.SimpleType(xpt.Type.Tags.void)) + m = xpt.Method("Bar", p) + i = xpt.Interface("IFoo", iid="11223344-5566-7788-9900-aabbccddeeff", + methods=[m]) + t = xpt.Typelib(interfaces=[i]) + self.checkRoundtrip(t) + # add some more methods + i.methods.append(xpt.Method("One", xpt.Param(xpt.SimpleType(xpt.Type.Tags.int32)), + params=[ + xpt.Param(xpt.SimpleType(xpt.Type.Tags.int64)), + xpt.Param(xpt.SimpleType(xpt.Type.Tags.float, pointer=True)) + ])) + self.checkRoundtrip(t) + # test some other types (should really be more thorough) + i.methods.append(xpt.Method("Two", xpt.Param(xpt.SimpleType(xpt.Type.Tags.int32)), + params=[ + xpt.Param(xpt.SimpleType(xpt.Type.Tags.UTF8String, pointer=True)), + xpt.Param(xpt.SimpleType(xpt.Type.Tags.wchar_t_ptr, pointer=True)) + ])) + self.checkRoundtrip(t) + # add a method with an InterfaceType argument + bar = xpt.Interface("IBar") + t.interfaces.append(bar) + i.methods.append(xpt.Method("IFaceMethod", xpt.Param(xpt.SimpleType(xpt.Type.Tags.int32)), + params=[ + xpt.Param(xpt.InterfaceType(bar)) + ])) + self.checkRoundtrip(t) + + # add a method with an InterfaceIsType argument + i.methods.append(xpt.Method("IFaceIsMethod", xpt.Param(xpt.SimpleType(xpt.Type.Tags.void)), + params=[ + xpt.Param(xpt.InterfaceIsType(1)), + xpt.Param(xpt.SimpleType(xpt.Type.Tags.nsIID)) + ])) + self.checkRoundtrip(t) + + # add a method with an ArrayType argument + i.methods.append(xpt.Method("ArrayMethod", xpt.Param(xpt.SimpleType(xpt.Type.Tags.void)), + params=[ + xpt.Param(xpt.ArrayType( + xpt.SimpleType(xpt.Type.Tags.int32), + 1, 2)), + xpt.Param(xpt.SimpleType(xpt.Type.Tags.int32)), + xpt.Param(xpt.SimpleType(xpt.Type.Tags.int32)), + ])) + self.checkRoundtrip(t) + + # add a method with a StringWithSize and WideStringWithSize arguments + i.methods.append(xpt.Method("StringWithSizeMethod", xpt.Param(xpt.SimpleType(xpt.Type.Tags.void)), + params=[ + xpt.Param(xpt.StringWithSizeType( + 1, 2)), + xpt.Param(xpt.SimpleType(xpt.Type.Tags.int32)), + xpt.Param(xpt.SimpleType(xpt.Type.Tags.int32)), + xpt.Param(xpt.WideStringWithSizeType( + 4, 5)), + xpt.Param(xpt.SimpleType(xpt.Type.Tags.int32)), + xpt.Param(xpt.SimpleType(xpt.Type.Tags.int32)), + ])) + self.checkRoundtrip(t) + + +class TestInterfaceCmp(unittest.TestCase): + def test_unresolvedName(self): + """ + Test comparison function on xpt.Interface by name. + + """ + i1 = xpt.Interface("ABC") + i2 = xpt.Interface("DEF") + self.assert_(i1 < i2) + self.assert_(i1 != i2) + + def test_unresolvedEqual(self): + """ + Test comparison function on xpt.Interface with equal names and IIDs. + + """ + i1 = xpt.Interface("ABC") + i2 = xpt.Interface("ABC") + self.assert_(i1 == i2) + + def test_unresolvedIID(self): + """ + Test comparison function on xpt.Interface with different IIDs. + + """ + # IIDs sort before names + i1 = xpt.Interface("ABC", iid="22334411-5566-7788-9900-aabbccddeeff") + i2 = xpt.Interface("DEF", iid="11223344-5566-7788-9900-aabbccddeeff") + self.assert_(i2 < i1) + self.assert_(i2 != i1) + + def test_unresolvedResolved(self): + """ + Test comparison function on xpt.Interface with interfaces with + identical names and IIDs but different resolved status. + + """ + i1 = xpt.Interface("ABC", iid="11223344-5566-7788-9900-aabbccddeeff") + p = xpt.Param(xpt.SimpleType(xpt.Type.Tags.void)) + m = xpt.Method("Bar", p) + i2 = xpt.Interface("ABC", iid="11223344-5566-7788-9900-aabbccddeeff", + methods=[m]) + self.assert_(i2 < i1) + self.assert_(i2 != i1) + + def test_resolvedIdentical(self): + """ + Test comparison function on xpt.Interface with interfaces with + identical names and IIDs, both of which are resolved. + + """ + p = xpt.Param(xpt.SimpleType(xpt.Type.Tags.void)) + m = xpt.Method("Bar", p) + i1 = xpt.Interface("ABC", iid="11223344-5566-7788-9900-aabbccddeeff", + methods=[m]) + i2 = xpt.Interface("ABC", iid="11223344-5566-7788-9900-aabbccddeeff", + methods=[m]) + self.assert_(i2 == i1) + + +class TestXPTLink(unittest.TestCase): + def test_mergeDifferent(self): + """ + Test that merging two typelibs with completely different interfaces + produces the correctly merged typelib. + + """ + t1 = xpt.Typelib() + # add an unresolved interface + t1.interfaces.append(xpt.Interface("IFoo", scriptable=True)) + t2 = xpt.Typelib() + # add an unresolved interface + t2.interfaces.append(xpt.Interface("IBar", scriptable=True)) + t3 = xpt.xpt_link([t1, t2]) + + self.assertEqual(2, len(t3.interfaces)) + # Interfaces should wind up sorted + self.assertEqual("IBar", t3.interfaces[0].name) + self.assertEqual("IFoo", t3.interfaces[1].name) + + # Add some IID values + t1 = xpt.Typelib() + # add an unresolved interface + t1.interfaces.append(xpt.Interface("IFoo", iid="11223344-5566-7788-9900-aabbccddeeff", scriptable=True)) + t2 = xpt.Typelib() + # add an unresolved interface + t2.interfaces.append(xpt.Interface("IBar", iid="44332211-6655-8877-0099-aabbccddeeff", scriptable=True)) + t3 = xpt.xpt_link([t1, t2]) + + self.assertEqual(2, len(t3.interfaces)) + # Interfaces should wind up sorted + self.assertEqual("IFoo", t3.interfaces[0].name) + self.assertEqual("IBar", t3.interfaces[1].name) + + def test_mergeConflict(self): + """ + Test that merging two typelibs with conflicting interface definitions + raises an error. + + """ + # Same names, different IIDs + t1 = xpt.Typelib() + # add an unresolved interface + t1.interfaces.append(xpt.Interface("IFoo", iid="11223344-5566-7788-9900-aabbccddeeff")) + t2 = xpt.Typelib() + # add an unresolved interface, same name different IID + t2.interfaces.append(xpt.Interface("IFoo", iid="44332211-6655-8877-0099-aabbccddeeff")) + self.assertRaises(xpt.DataError, xpt.xpt_link, [t1, t2]) + + # Same IIDs, different names + t1 = xpt.Typelib() + # add an unresolved interface + t1.interfaces.append(xpt.Interface("IFoo", iid="11223344-5566-7788-9900-aabbccddeeff")) + t2 = xpt.Typelib() + # add an unresolved interface, same IID different name + t2.interfaces.append(xpt.Interface("IBar", iid="11223344-5566-7788-9900-aabbccddeeff")) + self.assertRaises(xpt.DataError, xpt.xpt_link, [t1, t2]) + + def test_mergeUnresolvedIID(self): + """ + Test that merging a typelib with an unresolved definition of + an interface that's also unresolved in this typelib, but one + has a valid IID copies the IID value to the resulting typelib. + + """ + # Unresolved in both, but t1 has an IID value + t1 = xpt.Typelib() + # add an unresolved interface with a valid IID + t1.interfaces.append(xpt.Interface("IFoo", iid="11223344-5566-7788-9900-aabbccddeeff", scriptable=True)) + t2 = xpt.Typelib() + # add an unresolved interface, no IID + t2.interfaces.append(xpt.Interface("IFoo")) + t3 = xpt.xpt_link([t1, t2]) + + self.assertEqual(1, len(t3.interfaces)) + self.assertEqual("IFoo", t3.interfaces[0].name) + self.assertEqual("11223344-5566-7788-9900-aabbccddeeff", t3.interfaces[0].iid) + # Unresolved in both, but t2 has an IID value + t1 = xpt.Typelib() + # add an unresolved interface, no IID + t1.interfaces.append(xpt.Interface("IFoo")) + t2 = xpt.Typelib() + # add an unresolved interface with a valid IID + t2.interfaces.append(xpt.Interface("IFoo", iid="11223344-5566-7788-9900-aabbccddeeff", scriptable=True)) + t3 = xpt.xpt_link([t1, t2]) + + self.assertEqual(1, len(t3.interfaces)) + self.assertEqual("IFoo", t3.interfaces[0].name) + self.assertEqual("11223344-5566-7788-9900-aabbccddeeff", t3.interfaces[0].iid) + + def test_mergeResolvedUnresolved(self): + """ + Test that merging two typelibs, one of which contains an unresolved + reference to an interface, and the other of which contains a + resolved reference to the same interface results in keeping the + resolved reference. + + """ + # t1 has an unresolved interface, t2 has a resolved version + t1 = xpt.Typelib() + # add an unresolved interface + t1.interfaces.append(xpt.Interface("IFoo")) + t2 = xpt.Typelib() + # add a resolved interface + p = xpt.Param(xpt.SimpleType(xpt.Type.Tags.void)) + m = xpt.Method("Bar", p) + t2.interfaces.append(xpt.Interface("IFoo", iid="11223344-5566-7788-9900-aabbccddeeff", + methods=[m], scriptable=True)) + t3 = xpt.xpt_link([t1, t2]) + + self.assertEqual(1, len(t3.interfaces)) + self.assertEqual("IFoo", t3.interfaces[0].name) + self.assertEqual("11223344-5566-7788-9900-aabbccddeeff", t3.interfaces[0].iid) + self.assert_(t3.interfaces[0].resolved) + self.assertEqual(1, len(t3.interfaces[0].methods)) + self.assertEqual("Bar", t3.interfaces[0].methods[0].name) + + # t1 has a resolved interface, t2 has an unresolved version + t1 = xpt.Typelib() + # add a resolved interface + p = xpt.Param(xpt.SimpleType(xpt.Type.Tags.void)) + m = xpt.Method("Bar", p) + t1.interfaces.append(xpt.Interface("IFoo", iid="11223344-5566-7788-9900-aabbccddeeff", + methods=[m], scriptable=True)) + t2 = xpt.Typelib() + # add an unresolved interface + t2.interfaces.append(xpt.Interface("IFoo")) + t3 = xpt.xpt_link([t1, t2]) + + self.assertEqual(1, len(t3.interfaces)) + self.assertEqual("IFoo", t3.interfaces[0].name) + self.assertEqual("11223344-5566-7788-9900-aabbccddeeff", t3.interfaces[0].iid) + self.assert_(t3.interfaces[0].resolved) + self.assertEqual(1, len(t3.interfaces[0].methods)) + self.assertEqual("Bar", t3.interfaces[0].methods[0].name) + + def test_mergeReplaceParents(self): + """ + Test that merging an interface results in other interfaces' parent + member being updated properly. + + """ + # t1 has an unresolved interface, t2 has a resolved version, + # but t1 also has another interface whose parent is the unresolved + # interface. + t1 = xpt.Typelib() + # add an unresolved interface + pi = xpt.Interface("IFoo") + t1.interfaces.append(pi) + # add a child of the unresolved interface + t1.interfaces.append(xpt.Interface("IChild", iid="11111111-1111-1111-1111-111111111111", + resolved=True, parent=pi, scriptable=True)) + t2 = xpt.Typelib() + # add a resolved interface + p = xpt.Param(xpt.SimpleType(xpt.Type.Tags.void)) + m = xpt.Method("Bar", p) + t2.interfaces.append(xpt.Interface("IFoo", iid="11223344-5566-7788-9900-aabbccddeeff", + methods=[m], scriptable=True)) + t3 = xpt.xpt_link([t1, t2]) + + self.assertEqual(2, len(t3.interfaces)) + self.assertEqual("IChild", t3.interfaces[0].name) + self.assertEqual("11111111-1111-1111-1111-111111111111", t3.interfaces[0].iid) + self.assertEqual("IFoo", t3.interfaces[1].name) + self.assertEqual("11223344-5566-7788-9900-aabbccddeeff", t3.interfaces[1].iid) + self.assert_(t3.interfaces[0].resolved) + # Ensure that IChild's parent has been updated + self.assertEqual(t3.interfaces[1], t3.interfaces[0].parent) + self.assert_(t3.interfaces[0].parent.resolved) + + # t1 has a resolved interface, t2 has an unresolved version, + # but t2 also has another interface whose parent is the unresolved + # interface. + t1 = xpt.Typelib() + # add a resolved interface + p = xpt.Param(xpt.SimpleType(xpt.Type.Tags.void)) + m = xpt.Method("Bar", p) + t1.interfaces.append(xpt.Interface("IFoo", iid="11223344-5566-7788-9900-aabbccddeeff", + methods=[m], scriptable=True)) + t2 = xpt.Typelib() + # add an unresolved interface + pi = xpt.Interface("IFoo") + t2.interfaces.append(pi) + # add a child of the unresolved interface + t2.interfaces.append(xpt.Interface("IChild", iid="11111111-1111-1111-1111-111111111111", + resolved=True, parent=pi, scriptable=True)) + t3 = xpt.xpt_link([t1, t2]) + + self.assertEqual(2, len(t3.interfaces)) + self.assertEqual("IChild", t3.interfaces[0].name) + self.assertEqual("11111111-1111-1111-1111-111111111111", t3.interfaces[0].iid) + self.assertEqual("IFoo", t3.interfaces[1].name) + self.assertEqual("11223344-5566-7788-9900-aabbccddeeff", t3.interfaces[1].iid) + self.assert_(t3.interfaces[0].resolved) + # Ensure that IChild's parent has been updated + self.assertEqual(t3.interfaces[1], t3.interfaces[0].parent) + self.assert_(t3.interfaces[0].parent.resolved) + + def test_mergeReplaceRetval(self): + """ + Test that merging an interface correctly updates InterfaceType + return values on methods of other interfaces. + + """ + # t1 has an unresolved interface and an interface that uses the + # unresolved interface as a return value from a method. t2 + # has a resolved version of the unresolved interface. + t1 = xpt.Typelib() + # add an unresolved interface + i = xpt.Interface("IFoo") + t1.interfaces.append(i) + # add an interface that uses the unresolved interface + # as a return value in a method. + p = xpt.Param(xpt.InterfaceType(i)) + m = xpt.Method("ReturnIface", p) + t1.interfaces.append(xpt.Interface("IRetval", iid="11111111-1111-1111-1111-111111111111", + methods=[m], scriptable=True)) + t2 = xpt.Typelib() + # add a resolved interface + p = xpt.Param(xpt.SimpleType(xpt.Type.Tags.void)) + m = xpt.Method("Bar", p) + t2.interfaces.append(xpt.Interface("IFoo", iid="11223344-5566-7788-9900-aabbccddeeff", + methods=[m], scriptable=True)) + t3 = xpt.xpt_link([t1, t2]) + + self.assertEqual(2, len(t3.interfaces)) + self.assertEqual("IRetval", t3.interfaces[0].name) + self.assertEqual("11111111-1111-1111-1111-111111111111", t3.interfaces[0].iid) + self.assertEqual("IFoo", t3.interfaces[1].name) + self.assertEqual("11223344-5566-7788-9900-aabbccddeeff", t3.interfaces[1].iid) + self.assert_(t3.interfaces[1].resolved) + # Ensure that IRetval's method's return value type has been updated. + self.assertEqual(1, len(t3.interfaces[0].methods)) + self.assert_(t3.interfaces[0].methods[0].result.type.iface.resolved) + self.assertEqual(t3.interfaces[1], + t3.interfaces[0].methods[0].result.type.iface) + + # t1 has a resolved interface. t2 has an unresolved version and + # an interface that uses the unresolved interface as a return value + # from a method. + t1 = xpt.Typelib() + # add a resolved interface + p = xpt.Param(xpt.SimpleType(xpt.Type.Tags.void)) + m = xpt.Method("Bar", p) + t1.interfaces.append(xpt.Interface("IFoo", iid="11223344-5566-7788-9900-aabbccddeeff", + methods=[m], scriptable=True)) + t2 = xpt.Typelib() + # add an unresolved interface + i = xpt.Interface("IFoo") + t2.interfaces.append(i) + # add an interface that uses the unresolved interface + # as a return value in a method. + p = xpt.Param(xpt.InterfaceType(i)) + m = xpt.Method("ReturnIface", p) + t2.interfaces.append(xpt.Interface("IRetval", iid="11111111-1111-1111-1111-111111111111", + methods=[m], scriptable=True)) + t3 = xpt.xpt_link([t1, t2]) + + self.assertEqual(2, len(t3.interfaces)) + self.assertEqual("IRetval", t3.interfaces[0].name) + self.assertEqual("11111111-1111-1111-1111-111111111111", t3.interfaces[0].iid) + self.assertEqual("IFoo", t3.interfaces[1].name) + self.assertEqual("11223344-5566-7788-9900-aabbccddeeff", t3.interfaces[1].iid) + self.assert_(t3.interfaces[1].resolved) + # Ensure that IRetval's method's return value type has been updated. + self.assertEqual(1, len(t3.interfaces[0].methods)) + self.assert_(t3.interfaces[0].methods[0].result.type.iface.resolved) + self.assertEqual(t3.interfaces[1], + t3.interfaces[0].methods[0].result.type.iface) + + def test_mergeReplaceParams(self): + """ + Test that merging an interface correctly updates InterfaceType + params on methods of other interfaces. + + """ + # t1 has an unresolved interface and an interface that uses the + # unresolved interface as a param value in a method. t2 + # has a resolved version of the unresolved interface. + t1 = xpt.Typelib() + # add an unresolved interface + i = xpt.Interface("IFoo") + t1.interfaces.append(i) + # add an interface that uses the unresolved interface + # as a param value in a method. + vp = xpt.Param(xpt.SimpleType(xpt.Type.Tags.void)) + p = xpt.Param(xpt.InterfaceType(i)) + m = xpt.Method("IfaceParam", vp, params=[p]) + t1.interfaces.append(xpt.Interface("IParam", iid="11111111-1111-1111-1111-111111111111", + methods=[m], scriptable=True)) + t2 = xpt.Typelib() + # add a resolved interface + m = xpt.Method("Bar", vp) + t2.interfaces.append(xpt.Interface("IFoo", iid="11223344-5566-7788-9900-aabbccddeeff", + methods=[m], scriptable=True)) + t3 = xpt.xpt_link([t1, t2]) + + self.assertEqual(2, len(t3.interfaces)) + self.assertEqual("IParam", t3.interfaces[0].name) + self.assertEqual("11111111-1111-1111-1111-111111111111", t3.interfaces[0].iid) + self.assertEqual("IFoo", t3.interfaces[1].name) + self.assertEqual("11223344-5566-7788-9900-aabbccddeeff", t3.interfaces[1].iid) + self.assert_(t3.interfaces[1].resolved) + # Ensure that IRetval's method's param type has been updated. + self.assertEqual(1, len(t3.interfaces[0].methods)) + self.assert_(t3.interfaces[0].methods[0].params[0].type.iface.resolved) + self.assertEqual(t3.interfaces[1], + t3.interfaces[0].methods[0].params[0].type.iface) + + # t1 has a resolved interface. t2 has an unresolved version + # and an interface that uses the unresolved interface as a + # param value in a method. + t1 = xpt.Typelib() + # add a resolved interface + m = xpt.Method("Bar", vp) + t1.interfaces.append(xpt.Interface("IFoo", iid="11223344-5566-7788-9900-aabbccddeeff", + methods=[m], scriptable=True)) + t2 = xpt.Typelib() + # add an unresolved interface + i = xpt.Interface("IFoo") + t2.interfaces.append(i) + # add an interface that uses the unresolved interface + # as a param value in a method. + vp = xpt.Param(xpt.SimpleType(xpt.Type.Tags.void)) + p = xpt.Param(xpt.InterfaceType(i)) + m = xpt.Method("IfaceParam", vp, params=[p]) + t2.interfaces.append(xpt.Interface("IParam", iid="11111111-1111-1111-1111-111111111111", + methods=[m], scriptable=True)) + t3 = xpt.xpt_link([t1, t2]) + + self.assertEqual(2, len(t3.interfaces)) + self.assertEqual("IParam", t3.interfaces[0].name) + self.assertEqual("11111111-1111-1111-1111-111111111111", t3.interfaces[0].iid) + self.assertEqual("IFoo", t3.interfaces[1].name) + self.assertEqual("11223344-5566-7788-9900-aabbccddeeff", t3.interfaces[1].iid) + self.assert_(t3.interfaces[1].resolved) + # Ensure that IRetval's method's param type has been updated. + self.assertEqual(1, len(t3.interfaces[0].methods)) + self.assert_(t3.interfaces[0].methods[0].params[0].type.iface.resolved) + self.assertEqual(t3.interfaces[1], + t3.interfaces[0].methods[0].params[0].type.iface) + + def test_mergeReplaceArrayTypeParams(self): + """ + Test that merging an interface correctly updates ArrayType + params whose element_type is an InterfaceType on methods + of other interfaces. + + """ + # t1 has an unresolved interface and an interface that uses the + # unresolved interface as a type in an ArrayType in a parameter + # of a method. t2 has a resolved version of the unresolved interface. + t1 = xpt.Typelib() + # add an unresolved interface + i = xpt.Interface("IFoo") + t1.interfaces.append(i) + # add an interface that uses the unresolved interface + # as a type in an ArrayType in a param value in a method. + vp = xpt.Param(xpt.SimpleType(xpt.Type.Tags.void)) + intp = xpt.Param(xpt.SimpleType(xpt.Type.Tags.int32)) + p = xpt.Param(xpt.ArrayType(xpt.InterfaceType(i), 1, 2)) + m = xpt.Method("ArrayIfaceParam", vp, params=[p, intp, intp]) + t1.interfaces.append(xpt.Interface("IParam", iid="11111111-1111-1111-1111-111111111111", + methods=[m], scriptable=True)) + t2 = xpt.Typelib() + # add a resolved interface + m = xpt.Method("Bar", vp) + t2.interfaces.append(xpt.Interface("IFoo", iid="11223344-5566-7788-9900-aabbccddeeff", + methods=[m], scriptable=True)) + t3 = xpt.xpt_link([t1, t2]) + + self.assertEqual(2, len(t3.interfaces)) + self.assertEqual("IParam", t3.interfaces[0].name) + self.assertEqual("11111111-1111-1111-1111-111111111111", t3.interfaces[0].iid) + self.assertEqual("IFoo", t3.interfaces[1].name) + self.assertEqual("11223344-5566-7788-9900-aabbccddeeff", t3.interfaces[1].iid) + self.assert_(t3.interfaces[1].resolved) + # Ensure that IRetval's method's param type has been updated. + self.assertEqual(1, len(t3.interfaces[0].methods)) + self.assert_(t3.interfaces[0].methods[0].params[0].type.element_type.iface.resolved) + self.assertEqual(t3.interfaces[1], + t3.interfaces[0].methods[0].params[0].type.element_type.iface) + +if __name__ == '__main__': + mozunit.main() diff --git a/xpcom/typelib/xpt/tools/xpt.py b/xpcom/typelib/xpt/tools/xpt.py new file mode 100755 index 000000000..67d40f7b2 --- /dev/null +++ b/xpcom/typelib/xpt/tools/xpt.py @@ -0,0 +1,1540 @@ +#!/usr/bin/env python +# Copyright 2010,2011 Mozilla Foundation. 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. +# +# THIS SOFTWARE IS PROVIDED BY THE MOZILLA FOUNDATION ``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 MOZILLA FOUNDATION 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. +# +# The views and conclusions contained in the software and documentation +# are those of the authors and should not be interpreted as representing +# official policies, either expressed or implied, of the Mozilla +# Foundation. + +""" +A module for working with XPCOM Type Libraries. + +The XPCOM Type Library File Format is described at: +http://www.mozilla.org/scriptable/typelib_file.html . It is used +to provide type information for calling methods on XPCOM objects +from scripting languages such as JavaScript. + +This module provides a set of classes representing the parts of +a typelib in a high-level manner, as well as methods for reading +and writing them from files. + +The usable public interfaces are currently: +Typelib.read(input_file) - read a typelib from a file on disk or file-like + object, return a Typelib object. + +xpt_dump(filename) - read a typelib from a file on disk, dump + the contents to stdout in a human-readable + format. + +Typelib() - construct a new Typelib object +Interface() - construct a new Interface object +Method() - construct a new object representing a method + defined on an Interface +Constant() - construct a new object representing a constant + defined on an Interface +Param() - construct a new object representing a parameter + to a method +SimpleType() - construct a new object representing a simple + data type +InterfaceType() - construct a new object representing a type that + is an IDL-defined interface + +""" + +from __future__ import with_statement +import os +import sys +import struct +import operator + +# header magic +XPT_MAGIC = "XPCOM\nTypeLib\r\n\x1a" +TYPELIB_VERSION = (1, 2) + + +class FileFormatError(Exception): + pass + + +class DataError(Exception): + pass + + +# Magic for creating enums +def M_add_class_attribs(attribs): + def foo(name, bases, dict_): + for v, k in attribs: + dict_[k] = v + return type(name, bases, dict_) + return foo + + +def enum(*names): + class Foo(object): + __metaclass__ = M_add_class_attribs(enumerate(names)) + + def __setattr__(self, name, value): # this makes it read-only + raise NotImplementedError + return Foo() + + +# Descriptor types as described in the spec +class Type(object): + """ + Data type of a method parameter or return value. Do not instantiate + this class directly. Rather, use one of its subclasses. + + """ + _prefixdescriptor = struct.Struct(">B") + Tags = enum( + # The first 18 entries are SimpleTypeDescriptor + 'int8', + 'int16', + 'int32', + 'int64', + 'uint8', + 'uint16', + 'uint32', + 'uint64', + 'float', + 'double', + 'boolean', + 'char', + 'wchar_t', + 'void', + # the following four values are only valid as pointers + 'nsIID', + 'DOMString', + 'char_ptr', + 'wchar_t_ptr', + # InterfaceTypeDescriptor + 'Interface', + # InterfaceIsTypeDescriptor + 'InterfaceIs', + # ArrayTypeDescriptor + 'Array', + # StringWithSizeTypeDescriptor + 'StringWithSize', + # WideStringWithSizeTypeDescriptor + 'WideStringWithSize', + # XXX: These are also SimpleTypes (but not in the spec) + # http://hg.mozilla.org/mozilla-central/annotate/0e0e2516f04e/xpcom/typelib/xpt/tools/xpt_dump.c#l69 + 'UTF8String', + 'CString', + 'AString', + 'jsval', + ) + + def __init__(self, pointer=False, reference=False): + self.pointer = pointer + self.reference = reference + if reference and not pointer: + raise Exception("If reference is True pointer must be True too") + + def __cmp__(self, other): + return ( + # First make sure we have two Types of the same type (no pun intended!) + cmp(type(self), type(other)) or + cmp(self.pointer, other.pointer) or + cmp(self.reference, other.reference) + ) + + @staticmethod + def decodeflags(byte): + """ + Given |byte|, an unsigned uint8 containing flag bits, + decode the flag bits as described in + http://www.mozilla.org/scriptable/typelib_file.html#TypeDescriptor + and return a dict of flagname: (True|False) suitable + for passing to Type.__init__ as **kwargs. + + """ + return {'pointer': bool(byte & 0x80), + 'reference': bool(byte & 0x20), + } + + def encodeflags(self): + """ + Encode the flag bits of this Type object. Returns a byte. + + """ + flags = 0 + if self.pointer: + flags |= 0x80 + if self.reference: + flags |= 0x20 + return flags + + @staticmethod + def read(typelib, map, data_pool, offset): + """ + Read a TypeDescriptor at |offset| from the mmaped file |map| with + data pool offset |data_pool|. Returns (Type, next offset), + where |next offset| is an offset suitable for reading the data + following this TypeDescriptor. + + """ + start = data_pool + offset - 1 + (data,) = Type._prefixdescriptor.unpack_from(map, start) + # first three bits are the flags + flags = data & 0xE0 + flags = Type.decodeflags(flags) + # last five bits is the tag + tag = data & 0x1F + offset += Type._prefixdescriptor.size + t = None + if tag <= Type.Tags.wchar_t_ptr or tag >= Type.Tags.UTF8String: + t = SimpleType.get(data, tag, flags) + elif tag == Type.Tags.Interface: + t, offset = InterfaceType.read(typelib, map, data_pool, offset, flags) + elif tag == Type.Tags.InterfaceIs: + t, offset = InterfaceIsType.read(typelib, map, data_pool, offset, flags) + elif tag == Type.Tags.Array: + t, offset = ArrayType.read(typelib, map, data_pool, offset, flags) + elif tag == Type.Tags.StringWithSize: + t, offset = StringWithSizeType.read(typelib, map, data_pool, offset, flags) + elif tag == Type.Tags.WideStringWithSize: + t, offset = WideStringWithSizeType.read(typelib, map, data_pool, offset, flags) + return t, offset + + def write(self, typelib, file): + """ + Write a TypeDescriptor to |file|, which is assumed + to be seeked to the proper position. For types other than + SimpleType, this is not sufficient for writing the TypeDescriptor, + and the subclass method must be called. + + """ + file.write(Type._prefixdescriptor.pack(self.encodeflags() | self.tag)) + + +class SimpleType(Type): + """ + A simple data type. (SimpleTypeDescriptor from the typelib specification.) + + """ + _cache = {} + + def __init__(self, tag, **kwargs): + Type.__init__(self, **kwargs) + self.tag = tag + + def __cmp__(self, other): + return ( + Type.__cmp__(self, other) or + cmp(self.tag, other.tag) + ) + + @staticmethod + def get(data, tag, flags): + """ + Get a SimpleType object representing |data| (a TypeDescriptorPrefix). + May return an already-created object. If no cached object is found, + construct one with |tag| and |flags|. + + """ + if data not in SimpleType._cache: + SimpleType._cache[data] = SimpleType(tag, **flags) + return SimpleType._cache[data] + + def __str__(self): + s = "unknown" + if self.tag == Type.Tags.char_ptr and self.pointer: + return "string" + if self.tag == Type.Tags.wchar_t_ptr and self.pointer: + return "wstring" + for t in dir(Type.Tags): + if self.tag == getattr(Type.Tags, t): + s = t + break + + if self.pointer: + if self.reference: + s += " &" + else: + s += " *" + return s + + +class InterfaceType(Type): + """ + A type representing a pointer to an IDL-defined interface. + (InterfaceTypeDescriptor from the typelib specification.) + + """ + _descriptor = struct.Struct(">H") + + def __init__(self, iface, pointer=True, **kwargs): + if not pointer: + raise DataError("InterfaceType is not valid with pointer=False") + Type.__init__(self, pointer=pointer, **kwargs) + self.iface = iface + self.tag = Type.Tags.Interface + + def __cmp__(self, other): + return ( + Type.__cmp__(self, other) or + # When comparing interface types, only look at the name. + cmp(self.iface.name, other.iface.name) or + cmp(self.tag, other.tag) + ) + + @staticmethod + def read(typelib, map, data_pool, offset, flags): + """ + Read an InterfaceTypeDescriptor at |offset| from the mmaped + file |map| with data pool offset |data_pool|. + Returns (InterfaceType, next offset), + where |next offset| is an offset suitable for reading the data + following this InterfaceTypeDescriptor. + + """ + if not flags['pointer']: + return None, offset + start = data_pool + offset - 1 + (iface_index,) = InterfaceType._descriptor.unpack_from(map, start) + offset += InterfaceType._descriptor.size + iface = None + # interface indices are 1-based + if iface_index > 0 and iface_index <= len(typelib.interfaces): + iface = typelib.interfaces[iface_index - 1] + return InterfaceType(iface, **flags), offset + + def write(self, typelib, file): + """ + Write an InterfaceTypeDescriptor to |file|, which is assumed + to be seeked to the proper position. + + """ + Type.write(self, typelib, file) + # write out the interface index (1-based) + file.write(InterfaceType._descriptor.pack(typelib.interfaces.index(self.iface) + 1)) + + def __str__(self): + if self.iface: + return self.iface.name + return "unknown interface" + + +class InterfaceIsType(Type): + """ + A type representing an interface described by one of the other + arguments to the method. (InterfaceIsTypeDescriptor from the + typelib specification.) + + """ + _descriptor = struct.Struct(">B") + _cache = {} + + def __init__(self, param_index, pointer=True, **kwargs): + if not pointer: + raise DataError("InterfaceIsType is not valid with pointer=False") + Type.__init__(self, pointer=pointer, **kwargs) + self.param_index = param_index + self.tag = Type.Tags.InterfaceIs + + def __cmp__(self, other): + return ( + Type.__cmp__(self, other) or + cmp(self.param_index, other.param_index) or + cmp(self.tag, other.tag) + ) + + @staticmethod + def read(typelib, map, data_pool, offset, flags): + """ + Read an InterfaceIsTypeDescriptor at |offset| from the mmaped + file |map| with data pool offset |data_pool|. + Returns (InterfaceIsType, next offset), + where |next offset| is an offset suitable for reading the data + following this InterfaceIsTypeDescriptor. + May return a cached value. + + """ + if not flags['pointer']: + return None, offset + start = data_pool + offset - 1 + (param_index,) = InterfaceIsType._descriptor.unpack_from(map, start) + offset += InterfaceIsType._descriptor.size + if param_index not in InterfaceIsType._cache: + InterfaceIsType._cache[param_index] = InterfaceIsType(param_index, **flags) + return InterfaceIsType._cache[param_index], offset + + def write(self, typelib, file): + """ + Write an InterfaceIsTypeDescriptor to |file|, which is assumed + to be seeked to the proper position. + + """ + Type.write(self, typelib, file) + file.write(InterfaceIsType._descriptor.pack(self.param_index)) + + def __str__(self): + return "InterfaceIs *" + + +class ArrayType(Type): + """ + A type representing an Array of elements of another type, whose + size and length are passed as separate parameters to a method. + (ArrayTypeDescriptor from the typelib specification.) + + """ + _descriptor = struct.Struct(">BB") + + def __init__(self, element_type, size_is_arg_num, length_is_arg_num, + pointer=True, **kwargs): + if not pointer: + raise DataError("ArrayType is not valid with pointer=False") + Type.__init__(self, pointer=pointer, **kwargs) + self.element_type = element_type + self.size_is_arg_num = size_is_arg_num + self.length_is_arg_num = length_is_arg_num + self.tag = Type.Tags.Array + + def __cmp__(self, other): + return ( + Type.__cmp__(self, other) or + cmp(self.element_type, other.element_type) or + cmp(self.size_is_arg_num, other.size_is_arg_num) or + cmp(self.length_is_arg_num, other.length_is_arg_num) or + cmp(self.tag, other.tag) + ) + + @staticmethod + def read(typelib, map, data_pool, offset, flags): + """ + Read an ArrayTypeDescriptor at |offset| from the mmaped + file |map| with data pool offset |data_pool|. + Returns (ArrayType, next offset), + where |next offset| is an offset suitable for reading the data + following this ArrayTypeDescriptor. + """ + if not flags['pointer']: + return None, offset + start = data_pool + offset - 1 + (size_is_arg_num, length_is_arg_num) = ArrayType._descriptor.unpack_from(map, start) + offset += ArrayType._descriptor.size + t, offset = Type.read(typelib, map, data_pool, offset) + return ArrayType(t, size_is_arg_num, length_is_arg_num, **flags), offset + + def write(self, typelib, file): + """ + Write an ArrayTypeDescriptor to |file|, which is assumed + to be seeked to the proper position. + + """ + Type.write(self, typelib, file) + file.write(ArrayType._descriptor.pack(self.size_is_arg_num, + self.length_is_arg_num)) + self.element_type.write(typelib, file) + + def __str__(self): + return "%s []" % str(self.element_type) + + +class StringWithSizeType(Type): + """ + A type representing a UTF-8 encoded string whose size and length + are passed as separate arguments to a method. (StringWithSizeTypeDescriptor + from the typelib specification.) + + """ + _descriptor = struct.Struct(">BB") + + def __init__(self, size_is_arg_num, length_is_arg_num, + pointer=True, **kwargs): + if not pointer: + raise DataError("StringWithSizeType is not valid with pointer=False") + Type.__init__(self, pointer=pointer, **kwargs) + self.size_is_arg_num = size_is_arg_num + self.length_is_arg_num = length_is_arg_num + self.tag = Type.Tags.StringWithSize + + def __cmp__(self, other): + return ( + Type.__cmp__(self, other) or + cmp(self.size_is_arg_num, other.size_is_arg_num) or + cmp(self.length_is_arg_num, other.length_is_arg_num) or + cmp(self.tag, other.tag) + ) + + @staticmethod + def read(typelib, map, data_pool, offset, flags): + """ + Read an StringWithSizeTypeDescriptor at |offset| from the mmaped + file |map| with data pool offset |data_pool|. + Returns (StringWithSizeType, next offset), + where |next offset| is an offset suitable for reading the data + following this StringWithSizeTypeDescriptor. + """ + if not flags['pointer']: + return None, offset + start = data_pool + offset - 1 + (size_is_arg_num, length_is_arg_num) = StringWithSizeType._descriptor.unpack_from(map, start) + offset += StringWithSizeType._descriptor.size + return StringWithSizeType(size_is_arg_num, length_is_arg_num, **flags), offset + + def write(self, typelib, file): + """ + Write a StringWithSizeTypeDescriptor to |file|, which is assumed + to be seeked to the proper position. + + """ + Type.write(self, typelib, file) + file.write(StringWithSizeType._descriptor.pack(self.size_is_arg_num, + self.length_is_arg_num)) + + def __str__(self): + return "string_s" + + +class WideStringWithSizeType(Type): + """ + A type representing a UTF-16 encoded string whose size and length + are passed as separate arguments to a method. + (WideStringWithSizeTypeDescriptor from the typelib specification.) + + """ + _descriptor = struct.Struct(">BB") + + def __init__(self, size_is_arg_num, length_is_arg_num, + pointer=True, **kwargs): + if not pointer: + raise DataError("WideStringWithSizeType is not valid with pointer=False") + Type.__init__(self, pointer=pointer, **kwargs) + self.size_is_arg_num = size_is_arg_num + self.length_is_arg_num = length_is_arg_num + self.tag = Type.Tags.WideStringWithSize + + def __cmp__(self, other): + return ( + Type.__cmp__(self, other) or + cmp(self.size_is_arg_num, other.size_is_arg_num) or + cmp(self.length_is_arg_num, other.length_is_arg_num) or + cmp(self.tag, other.tag) + ) + + @staticmethod + def read(typelib, map, data_pool, offset, flags): + """ + Read an WideStringWithSizeTypeDescriptor at |offset| from the mmaped + file |map| with data pool offset |data_pool|. + Returns (WideStringWithSizeType, next offset), + where |next offset| is an offset suitable for reading the data + following this WideStringWithSizeTypeDescriptor. + """ + if not flags['pointer']: + return None, offset + start = data_pool + offset - 1 + (size_is_arg_num, length_is_arg_num) = WideStringWithSizeType._descriptor.unpack_from(map, start) + offset += WideStringWithSizeType._descriptor.size + return WideStringWithSizeType(size_is_arg_num, length_is_arg_num, **flags), offset + + def write(self, typelib, file): + """ + Write a WideStringWithSizeTypeDescriptor to |file|, which is assumed + to be seeked to the proper position. + + """ + Type.write(self, typelib, file) + file.write(WideStringWithSizeType._descriptor.pack(self.size_is_arg_num, + self.length_is_arg_num)) + + def __str__(self): + return "wstring_s" + + +class Param(object): + """ + A parameter to a method, or the return value of a method. + (ParamDescriptor from the typelib specification.) + + """ + _descriptorstart = struct.Struct(">B") + + def __init__(self, type, in_=True, out=False, retval=False, + shared=False, dipper=False, optional=False): + """ + Construct a Param object with the specified |type| and + flags. Params default to "in". + + """ + + self.type = type + self.in_ = in_ + self.out = out + self.retval = retval + self.shared = shared + self.dipper = dipper + self.optional = optional + + def __cmp__(self, other): + return ( + cmp(self.type, other.type) or + cmp(self.in_, other.in_) or + cmp(self.out, other.out) or + cmp(self.retval, other.retval) or + cmp(self.shared, other.shared) or + cmp(self.dipper, other.dipper) or + cmp(self.optional, other.optional) + ) + + @staticmethod + def decodeflags(byte): + """ + Given |byte|, an unsigned uint8 containing flag bits, + decode the flag bits as described in + http://www.mozilla.org/scriptable/typelib_file.html#ParamDescriptor + and return a dict of flagname: (True|False) suitable + for passing to Param.__init__ as **kwargs + """ + return {'in_': bool(byte & 0x80), + 'out': bool(byte & 0x40), + 'retval': bool(byte & 0x20), + 'shared': bool(byte & 0x10), + 'dipper': bool(byte & 0x08), + # XXX: Not in the spec, see: + # http://hg.mozilla.org/mozilla-central/annotate/0e0e2516f04e/xpcom/typelib/xpt/public/xpt_struct.h#l456 + 'optional': bool(byte & 0x04), + } + + def encodeflags(self): + """ + Encode the flags of this Param. Return a byte suitable for + writing to a typelib file. + + """ + flags = 0 + if self.in_: + flags |= 0x80 + if self.out: + flags |= 0x40 + if self.retval: + flags |= 0x20 + if self.shared: + flags |= 0x10 + if self.dipper: + flags |= 0x08 + if self.optional: + flags |= 0x04 + return flags + + @staticmethod + def read(typelib, map, data_pool, offset): + """ + Read a ParamDescriptor at |offset| from the mmaped file |map| with + data pool offset |data_pool|. Returns (Param, next offset), + where |next offset| is an offset suitable for reading the data + following this ParamDescriptor. + """ + start = data_pool + offset - 1 + (flags,) = Param._descriptorstart.unpack_from(map, start) + # only the first five bits are flags + flags &= 0xFC + flags = Param.decodeflags(flags) + offset += Param._descriptorstart.size + t, offset = Type.read(typelib, map, data_pool, offset) + p = Param(t, **flags) + return p, offset + + def write(self, typelib, file): + """ + Write a ParamDescriptor to |file|, which is assumed to be seeked + to the correct position. + + """ + file.write(Param._descriptorstart.pack(self.encodeflags())) + self.type.write(typelib, file) + + def prefix(self): + """ + Return a human-readable string representing the flags set + on this Param. + + """ + s = "" + if self.out: + if self.in_: + s = "inout " + else: + s = "out " + else: + s = "in " + if self.dipper: + s += "dipper " + if self.retval: + s += "retval " + if self.shared: + s += "shared " + if self.optional: + s += "optional " + return s + + def __str__(self): + return self.prefix() + str(self.type) + + +class Method(object): + """ + A method of an interface, defining its associated parameters + and return value. + (MethodDescriptor from the typelib specification.) + + """ + _descriptorstart = struct.Struct(">BIB") + + def __init__(self, name, result, + params=[], getter=False, setter=False, notxpcom=False, + constructor=False, hidden=False, optargc=False, + implicit_jscontext=False): + self.name = name + self._name_offset = 0 + self.getter = getter + self.setter = setter + self.notxpcom = notxpcom + self.constructor = constructor + self.hidden = hidden + self.optargc = optargc + self.implicit_jscontext = implicit_jscontext + self.params = list(params) + if result and not isinstance(result, Param): + raise Exception("result must be a Param!") + self.result = result + + def __cmp__(self, other): + return ( + cmp(self.name, other.name) or + cmp(self.getter, other.getter) or + cmp(self.setter, other.setter) or + cmp(self.notxpcom, other.notxpcom) or + cmp(self.constructor, other.constructor) or + cmp(self.hidden, other.hidden) or + cmp(self.optargc, other.optargc) or + cmp(self.implicit_jscontext, other.implicit_jscontext) or + cmp(self.params, other.params) or + cmp(self.result, other.result) + ) + + def read_params(self, typelib, map, data_pool, offset, num_args): + """ + Read |num_args| ParamDescriptors representing this Method's arguments + from the mmaped file |map| with data pool at the offset |data_pool|, + starting at |offset| into self.params. Returns the offset + suitable for reading the data following the ParamDescriptor array. + + """ + for i in range(num_args): + p, offset = Param.read(typelib, map, data_pool, offset) + self.params.append(p) + return offset + + def read_result(self, typelib, map, data_pool, offset): + """ + Read a ParamDescriptor representing this Method's return type + from the mmaped file |map| with data pool at the offset |data_pool|, + starting at |offset| into self.result. Returns the offset + suitable for reading the data following the ParamDescriptor. + + """ + self.result, offset = Param.read(typelib, map, data_pool, offset) + return offset + + @staticmethod + def decodeflags(byte): + """ + Given |byte|, an unsigned uint8 containing flag bits, + decode the flag bits as described in + http://www.mozilla.org/scriptable/typelib_file.html#MethodDescriptor + and return a dict of flagname: (True|False) suitable + for passing to Method.__init__ as **kwargs + + """ + return {'getter': bool(byte & 0x80), + 'setter': bool(byte & 0x40), + 'notxpcom': bool(byte & 0x20), + 'constructor': bool(byte & 0x10), + 'hidden': bool(byte & 0x08), + # Not in the spec, see + # http://hg.mozilla.org/mozilla-central/annotate/0e0e2516f04e/xpcom/typelib/xpt/public/xpt_struct.h#l489 + 'optargc': bool(byte & 0x04), + 'implicit_jscontext': bool(byte & 0x02), + } + + def encodeflags(self): + """ + Encode the flags of this Method object, return a byte suitable + for writing to a typelib file. + + """ + flags = 0 + if self.getter: + flags |= 0x80 + if self.setter: + flags |= 0x40 + if self.notxpcom: + flags |= 0x20 + if self.constructor: + flags |= 0x10 + if self.hidden: + flags |= 0x08 + if self.optargc: + flags |= 0x04 + if self.implicit_jscontext: + flags |= 0x02 + return flags + + @staticmethod + def read(typelib, map, data_pool, offset): + """ + Read a MethodDescriptor at |offset| from the mmaped file |map| with + data pool offset |data_pool|. Returns (Method, next offset), + where |next offset| is an offset suitable for reading the data + following this MethodDescriptor. + + """ + start = data_pool + offset - 1 + flags, name_offset, num_args = Method._descriptorstart.unpack_from(map, start) + # only the first seven bits are flags + flags &= 0xFE + flags = Method.decodeflags(flags) + name = Typelib.read_string(map, data_pool, name_offset) + m = Method(name, None, **flags) + offset += Method._descriptorstart.size + offset = m.read_params(typelib, map, data_pool, offset, num_args) + offset = m.read_result(typelib, map, data_pool, offset) + return m, offset + + def write(self, typelib, file): + """ + Write a MethodDescriptor to |file|, which is assumed to be + seeked to the right position. + + """ + file.write(Method._descriptorstart.pack(self.encodeflags(), + self._name_offset, + len(self.params))) + for p in self.params: + p.write(typelib, file) + self.result.write(typelib, file) + + def write_name(self, file, data_pool_offset): + """ + Write this method's name to |file|. + Assumes that |file| is currently seeked to an unused portion + of the data pool. + + """ + if self.name: + self._name_offset = file.tell() - data_pool_offset + 1 + file.write(self.name + "\x00") + else: + self._name_offset = 0 + + +class Constant(object): + """ + A constant value of a specific type defined on an interface. + (ConstantDesciptor from the typelib specification.) + + """ + _descriptorstart = struct.Struct(">I") + # Actual value is restricted to this set of types + # XXX: the spec lies, the source allows a bunch more + # http://hg.mozilla.org/mozilla-central/annotate/9c85f9aaec8c/xpcom/typelib/xpt/src/xpt_struct.c#l689 + typemap = {Type.Tags.int16: '>h', + Type.Tags.uint16: '>H', + Type.Tags.int32: '>i', + Type.Tags.uint32: '>I'} + + def __init__(self, name, type, value): + self.name = name + self._name_offset = 0 + self.type = type + self.value = value + + def __cmp__(self, other): + return ( + cmp(self.name, other.name) or + cmp(self.type, other.type) or + cmp(self.value, other.value) + ) + + @staticmethod + def read(typelib, map, data_pool, offset): + """ + Read a ConstDescriptor at |offset| from the mmaped file |map| with + data pool offset |data_pool|. Returns (Constant, next offset), + where |next offset| is an offset suitable for reading the data + following this ConstDescriptor. + + """ + start = data_pool + offset - 1 + (name_offset,) = Constant._descriptorstart.unpack_from(map, start) + name = Typelib.read_string(map, data_pool, name_offset) + offset += Constant._descriptorstart.size + # Read TypeDescriptor + t, offset = Type.read(typelib, map, data_pool, offset) + c = None + if isinstance(t, SimpleType) and t.tag in Constant.typemap: + tt = Constant.typemap[t.tag] + start = data_pool + offset - 1 + (val,) = struct.unpack_from(tt, map, start) + offset += struct.calcsize(tt) + c = Constant(name, t, val) + return c, offset + + def write(self, typelib, file): + """ + Write a ConstDescriptor to |file|, which is assumed + to be seeked to the proper position. + + """ + file.write(Constant._descriptorstart.pack(self._name_offset)) + self.type.write(typelib, file) + tt = Constant.typemap[self.type.tag] + file.write(struct.pack(tt, self.value)) + + def write_name(self, file, data_pool_offset): + """ + Write this constants's name to |file|. + Assumes that |file| is currently seeked to an unused portion + of the data pool. + + """ + if self.name: + self._name_offset = file.tell() - data_pool_offset + 1 + file.write(self.name + "\x00") + else: + self._name_offset = 0 + + def __repr__(self): + return "Constant(%s, %s, %d)" % (self.name, str(self.type), self.value) + + +class Interface(object): + """ + An Interface represents an object, with its associated methods + and constant values. + (InterfaceDescriptor from the typelib specification.) + + """ + _direntry = struct.Struct(">16sIII") + _descriptorstart = struct.Struct(">HH") + + UNRESOLVED_IID = "00000000-0000-0000-0000-000000000000" + + def __init__(self, name, iid=UNRESOLVED_IID, namespace="", + resolved=False, parent=None, methods=[], constants=[], + scriptable=False, function=False, builtinclass=False, + main_process_scriptable_only=False): + self.resolved = resolved + # TODO: should validate IIDs! + self.iid = iid + self.name = name + self.namespace = namespace + # if unresolved, all the members following this are unusable + self.parent = parent + self.methods = list(methods) + self.constants = list(constants) + self.scriptable = scriptable + self.function = function + self.builtinclass = builtinclass + self.main_process_scriptable_only = main_process_scriptable_only + # For sanity, if someone constructs an Interface and passes + # in methods or constants, then it's resolved. + if self.methods or self.constants: + # make sure it has a valid IID + if self.iid == Interface.UNRESOLVED_IID: + raise DataError("Cannot instantiate Interface %s containing methods or constants with an unresolved IID" % self.name) + self.resolved = True + # These are only used for writing out the interface + self._descriptor_offset = 0 + self._name_offset = 0 + self._namespace_offset = 0 + self.xpt_filename = None + + def __repr__(self): + return "Interface('%s', '%s', '%s', methods=%s)" % (self.name, self.iid, self.namespace, self.methods) + + def __str__(self): + return "Interface(name='%s', iid='%s')" % (self.name, self.iid) + + def __hash__(self): + return hash((self.name, self.iid)) + + def __cmp__(self, other): + c = cmp(self.iid, other.iid) + if c != 0: + return c + c = cmp(self.name, other.name) + if c != 0: + return c + c = cmp(self.namespace, other.namespace) + if c != 0: + return c + # names and IIDs are the same, check resolved + if self.resolved != other.resolved: + if self.resolved: + return -1 + else: + return 1 + else: + if not self.resolved: + # both unresolved, but names and IIDs are the same, so equal + return 0 + # When comparing parents, only look at the name. + if (self.parent is None) != (other.parent is None): + if self.parent is None: + return -1 + else: + return 1 + elif self.parent is not None: + c = cmp(self.parent.name, other.parent.name) + if c != 0: + return c + return ( + cmp(self.methods, other.methods) or + cmp(self.constants, other.constants) or + cmp(self.scriptable, other.scriptable) or + cmp(self.function, other.function) or + cmp(self.builtinclass, other.builtinclass) or + cmp(self.main_process_scriptable_only, other.main_process_scriptable_only) + ) + + def read_descriptor(self, typelib, map, data_pool): + offset = self._descriptor_offset + if offset == 0: + return + start = data_pool + offset - 1 + parent, num_methods = Interface._descriptorstart.unpack_from(map, start) + if parent > 0 and parent <= len(typelib.interfaces): + self.parent = typelib.interfaces[parent - 1] + # Read methods + offset += Interface._descriptorstart.size + for i in range(num_methods): + m, offset = Method.read(typelib, map, data_pool, offset) + self.methods.append(m) + # Read constants + start = data_pool + offset - 1 + (num_constants, ) = struct.unpack_from(">H", map, start) + offset = offset + struct.calcsize(">H") + for i in range(num_constants): + c, offset = Constant.read(typelib, map, data_pool, offset) + self.constants.append(c) + # Read flags + start = data_pool + offset - 1 + (flags, ) = struct.unpack_from(">B", map, start) + offset = offset + struct.calcsize(">B") + # only the first two bits are flags + flags &= 0xf0 + if flags & 0x80: + self.scriptable = True + if flags & 0x40: + self.function = True + if flags & 0x20: + self.builtinclass = True + if flags & 0x10: + self.main_process_scriptable_only = True + self.resolved = True + + def write_directory_entry(self, file): + """ + Write an InterfaceDirectoryEntry for this interface + to |file|, which is assumed to be seeked to the correct offset. + + """ + file.write(Interface._direntry.pack(Typelib.string_to_iid(self.iid), + self._name_offset, + self._namespace_offset, + self._descriptor_offset)) + + def write(self, typelib, file, data_pool_offset): + """ + Write an InterfaceDescriptor to |file|, which is assumed + to be seeked to the proper position. If this interface + is not resolved, do not write any data. + + """ + if not self.resolved: + self._descriptor_offset = 0 + return + self._descriptor_offset = file.tell() - data_pool_offset + 1 + parent_idx = 0 + if self.parent: + parent_idx = typelib.interfaces.index(self.parent) + 1 + file.write(Interface._descriptorstart.pack(parent_idx, len(self.methods))) + for m in self.methods: + m.write(typelib, file) + file.write(struct.pack(">H", len(self.constants))) + for c in self.constants: + c.write(typelib, file) + flags = 0 + if self.scriptable: + flags |= 0x80 + if self.function: + flags |= 0x40 + if self.builtinclass: + flags |= 0x20 + if self.main_process_scriptable_only: + flags |= 0x10 + file.write(struct.pack(">B", flags)) + + def write_names(self, file, data_pool_offset): + """ + Write this interface's name and namespace to |file|, + as well as the names of all of its methods and constants. + Assumes that |file| is currently seeked to an unused portion + of the data pool. + + """ + if self.name: + self._name_offset = file.tell() - data_pool_offset + 1 + file.write(self.name + "\x00") + else: + self._name_offset = 0 + if self.namespace: + self._namespace_offset = file.tell() - data_pool_offset + 1 + file.write(self.namespace + "\x00") + else: + self._namespace_offset = 0 + for m in self.methods: + m.write_name(file, data_pool_offset) + for c in self.constants: + c.write_name(file, data_pool_offset) + + +class Typelib(object): + """ + A typelib represents one entire typelib file and all the interfaces + referenced within, whether defined entirely within the typelib or + merely referenced by name or IID. + + Typelib objects may be instantiated directly and populated with data, + or the static Typelib.read method may be called to read one from a file. + + """ + _header = struct.Struct(">16sBBHIII") + + def __init__(self, version=TYPELIB_VERSION, interfaces=[], annotations=[]): + """ + Instantiate a new Typelib. + + """ + self.version = version + self.interfaces = list(interfaces) + self.annotations = list(annotations) + self.filename = None + + @staticmethod + def iid_to_string(iid): + """ + Convert a 16-byte IID into a UUID string. + + """ + def hexify(s): + return ''.join(["%02x" % ord(x) for x in s]) + + return "%s-%s-%s-%s-%s" % (hexify(iid[:4]), hexify(iid[4:6]), + hexify(iid[6:8]), hexify(iid[8:10]), + hexify(iid[10:])) + + @staticmethod + def string_to_iid(iid_str): + """ + Convert a UUID string into a 16-byte IID. + + """ + s = iid_str.replace('-', '') + return ''.join([chr(int(s[i:i+2], 16)) for i in range(0, len(s), 2)]) + + @staticmethod + def read_string(map, data_pool, offset): + if offset == 0: + return "" + sz = map.find('\x00', data_pool + offset - 1) + if sz == -1: + return "" + return map[data_pool + offset - 1:sz] + + @staticmethod + def read(input_file): + """ + Read a typelib from |input_file| and return + the constructed Typelib object. |input_file| can be a filename + or a file-like object. + + """ + filename = "" + data = None + expected_size = None + if isinstance(input_file, basestring): + filename = input_file + with open(input_file, "rb") as f: + st = os.fstat(f.fileno()) + data = f.read(st.st_size) + expected_size = st.st_size + else: + data = input_file.read() + + (magic, + major_ver, + minor_ver, + num_interfaces, + file_length, + interface_directory_offset, + data_pool_offset) = Typelib._header.unpack_from(data) + if magic != XPT_MAGIC: + raise FileFormatError("Bad magic: %s" % magic) + xpt = Typelib((major_ver, minor_ver)) + xpt.filename = filename + if expected_size and file_length != expected_size: + raise FileFormatError("File is of wrong length, got %d bytes, expected %d" % (expected_size, file_length)) + # XXX: by spec this is a zero-based file offset. however, + # the xpt_xdr code always subtracts 1 from data offsets + # (because that's what you do in the data pool) so it + # winds up accidentally treating this as 1-based. + # Filed as: https://bugzilla.mozilla.org/show_bug.cgi?id=575343 + interface_directory_offset -= 1 + # make a half-hearted attempt to read Annotations, + # since XPIDL doesn't produce any anyway. + start = Typelib._header.size + (anno, ) = struct.unpack_from(">B", data, start) + tag = anno & 0x7F + if tag == 0: # EmptyAnnotation + xpt.annotations.append(None) + # We don't bother handling PrivateAnnotations or anything + + for i in range(num_interfaces): + # iid, name, namespace, interface_descriptor + start = interface_directory_offset + i * Interface._direntry.size + ide = Interface._direntry.unpack_from(data, start) + iid = Typelib.iid_to_string(ide[0]) + name = Typelib.read_string(data, data_pool_offset, ide[1]) + namespace = Typelib.read_string(data, data_pool_offset, ide[2]) + iface = Interface(name, iid, namespace) + iface._descriptor_offset = ide[3] + iface.xpt_filename = xpt.filename + xpt.interfaces.append(iface) + for iface in xpt.interfaces: + iface.read_descriptor(xpt, data, data_pool_offset) + return xpt + + def __repr__(self): + return "" % len(self.interfaces) + + def _sanityCheck(self): + """ + Check certain assumptions about data contained in this typelib. + Sort the interfaces array by IID, check that all interfaces + referenced by methods exist in the array. + + """ + self.interfaces.sort() + for i in self.interfaces: + if i.parent and i.parent not in self.interfaces: + raise DataError("Interface %s has parent %s not present in typelib!" % (i.name, i.parent.name)) + for m in i.methods: + for n, p in enumerate(m.params): + if isinstance(p, InterfaceType) and \ + p.iface not in self.interfaces: + raise DataError("Interface method %s::%s, parameter %d references interface %s not present in typelib!" % (i.name, m.name, n, p.iface.name)) + if isinstance(m.result, InterfaceType) and m.result.iface not in self.interfaces: + raise DataError("Interface method %s::%s, result references interface %s not present in typelib!" % (i.name, m.name, m.result.iface.name)) + + def writefd(self, fd): + # write out space for a header + one empty annotation, + # padded to 4-byte alignment. + headersize = (Typelib._header.size + 1) + if headersize % 4: + headersize += 4 - headersize % 4 + fd.write("\x00" * headersize) + # save this offset, it's the interface directory offset. + interface_directory_offset = fd.tell() + # write out space for an interface directory + fd.write("\x00" * Interface._direntry.size * len(self.interfaces)) + # save this offset, it's the data pool offset. + data_pool_offset = fd.tell() + # write out all the interface descriptors to the data pool + for i in self.interfaces: + i.write_names(fd, data_pool_offset) + i.write(self, fd, data_pool_offset) + # now, seek back and write the header + file_len = fd.tell() + fd.seek(0) + fd.write(Typelib._header.pack(XPT_MAGIC, + TYPELIB_VERSION[0], + TYPELIB_VERSION[1], + len(self.interfaces), + file_len, + interface_directory_offset, + data_pool_offset)) + # write an empty annotation + fd.write(struct.pack(">B", 0x80)) + # now write the interface directory + # XXX: bug-compatible with existing xpt lib, put it one byte + # ahead of where it's supposed to be. + fd.seek(interface_directory_offset - 1) + for i in self.interfaces: + i.write_directory_entry(fd) + + def write(self, output_file): + """ + Write the contents of this typelib to |output_file|, + which can be either a filename or a file-like object. + + """ + self._sanityCheck() + if isinstance(output_file, basestring): + with open(output_file, "wb") as f: + self.writefd(f) + else: + self.writefd(output_file) + + def dump(self, out): + """ + Print a human-readable listing of the contents of this typelib + to |out|, in the format of xpt_dump. + + """ + out.write("""Header: + Major version: %d + Minor version: %d + Number of interfaces: %d + Annotations:\n""" % (self.version[0], self.version[1], len(self.interfaces))) + for i, a in enumerate(self.annotations): + if a is None: + out.write(" Annotation #%d is empty.\n" % i) + out.write("\nInterface Directory:\n") + for i in self.interfaces: + out.write(" - %s::%s (%s):\n" % (i.namespace, i.name, i.iid)) + if not i.resolved: + out.write(" [Unresolved]\n") + else: + if i.parent: + out.write(" Parent: %s::%s\n" % (i.parent.namespace, + i.parent.name)) + out.write(""" Flags: + Scriptable: %s + BuiltinClass: %s + Function: %s\n""" % (i.scriptable and "TRUE" or "FALSE", + i.builtinclass and "TRUE" or "FALSE", + i.function and "TRUE" or "FALSE")) + out.write(" Methods:\n") + if len(i.methods) == 0: + out.write(" No Methods\n") + else: + for m in i.methods: + out.write(" %s%s%s%s%s%s%s %s %s(%s);\n" % ( + m.getter and "G" or " ", + m.setter and "S" or " ", + m.hidden and "H" or " ", + m.notxpcom and "N" or " ", + m.constructor and "C" or " ", + m.optargc and "O" or " ", + m.implicit_jscontext and "J" or " ", + str(m.result.type), + m.name, + m.params and ", ".join(str(p) for p in m.params) or "" + )) + out.write(" Constants:\n") + if len(i.constants) == 0: + out.write(" No Constants\n") + else: + for c in i.constants: + out.write(" %s %s = %d;\n" % (c.type, c.name, c.value)) + + +def xpt_dump(file): + """ + Dump the contents of |file| to stdout in the format of xpt_dump. + + """ + t = Typelib.read(file) + t.dump(sys.stdout) + + +def xpt_link(inputs): + """ + Link all of the xpt files in |inputs| together and return the result + as a Typelib object. All entries in inputs may be filenames or + file-like objects. Non-scriptable interfaces that are unreferenced + from scriptable interfaces will be removed during linking. + + """ + def read_input(i): + if isinstance(i, Typelib): + return i + return Typelib.read(i) + + if not inputs: + print >>sys.stderr, "Usage: xpt_link " + return None + # This is the aggregate list of interfaces. + interfaces = [] + # This will be a dict of replaced interface -> replaced with + # containing interfaces that were replaced with interfaces from + # another typelib, and the interface that replaced them. + merged_interfaces = {} + for f in inputs: + t = read_input(f) + interfaces.extend(t.interfaces) + # Sort interfaces by name so we can merge adjacent duplicates + interfaces.sort(key=operator.attrgetter('name')) + + Result = enum('Equal', # Interfaces the same, doesn't matter + 'NotEqual', # Interfaces differ, keep both + 'KeepFirst', # Replace second interface with first + 'KeepSecond') # Replace first interface with second + + def compare(i, j): + """ + Compare two interfaces, determine if they're equal or + completely different, or should be merged (and indicate which + one to keep in that case). + + """ + if i == j: + # Arbitrary, just pick one + return Result.Equal + if i.name != j.name: + if i.iid == j.iid and i.iid != Interface.UNRESOLVED_IID: + # Same IID but different names: raise an exception. + raise DataError( + "Typelibs contain definitions of interface %s" + " with different names (%s (%s) vs %s (%s))!" % + (i.iid, i.name, i.xpt_filename, j.name, j.xpt_filename)) + # Otherwise just different interfaces. + return Result.NotEqual + # Interfaces have the same name, so either they need to be merged + # or there's a data error. Sort out which one to keep + if i.resolved != j.resolved: + # prefer resolved interfaces over unresolved + if j.resolved: + assert i.iid == j.iid or i.iid == Interface.UNRESOLVED_IID + # keep j + return Result.KeepSecond + else: + assert i.iid == j.iid or j.iid == Interface.UNRESOLVED_IID + # replace j with i + return Result.KeepFirst + elif i.iid != j.iid: + # Prefer unresolved interfaces with valid IIDs + if j.iid == Interface.UNRESOLVED_IID: + # replace j with i + assert not j.resolved + return Result.KeepFirst + elif i.iid == Interface.UNRESOLVED_IID: + # keep j + assert not i.resolved + return Result.KeepSecond + else: + # Same name but different IIDs: raise an exception. + raise DataError( + "Typelibs contain definitions of interface %s" + " with different IIDs (%s (%s) vs %s (%s))!" % + (i.name, i.iid, i.xpt_filename, + j.iid, j.xpt_filename)) + raise DataError("No idea what happened here: %s:%s (%s), %s:%s (%s)" % + (i.name, i.iid, i.xpt_filename, j.name, j.iid, j.xpt_filename)) + + # Compare interfaces pairwise to find duplicates that should be merged. + i = 1 + while i < len(interfaces): + res = compare(interfaces[i-1], interfaces[i]) + if res == Result.NotEqual: + i += 1 + elif res == Result.Equal: + # Need to drop one but it doesn't matter which + del interfaces[i] + elif res == Result.KeepFirst: + merged_interfaces[interfaces[i]] = interfaces[i-1] + del interfaces[i] + elif res == Result.KeepSecond: + merged_interfaces[interfaces[i-1]] = interfaces[i] + del interfaces[i-1] + + # Now fixup any merged interfaces + def checkType(t): + if isinstance(t, InterfaceType) and t.iface in merged_interfaces: + t.iface = merged_interfaces[t.iface] + elif isinstance(t, ArrayType) and \ + isinstance(t.element_type, InterfaceType) and \ + t.element_type.iface in merged_interfaces: + t.element_type.iface = merged_interfaces[t.element_type.iface] + + for i in interfaces: + # Replace parent references + if i.parent in merged_interfaces: + i.parent = merged_interfaces[i.parent] + for m in i.methods: + # Replace InterfaceType params and return values + checkType(m.result.type) + for p in m.params: + checkType(p.type) + + # There's no need to have non-scriptable interfaces in a typelib, and + # removing them saves memory when typelibs are loaded. But we can't + # just blindly remove all non-scriptable interfaces, since we still + # need to know about non-scriptable interfaces referenced from + # scriptable interfaces. + worklist = set(i for i in interfaces if i.scriptable) + required_interfaces = set() + + def maybe_add_to_worklist(iface): + if iface in required_interfaces or iface in worklist: + return + worklist.add(iface) + + while worklist: + i = worklist.pop() + required_interfaces.add(i) + if i.parent: + maybe_add_to_worklist(i.parent) + for m in i.methods: + if isinstance(m.result.type, InterfaceType): + maybe_add_to_worklist(m.result.type.iface) + for p in m.params: + if isinstance(p.type, InterfaceType): + maybe_add_to_worklist(p.type.iface) + elif isinstance(p.type, ArrayType) and isinstance(p.type.element_type, InterfaceType): + maybe_add_to_worklist(p.type.element_type.iface) + + interfaces = list(required_interfaces) + + # Re-sort interfaces (by IID) + interfaces.sort() + return Typelib(interfaces=interfaces) + +if __name__ == '__main__': + if len(sys.argv) < 3: + print >>sys.stderr, "xpt " + sys.exit(1) + if sys.argv[1] == 'dump': + xpt_dump(sys.argv[2]) + elif sys.argv[1] == 'link': + xpt_link(sys.argv[3:]).write(sys.argv[2]) diff --git a/xpcom/typelib/xpt/xpt_arena.cpp b/xpcom/typelib/xpt/xpt_arena.cpp new file mode 100644 index 000000000..21be3c00b --- /dev/null +++ b/xpcom/typelib/xpt/xpt_arena.cpp @@ -0,0 +1,196 @@ +/* -*- 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/. */ + +/* Quick arena hack for xpt. */ + +/* XXX This exists because we don't want to drag in NSPR. It *seemed* +* to make more sense to write a quick and dirty arena than to clone +* plarena (like js/src did). This is not optimal, but it works. +*/ + +#include "xpt_arena.h" +#include "mozilla/MemoryReporting.h" +#include +#include +#include + +/****************************************************/ + +/* Block header for each block in the arena */ +struct BLK_HDR +{ + BLK_HDR *next; +}; + +#define XPT_MIN_BLOCK_SIZE 32 + +/* XXX this is lame. Should clone the code to do this bitwise */ +#define ALIGN_RND(s,a) ((a)==1?(s):((((s)+(a)-1)/(a))*(a))) + +struct XPTSubArena +{ + BLK_HDR *first; + uint8_t *next; + size_t space; + size_t block_size; +}; + +struct XPTArena +{ + // We have one sub-arena with 8-byte alignment for most allocations, and + // one with 1-byte alignment for C string allocations. The latter sub-arena + // avoids significant amounts of unnecessary padding between C strings. + XPTSubArena subarena8; + XPTSubArena subarena1; +}; + +XPT_PUBLIC_API(XPTArena *) +XPT_NewArena(size_t block_size8, size_t block_size1) +{ + XPTArena *arena = static_cast(calloc(1, sizeof(XPTArena))); + if (arena) { + if (block_size8 < XPT_MIN_BLOCK_SIZE) + block_size8 = XPT_MIN_BLOCK_SIZE; + arena->subarena8.block_size = ALIGN_RND(block_size8, 8); + + if (block_size1 < XPT_MIN_BLOCK_SIZE) + block_size1 = XPT_MIN_BLOCK_SIZE; + arena->subarena1.block_size = block_size1; + } + return arena; +} + +static void +DestroySubArena(XPTSubArena *subarena) +{ + BLK_HDR* cur = subarena->first; + while (cur) { + BLK_HDR* next = cur->next; + free(cur); + cur = next; + } +} + +XPT_PUBLIC_API(void) +XPT_DestroyArena(XPTArena *arena) +{ + DestroySubArena(&arena->subarena8); + DestroySubArena(&arena->subarena1); + free(arena); +} + +/* +* Our alignment rule is that we always round up the size of each allocation +* so that the 'arena->next' pointer one will point to properly aligned space. +*/ + +XPT_PUBLIC_API(void *) +XPT_ArenaCalloc(XPTArena *arena, size_t size, size_t alignment) +{ + if (!size) + return NULL; + + if (!arena) { + XPT_ASSERT(0); + return NULL; + } + + XPTSubArena *subarena; + if (alignment == 8) { + subarena = &arena->subarena8; + } else if (alignment == 1) { + subarena = &arena->subarena1; + } else { + XPT_ASSERT(0); + return NULL; + } + + size_t bytes = ALIGN_RND(size, alignment); + + if (bytes > subarena->space) { + BLK_HDR* new_block; + size_t block_header_size = ALIGN_RND(sizeof(BLK_HDR), alignment); + size_t new_space = subarena->block_size; + + while (bytes > new_space - block_header_size) + new_space += subarena->block_size; + + new_block = + static_cast(calloc(new_space / alignment, alignment)); + if (!new_block) { + subarena->next = NULL; + subarena->space = 0; + return NULL; + } + + /* link block into the list of blocks for use when we destroy */ + new_block->next = subarena->first; + subarena->first = new_block; + + /* set info for current block */ + subarena->next = + reinterpret_cast(new_block) + block_header_size; + subarena->space = new_space - block_header_size; + +#ifdef DEBUG + /* mark block for corruption check */ + memset(subarena->next, 0xcd, subarena->space); +#endif + } + +#ifdef DEBUG + { + /* do corruption check */ + size_t i; + for (i = 0; i < bytes; ++i) { + XPT_ASSERT(subarena->next[i] == 0xcd); + } + /* we guarantee that the block will be filled with zeros */ + memset(subarena->next, 0, bytes); + } +#endif + + uint8_t* p = subarena->next; + subarena->next += bytes; + subarena->space -= bytes; + + return p; +} + +/***************************************************************************/ + +#ifdef DEBUG +XPT_PUBLIC_API(void) +XPT_AssertFailed(const char *s, const char *file, uint32_t lineno) +{ + fprintf(stderr, "Assertion failed: %s, file %s, line %d\n", + s, file, lineno); + abort(); +} +#endif + +static size_t +SizeOfSubArenaExcludingThis(XPTSubArena *subarena, MozMallocSizeOf mallocSizeOf) +{ + size_t n = 0; + + BLK_HDR* cur = subarena->first; + while (cur) { + BLK_HDR* next = cur->next; + n += mallocSizeOf(cur); + cur = next; + } + + return n; +} + +XPT_PUBLIC_API(size_t) +XPT_SizeOfArenaIncludingThis(XPTArena *arena, MozMallocSizeOf mallocSizeOf) +{ + size_t n = mallocSizeOf(arena); + n += SizeOfSubArenaExcludingThis(&arena->subarena8, mallocSizeOf); + n += SizeOfSubArenaExcludingThis(&arena->subarena1, mallocSizeOf); + return n; +} diff --git a/xpcom/typelib/xpt/xpt_arena.h b/xpcom/typelib/xpt/xpt_arena.h new file mode 100644 index 000000000..6ac146ffe --- /dev/null +++ b/xpcom/typelib/xpt/xpt_arena.h @@ -0,0 +1,70 @@ +/* -*- 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/. */ + +/* + * Simple arena allocator for xpt (which avoids using NSPR). + */ + +#ifndef __xpt_arena_h__ +#define __xpt_arena_h__ + +#include +#include "mozilla/Attributes.h" +#include "mozilla/MemoryReporting.h" +#include + + +/* + * The XPT library is statically linked: no functions are exported from + * shared libraries. + */ +#define XPT_PUBLIC_API(t) t +#define XPT_PUBLIC_DATA(t) t + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Simple Arena support. Use with caution! + */ + +typedef struct XPTArena XPTArena; + +XPT_PUBLIC_API(XPTArena *) +XPT_NewArena(size_t block_size8, size_t block_size1); + +XPT_PUBLIC_API(void) +XPT_DestroyArena(XPTArena *arena); + +XPT_PUBLIC_API(void *) +XPT_ArenaCalloc(XPTArena *arena, size_t size, size_t alignment); + +XPT_PUBLIC_API(size_t) +XPT_SizeOfArenaIncludingThis(XPTArena *arena, MozMallocSizeOf mallocSizeOf); + +/* --------------------------------------------------------- */ + +#define XPT_CALLOC8(_arena, _bytes) XPT_ArenaCalloc((_arena), (_bytes), 8) +#define XPT_CALLOC1(_arena, _bytes) XPT_ArenaCalloc((_arena), (_bytes), 1) +#define XPT_NEWZAP(_arena, _struct) ((_struct *) XPT_CALLOC8((_arena), sizeof(_struct))) + +/* --------------------------------------------------------- */ + +#ifdef DEBUG +XPT_PUBLIC_API(void) +XPT_AssertFailed(const char *s, const char *file, uint32_t lineno) + MOZ_PRETEND_NORETURN_FOR_STATIC_ANALYSIS; +#define XPT_ASSERT(_expr) \ + ((_expr)?((void)0):XPT_AssertFailed(# _expr, __FILE__, __LINE__)) +#else +#define XPT_ASSERT(_expr) ((void)0) +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* __xpt_arena_h__ */ diff --git a/xpcom/typelib/xpt/xpt_struct.cpp b/xpcom/typelib/xpt/xpt_struct.cpp new file mode 100644 index 000000000..c2e09abf0 --- /dev/null +++ b/xpcom/typelib/xpt/xpt_struct.cpp @@ -0,0 +1,432 @@ +/* -*- 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/. */ + +/* Implementation of XDR routines for typelib structures. */ + +#include "xpt_xdr.h" +#include "xpt_struct.h" +#include +#include + +using mozilla::WrapNotNull; + +/***************************************************************************/ +/* Forward declarations. */ + +static bool +DoInterfaceDirectoryEntry(XPTArena *arena, NotNull cursor, + XPTInterfaceDirectoryEntry *ide); + +static bool +DoConstDescriptor(XPTArena *arena, NotNull cursor, + XPTConstDescriptor *cd, XPTInterfaceDescriptor *id); + +static bool +DoMethodDescriptor(XPTArena *arena, NotNull cursor, + XPTMethodDescriptor *md, XPTInterfaceDescriptor *id); + +static bool +SkipAnnotation(NotNull cursor, bool *isLast); + +static bool +DoInterfaceDescriptor(XPTArena *arena, NotNull outer, + XPTInterfaceDescriptor **idp); + +static bool +DoTypeDescriptorPrefix(XPTArena *arena, NotNull cursor, + XPTTypeDescriptorPrefix *tdp); + +static bool +DoTypeDescriptor(XPTArena *arena, NotNull cursor, + XPTTypeDescriptor *td, XPTInterfaceDescriptor *id); + +static bool +DoParamDescriptor(XPTArena *arena, NotNull cursor, + XPTParamDescriptor *pd, XPTInterfaceDescriptor *id); + +/***************************************************************************/ + +XPT_PUBLIC_API(bool) +XPT_DoHeader(XPTArena *arena, NotNull cursor, XPTHeader **headerp) +{ + unsigned int i; + uint32_t file_length = 0; + uint32_t ide_offset; + + XPTHeader* header = XPT_NEWZAP(arena, XPTHeader); + if (!header) + return false; + *headerp = header; + + uint8_t magic[16]; + for (i = 0; i < sizeof(magic); i++) { + if (!XPT_Do8(cursor, &magic[i])) + return false; + } + + if (strncmp((const char*)magic, XPT_MAGIC, 16) != 0) { + /* Require that the header contain the proper magic */ + fprintf(stderr, + "libxpt: bad magic header in input file; " + "found '%s', expected '%s'\n", + magic, XPT_MAGIC_STRING); + return false; + } + + if (!XPT_Do8(cursor, &header->major_version) || + !XPT_Do8(cursor, &header->minor_version)) { + return false; + } + + if (header->major_version >= XPT_MAJOR_INCOMPATIBLE_VERSION) { + /* This file is newer than we are and set to an incompatible version + * number. We must set the header state thusly and return. + */ + header->num_interfaces = 0; + return true; + } + + if (!XPT_Do16(cursor, &header->num_interfaces) || + !XPT_Do32(cursor, &file_length) || + !XPT_Do32(cursor, &ide_offset)) { + return false; + } + + /* + * Make sure the file length reported in the header is the same size as + * as our buffer unless it is zero (not set) + */ + if (file_length != 0 && + cursor->state->pool_allocated < file_length) { + fputs("libxpt: File length in header does not match actual length. File may be corrupt\n", + stderr); + return false; + } + + uint32_t data_pool; + if (!XPT_Do32(cursor, &data_pool)) + return false; + + XPT_SetDataOffset(cursor->state, data_pool); + + if (header->num_interfaces) { + size_t n = header->num_interfaces * sizeof(XPTInterfaceDirectoryEntry); + header->interface_directory = + static_cast(XPT_CALLOC8(arena, n)); + if (!header->interface_directory) + return false; + } + + /* + * Iterate through the annotations rather than recurring, to avoid blowing + * the stack on large xpt files. We don't actually store annotations, we + * just skip over them. + */ + bool isLast; + do { + if (!SkipAnnotation(cursor, &isLast)) + return false; + } while (!isLast); + + /* shouldn't be necessary now, but maybe later */ + XPT_SeekTo(cursor, ide_offset); + + for (i = 0; i < header->num_interfaces; i++) { + if (!DoInterfaceDirectoryEntry(arena, cursor, + &header->interface_directory[i])) + return false; + } + + return true; +} + +/* InterfaceDirectoryEntry records go in the header */ +bool +DoInterfaceDirectoryEntry(XPTArena *arena, NotNull cursor, + XPTInterfaceDirectoryEntry *ide) +{ + char* dummy_name_space; + + /* write the IID in our cursor space */ + if (!XPT_DoIID(cursor, &(ide->iid)) || + + /* write the name string in the data pool, and the offset in our + cursor space */ + !XPT_DoCString(arena, cursor, &(ide->name)) || + + /* don't write the name_space string in the data pool, because we don't + * need it. Do write the offset in our cursor space */ + !XPT_DoCString(arena, cursor, &dummy_name_space, /* ignore = */ true) || + + /* do InterfaceDescriptors */ + !DoInterfaceDescriptor(arena, cursor, &ide->interface_descriptor)) { + return false; + } + + return true; +} + +static bool +InterfaceDescriptorAddTypes(XPTArena *arena, XPTInterfaceDescriptor *id, + uint16_t num) +{ + XPTTypeDescriptor *old = id->additional_types; + XPTTypeDescriptor *new_; + size_t old_size = id->num_additional_types * sizeof(XPTTypeDescriptor); + size_t new_size = (num * sizeof(XPTTypeDescriptor)) + old_size; + + /* XXX should grow in chunks to minimize alloc overhead */ + new_ = static_cast(XPT_CALLOC8(arena, new_size)); + if (!new_) + return false; + if (old) { + memcpy(new_, old, old_size); + } + id->additional_types = new_; + + if (num + uint16_t(id->num_additional_types) > 256) + return false; + + id->num_additional_types += num; + return true; +} + +bool +DoInterfaceDescriptor(XPTArena *arena, NotNull outer, + XPTInterfaceDescriptor **idp) +{ + XPTInterfaceDescriptor *id; + XPTCursor curs; + NotNull cursor = WrapNotNull(&curs); + uint32_t i, id_sz = 0; + + id = XPT_NEWZAP(arena, XPTInterfaceDescriptor); + if (!id) + return false; + *idp = id; + + if (!XPT_MakeCursor(outer->state, XPT_DATA, id_sz, cursor)) + return false; + + if (!XPT_Do32(outer, &cursor->offset)) + return false; + if (!cursor->offset) { + *idp = NULL; + return true; + } + if(!XPT_Do16(cursor, &id->parent_interface) || + !XPT_Do16(cursor, &id->num_methods)) { + return false; + } + + if (id->num_methods) { + size_t n = id->num_methods * sizeof(XPTMethodDescriptor); + id->method_descriptors = + static_cast(XPT_CALLOC8(arena, n)); + if (!id->method_descriptors) + return false; + } + + for (i = 0; i < id->num_methods; i++) { + if (!DoMethodDescriptor(arena, cursor, &id->method_descriptors[i], id)) + return false; + } + + if (!XPT_Do16(cursor, &id->num_constants)) { + return false; + } + + if (id->num_constants) { + size_t n = id->num_constants * sizeof(XPTConstDescriptor); + id->const_descriptors = + static_cast(XPT_CALLOC8(arena, n)); + if (!id->const_descriptors) + return false; + } + + for (i = 0; i < id->num_constants; i++) { + if (!DoConstDescriptor(arena, cursor, &id->const_descriptors[i], id)) { + return false; + } + } + + if (!XPT_Do8(cursor, &id->flags)) { + return false; + } + + return true; +} + +bool +DoConstDescriptor(XPTArena *arena, NotNull cursor, + XPTConstDescriptor *cd, XPTInterfaceDescriptor *id) +{ + bool ok = false; + + if (!XPT_DoCString(arena, cursor, &cd->name) || + !DoTypeDescriptor(arena, cursor, &cd->type, id)) { + + return false; + } + + switch(XPT_TDP_TAG(cd->type.prefix)) { + case TD_INT8: + ok = XPT_Do8(cursor, (uint8_t*) &cd->value.i8); + break; + case TD_INT16: + ok = XPT_Do16(cursor, (uint16_t*) &cd->value.i16); + break; + case TD_INT32: + ok = XPT_Do32(cursor, (uint32_t*) &cd->value.i32); + break; + case TD_INT64: + ok = XPT_Do64(cursor, &cd->value.i64); + break; + case TD_UINT8: + ok = XPT_Do8(cursor, &cd->value.ui8); + break; + case TD_UINT16: + ok = XPT_Do16(cursor, &cd->value.ui16); + break; + case TD_UINT32: + ok = XPT_Do32(cursor, &cd->value.ui32); + break; + case TD_UINT64: + ok = XPT_Do64(cursor, (int64_t *)&cd->value.ui64); + break; + case TD_CHAR: + ok = XPT_Do8(cursor, (uint8_t*) &cd->value.ch); + break; + case TD_WCHAR: + ok = XPT_Do16(cursor, &cd->value.wch); + break; + /* fall-through */ + default: + fprintf(stderr, "illegal type!\n"); + break; + } + + return ok; + +} + +bool +DoMethodDescriptor(XPTArena *arena, NotNull cursor, + XPTMethodDescriptor *md, XPTInterfaceDescriptor *id) +{ + int i; + + if (!XPT_Do8(cursor, &md->flags) || + !XPT_DoCString(arena, cursor, &md->name) || + !XPT_Do8(cursor, &md->num_args)) + return false; + + if (md->num_args) { + size_t n = md->num_args * sizeof(XPTParamDescriptor); + md->params = static_cast(XPT_CALLOC8(arena, n)); + if (!md->params) + return false; + } + + for(i = 0; i < md->num_args; i++) { + if (!DoParamDescriptor(arena, cursor, &md->params[i], id)) + return false; + } + + if (!DoParamDescriptor(arena, cursor, &md->result, id)) + return false; + + return true; +} + +bool +DoParamDescriptor(XPTArena *arena, NotNull cursor, + XPTParamDescriptor *pd, XPTInterfaceDescriptor *id) +{ + if (!XPT_Do8(cursor, &pd->flags) || + !DoTypeDescriptor(arena, cursor, &pd->type, id)) + return false; + + return true; +} + +bool +DoTypeDescriptorPrefix(XPTArena *arena, NotNull cursor, + XPTTypeDescriptorPrefix *tdp) +{ + return XPT_Do8(cursor, &tdp->flags); +} + +bool +DoTypeDescriptor(XPTArena *arena, NotNull cursor, + XPTTypeDescriptor *td, XPTInterfaceDescriptor *id) +{ + if (!DoTypeDescriptorPrefix(arena, cursor, &td->prefix)) { + return false; + } + + switch (XPT_TDP_TAG(td->prefix)) { + case TD_INTERFACE_TYPE: + uint16_t iface; + if (!XPT_Do16(cursor, &iface)) + return false; + td->u.iface.iface_hi8 = (iface >> 8) & 0xff; + td->u.iface.iface_lo8 = iface & 0xff; + break; + case TD_INTERFACE_IS_TYPE: + if (!XPT_Do8(cursor, &td->u.interface_is.argnum)) + return false; + break; + case TD_ARRAY: { + // argnum2 appears in the on-disk format but it isn't used. + uint8_t argnum2 = 0; + if (!XPT_Do8(cursor, &td->u.array.argnum) || + !XPT_Do8(cursor, &argnum2)) + return false; + + if (!InterfaceDescriptorAddTypes(arena, id, 1)) + return false; + td->u.array.additional_type = id->num_additional_types - 1; + + if (!DoTypeDescriptor(arena, cursor, + &id->additional_types[td->u.array.additional_type], + id)) + return false; + break; + } + case TD_PSTRING_SIZE_IS: + case TD_PWSTRING_SIZE_IS: { + // argnum2 appears in the on-disk format but it isn't used. + uint8_t argnum2 = 0; + if (!XPT_Do8(cursor, &td->u.pstring_is.argnum) || + !XPT_Do8(cursor, &argnum2)) + return false; + break; + } + default: + /* nothing special */ + break; + } + return true; +} + +bool +SkipAnnotation(NotNull cursor, bool *isLast) +{ + uint8_t flags; + if (!XPT_Do8(cursor, &flags)) + return false; + + *isLast = XPT_ANN_IS_LAST(flags); + + if (XPT_ANN_IS_PRIVATE(flags)) { + if (!XPT_SkipStringInline(cursor) || + !XPT_SkipStringInline(cursor)) + return false; + } + + return true; +} + diff --git a/xpcom/typelib/xpt/xpt_struct.h b/xpcom/typelib/xpt/xpt_struct.h new file mode 100644 index 000000000..b4da4a3fd --- /dev/null +++ b/xpcom/typelib/xpt/xpt_struct.h @@ -0,0 +1,366 @@ +/* -*- 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/. */ + +/* + * Structures matching the in-memory representation of typelib structures. + * http://www.mozilla.org/scriptable/typelib_file.html + */ + +#ifndef __xpt_struct_h__ +#define __xpt_struct_h__ + +#include "xpt_arena.h" +#include + +extern "C" { + +/* + * Originally, I was going to have structures that exactly matched the on-disk + * representation, but that proved difficult: different compilers can pack + * their structs differently, and that makes overlaying them atop a + * read-from-disk byte buffer troublesome. So now I just have some structures + * that are used in memory, and we're going to write a nice XDR library to + * write them to disk and stuff. It is pure joy. -- shaver + */ + +/* Structures for the typelib components */ + +typedef struct XPTHeader XPTHeader; +typedef struct XPTInterfaceDirectoryEntry XPTInterfaceDirectoryEntry; +typedef struct XPTInterfaceDescriptor XPTInterfaceDescriptor; +typedef struct XPTConstDescriptor XPTConstDescriptor; +typedef struct XPTMethodDescriptor XPTMethodDescriptor; +typedef struct XPTParamDescriptor XPTParamDescriptor; +typedef struct XPTTypeDescriptor XPTTypeDescriptor; +typedef struct XPTTypeDescriptorPrefix XPTTypeDescriptorPrefix; + +#ifndef nsID_h__ +/* + * We can't include nsID.h, because it's full of C++ goop and we're not doing + * C++ here, so we define our own minimal struct. We protect against multiple + * definitions of this struct, though, and use the same field naming. + */ +struct nsID { + uint32_t m0; + uint16_t m1; + uint16_t m2; + uint8_t m3[8]; +}; + +typedef struct nsID nsID; +#endif + +/* + * Every XPCOM typelib file begins with a header. + */ +struct XPTHeader { + // Some of these fields exists in the on-disk format but don't need to be + // stored in memory (other than very briefly, which can be done with local + // variables). + + //uint8_t magic[16]; + uint8_t major_version; + uint8_t minor_version; + uint16_t num_interfaces; + //uint32_t file_length; + XPTInterfaceDirectoryEntry *interface_directory; + //uint32_t data_pool; +}; + +#define XPT_MAGIC "XPCOM\nTypeLib\r\n\032" +/* For error messages. */ +#define XPT_MAGIC_STRING "XPCOM\\nTypeLib\\r\\n\\032" +#define XPT_MAJOR_VERSION 0x01 +#define XPT_MINOR_VERSION 0x02 + +/* Any file with a major version number of XPT_MAJOR_INCOMPATIBLE_VERSION + * or higher is to be considered incompatible by this version of xpt and + * we will refuse to read it. We will return a header with magic, major and + * minor versions set from the file. num_interfaces will be set to zero to + * confirm our inability to read the file; i.e. even if some client of this + * library gets out of sync with us regarding the agreed upon value for + * XPT_MAJOR_INCOMPATIBLE_VERSION, anytime num_interfaces is zero we *know* + * that this library refused to read the file due to version incompatibility. + */ +#define XPT_MAJOR_INCOMPATIBLE_VERSION 0x02 + +/* + * A contiguous array of fixed-size InterfaceDirectoryEntry records begins at + * the byte offset identified by the interface_directory field in the file + * header. The array is used to quickly locate an interface description + * using its IID. No interface should appear more than once in the array. + */ +struct XPTInterfaceDirectoryEntry { + nsID iid; + char *name; + + // This field exists in the on-disk format. But it isn't used so we don't + // allocate space for it in memory. + //char *name_space; + + XPTInterfaceDescriptor *interface_descriptor; +}; + +/* + * An InterfaceDescriptor describes a single XPCOM interface, including all of + * its methods. + */ +struct XPTInterfaceDescriptor { + /* This field ordering minimizes the size of this struct. + * The fields are serialized on disk in a different order. + * See DoInterfaceDescriptor(). + */ + XPTMethodDescriptor *method_descriptors; + XPTConstDescriptor *const_descriptors; + XPTTypeDescriptor *additional_types; + uint16_t parent_interface; + uint16_t num_methods; + uint16_t num_constants; + uint8_t flags; + + /* additional_types are used for arrays where we may need multiple + * XPTTypeDescriptors for a single XPTMethodDescriptor. Since we still + * want to have a simple array of XPTMethodDescriptor (each with a single + * embedded XPTTypeDescriptor), a XPTTypeDescriptor can have a reference + * to an 'additional_type'. That reference is an index in this + * "additional_types" array. So a given XPTMethodDescriptor might have + * a whole chain of these XPTTypeDescriptors to represent, say, a multi + * dimensional array. + * + * Note that in the typelib file these additional types are stored 'inline' + * in the MethodDescriptor. But, in the typelib MethodDescriptors can be + * of varying sizes, where in XPT's in memory mapping of the data we want + * them to be of fixed size. This additional_types scheme is here to allow + * for that. + */ + uint8_t num_additional_types; +}; + +#define XPT_ID_SCRIPTABLE 0x80 +#define XPT_ID_FUNCTION 0x40 +#define XPT_ID_BUILTINCLASS 0x20 +#define XPT_ID_MAIN_PROCESS_SCRIPTABLE_ONLY 0x10 +#define XPT_ID_FLAGMASK 0xf0 + +#define XPT_ID_IS_SCRIPTABLE(flags) (!!(flags & XPT_ID_SCRIPTABLE)) +#define XPT_ID_IS_FUNCTION(flags) (!!(flags & XPT_ID_FUNCTION)) +#define XPT_ID_IS_BUILTINCLASS(flags) (!!(flags & XPT_ID_BUILTINCLASS)) +#define XPT_ID_IS_MAIN_PROCESS_SCRIPTABLE_ONLY(flags) (!!(flags & XPT_ID_MAIN_PROCESS_SCRIPTABLE_ONLY)) + +/* + * A TypeDescriptor is a variable-size record used to identify the type of a + * method argument or return value. + * + * There are three types of TypeDescriptors: + * + * SimpleTypeDescriptor + * InterfaceTypeDescriptor + * InterfaceIsTypeDescriptor + * + * The tag field in the prefix indicates which of the variant TypeDescriptor + * records is being used, and hence the way any remaining fields should be + * parsed. Values from 0 to 17 refer to SimpleTypeDescriptors. The value 18 + * designates an InterfaceTypeDescriptor, while 19 represents an + * InterfaceIsTypeDescriptor. + */ + +/* why bother with a struct? - other code relies on this being a struct */ +struct XPTTypeDescriptorPrefix { + uint8_t flags; +}; + +/* flag bits */ + +#define XPT_TDP_FLAGMASK 0xe0 +#define XPT_TDP_TAGMASK (~XPT_TDP_FLAGMASK) +#define XPT_TDP_TAG(tdp) ((tdp).flags & XPT_TDP_TAGMASK) + +/* + * The following enum maps mnemonic names to the different numeric values + * of XPTTypeDescriptor->tag. + */ +enum XPTTypeDescriptorTags { + 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_VOID = 13, + TD_PNSIID = 14, + TD_DOMSTRING = 15, + TD_PSTRING = 16, + TD_PWSTRING = 17, + TD_INTERFACE_TYPE = 18, + TD_INTERFACE_IS_TYPE = 19, + TD_ARRAY = 20, + TD_PSTRING_SIZE_IS = 21, + TD_PWSTRING_SIZE_IS = 22, + TD_UTF8STRING = 23, + TD_CSTRING = 24, + TD_ASTRING = 25, + TD_JSVAL = 26 +}; + +struct XPTTypeDescriptor { + XPTTypeDescriptorPrefix prefix; + + // The memory layout here doesn't exactly match (for the appropriate types) + // the on-disk format. This is to save memory. + union { + // Used for TD_INTERFACE_IS_TYPE. + struct { + uint8_t argnum; + } interface_is; + + // Used for TD_PSTRING_SIZE_IS, TD_PWSTRING_SIZE_IS. + struct { + uint8_t argnum; + //uint8_t argnum2; // Present on disk, omitted here. + } pstring_is; + + // Used for TD_ARRAY. + struct { + uint8_t argnum; + //uint8_t argnum2; // Present on disk, omitted here. + uint8_t additional_type; // uint16_t on disk, uint8_t here; + // in practice it never exceeds 20. + } array; + + // Used for TD_INTERFACE_TYPE. + struct { + // 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. + uint8_t iface_hi8; + uint8_t iface_lo8; + } iface; + } u; +}; + +/* + * A ConstDescriptor is a variable-size record that records the name and + * value of a scoped interface constant. + * + * The types of the method parameter are restricted to the following subset + * of TypeDescriptors: + * + * int8_t, uint8_t, int16_t, uint16_t, int32_t, uint32_t, + * int64_t, uint64_t, wchar_t, char + * + * The type (and thus the size) of the value record is determined by the + * contents of the associated TypeDescriptor record. For instance, if type + * corresponds to int16_t, then value is a two-byte record consisting of a + * 16-bit signed integer. + */ +union XPTConstValue { + int8_t i8; + uint8_t ui8; + int16_t i16; + uint16_t ui16; + int32_t i32; + uint32_t ui32; + int64_t i64; + uint64_t ui64; + char ch; + uint16_t wch; +}; /* varies according to type */ + +struct XPTConstDescriptor { + char *name; + XPTTypeDescriptor type; + union XPTConstValue value; +}; + +/* + * A ParamDescriptor is a variable-size record used to describe either a + * single argument to a method or a method's result. + */ +struct XPTParamDescriptor { + uint8_t flags; + XPTTypeDescriptor type; +}; + +/* flag bits */ +#define XPT_PD_IN 0x80 +#define XPT_PD_OUT 0x40 +#define XPT_PD_RETVAL 0x20 +#define XPT_PD_SHARED 0x10 +#define XPT_PD_DIPPER 0x08 +#define XPT_PD_OPTIONAL 0x04 +#define XPT_PD_FLAGMASK 0xfc + +#define XPT_PD_IS_IN(flags) (flags & XPT_PD_IN) +#define XPT_PD_IS_OUT(flags) (flags & XPT_PD_OUT) +#define XPT_PD_IS_RETVAL(flags) (flags & XPT_PD_RETVAL) +#define XPT_PD_IS_SHARED(flags) (flags & XPT_PD_SHARED) +#define XPT_PD_IS_DIPPER(flags) (flags & XPT_PD_DIPPER) +#define XPT_PD_IS_OPTIONAL(flags) (flags & XPT_PD_OPTIONAL) + +/* + * A MethodDescriptor is a variable-size record used to describe a single + * interface method. + */ +struct XPTMethodDescriptor { + char *name; + XPTParamDescriptor *params; + XPTParamDescriptor result; + uint8_t flags; + uint8_t num_args; +}; + +/* flag bits */ +#define XPT_MD_GETTER 0x80 +#define XPT_MD_SETTER 0x40 +#define XPT_MD_NOTXPCOM 0x20 +#define XPT_MD_HIDDEN 0x08 +#define XPT_MD_OPT_ARGC 0x04 +#define XPT_MD_CONTEXT 0x02 +#define XPT_MD_FLAGMASK 0xfe + +#define XPT_MD_IS_GETTER(flags) (flags & XPT_MD_GETTER) +#define XPT_MD_IS_SETTER(flags) (flags & XPT_MD_SETTER) +#define XPT_MD_IS_NOTXPCOM(flags) (flags & XPT_MD_NOTXPCOM) +#define XPT_MD_IS_HIDDEN(flags) (flags & XPT_MD_HIDDEN) +#define XPT_MD_WANTS_OPT_ARGC(flags) (flags & XPT_MD_OPT_ARGC) +#define XPT_MD_WANTS_CONTEXT(flags) (flags & XPT_MD_CONTEXT) + +/* + * Annotation records are variable-size records used to store secondary + * information about the typelib, e.g. such as the name of the tool that + * generated the typelib file, the date it was generated, etc. The + * information is stored with very loose format requirements so as to + * allow virtually any private data to be stored in the typelib. + * + * There are two types of Annotations: + * + * EmptyAnnotation + * PrivateAnnotation + * + * The tag field of the prefix discriminates among the variant record + * types for Annotation's. If the tag is 0, this record is an + * EmptyAnnotation. EmptyAnnotation's are ignored - they're only used to + * indicate an array of Annotation's that's completely empty. If the tag + * is 1, the record is a PrivateAnnotation. + * + * We don't actually store annotations; we just skip over them if they are + * present. + */ + +#define XPT_ANN_LAST 0x80 +#define XPT_ANN_IS_LAST(flags) (flags & XPT_ANN_LAST) +#define XPT_ANN_PRIVATE 0x40 +#define XPT_ANN_IS_PRIVATE(flags) (flags & XPT_ANN_PRIVATE) + +} + +#endif /* __xpt_struct_h__ */ diff --git a/xpcom/typelib/xpt/xpt_xdr.cpp b/xpcom/typelib/xpt/xpt_xdr.cpp new file mode 100644 index 000000000..04b90422a --- /dev/null +++ b/xpcom/typelib/xpt/xpt_xdr.cpp @@ -0,0 +1,227 @@ +/* -*- 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/. */ + +/* Implementation of XDR primitives. */ + +#include "xpt_xdr.h" +#include "nscore.h" +#include /* strchr */ +#include "mozilla/EndianUtils.h" + +#define CURS_POOL_OFFSET_RAW(cursor) \ + ((cursor)->pool == XPT_HEADER \ + ? (cursor)->offset \ + : (XPT_ASSERT((cursor)->state->data_offset), \ + (cursor)->offset + (cursor)->state->data_offset)) + +#define CURS_POOL_OFFSET(cursor) \ + (CURS_POOL_OFFSET_RAW(cursor) - 1) + +/* can be used as lvalue */ +#define CURS_POINT(cursor) \ + ((cursor)->state->pool_data[CURS_POOL_OFFSET(cursor)]) + +static bool +CHECK_COUNT(NotNull cursor, uint32_t space) +{ + // Fail if we're in the data area and about to exceed the allocation. + // XXX Also fail if we're in the data area and !state->data_offset + if (cursor->pool == XPT_DATA && + (CURS_POOL_OFFSET(cursor) + space > (cursor)->state->pool_allocated)) { + XPT_ASSERT(0); + fprintf(stderr, "FATAL: no room for %d in cursor\n", space); + return false; + } + + return true; +} + +XPT_PUBLIC_API(void) +XPT_InitXDRState(XPTState* state, char *data, uint32_t len) +{ + state->next_cursor[0] = state->next_cursor[1] = 1; + state->pool_data = data; + state->pool_allocated = len; +} + +/* All offsets are 1-based */ +XPT_PUBLIC_API(void) +XPT_SetDataOffset(XPTState *state, uint32_t data_offset) +{ + state->data_offset = data_offset; +} + +XPT_PUBLIC_API(bool) +XPT_MakeCursor(XPTState *state, XPTPool pool, uint32_t len, + NotNull cursor) +{ + cursor->state = state; + cursor->pool = pool; + cursor->bits = 0; + cursor->offset = state->next_cursor[pool]; + + if (!(CHECK_COUNT(cursor, len))) + return false; + + /* this check should be in CHECK_CURSOR */ + if (pool == XPT_DATA && !state->data_offset) { + fprintf(stderr, "no data offset for XPT_DATA cursor!\n"); + return false; + } + + state->next_cursor[pool] += len; + + return true; +} + +XPT_PUBLIC_API(bool) +XPT_SeekTo(NotNull cursor, uint32_t offset) +{ + /* XXX do some real checking and update len and stuff */ + cursor->offset = offset; + return true; +} + +XPT_PUBLIC_API(bool) +XPT_SkipStringInline(NotNull cursor) +{ + uint16_t length; + if (!XPT_Do16(cursor, &length)) + return false; + + uint8_t byte; + for (uint16_t i = 0; i < length; i++) + if (!XPT_Do8(cursor, &byte)) + return false; + + return true; +} + +XPT_PUBLIC_API(bool) +XPT_DoCString(XPTArena *arena, NotNull cursor, char **identp, + bool ignore) +{ + uint32_t offset = 0; + if (!XPT_Do32(cursor, &offset)) + return false; + + if (!offset) { + *identp = NULL; + return true; + } + + XPTCursor my_cursor; + my_cursor.pool = XPT_DATA; + my_cursor.offset = offset; + my_cursor.state = cursor->state; + char* start = &CURS_POINT(&my_cursor); + + char* end = strchr(start, 0); /* find the end of the string */ + if (!end) { + fprintf(stderr, "didn't find end of string on decode!\n"); + return false; + } + int len = end - start; + XPT_ASSERT(len > 0); + + if (!ignore) { + char *ident = (char*)XPT_CALLOC1(arena, len + 1u); + if (!ident) + return false; + + memcpy(ident, start, (size_t)len); + ident[len] = 0; + *identp = ident; + } + + return true; +} + +/* + * IIDs are written in struct order, in the usual big-endian way. From the + * typelib file spec: + * + * "For example, this IID: + * {00112233-4455-6677-8899-aabbccddeeff} + * is converted to the 128-bit value + * 0x00112233445566778899aabbccddeeff + * Note that the byte storage order corresponds to the layout of the nsIID + * C-struct on a big-endian architecture." + * + * (http://www.mozilla.org/scriptable/typelib_file.html#iid) + */ +XPT_PUBLIC_API(bool) +XPT_DoIID(NotNull cursor, nsID *iidp) +{ + int i; + + if (!XPT_Do32(cursor, &iidp->m0) || + !XPT_Do16(cursor, &iidp->m1) || + !XPT_Do16(cursor, &iidp->m2)) + return false; + + for (i = 0; i < 8; i++) + if (!XPT_Do8(cursor, (uint8_t *)&iidp->m3[i])) + return false; + + return true; +} + +// MSVC apparently cannot handle functions as template parameters very well, +// so we need to use a macro approach here. + +#define XPT_DOINT(T, func, valuep) \ + do { \ + const size_t sz = sizeof(T); \ + \ + if (!CHECK_COUNT(cursor, sz)) { \ + return false; \ + } \ + \ + *valuep = func(&CURS_POINT(cursor)); \ + cursor->offset += sz; \ + return true; \ + } while(0) + +XPT_PUBLIC_API(bool) +XPT_Do64(NotNull cursor, int64_t *u64p) +{ + XPT_DOINT(int64_t, mozilla::BigEndian::readInt64, u64p); +} + +/* + * When we're handling 32- or 16-bit quantities, we handle a byte at a time to + * avoid alignment issues. Someone could come and optimize this to detect + * well-aligned cases and do a single store, if they cared. I might care + * later. + */ +XPT_PUBLIC_API(bool) +XPT_Do32(NotNull cursor, uint32_t *u32p) +{ + XPT_DOINT(uint32_t, mozilla::BigEndian::readUint32, u32p); +} + +XPT_PUBLIC_API(bool) +XPT_Do16(NotNull cursor, uint16_t *u16p) +{ + XPT_DOINT(uint16_t, mozilla::BigEndian::readUint16, u16p); +} + +#undef XPT_DOINT + +XPT_PUBLIC_API(bool) +XPT_Do8(NotNull cursor, uint8_t *u8p) +{ + if (!CHECK_COUNT(cursor, 1)) + return false; + + *u8p = CURS_POINT(cursor); + + cursor->offset++; + + return true; +} + + diff --git a/xpcom/typelib/xpt/xpt_xdr.h b/xpcom/typelib/xpt/xpt_xdr.h new file mode 100644 index 000000000..7bf23aa40 --- /dev/null +++ b/xpcom/typelib/xpt/xpt_xdr.h @@ -0,0 +1,86 @@ +/* -*- 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/. */ + +/* + * Basic APIs for streaming typelib structures from disk. + */ + +#ifndef __xpt_xdr_h__ +#define __xpt_xdr_h__ + +#include "xpt_struct.h" +#include "mozilla/NotNull.h" + +using mozilla::NotNull; + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct XPTState XPTState; +typedef struct XPTCursor XPTCursor; + +extern XPT_PUBLIC_API(bool) +XPT_SkipStringInline(NotNull cursor); + +extern XPT_PUBLIC_API(bool) +XPT_DoCString(XPTArena *arena, NotNull cursor, char **strp, + bool ignore = false); + +extern XPT_PUBLIC_API(bool) +XPT_DoIID(NotNull cursor, nsID *iidp); + +extern XPT_PUBLIC_API(bool) +XPT_Do64(NotNull cursor, int64_t *u64p); + +extern XPT_PUBLIC_API(bool) +XPT_Do32(NotNull cursor, uint32_t *u32p); + +extern XPT_PUBLIC_API(bool) +XPT_Do16(NotNull cursor, uint16_t *u16p); + +extern XPT_PUBLIC_API(bool) +XPT_Do8(NotNull cursor, uint8_t *u8p); + +extern XPT_PUBLIC_API(bool) +XPT_DoHeader(XPTArena *arena, NotNull cursor, XPTHeader **headerp); + +typedef enum { + XPT_HEADER = 0, + XPT_DATA = 1 +} XPTPool; + +struct XPTState { + uint32_t data_offset; + uint32_t next_cursor[2]; + char *pool_data; + uint32_t pool_allocated; +}; + +struct XPTCursor { + XPTState *state; + XPTPool pool; + uint32_t offset; + uint8_t bits; +}; + +extern XPT_PUBLIC_API(void) +XPT_InitXDRState(XPTState* state, char* data, uint32_t len); + +extern XPT_PUBLIC_API(bool) +XPT_MakeCursor(XPTState *state, XPTPool pool, uint32_t len, + NotNull cursor); + +extern XPT_PUBLIC_API(bool) +XPT_SeekTo(NotNull cursor, uint32_t offset); + +extern XPT_PUBLIC_API(void) +XPT_SetDataOffset(XPTState *state, uint32_t data_offset); + +#ifdef __cplusplus +} +#endif + +#endif /* __xpt_xdr_h__ */ diff --git a/xpcom/windbgdlg/Makefile.in b/xpcom/windbgdlg/Makefile.in new file mode 100644 index 000000000..254509ee7 --- /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 000000000..f0343d31a --- /dev/null +++ b/xpcom/windbgdlg/moz.build @@ -0,0 +1,9 @@ +# -*- 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' +]) diff --git a/xpcom/windbgdlg/windbgdlg.cpp b/xpcom/windbgdlg/windbgdlg.cpp new file mode 100644 index 000000000..a51fe134f --- /dev/null +++ b/xpcom/windbgdlg/windbgdlg.cpp @@ -0,0 +1,121 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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); + int argc =0; + 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 000000000..7b2de5025 --- /dev/null +++ b/xpcom/xpcom-config.h.in @@ -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/. */ + +/* Global defines needed by xpcom clients */ + +#ifndef _XPCOM_CONFIG_H_ +#define _XPCOM_CONFIG_H_ + +/* Define this to throw() if the compiler complains about + * constructors returning NULL + */ +#undef CPP_THROW_NEW + +/* Define if the c++ compiler can resolve ambiguity with |using| */ +#undef HAVE_CPP_AMBIGUITY_RESOLVING_USING + +/* Define if a dyanmic_cast to void* gives the most derived object */ +#undef HAVE_CPP_DYNAMIC_CAST_TO_VOID_PTR + +/* Define to a string describing the XPCOM ABI in use */ +#undef TARGET_XPCOM_ABI + +#endif /* _XPCOM_CONFIG_H_ */ diff --git a/xpcom/xpcom-private.h.in b/xpcom/xpcom-private.h.in new file mode 100644 index 000000000..66c024fd8 --- /dev/null +++ b/xpcom/xpcom-private.h.in @@ -0,0 +1,50 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 following defines are only used by the xpcom implementation */ + +#ifndef _XPCOM_PRIVATE_H_ +#define _XPCOM_PRIVATE_H_ + +/* Define if getpagesize() is available */ +#undef HAVE_GETPAGESIZE + +/* Define if iconv() is available */ +#undef HAVE_ICONV + +/* Define if iconv() supports const input */ +#undef HAVE_ICONV_WITH_CONST_INPUT + +/* Define if mbrtowc() is available */ +#undef HAVE_MBRTOWC + +/* Define if wcrtomb() is available */ +#undef HAVE_WCRTOMB + +/* Define if statvfs64() is available */ +#undef HAVE_STATVFS64 + +/* Define if statvfs() is available */ +#undef HAVE_STATVFS + +/* Define if statfs64() is available */ +#undef HAVE_STATFS64 + +/* Define if statfs() is available */ +#undef HAVE_STATFS + +/* Define if is present */ +#undef HAVE_SYS_STATVFS_H + +/* Define if is present */ +#undef HAVE_SYS_STATFS_H + +/* Define if is present */ +#undef HAVE_SYS_VFS_H + +/* Define if is present */ +#undef HAVE_SYS_MOUNT_H + +#endif /* _XPCOM_PRIVATE_H_ */ + diff --git a/xpcom/xpidl/Makefile.in b/xpcom/xpidl/Makefile.in new file mode 100644 index 000000000..b58ad4866 --- /dev/null +++ b/xpcom/xpidl/Makefile.in @@ -0,0 +1,10 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.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 py_action,process_install_manifest,$(DIST)/idl $(DEPTH)/_build_manifests/install/dist_idl) + $(call SUBMAKE,xpidl,$(DEPTH)/config/makefiles/xpidl) + +clean clobber realclean clobber_all distclean:: + $(call SUBMAKE,$@,$(DEPTH)/config/makefiles/xpidl) diff --git a/xpcom/xpidl/moz.build b/xpcom/xpidl/moz.build new file mode 100644 index 000000000..568f361a5 --- /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