Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/dom/ipc/SharedMap.cpp
Line
Count
Source (jump to first uncovered line)
1
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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 "SharedMap.h"
8
#include "SharedMapChangeEvent.h"
9
10
#include "MemMapSnapshot.h"
11
#include "ScriptPreloader-inl.h"
12
13
#include "mozilla/dom/ContentParent.h"
14
#include "mozilla/dom/ContentProcessMessageManager.h"
15
#include "mozilla/dom/IPCBlobUtils.h"
16
#include "mozilla/dom/ScriptSettings.h"
17
18
using namespace mozilla::loader;
19
20
namespace mozilla {
21
22
using namespace ipc;
23
24
namespace dom {
25
namespace ipc {
26
27
// Align to size of uintptr_t here, to be safe. It's probably not strictly
28
// necessary, though.
29
constexpr size_t kStructuredCloneAlign = sizeof(uintptr_t);
30
31
32
static inline void
33
AlignTo(size_t* aOffset, size_t aAlign)
34
0
{
35
0
  if (auto mod = *aOffset % aAlign) {
36
0
    *aOffset += aAlign - mod;
37
0
  }
38
0
}
39
40
41
SharedMap::SharedMap()
42
  : DOMEventTargetHelper()
43
0
{}
44
45
SharedMap::SharedMap(nsIGlobalObject* aGlobal, const FileDescriptor& aMapFile,
46
                     size_t aMapSize, nsTArray<RefPtr<BlobImpl>>&& aBlobs)
47
  : DOMEventTargetHelper(aGlobal)
48
  , mBlobImpls(std::move(aBlobs))
49
0
{
50
0
  mMapFile.reset(new FileDescriptor(aMapFile));
51
0
  mMapSize = aMapSize;
52
0
}
53
54
55
bool
56
SharedMap::Has(const nsACString& aName)
57
0
{
58
0
  return mEntries.Contains(aName);
59
0
}
60
61
void
62
SharedMap::Get(JSContext* aCx,
63
               const nsACString& aName,
64
               JS::MutableHandleValue aRetVal,
65
               ErrorResult& aRv)
66
0
{
67
0
  auto res = MaybeRebuild();
68
0
  if (res.isErr()) {
69
0
    aRv.Throw(res.unwrapErr());
70
0
    return;
71
0
  }
72
0
73
0
  Entry* entry = mEntries.Get(aName);
74
0
  if (!entry) {
75
0
    aRetVal.setNull();
76
0
    return;
77
0
  }
78
0
79
0
  entry->Read(aCx, aRetVal, aRv);
80
0
}
81
82
void
83
SharedMap::Entry::Read(JSContext* aCx,
84
                       JS::MutableHandleValue aRetVal,
85
                       ErrorResult& aRv)
86
0
{
87
0
  if (mData.is<StructuredCloneData>()) {
88
0
    // We have a temporary buffer for a key that was changed after the last
89
0
    // snapshot. Just decode it directly.
90
0
    auto& holder = mData.as<StructuredCloneData>();
91
0
    holder.Read(aCx, aRetVal, aRv);
92
0
    return;
93
0
  }
94
0
95
0
  // We have a pointer to a shared memory region containing our structured
96
0
  // clone data. Create a temporary buffer to decode that data, and then
97
0
  // discard it so that we don't keep a separate process-local copy around any
98
0
  // longer than necessary.
99
0
  StructuredCloneData holder;
100
0
  if (!holder.CopyExternalData(Data(), Size())) {
101
0
    aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
102
0
    return;
103
0
  }
104
0
  if (mBlobCount) {
105
0
    holder.BlobImpls().AppendElements(Blobs());
106
0
  }
107
0
  holder.Read(aCx, aRetVal, aRv);
108
0
}
109
110
FileDescriptor
111
SharedMap::CloneMapFile() const
112
0
{
113
0
  if (mMap.initialized()) {
114
0
    return mMap.cloneHandle();
115
0
  }
116
0
  return *mMapFile;
117
0
}
118
119
void
120
SharedMap::Update(const FileDescriptor& aMapFile, size_t aMapSize,
121
                  nsTArray<RefPtr<BlobImpl>>&& aBlobs,
122
                  nsTArray<nsCString>&& aChangedKeys)
123
0
{
124
0
  MOZ_DIAGNOSTIC_ASSERT(!mWritable);
125
0
126
0
  mMap.reset();
127
0
  if (mMapFile) {
128
0
    *mMapFile = aMapFile;
129
0
  } else {
130
0
    mMapFile.reset(new FileDescriptor(aMapFile));
131
0
  }
132
0
  mMapSize = aMapSize;
133
0
  mEntries.Clear();
134
0
  mEntryArray.reset();
135
0
136
0
  mBlobImpls = std::move(aBlobs);
137
0
138
0
139
0
  AutoEntryScript aes(GetParentObject(), "SharedMap change event");
140
0
  JSContext* cx = aes.cx();
141
0
142
0
  RootedDictionary<MozSharedMapChangeEventInit> init(cx);
143
0
  if (!init.mChangedKeys.SetCapacity(aChangedKeys.Length(), fallible)) {
144
0
    NS_WARNING("Failed to dispatch SharedMap change event");
145
0
    return;
146
0
  }
147
0
  for (auto& key : aChangedKeys) {
148
0
    Unused << init.mChangedKeys.AppendElement(NS_ConvertUTF8toUTF16(key),
149
0
                                              fallible);
150
0
  }
151
0
152
0
  RefPtr<SharedMapChangeEvent> event =
153
0
    SharedMapChangeEvent::Constructor(this, NS_LITERAL_STRING("change"), init);
154
0
  event->SetTrusted(true);
155
0
156
0
  DispatchEvent(*event);
157
0
}
158
159
160
const nsTArray<SharedMap::Entry*>&
161
SharedMap::EntryArray() const
162
0
{
163
0
  if (mEntryArray.isNothing()) {
164
0
    MaybeRebuild();
165
0
166
0
    mEntryArray.emplace(mEntries.Count());
167
0
    auto& array = mEntryArray.ref();
168
0
    for (auto& entry : IterHash(mEntries)) {
169
0
      array.AppendElement(entry);
170
0
    }
171
0
  }
172
0
173
0
  return mEntryArray.ref();
174
0
}
175
176
const nsString
177
SharedMap::GetKeyAtIndex(uint32_t aIndex) const
178
0
{
179
0
  return NS_ConvertUTF8toUTF16(EntryArray()[aIndex]->Name());
180
0
}
181
182
bool
183
SharedMap::GetValueAtIndex(JSContext* aCx, uint32_t aIndex,
184
                           JS::MutableHandle<JS::Value> aResult) const
185
0
{
186
0
  ErrorResult rv;
187
0
  EntryArray()[aIndex]->Read(aCx, aResult, rv);
188
0
  if (rv.MaybeSetPendingException(aCx)) {
189
0
    return false;
190
0
  }
191
0
  return true;
192
0
}
193
194
void
195
SharedMap::Entry::TakeData(StructuredCloneData&& aHolder)
196
0
{
197
0
  mData = AsVariant(std::move(aHolder));
198
0
199
0
  mSize = Holder().Data().Size();
200
0
  mBlobCount = Holder().BlobImpls().Length();
201
0
}
202
203
void
204
SharedMap::Entry::ExtractData(char* aDestPtr, uint32_t aNewOffset, uint16_t aNewBlobOffset)
205
0
{
206
0
  if (mData.is<StructuredCloneData>()) {
207
0
    char* ptr = aDestPtr;
208
0
    Holder().Data().ForEachDataChunk([&](const char* aData, size_t aSize) {
209
0
        memcpy(ptr, aData, aSize);
210
0
        ptr += aSize;
211
0
        return true;
212
0
    });
213
0
    MOZ_ASSERT(uint32_t(ptr - aDestPtr) == mSize);
214
0
  } else {
215
0
    memcpy(aDestPtr, Data(), mSize);
216
0
  }
217
0
218
0
  mData = AsVariant(aNewOffset);
219
0
  mBlobOffset = aNewBlobOffset;
220
0
}
221
222
Result<Ok, nsresult>
223
SharedMap::MaybeRebuild()
224
0
{
225
0
  if (!mMapFile) {
226
0
    return Ok();
227
0
  }
228
0
229
0
  // This function maps a shared memory region created by Serialize() and reads
230
0
  // its header block to build a new mEntries hashtable of its contents.
231
0
  //
232
0
  // The entries created by this function contain a pointer to this SharedMap
233
0
  // instance, and the offsets and sizes of their structured clone data within
234
0
  // its shared memory region. When needed, that structured clone data is
235
0
  // retrieved directly as indexes into the SharedMap's shared memory region.
236
0
237
0
  MOZ_TRY(mMap.initWithHandle(*mMapFile, mMapSize));
238
0
  mMapFile.reset();
239
0
240
0
  // We should be able to pass this range as an initializer list or an immediate
241
0
  // param, but gcc currently chokes on that if optimization is enabled, and
242
0
  // initializes everything to 0.
243
0
  Range<uint8_t> range(&mMap.get<uint8_t>()[0], mMap.size());
244
0
  InputBuffer buffer(range);
245
0
246
0
  uint32_t count;
247
0
  buffer.codeUint32(count);
248
0
249
0
  for (uint32_t i = 0; i < count; i++) {
250
0
    auto entry = MakeUnique<Entry>(*this);
251
0
    entry->Code(buffer);
252
0
253
0
    // This buffer was created at runtime, during this session, so any errors
254
0
    // indicate memory corruption, and are fatal.
255
0
    MOZ_RELEASE_ASSERT(!buffer.error());
256
0
257
0
    // Note: Order of evaluation of function arguments is not guaranteed, so we
258
0
    // can't use entry.release() in place of entry.get() without entry->Name()
259
0
    // sometimes resulting in a null dereference.
260
0
    mEntries.Put(entry->Name(), entry.get());
261
0
    Unused << entry.release();
262
0
  }
263
0
264
0
  return Ok();
265
0
}
266
267
void
268
SharedMap::MaybeRebuild() const
269
0
{
270
0
  Unused << const_cast<SharedMap*>(this)->MaybeRebuild();
271
0
}
272
273
WritableSharedMap::WritableSharedMap()
274
  : SharedMap()
275
0
{
276
0
  mWritable = true;
277
0
  // Serialize the initial empty contents of the map immediately so that we
278
0
  // always have a file descriptor to send to callers of CloneMapFile().
279
0
  Unused << Serialize();
280
0
  MOZ_RELEASE_ASSERT(mMap.initialized());
281
0
}
282
283
SharedMap*
284
WritableSharedMap::GetReadOnly()
285
0
{
286
0
  if (!mReadOnly) {
287
0
    nsTArray<RefPtr<BlobImpl>> blobs(mBlobImpls);
288
0
    mReadOnly = new SharedMap(ContentProcessMessageManager::Get()->GetParentObject(),
289
0
                              CloneMapFile(), MapSize(), std::move(blobs));
290
0
  }
291
0
  return mReadOnly;
292
0
}
293
294
Result<Ok, nsresult>
295
WritableSharedMap::Serialize()
296
0
{
297
0
  // Serializes a new snapshot of the map, initializes a new read-only shared
298
0
  // memory region with its contents, and updates all entries to point to that
299
0
  // new snapshot.
300
0
  //
301
0
  // The layout of the snapshot is as follows:
302
0
  //
303
0
  // - A header containing a uint32 count field containing the number of
304
0
  //   entries in the map, followed by that number of serialized entry headers,
305
0
  //   as produced by Entry::Code.
306
0
  //
307
0
  // - A data block containing structured clone data for each of the entries'
308
0
  //   values. This data is referenced by absolute byte offsets from the start
309
0
  //   of the shared memory region, encoded in each of the entry header values.
310
0
  //   Each entry's data is aligned to kStructuredCloneAlign, and therefore may
311
0
  //   have alignment padding before it.
312
0
  //
313
0
  // This serialization format is decoded by the MaybeRebuild() method of
314
0
  // read-only SharedMap() instances, and used to populate their mEntries
315
0
  // hashtables.
316
0
  //
317
0
  // Writable instances never read the header blocks, but instead directly
318
0
  // update their Entry instances to point to the appropriate offsets in the
319
0
  // shared memory region created by this function.
320
0
321
0
  uint32_t count = mEntries.Count();
322
0
323
0
  size_t dataSize = 0;
324
0
  size_t headerSize = sizeof(count);
325
0
  size_t blobCount = 0;
326
0
327
0
  for (auto& entry : IterHash(mEntries)) {
328
0
    headerSize += entry->HeaderSize();
329
0
    blobCount += entry->BlobCount();
330
0
331
0
    dataSize += entry->Size();
332
0
    AlignTo(&dataSize, kStructuredCloneAlign);
333
0
  }
334
0
335
0
  size_t offset = headerSize;
336
0
  AlignTo(&offset, kStructuredCloneAlign);
337
0
338
0
  OutputBuffer header;
339
0
  header.codeUint32(count);
340
0
341
0
  MemMapSnapshot mem;
342
0
  MOZ_TRY(mem.Init(offset + dataSize));
343
0
344
0
  auto ptr = mem.Get<char>();
345
0
346
0
  // We need to build the new array of blobs before we overwrite the existing
347
0
  // one, since previously-serialized entries will store their blob references
348
0
  // as indexes into our blobs array.
349
0
  nsTArray<RefPtr<BlobImpl>> blobImpls(blobCount);
350
0
351
0
  for (auto& entry : IterHash(mEntries)) {
352
0
    AlignTo(&offset, kStructuredCloneAlign);
353
0
354
0
    size_t blobOffset = blobImpls.Length();
355
0
    if (entry->BlobCount()) {
356
0
      blobImpls.AppendElements(entry->Blobs());
357
0
    }
358
0
359
0
    entry->ExtractData(&ptr[offset], offset, blobOffset);
360
0
    entry->Code(header);
361
0
362
0
    offset += entry->Size();
363
0
  }
364
0
365
0
  mBlobImpls = std::move(blobImpls);
366
0
367
0
  // FIXME: We should create a separate OutputBuffer class which can encode to
368
0
  // a static memory region rather than dynamically allocating and then
369
0
  // copying.
370
0
  MOZ_ASSERT(header.cursor() == headerSize);
371
0
  memcpy(ptr.get(), header.Get(), header.cursor());
372
0
373
0
  // We've already updated offsets at this point. We need this to succeed.
374
0
  mMap.reset();
375
0
  MOZ_RELEASE_ASSERT(mem.Finalize(mMap).isOk());
376
0
377
0
  return Ok();
378
0
}
379
380
void
381
WritableSharedMap::SendTo(ContentParent* aParent) const
382
0
{
383
0
    nsTArray<IPCBlob> blobs(mBlobImpls.Length());
384
0
385
0
    for (auto& blobImpl : mBlobImpls) {
386
0
      nsresult rv = IPCBlobUtils::Serialize(blobImpl, aParent,
387
0
                                            *blobs.AppendElement());
388
0
      if (NS_WARN_IF(NS_FAILED(rv))) {
389
0
        continue;
390
0
      }
391
0
    }
392
0
393
0
    Unused << aParent->SendUpdateSharedData(CloneMapFile(), mMap.size(),
394
0
                                            blobs, mChangedKeys);
395
0
}
396
397
void
398
WritableSharedMap::BroadcastChanges()
399
0
{
400
0
  if (mChangedKeys.IsEmpty()) {
401
0
    return;
402
0
  }
403
0
404
0
  if (!Serialize().isOk()) {
405
0
    return;
406
0
  }
407
0
408
0
  nsTArray<ContentParent*> parents;
409
0
  ContentParent::GetAll(parents);
410
0
  for (auto& parent : parents) {
411
0
    SendTo(parent);
412
0
  }
413
0
414
0
  if (mReadOnly) {
415
0
    nsTArray<RefPtr<BlobImpl>> blobImpls(mBlobImpls);
416
0
    mReadOnly->Update(CloneMapFile(), mMap.size(),
417
0
                      std::move(blobImpls),
418
0
                      std::move(mChangedKeys));
419
0
  }
420
0
421
0
  mChangedKeys.Clear();
422
0
}
423
424
void
425
WritableSharedMap::Delete(const nsACString& aName)
426
0
{
427
0
  if (mEntries.Remove(aName)) {
428
0
    KeyChanged(aName);
429
0
  }
430
0
}
431
432
void
433
WritableSharedMap::Set(JSContext* aCx,
434
                       const nsACString& aName,
435
                       JS::HandleValue aValue,
436
                       ErrorResult& aRv)
437
0
{
438
0
  StructuredCloneData holder;
439
0
440
0
  holder.Write(aCx, aValue, aRv);
441
0
  if (aRv.Failed()) {
442
0
    return;
443
0
  }
444
0
445
0
  if (!holder.InputStreams().IsEmpty()) {
446
0
    aRv.Throw(NS_ERROR_INVALID_ARG);
447
0
    return;
448
0
  }
449
0
450
0
  Entry* entry = mEntries.LookupOrAdd(aName, *this, aName);
451
0
  entry->TakeData(std::move(holder));
452
0
453
0
  KeyChanged(aName);
454
0
}
455
456
void
457
WritableSharedMap::Flush()
458
0
{
459
0
  BroadcastChanges();
460
0
}
461
462
void
463
WritableSharedMap::IdleFlush()
464
0
{
465
0
  mPendingFlush = false;
466
0
  Flush();
467
0
}
468
469
nsresult
470
WritableSharedMap::KeyChanged(const nsACString& aName)
471
0
{
472
0
  if (!mChangedKeys.ContainsSorted(aName)) {
473
0
    mChangedKeys.InsertElementSorted(aName);
474
0
  }
475
0
  mEntryArray.reset();
476
0
477
0
  if (!mPendingFlush) {
478
0
      MOZ_TRY(NS_IdleDispatchToCurrentThread(
479
0
        NewRunnableMethod("WritableSharedMap::IdleFlush",
480
0
                          this,
481
0
                          &WritableSharedMap::IdleFlush)));
482
0
      mPendingFlush = true;
483
0
  }
484
0
  return NS_OK;
485
0
}
486
487
488
JSObject*
489
SharedMap::WrapObject(JSContext* aCx, JS::HandleObject aGivenProto)
490
0
{
491
0
  return MozSharedMap_Binding::Wrap(aCx, this, aGivenProto);
492
0
}
493
494
JSObject*
495
WritableSharedMap::WrapObject(JSContext* aCx, JS::HandleObject aGivenProto)
496
0
{
497
0
  return MozWritableSharedMap_Binding::Wrap(aCx, this, aGivenProto);
498
0
}
499
500
/* static */ already_AddRefed<SharedMapChangeEvent>
501
SharedMapChangeEvent::Constructor(EventTarget* aEventTarget,
502
                                  const nsAString& aType,
503
                                  const MozSharedMapChangeEventInit& aInit)
504
0
{
505
0
  RefPtr<SharedMapChangeEvent> event = new SharedMapChangeEvent(aEventTarget);
506
0
507
0
  bool trusted = event->Init(aEventTarget);
508
0
  event->InitEvent(aType, aInit.mBubbles, aInit.mCancelable);
509
0
  event->SetTrusted(trusted);
510
0
  event->SetComposed(aInit.mComposed);
511
0
512
0
  event->mChangedKeys = aInit.mChangedKeys;
513
0
514
0
  return event.forget();
515
0
}
516
517
NS_IMPL_CYCLE_COLLECTION_INHERITED(WritableSharedMap, SharedMap, mReadOnly)
518
519
0
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WritableSharedMap)
520
0
NS_INTERFACE_MAP_END_INHERITING(SharedMap)
521
522
NS_IMPL_ADDREF_INHERITED(WritableSharedMap, SharedMap)
523
NS_IMPL_RELEASE_INHERITED(WritableSharedMap, SharedMap)
524
525
} // ipc
526
} // dom
527
} // mozilla