1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 #include "MemoryBlobImpl.h"
8 #include "mozilla/ipc/InputStreamUtils.h"
9 #include "mozilla/IntegerPrintfMacros.h"
10 #include "mozilla/SHA1.h"
11 #include "nsIMemoryReporter.h"
12 #include "nsMemory.h"
13 #include "nsPrintfCString.h"
14 #include "nsRFPService.h"
15 #include "nsStringStream.h"
16 #include "prtime.h"
17 
18 namespace mozilla::dom {
19 
20 NS_IMPL_ADDREF(MemoryBlobImpl::DataOwnerAdapter)
NS_IMPL_RELEASE(MemoryBlobImpl::DataOwnerAdapter)21 NS_IMPL_RELEASE(MemoryBlobImpl::DataOwnerAdapter)
22 
23 NS_INTERFACE_MAP_BEGIN(MemoryBlobImpl::DataOwnerAdapter)
24   NS_INTERFACE_MAP_ENTRY(nsIInputStream)
25   NS_INTERFACE_MAP_ENTRY(nsISeekableStream)
26   NS_INTERFACE_MAP_ENTRY(nsITellableStream)
27   NS_INTERFACE_MAP_ENTRY(nsICloneableInputStream)
28   NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIIPCSerializableInputStream,
29                                      mSerializableInputStream)
30   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIInputStream)
31 NS_INTERFACE_MAP_END
32 
33 // static
34 already_AddRefed<MemoryBlobImpl> MemoryBlobImpl::CreateWithCustomLastModified(
35     void* aMemoryBuffer, uint64_t aLength, const nsAString& aName,
36     const nsAString& aContentType, int64_t aLastModifiedDate) {
37   RefPtr<MemoryBlobImpl> blobImpl = new MemoryBlobImpl(
38       aMemoryBuffer, aLength, aName, aContentType, aLastModifiedDate);
39   return blobImpl.forget();
40 }
41 
42 // static
CreateWithLastModifiedNow(void * aMemoryBuffer,uint64_t aLength,const nsAString & aName,const nsAString & aContentType,bool aCrossOriginIsolated)43 already_AddRefed<MemoryBlobImpl> MemoryBlobImpl::CreateWithLastModifiedNow(
44     void* aMemoryBuffer, uint64_t aLength, const nsAString& aName,
45     const nsAString& aContentType, bool aCrossOriginIsolated) {
46   int64_t lastModificationDate = nsRFPService::ReduceTimePrecisionAsUSecs(
47       PR_Now(), 0,
48       /* aIsSystemPrincipal */ false, aCrossOriginIsolated);
49   return CreateWithCustomLastModified(aMemoryBuffer, aLength, aName,
50                                       aContentType, lastModificationDate);
51 }
52 
Create(DataOwner * aDataOwner,uint32_t aStart,uint32_t aLength,nsIInputStream ** _retval)53 nsresult MemoryBlobImpl::DataOwnerAdapter::Create(DataOwner* aDataOwner,
54                                                   uint32_t aStart,
55                                                   uint32_t aLength,
56                                                   nsIInputStream** _retval) {
57   nsresult rv;
58   MOZ_ASSERT(aDataOwner, "Uh ...");
59 
60   nsCOMPtr<nsIInputStream> stream;
61 
62   rv = NS_NewByteInputStream(
63       getter_AddRefs(stream),
64       Span(static_cast<const char*>(aDataOwner->mData) + aStart, aLength),
65       NS_ASSIGNMENT_DEPEND);
66   NS_ENSURE_SUCCESS(rv, rv);
67 
68   NS_ADDREF(*_retval = new MemoryBlobImpl::DataOwnerAdapter(aDataOwner, stream,
69                                                             aLength));
70 
71   return NS_OK;
72 }
73 
Serialize(mozilla::ipc::InputStreamParams & aParams,FileDescriptorArray & aFileDescriptors,bool aDelayedStart,uint32_t aMaxSize,uint32_t * aSizeUsed,mozilla::ipc::ChildToParentStreamActorManager * aManager)74 void MemoryBlobImpl::DataOwnerAdapter::Serialize(
75     mozilla::ipc::InputStreamParams& aParams,
76     FileDescriptorArray& aFileDescriptors, bool aDelayedStart,
77     uint32_t aMaxSize, uint32_t* aSizeUsed,
78     mozilla::ipc::ChildToParentStreamActorManager* aManager) {
79   SerializeInternal(aParams, aFileDescriptors, aDelayedStart, aMaxSize,
80                     aSizeUsed, aManager);
81 }
82 
Serialize(mozilla::ipc::InputStreamParams & aParams,FileDescriptorArray & aFileDescriptors,bool aDelayedStart,uint32_t aMaxSize,uint32_t * aSizeUsed,mozilla::ipc::ParentToChildStreamActorManager * aManager)83 void MemoryBlobImpl::DataOwnerAdapter::Serialize(
84     mozilla::ipc::InputStreamParams& aParams,
85     FileDescriptorArray& aFileDescriptors, bool aDelayedStart,
86     uint32_t aMaxSize, uint32_t* aSizeUsed,
87     mozilla::ipc::ParentToChildStreamActorManager* aManager) {
88   SerializeInternal(aParams, aFileDescriptors, aDelayedStart, aMaxSize,
89                     aSizeUsed, aManager);
90 }
91 
92 template <typename M>
SerializeInternal(mozilla::ipc::InputStreamParams & aParams,FileDescriptorArray & aFileDescriptors,bool aDelayedStart,uint32_t aMaxSize,uint32_t * aSizeUsed,M * aManager)93 void MemoryBlobImpl::DataOwnerAdapter::SerializeInternal(
94     mozilla::ipc::InputStreamParams& aParams,
95     FileDescriptorArray& aFileDescriptors, bool aDelayedStart,
96     uint32_t aMaxSize, uint32_t* aSizeUsed, M* aManager) {
97   MOZ_ASSERT(aSizeUsed);
98 
99   // If we'd be serializing the underlying nsStringInputStream as a pipe,
100   // serialize ourselves as a pipe directly instead to avoid the string copy in
101   // nsStringInputStream's serialize method. Otherwise we'll delegate and
102   // serialize as a string internally.
103   if (mLength >= aMaxSize) {
104     *aSizeUsed = 0;
105     mozilla::ipc::InputStreamHelper::SerializeInputStreamAsPipe(
106         this, aParams, aDelayedStart, aManager);
107     return;
108   }
109 
110   mSerializableInputStream->Serialize(aParams, aFileDescriptors, aDelayedStart,
111                                       aMaxSize, aSizeUsed, aManager);
112 }
113 
Deserialize(const mozilla::ipc::InputStreamParams &,const FileDescriptorArray &)114 bool MemoryBlobImpl::DataOwnerAdapter::Deserialize(
115     const mozilla::ipc::InputStreamParams&, const FileDescriptorArray&) {
116   MOZ_CRASH("This method should never be called");
117   return false;
118 }
119 
CreateSlice(uint64_t aStart,uint64_t aLength,const nsAString & aContentType,ErrorResult & aRv)120 already_AddRefed<BlobImpl> MemoryBlobImpl::CreateSlice(
121     uint64_t aStart, uint64_t aLength, const nsAString& aContentType,
122     ErrorResult& aRv) {
123   RefPtr<BlobImpl> impl =
124       new MemoryBlobImpl(this, aStart, aLength, aContentType);
125   return impl.forget();
126 }
127 
CreateInputStream(nsIInputStream ** aStream,ErrorResult & aRv)128 void MemoryBlobImpl::CreateInputStream(nsIInputStream** aStream,
129                                        ErrorResult& aRv) {
130   if (mLength >= INT32_MAX) {
131     aRv.Throw(NS_ERROR_FAILURE);
132     return;
133   }
134 
135   aRv = MemoryBlobImpl::DataOwnerAdapter::Create(mDataOwner, mStart, mLength,
136                                                  aStream);
137 }
138 
139 /* static */
140 StaticMutex MemoryBlobImpl::DataOwner::sDataOwnerMutex;
141 
142 /* static */ StaticAutoPtr<LinkedList<MemoryBlobImpl::DataOwner>>
143     MemoryBlobImpl::DataOwner::sDataOwners;
144 
145 /* static */
146 bool MemoryBlobImpl::DataOwner::sMemoryReporterRegistered = false;
147 
148 MOZ_DEFINE_MALLOC_SIZE_OF(MemoryFileDataOwnerMallocSizeOf)
149 
150 class MemoryBlobImplDataOwnerMemoryReporter final : public nsIMemoryReporter {
151   ~MemoryBlobImplDataOwnerMemoryReporter() = default;
152 
153  public:
154   NS_DECL_THREADSAFE_ISUPPORTS
155 
CollectReports(nsIHandleReportCallback * aHandleReport,nsISupports * aData,bool aAnonymize)156   NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport,
157                             nsISupports* aData, bool aAnonymize) override {
158     typedef MemoryBlobImpl::DataOwner DataOwner;
159 
160     StaticMutexAutoLock lock(DataOwner::sDataOwnerMutex);
161 
162     if (!DataOwner::sDataOwners) {
163       return NS_OK;
164     }
165 
166     const size_t LARGE_OBJECT_MIN_SIZE = 8 * 1024;
167     size_t smallObjectsTotal = 0;
168 
169     for (DataOwner* owner = DataOwner::sDataOwners->getFirst(); owner;
170          owner = owner->getNext()) {
171       size_t size = MemoryFileDataOwnerMallocSizeOf(owner->mData);
172 
173       if (size < LARGE_OBJECT_MIN_SIZE) {
174         smallObjectsTotal += size;
175       } else {
176         SHA1Sum sha1;
177         sha1.update(owner->mData, owner->mLength);
178         uint8_t digest[SHA1Sum::kHashSize];  // SHA1 digests are 20 bytes long.
179         sha1.finish(digest);
180 
181         nsAutoCString digestString;
182         for (size_t i = 0; i < sizeof(digest); i++) {
183           digestString.AppendPrintf("%02x", digest[i]);
184         }
185 
186         aHandleReport->Callback(
187             /* process */ ""_ns,
188             nsPrintfCString(
189                 "explicit/dom/memory-file-data/large/file(length=%" PRIu64
190                 ", sha1=%s)",
191                 owner->mLength,
192                 aAnonymize ? "<anonymized>" : digestString.get()),
193             KIND_HEAP, UNITS_BYTES, size,
194             nsPrintfCString(
195                 "Memory used to back a memory file of length %" PRIu64
196                 " bytes.  The file "
197                 "has a sha1 of %s.\n\n"
198                 "Note that the allocator may round up a memory file's length "
199                 "-- "
200                 "that is, an N-byte memory file may take up more than N bytes "
201                 "of "
202                 "memory.",
203                 owner->mLength, digestString.get()),
204             aData);
205       }
206     }
207 
208     if (smallObjectsTotal > 0) {
209       aHandleReport->Callback(
210           /* process */ ""_ns, "explicit/dom/memory-file-data/small"_ns,
211           KIND_HEAP, UNITS_BYTES, smallObjectsTotal,
212           nsPrintfCString(
213               "Memory used to back small memory files (i.e. those taking up "
214               "less "
215               "than %zu bytes of memory each).\n\n"
216               "Note that the allocator may round up a memory file's length -- "
217               "that is, an N-byte memory file may take up more than N bytes of "
218               "memory.",
219               LARGE_OBJECT_MIN_SIZE),
220           aData);
221     }
222 
223     return NS_OK;
224   }
225 };
226 
NS_IMPL_ISUPPORTS(MemoryBlobImplDataOwnerMemoryReporter,nsIMemoryReporter)227 NS_IMPL_ISUPPORTS(MemoryBlobImplDataOwnerMemoryReporter, nsIMemoryReporter)
228 
229 /* static */
230 void MemoryBlobImpl::DataOwner::EnsureMemoryReporterRegistered() {
231   sDataOwnerMutex.AssertCurrentThreadOwns();
232   if (sMemoryReporterRegistered) {
233     return;
234   }
235 
236   RegisterStrongMemoryReporter(new MemoryBlobImplDataOwnerMemoryReporter());
237 
238   sMemoryReporterRegistered = true;
239 }
240 
241 }  // namespace mozilla::dom
242