Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/dom/bindings/WebIDLGlobalNameHash.cpp
Line
Count
Source (jump to first uncovered line)
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 file,
5
 * You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7
#include "WebIDLGlobalNameHash.h"
8
#include "js/Class.h"
9
#include "js/GCAPI.h"
10
#include "js/Id.h"
11
#include "js/Wrapper.h"
12
#include "jsapi.h"
13
#include "jsfriendapi.h"
14
#include "mozilla/ArrayUtils.h"
15
#include "mozilla/ErrorResult.h"
16
#include "mozilla/HashFunctions.h"
17
#include "mozilla/Maybe.h"
18
#include "mozilla/dom/DOMJSClass.h"
19
#include "mozilla/dom/DOMJSProxyHandler.h"
20
#include "mozilla/dom/JSSlots.h"
21
#include "mozilla/dom/PrototypeList.h"
22
#include "mozilla/dom/RegisterBindings.h"
23
#include "nsGlobalWindow.h"
24
#include "nsIMemoryReporter.h"
25
#include "nsTHashtable.h"
26
#include "WrapperFactory.h"
27
28
namespace mozilla {
29
namespace dom {
30
31
struct MOZ_STACK_CLASS WebIDLNameTableKey
32
{
33
  explicit WebIDLNameTableKey(JSFlatString* aJSString)
34
    : mLength(js::GetFlatStringLength(aJSString))
35
0
  {
36
0
    mNogc.emplace();
37
0
    JSLinearString* jsString = js::FlatStringToLinearString(aJSString);
38
0
    if (js::LinearStringHasLatin1Chars(jsString)) {
39
0
      mLatin1String = reinterpret_cast<const char*>(
40
0
        js::GetLatin1LinearStringChars(*mNogc, jsString));
41
0
      mTwoBytesString = nullptr;
42
0
      mHash = mLatin1String ? HashString(mLatin1String, mLength) : 0;
43
0
    } else {
44
0
      mLatin1String = nullptr;
45
0
      mTwoBytesString = js::GetTwoByteLinearStringChars(*mNogc, jsString);
46
0
      mHash = mTwoBytesString ? HashString(mTwoBytesString, mLength) : 0;
47
0
    }
48
0
  }
49
  explicit WebIDLNameTableKey(const char* aString, size_t aLength)
50
    : mLatin1String(aString),
51
      mTwoBytesString(nullptr),
52
      mLength(aLength),
53
      mHash(HashString(aString, aLength))
54
0
  {
55
0
    MOZ_ASSERT(aString[aLength] == '\0');
56
0
  }
57
58
  Maybe<JS::AutoCheckCannotGC> mNogc;
59
  const char* mLatin1String;
60
  const char16_t* mTwoBytesString;
61
  size_t mLength;
62
  PLDHashNumber mHash;
63
};
64
65
struct WebIDLNameTableEntry : public PLDHashEntryHdr
66
{
67
  typedef const WebIDLNameTableKey& KeyType;
68
  typedef const WebIDLNameTableKey* KeyTypePointer;
69
70
  explicit WebIDLNameTableEntry(KeyTypePointer aKey)
71
    : mNameOffset(0),
72
      mNameLength(0),
73
      mConstructorId(constructors::id::_ID_Count),
74
      mCreate(nullptr),
75
      mEnabled(nullptr)
76
0
  {}
77
  WebIDLNameTableEntry(WebIDLNameTableEntry&& aEntry)
78
    : mNameOffset(aEntry.mNameOffset),
79
      mNameLength(aEntry.mNameLength),
80
      mConstructorId(aEntry.mConstructorId),
81
      mCreate(aEntry.mCreate),
82
      mEnabled(aEntry.mEnabled)
83
0
  {}
84
  ~WebIDLNameTableEntry()
85
0
  {}
86
87
  bool KeyEquals(KeyTypePointer aKey) const
88
0
  {
89
0
    if (mNameLength != aKey->mLength) {
90
0
      return false;
91
0
    }
92
0
93
0
    const char* name = WebIDLGlobalNameHash::sNames + mNameOffset;
94
0
95
0
    if (aKey->mLatin1String) {
96
0
      return ArrayEqual(aKey->mLatin1String, name, aKey->mLength);
97
0
    }
98
0
99
0
    return nsCharTraits<char16_t>::compareASCII(aKey->mTwoBytesString, name,
100
0
                                                aKey->mLength) == 0;
101
0
  }
102
103
  static KeyTypePointer KeyToPointer(KeyType aKey)
104
0
  {
105
0
    return &aKey;
106
0
  }
107
108
  static PLDHashNumber HashKey(KeyTypePointer aKey)
109
0
  {
110
0
    return aKey->mHash;
111
0
  }
112
113
  enum { ALLOW_MEMMOVE = true };
114
115
  uint16_t mNameOffset;
116
  uint16_t mNameLength;
117
  constructors::id::ID mConstructorId;
118
  CreateInterfaceObjectsMethod mCreate;
119
  // May be null if enabled unconditionally
120
  WebIDLGlobalNameHash::ConstructorEnabled mEnabled;
121
};
122
123
static nsTHashtable<WebIDLNameTableEntry>* sWebIDLGlobalNames;
124
125
class WebIDLGlobalNamesHashReporter final : public nsIMemoryReporter
126
{
127
  MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf)
128
129
0
  ~WebIDLGlobalNamesHashReporter() {}
130
131
public:
132
  NS_DECL_ISUPPORTS
133
134
  NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport,
135
                            nsISupports* aData, bool aAnonymize) override
136
0
  {
137
0
    int64_t amount =
138
0
      sWebIDLGlobalNames ?
139
0
      sWebIDLGlobalNames->ShallowSizeOfIncludingThis(MallocSizeOf) : 0;
140
0
141
0
    MOZ_COLLECT_REPORT(
142
0
      "explicit/dom/webidl-globalnames", KIND_HEAP, UNITS_BYTES, amount,
143
0
      "Memory used by the hash table for WebIDL's global names.");
144
0
145
0
    return NS_OK;
146
0
  }
147
};
148
149
NS_IMPL_ISUPPORTS(WebIDLGlobalNamesHashReporter, nsIMemoryReporter)
150
151
/* static */
152
void
153
WebIDLGlobalNameHash::Init()
154
0
{
155
0
  sWebIDLGlobalNames = new nsTHashtable<WebIDLNameTableEntry>(sCount);
156
0
  RegisterWebIDLGlobalNames();
157
0
158
0
  RegisterStrongMemoryReporter(new WebIDLGlobalNamesHashReporter());
159
0
}
160
161
/* static */
162
void
163
WebIDLGlobalNameHash::Shutdown()
164
0
{
165
0
  delete sWebIDLGlobalNames;
166
0
}
167
168
/* static */
169
void
170
WebIDLGlobalNameHash::Register(uint16_t aNameOffset, uint16_t aNameLength,
171
                               CreateInterfaceObjectsMethod aCreate,
172
                               ConstructorEnabled aEnabled,
173
                               constructors::id::ID aConstructorId)
