Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/dom/bindings/DOMJSProxyHandler.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 "mozilla/dom/DOMJSProxyHandler.h"
8
#include "xpcpublic.h"
9
#include "xpcprivate.h"
10
#include "XPCWrapper.h"
11
#include "WrapperFactory.h"
12
#include "nsWrapperCacheInlines.h"
13
#include "mozilla/dom/BindingUtils.h"
14
15
#include "jsapi.h"
16
17
using namespace JS;
18
19
namespace mozilla {
20
namespace dom {
21
22
jsid s_length_id = JSID_VOID;
23
24
bool
25
DefineStaticJSVals(JSContext* cx)
26
3
{
27
3
  return AtomizeAndPinJSString(cx, s_length_id, "length");
28
3
}
29
30
const char DOMProxyHandler::family = 0;
31
32
js::DOMProxyShadowsResult
33
DOMProxyShadows(JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id)
34
0
{
35
0
  JS::Rooted<JSObject*> expando(cx, DOMProxyHandler::GetExpandoObject(proxy));
36
0
  JS::Value v = js::GetProxyPrivate(proxy);
37
0
  bool isOverrideBuiltins = !v.isObject() && !v.isUndefined();
38
0
  if (expando) {
39
0
    bool hasOwn;
40
0
    if (!JS_AlreadyHasOwnPropertyById(cx, expando, id, &hasOwn))
41
0
      return js::ShadowCheckFailed;
42
0
43
0
    if (hasOwn) {
44
0
      return isOverrideBuiltins ?
45
0
        js::ShadowsViaIndirectExpando : js::ShadowsViaDirectExpando;
46
0
    }
47
0
  }
48
0
49
0
  if (!isOverrideBuiltins) {
50
0
    // Our expando, if any, didn't shadow, so we're not shadowing at all.
51
0
    return js::DoesntShadow;
52
0
  }
53
0
54
0
  bool hasOwn;
55
0
  if (!GetProxyHandler(proxy)->hasOwn(cx, proxy, id, &hasOwn))
56
0
    return js::ShadowCheckFailed;
57
0
58
0
  return hasOwn ? js::Shadows : js::DoesntShadowUnique;
59
0
}
60
61
// Store the information for the specialized ICs.
62
struct SetDOMProxyInformation
63
{
64
3
  SetDOMProxyInformation() {
65
3
    js::SetDOMProxyInformation((const void*) &DOMProxyHandler::family,
66
3
                               DOMProxyShadows);
67
3
  }
68
};
69
70
SetDOMProxyInformation gSetDOMProxyInformation;
71
72
static inline void
73
CheckExpandoObject(JSObject* proxy, const JS::Value& expando)
74
0
{
75
#ifdef DEBUG
76
  JSObject* obj = &expando.toObject();
77
  MOZ_ASSERT(!js::gc::EdgeNeedsSweepUnbarriered(&obj));
78
  MOZ_ASSERT(js::GetObjectCompartment(proxy) == js::GetObjectCompartment(obj));
79
80
  // When we create an expando object in EnsureExpandoObject below, we preserve
81
  // the wrapper. The wrapper is released when the object is unlinked, but we
82
  // should never call these functions after that point.
83
  nsISupports* native = UnwrapDOMObject<nsISupports>(proxy);
84
  nsWrapperCache* cache;
85
  CallQueryInterface(native, &cache);
86
  MOZ_ASSERT(cache->PreservingWrapper());
87
#endif
88
}
89
90
static inline void
91
CheckExpandoAndGeneration(JSObject* proxy, js::ExpandoAndGeneration* expandoAndGeneration)
92
0
{
93
#ifdef DEBUG
94
  JS::Value value = expandoAndGeneration->expando;
95
  if (!value.isUndefined())
96
    CheckExpandoObject(proxy, value);
97
#endif
98
}
99
100
static inline void
101
CheckDOMProxy(JSObject* proxy)
102
0
{
103
#ifdef DEBUG
104
  MOZ_ASSERT(IsDOMProxy(proxy), "expected a DOM proxy object");
105
  MOZ_ASSERT(!js::gc::EdgeNeedsSweepUnbarriered(&proxy));
106
  nsISupports* native = UnwrapDOMObject<nsISupports>(proxy);
107
  nsWrapperCache* cache;
108
  // QI to nsWrapperCache cannot GC for very non-obvious reasons; see
109
  // https://searchfox.org/mozilla-central/rev/55da592d85c2baf8d8818010c41d9738c97013d2/js/xpconnect/src/XPCWrappedJSClass.cpp#521,545-548
110
  JS::AutoSuppressGCAnalysis nogc;
111
  CallQueryInterface(native, &cache);
112
  MOZ_ASSERT(cache->GetWrapperPreserveColor() == proxy);
113
#endif
114
}
115
116
// static
117
JSObject*
118
DOMProxyHandler::GetAndClearExpandoObject(JSObject* obj)
119
0
{
120
0
  CheckDOMProxy(obj);
121
0
122
0
  JS::Value v = js::GetProxyPrivate(obj);
123
0
  if (v.isUndefined()) {
124
0
    return nullptr;
125
0
  }
126
0
127
0
  if (v.isObject()) {
128
0
    js::SetProxyPrivate(obj, UndefinedValue());
129
0
  } else {
130
0
    js::ExpandoAndGeneration* expandoAndGeneration =
131
0
      static_cast<js::ExpandoAndGeneration*>(v.toPrivate());
132
0
    v = expandoAndGeneration->expando;
133
0
    if (v.isUndefined()) {
134
0
      return nullptr;
135
0
    }
136
0
    // We have to expose v to active JS here.  The reason for that is that we
137
0
    // might be in the middle of a GC right now.  If our proxy hasn't been
138
0
    // traced yet, when it _does_ get traced it won't trace the expando, since
139
0
    // we're breaking that link.  But the Rooted we're presumably being placed
140
0
    // into is also not going to trace us, because Rooted marking is done at
141
0
    // the very beginning of the GC.  In that situation, we need to manually
142
0
    // mark the expando as live here.  JS::ExposeValueToActiveJS will do just
143
0
    // that for us.
144
0
    //
145
0
    // We don't need to do this in the non-expandoAndGeneration case, because
146
0
    // in that case our value is stored in a slot and slots will already mark
147
0
    // the old thing live when the value in the slot changes.
148
0
    JS::ExposeValueToActiveJS(v);
149
0
    expandoAndGeneration->expando = UndefinedValue();
150
0
  }
151
0
152
0
  CheckExpandoObject(obj, v);
153
0
154
0
  return &v.toObject();
155
0
}
156
157
// static
158
JSObject*
159
DOMProxyHandler::EnsureExpandoObject(JSContext* cx, JS::Handle<JSObject*> obj)
160
0
{
161
0
  CheckDOMProxy(obj);
162
0
163
0
  JS::Value v = js::GetProxyPrivate(obj);
164
0
  if (v.isObject()) {
165
0
    CheckExpandoObject(obj, v);
166
0
    return &v.toObject();
167
0
  }
168
0
169
0
  js::ExpandoAndGeneration* expandoAndGeneration;
170
0
  if (!v.isUndefined()) {
171
0
    expandoAndGeneration = static_cast<js::ExpandoAndGeneration*>(v.toPrivate());
172
0
    CheckExpandoAndGeneration(obj, expandoAndGeneration);
173
0
    if (expandoAndGeneration->expando.isObject()) {
174
0
      return &expandoAndGeneration->expando.toObject();
175
0
    }
176
0
  } else {
177
0
    expandoAndGeneration = nullptr;
178
0
  }
179
0
180
0
  JS::Rooted<JSObject*> expando(cx,
181
0
    JS_NewObjectWithGivenProto(cx, nullptr, nullptr));
182
0
  if (!expando) {
183
0
    return nullptr;
184
0
  }
185
0
186
0
  nsISupports* native = UnwrapDOMObject<nsISupports>(obj);
187
0
  nsWrapperCache* cache;
188
0
  CallQueryInterface(native, &cache);
189
0
  cache->PreserveWrapper(native);
190
0
191
0
  if (expandoAndGeneration) {
192
0
    expandoAndGeneration->expando.setObject(*expando);
193
0
    return expando;
194
0
  }
195
0
196
0
  js::SetProxyPrivate(obj, ObjectValue(*expando));
197
0
198
0
  return expando;
199
0
}
200
201
bool
202
DOMProxyHandler::preventExtensions(JSContext* cx, JS::Handle<JSObject*> proxy,
203
                                   JS::ObjectOpResult& result) const
204
0
{
205
0
  // always extensible per WebIDL
206
0
  return result.failCantPreventExtensions();
207
0
}
208
209
bool
210
DOMProxyHandler::isExtensible(JSContext *cx, JS::Handle<JSObject*> proxy, bool *extensible) const
211
0
{
212
0
  *extensible = true;
213
0
  return true;
214
0
}
215
216
bool
217
BaseDOMProxyHandler::getOwnPropertyDescriptor(JSContext* cx,
218
                                              JS::Handle<JSObject*> proxy,
219
                                              JS::Handle<jsid> id,
220
                                              MutableHandle<PropertyDescriptor> desc) const
221
0
{
222
0
  return getOwnPropDescriptor(cx, proxy, id, /* ignoreNamedProps = */ false,
223
0
                              desc);
224
0
}
225
226
bool
227
DOMProxyHandler::defineProperty(JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
228
                                Handle<PropertyDescriptor> desc,
229
                                JS::ObjectOpResult &result, bool *defined) const
230
0
{
231
0
  if (xpc::WrapperFactory::IsXrayWrapper(proxy)) {
232
0
    return result.succeed();
233
0
  }
234
0
235
0
  JS::Rooted<JSObject*> expando(cx, EnsureExpandoObject(cx, proxy));
236
0
  if (!expando) {
237
0
    return false;
238
0
  }
239
0
240
0
  if (!JS_DefinePropertyById(cx, expando, id, desc, result)) {
241
0
    return false;
242
0
  }
243
0
  *defined = true;
244
0
  return true;
245
0
}
246
247
bool
248
DOMProxyHandler::set(JSContext *cx, Handle<JSObject*> proxy, Handle<jsid> id,
249
                     Handle<JS::Value> v, Handle<JS::Value> receiver,
250
                     ObjectOpResult &result) const
251
0
{
252
0
  MOZ_ASSERT(!xpc::WrapperFactory::IsXrayWrapper(proxy),
253
0
             "Should not have a XrayWrapper here");
254
0
  bool done;
255
0
  if (!setCustom(cx, proxy, id, v, &done)) {
256
0
    return false;
257
0
  }
258
0
  if (done) {
259
0
    return result.succeed();
260
0
  }
261
0
262
0
  // Make sure to ignore our named properties when checking for own
263
0
  // property descriptors for a set.
264
0
  JS::Rooted<PropertyDescriptor> ownDesc(cx);
265
0
  if (!getOwnPropDescriptor(cx, proxy, id, /* ignoreNamedProps = */ true,
266
0
                            &ownDesc)) {
267
0
    return false;
268
0
  }
269
0
  return js::SetPropertyIgnoringNamedGetter(cx, proxy, id, v, receiver, ownDesc, result);
270
0
}
271
272
bool
273
DOMProxyHandler::delete_(JSContext* cx, JS::Handle<JSObject*> proxy,
274
                         JS::Handle<jsid> id, JS::ObjectOpResult &result) const
275
0
{
276
0
  JS::Rooted<JSObject*> expando(cx);
277
0
  if (!xpc::WrapperFactory::IsXrayWrapper(proxy) && (expando = GetExpandoObject(proxy))) {
278
0
    return JS_DeletePropertyById(cx, expando, id, result);
279
0
  }
280
0
281
0
  return result.succeed();
282
0
}
283
284
bool
285
BaseDOMProxyHandler::ownPropertyKeys(JSContext* cx,
286
                                     JS::Handle<JSObject*> proxy,
287
                                     JS::AutoIdVector& props) const
288
0
{
289
0
  return ownPropNames(cx, proxy, JSITER_OWNONLY | JSITER_HIDDEN | JSITER_SYMBOLS, props);
290
0
}
291
292
bool
293
BaseDOMProxyHandler::getPrototypeIfOrdinary(JSContext* cx, JS::Handle<JSObject*> proxy,
294
                                            bool* isOrdinary,
295
                                            JS::MutableHandle<JSObject*> proto) const
296
0
{
297
0
  *isOrdinary = true;
298
0
  proto.set(GetStaticPrototype(proxy));
299
0
  return true;
300
0
}
301
302
bool
303
BaseDOMProxyHandler::getOwnEnumerablePropertyKeys(JSContext* cx,
304
                                                  JS::Handle<JSObject*> proxy,
305
                                                  JS::AutoIdVector& props) const
306
0
{
307
0
  return ownPropNames(cx, proxy, JSITER_OWNONLY, props);
308
0
}
309
310
bool
311
DOMProxyHandler::setCustom(JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
312
                           JS::Handle<JS::Value> v, bool *done) const
313
0
{
314
0
  *done = false;
315
0
  return true;
316
0
}
317
318
//static
319
JSObject *
320
DOMProxyHandler::GetExpandoObject(JSObject *obj)
321
0
{
322
0
  CheckDOMProxy(obj);
323
0
324
0
  JS::Value v = js::GetProxyPrivate(obj);
325
0
  if (v.isObject()) {
326
0
    CheckExpandoObject(obj, v);
327
0
    return &v.toObject();
328
0
  }
329
0
330
0
  if (v.isUndefined()) {
331
0
    return nullptr;
332
0
  }
333
0
334
0
  js::ExpandoAndGeneration* expandoAndGeneration =
335
0
    static_cast<js::ExpandoAndGeneration*>(v.toPrivate());
336
0
  CheckExpandoAndGeneration(obj, expandoAndGeneration);
337
0
338
0
  v = expandoAndGeneration->expando;
339
0
  return v.isUndefined() ? nullptr : &v.toObject();
340
0
}
341
342
void
343
ShadowingDOMProxyHandler::trace(JSTracer* trc, JSObject* proxy) const
344
0
{
345
0
  DOMProxyHandler::trace(trc, proxy);
346
0
347
0
  MOZ_ASSERT(IsDOMProxy(proxy), "expected a DOM proxy object");
348
0
  JS::Value v = js::GetProxyPrivate(proxy);
349
0
  MOZ_ASSERT(!v.isObject(), "Should not have expando object directly!");
350
0
351
0
  // The proxy's private slot is set when we allocate the proxy,
352
0
  // so it cannot be |undefined|.
353
0
  MOZ_ASSERT(!v.isUndefined());
354
0
355
0
  js::ExpandoAndGeneration* expandoAndGeneration =
356
0
    static_cast<js::ExpandoAndGeneration*>(v.toPrivate());
357
0
  JS::TraceEdge(trc, &expandoAndGeneration->expando,
358
0
                "Shadowing DOM proxy expando");
359
0
}
360
361
} // namespace dom
362
} // namespace mozilla