174
0
{
175
0
  const char* name = sNames + aNameOffset;
176
0
  WebIDLNameTableKey key(name, aNameLength);
177
0
  WebIDLNameTableEntry* entry = sWebIDLGlobalNames->PutEntry(key);
178
0
  entry->mNameOffset = aNameOffset;
179
0
  entry->mNameLength = aNameLength;
180
0
  entry->mCreate = aCreate;
181
0
  entry->mEnabled = aEnabled;
182
0
  entry->mConstructorId = aConstructorId;
183
0
}
184
185
/* static */
186
void
187
WebIDLGlobalNameHash::Remove(const char* aName, uint32_t aLength)
188
0
{
189
0
  WebIDLNameTableKey key(aName, aLength);
190
0
  sWebIDLGlobalNames->RemoveEntry(key);
191
0
}
192
193
static JSObject*
194
FindNamedConstructorForXray(JSContext* aCx, JS::Handle<jsid> aId,
195
                            const WebIDLNameTableEntry* aEntry)
196
0
{
197
0
  JSObject* interfaceObject =
198
0
    GetPerInterfaceObjectHandle(aCx, aEntry->mConstructorId,
199
0
                                aEntry->mCreate,
200
0
                                /* aDefineOnGlobal = */ false);
201
0
  if (!interfaceObject) {
202
0
    return nullptr;
203
0
  }
204
0
205
0
  // This is a call over Xrays, so we will actually use the return value
206
0
  // (instead of just having it defined on the global now).  Check for named
207
0
  // constructors with this id, in case that's what the caller is asking for.
208
0
  for (unsigned slot = DOM_INTERFACE_SLOTS_BASE;
209
0
       slot < JSCLASS_RESERVED_SLOTS(js::GetObjectClass(interfaceObject));
210
0
       ++slot) {
211
0
    JSObject* constructor = &js::GetReservedSlot(interfaceObject, slot).toObject();
212
0
    if (JS_GetFunctionId(JS_GetObjectFunction(constructor)) == JSID_TO_STRING(aId)) {
213
0
      return constructor;
214
0
    }
215
0
  }
216
0
217
0
  // None of the named constructors match, so the caller must want the
218
0
  // interface object itself.
219
0
  return interfaceObject;
220
0
}
221
222
/* static */
223
bool
224
WebIDLGlobalNameHash::DefineIfEnabled(JSContext* aCx,
225
                                      JS::Handle<JSObject*> aObj,
226
                                      JS::Handle<jsid> aId,
227
                                      JS::MutableHandle<JS::PropertyDescriptor> aDesc,
228
                                      bool* aFound)
229
0
{
230
0
  MOZ_ASSERT(JSID_IS_STRING(aId), "Check for string id before calling this!");
231
0
232
0
  const WebIDLNameTableEntry* entry;
233
0
  {
234
0
    WebIDLNameTableKey key(JSID_TO_FLAT_STRING(aId));
235
0
    // Rooting analysis thinks nsTHashtable<...>::GetEntry may GC because it
236
0
    // ends up calling through PLDHashTableOps' matchEntry function pointer, but
237
0
    // we know WebIDLNameTableEntry::KeyEquals can't cause a GC.
238
0
    JS::AutoSuppressGCAnalysis suppress;
239
0
    entry = sWebIDLGlobalNames->GetEntry(key);
240
0
  }
241
0
242
0
  if (!entry) {
243
0
    *aFound = false;
244
0
    return true;
245
0
  }
246
0
247
0
  *aFound = true;
248
0
249
0
  ConstructorEnabled checkEnabledForScope = entry->mEnabled;
250
0
  // We do the enabled check on the current compartment of aCx, but for the
251
0
  // actual object we pass in the underlying object in the Xray case.  That
252
0
  // way the callee can decide whether to allow access based on the caller
253
0
  // or the window being touched.
254
0
  JS::Rooted<JSObject*> global(aCx,
255
0
    js::CheckedUnwrap(aObj, /* stopAtWindowProxy = */ false));
256
0
  if (!global) {
257
0
    return Throw(aCx, NS_ERROR_DOM_SECURITY_ERR);
258
0
  }
259
0
260
0
  {
261
0
    // It's safe to pass "&global" here, because we've already unwrapped it, but
262
0
    // for general sanity better to not have debug code even having the
263
0
    // appearance of mutating things that opt code uses.
264
#ifdef DEBUG
265
    JS::Rooted<JSObject*> temp(aCx, global);
266
    DebugOnly<nsGlobalWindowInner*> win;
267
    MOZ_ASSERT(NS_SUCCEEDED(UNWRAP_OBJECT(Window, &temp, win)));
268
#endif
269
  }
270
0
271
0
  if (checkEnabledForScope && !checkEnabledForScope(aCx, global)) {
272
0
    return true;
273
0
  }
274
0
275
0
  // The DOM constructor resolve machinery interacts with Xrays in tricky
276
0
  // ways, and there are some asymmetries that are important to understand.
277
0
  //
278
0
  // In the regular (non-Xray) case, we only want to resolve constructors
279
0
  // once (so that if they're deleted, they don't reappear). We do this by
280
0
  // stashing the constructor in a slot on the global, such that we can see
281
0
  // during resolve whether we've created it already. This is rather
282
0
  // memory-intensive, so we don't try to maintain these semantics when
283
0
  // manipulating a global over Xray (so the properties just re-resolve if
284
0
  // they've been deleted).
285
0
  //
286
0
  // Unfortunately, there's a bit of an impedance-mismatch between the Xray
287
0
  // and non-Xray machinery. The Xray machinery wants an API that returns a
288
0
  // JS::PropertyDescriptor, so that the resolve hook doesn't have to get
289
0
  // snared up with trying to define a property on the Xray holder. At the
290
0
  // same time, the DefineInterface callbacks are set up to define things
291
0
  // directly on the global.  And re-jiggering them to return property
292
0
  // descriptors is tricky, because some DefineInterface callbacks define
293
0
  // multiple things (like the Image() alias for HTMLImageElement).
294
0
  //
295
0
  // So the setup is as-follows:
296
0
  //
297
0
  // * The resolve function takes a JS::PropertyDescriptor, but in the
298
0
  //   non-Xray case, callees may define things directly on the global, and
299
0
  //   set the value on the property descriptor to |undefined| to indicate
300
0
  //   that there's nothing more for the caller to do. We assert against
301
0
  //   this behavior in the Xray case.
302
0
  //
303
0
  // * We make sure that we do a non-Xray resolve first, so that all the
304
0
  //   slots are set up. In the Xray case, this means unwrapping and doing
305
0
  //   a non-Xray resolve before doing the Xray resolve.
306
0
  //
307
0
  // This all could use some grand refactoring, but for now we just limp
308
0
  // along.
309
0
  if (xpc::WrapperFactory::IsXrayWrapper(aObj)) {
310
0
    JS::Rooted<JSObject*> constructor(aCx);
311
0
    {
312
0
      JSAutoRealm ar(aCx, global);
313
0
      constructor = FindNamedConstructorForXray(aCx, aId, entry);
314
0
    }
315
0
    if (NS_WARN_IF(!constructor)) {
316
0
      return Throw(aCx, NS_ERROR_FAILURE);
317
0
    }
318
0
    if (!JS_WrapObject(aCx, &constructor)) {
319
0
      return Throw(aCx, NS_ERROR_FAILURE);
320
0
    }
321
0
322
0
    FillPropertyDescriptor(aDesc, aObj, 0, JS::ObjectValue(*constructor));
323
0
    return true;
324
0
  }
325
0
326
0
  JS::Rooted<JSObject*> interfaceObject(aCx,
327
0
    GetPerInterfaceObjectHandle(aCx, entry->mConstructorId,
328
0
                                entry->mCreate,
329
0
                                /* aDefineOnGlobal = */ true));
330
0
  if (NS_WARN_IF(!interfaceObject)) {
331
0
    return Throw(aCx, NS_ERROR_FAILURE);
332
0
  }
333
0
334
0
  // We've already defined the property.  We indicate this to the caller
335
0
  // by filling a property descriptor with JS::UndefinedValue() as the
336
0
  // value.  We still have to fill in a property descriptor, though, so
337
0
  // that the caller knows the property is in fact on this object.  It
338
0
  // doesn't matter what we pass for the "readonly" argument here.
339
0
  FillPropertyDescriptor(aDesc, aObj, JS::UndefinedValue(), false);
340
0
341
0
  return true;
342
0
}
343
344
/* static */
345
bool
346
WebIDLGlobalNameHash::MayResolve(jsid aId)
347
0
{
348
0
  WebIDLNameTableKey key(JSID_TO_FLAT_STRING(aId));
349
0
  // Rooting analysis thinks nsTHashtable<...>::Contains may GC because it ends
350
0
  // up calling through PLDHashTableOps' matchEntry function pointer, but we
351
0
  // know WebIDLNameTableEntry::KeyEquals can't cause a GC.
352
0
  JS::AutoSuppressGCAnalysis suppress;
353
0
  return sWebIDLGlobalNames->Contains(key);
354
0
}
355
356
/* static */
357
bool
358
WebIDLGlobalNameHash::GetNames(JSContext* aCx, JS::Handle<JSObject*> aObj,
359
                               NameType aNameType, JS::AutoIdVector& aNames)
360
0
{
361
0
  // aObj is always a Window here, so GetProtoAndIfaceCache on it is safe.
362
0
  ProtoAndIfaceCache* cache = GetProtoAndIfaceCache(aObj);
363
0
  for (auto iter = sWebIDLGlobalNames->Iter(); !iter.Done(); iter.Next()) {
364
0
    const WebIDLNameTableEntry* entry = iter.Get();
365
0
    // If aNameType is not AllNames, only include things whose entry slot in the
366
0
    // ProtoAndIfaceCache is null.
367
0
    if ((aNameType == AllNames ||
368
0
         !cache->HasEntryInSlot(entry->mConstructorId)) &&
369
0
        (!entry->mEnabled || entry->mEnabled(aCx, aObj))) {
370
0
      JSString* str = JS_AtomizeStringN(aCx, sNames + entry->mNameOffset,
371
0
                                        entry->mNameLength);
372
0
      if (!str || !aNames.append(NON_INTEGER_ATOM_TO_JSID(str))) {
373
0
        return false;
374
0
      }
375
0
    }
376
0
  }
377
0
378
0
  return true;
379
0
}
380
381
} // namespace dom
382
} // namespace mozilla