Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/js/src/vm/Compartment.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=4 et sw=4 tw=99:
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 "vm/Compartment-inl.h"
8
9
#include "mozilla/MemoryReporting.h"
10
11
#include <stddef.h>
12
13
#include "jsfriendapi.h"
14
15
#include "gc/Policy.h"
16
#include "gc/PublicIterators.h"
17
#include "js/Date.h"
18
#include "js/Proxy.h"
19
#include "js/RootingAPI.h"
20
#include "js/StableStringChars.h"
21
#include "js/Wrapper.h"
22
#include "proxy/DeadObjectProxy.h"
23
#include "vm/Debugger.h"
24
#include "vm/Iteration.h"
25
#include "vm/JSContext.h"
26
#include "vm/WrapperObject.h"
27
28
#include "gc/GC-inl.h"
29
#include "gc/Marking-inl.h"
30
#include "vm/JSAtom-inl.h"
31
#include "vm/JSFunction-inl.h"
32
#include "vm/JSObject-inl.h"
33
#include "vm/JSScript-inl.h"
34
#include "vm/NativeObject-inl.h"
35
#include "vm/UnboxedObject-inl.h"
36
37
using namespace js;
38
39
using JS::AutoStableStringChars;
40
41
Compartment::Compartment(Zone* zone)
42
  : zone_(zone),
43
    runtime_(zone->runtimeFromAnyThread()),
44
    crossCompartmentWrappers(0)
45
9
{}
46
47
#ifdef JSGC_HASH_TABLE_CHECKS
48
49
namespace {
50
struct CheckGCThingAfterMovingGCFunctor {
51
    template <class T> void operator()(T* t) { CheckGCThingAfterMovingGC(*t); }
52
};
53
} // namespace (anonymous)
54
55
void
56
Compartment::checkWrapperMapAfterMovingGC()
57
{
58
    /*
59
     * Assert that the postbarriers have worked and that nothing is left in
60
     * wrapperMap that points into the nursery, and that the hash table entries
61
     * are discoverable.
62
     */
63
    for (WrapperMap::Enum e(crossCompartmentWrappers); !e.empty(); e.popFront()) {
64
        e.front().mutableKey().applyToWrapped(CheckGCThingAfterMovingGCFunctor());
65
        e.front().mutableKey().applyToDebugger(CheckGCThingAfterMovingGCFunctor());
66
67
        WrapperMap::Ptr ptr = crossCompartmentWrappers.lookup(e.front().key());
68
        MOZ_RELEASE_ASSERT(ptr.found() && &*ptr == &e.front());
69
    }
70
}
71
72
#endif // JSGC_HASH_TABLE_CHECKS
73
74
bool
75
Compartment::putWrapper(JSContext* cx, const CrossCompartmentKey& wrapped,
76
                        const js::Value& wrapper)
77
0
{
78
0
    MOZ_ASSERT(wrapped.is<JSString*>() == wrapper.isString());
79
0
    MOZ_ASSERT_IF(!wrapped.is<JSString*>(), wrapper.isObject());
80
0
81
0
    if (!crossCompartmentWrappers.put(wrapped, wrapper)) {
82
0
        ReportOutOfMemory(cx);
83
0
        return false;
84
0
    }
85
0
86
0
    return true;
87
0
}
88
89
static JSString*
90
CopyStringPure(JSContext* cx, JSString* str)
91
0
{
92
0
    /*
93
0
     * Directly allocate the copy in the destination compartment, rather than
94
0
     * first flattening it (and possibly allocating in source compartment),
95
0
     * because we don't know whether the flattening will pay off later.
96
0
     */
97
0
98
0
    size_t len = str->length();
99
0
    JSString* copy;
100
0
    if (str->isLinear()) {
101
0
        /* Only use AutoStableStringChars if the NoGC allocation fails. */
102
0
        if (str->hasLatin1Chars()) {
103
0
            JS::AutoCheckCannotGC nogc;
104
0
            copy = NewStringCopyN<NoGC>(cx, str->asLinear().latin1Chars(nogc), len);
105
0
        } else {
106
0
            JS::AutoCheckCannotGC nogc;
107
0
            copy = NewStringCopyNDontDeflate<NoGC>(cx, str->asLinear().twoByteChars(nogc), len);
108
0
        }
109
0
        if (copy) {
110
0
            return copy;
111
0
        }
112
0
113
0
        AutoStableStringChars chars(cx);
114
0
        if (!chars.init(cx, str)) {
115
0
            return nullptr;
116
0
        }
117
0
118
0
        return chars.isLatin1()
119
0
               ? NewStringCopyN<CanGC>(cx, chars.latin1Range().begin().get(), len)
120
0
               : NewStringCopyNDontDeflate<CanGC>(cx, chars.twoByteRange().begin().get(), len);
121
0
    }
122
0
123
0
    if (str->hasLatin1Chars()) {
124
0
        UniquePtr<Latin1Char[], JS::FreePolicy> copiedChars = str->asRope().copyLatin1CharsZ(cx);
125
0
        if (!copiedChars) {
126
0
            return nullptr;
127
0
        }
128
0
129
0
        return NewString<CanGC>(cx, std::move(copiedChars), len);
130
0
    }
131
0
132
0
    UniqueTwoByteChars copiedChars = str->asRope().copyTwoByteCharsZ(cx);
133
0
    if (!copiedChars) {
134
0
        return nullptr;
135
0
    }
136
0
137
0
    return NewStringDontDeflate<CanGC>(cx, std::move(copiedChars), len);
138
0
}
139
140
bool
141
Compartment::wrap(JSContext* cx, MutableHandleString strp)
142
0
{
143
0
    MOZ_ASSERT(cx->compartment() == this);
144
0
145
0
    /* If the string is already in this compartment, we are done. */
146
0
    JSString* str = strp;
147
0
    if (str->zoneFromAnyThread() == zone()) {
148
0
        return true;
149
0
    }
150
0
151
0
    /*
152
0
     * If the string is an atom, we don't have to copy, but we do need to mark
153
0
     * the atom as being in use by the new zone.
154
0
     */
155
0
    if (str->isAtom()) {
156
0
        cx->markAtom(&str->asAtom());
157
0
        return true;
158
0
    }
159
0
160
0
    /* Check the cache. */
161
0
    RootedValue key(cx, StringValue(str));
162
0
    if (WrapperMap::Ptr p = crossCompartmentWrappers.lookup(CrossCompartmentKey(key))) {
163
0
        strp.set(p->value().get().toString());
164
0
        return true;
165
0
    }
166
0
167
0
    /* No dice. Make a copy, and cache it. */
168
0
    JSString* copy = CopyStringPure(cx, str);
169
0
    if (!copy) {
170
0
        return false;
171
0
    }
172
0
    if (!putWrapper(cx, CrossCompartmentKey(key), StringValue(copy))) {
173
0
        return false;
174
0
    }
175
0
176
0
    strp.set(copy);
177
0
    return true;
178
0
}
179
180
#ifdef ENABLE_BIGINT
181
bool
182
Compartment::wrap(JSContext* cx, MutableHandleBigInt bi)
183
{
184
    MOZ_ASSERT(cx->compartment() == this);
185
186
    if (bi->zone() == cx->zone()) {
187
        return true;
188
    }
189
190
    BigInt* copy = BigInt::copy(cx, bi);
191
    if (!copy) {
192
        return false;
193
    }
194
    bi.set(copy);
195
    return true;
196
}
197
#endif
198
199
bool
200
Compartment::getNonWrapperObjectForCurrentCompartment(JSContext* cx, MutableHandleObject obj)
201
8.11M
{
202
8.11M
    // Ensure that we have entered a realm.
203
8.11M
    MOZ_ASSERT(cx->global());
204
8.11M
205
8.11M
    // If we have a cross-compartment wrapper, make sure that the cx isn't
206
8.11M
    // associated with the self-hosting zone. We don't want to create
207
8.11M
    // wrappers for objects in other runtimes, which may be the case for the
208
8.11M
    // self-hosting zone.
209
8.11M
    MOZ_ASSERT(!cx->runtime()->isSelfHostingZone(cx->zone()));
210
8.11M
    MOZ_ASSERT(!cx->runtime()->isSelfHostingZone(obj->zone()));
211
8.11M
212
8.11M
    // The object is already in the right compartment. Normally same-
213
8.11M
    // compartment returns the object itself, however, windows are always
214
8.11M
    // wrapped by a proxy, so we have to check for that case here manually.
215
8.11M
    if (obj->compartment() == this) {
216
8.11M
        obj.set(ToWindowProxyIfWindow(obj));
217
8.11M
        return true;
218
8.11M
    }
219
0
220
0
    // Note that if the object is same-compartment, but has been wrapped into a
221
0
    // different compartment, we need to unwrap it and return the bare same-
222
0
    // compartment object. Note again that windows are always wrapped by a
223
0
    // WindowProxy even when same-compartment so take care not to strip this
224
0
    // particular wrapper.
225
0
    RootedObject objectPassedToWrap(cx, obj);
226
0
    obj.set(UncheckedUnwrap(obj, /* stopAtWindowProxy = */ true));
227
0
    if (obj->compartment() == this) {
228
0
        MOZ_ASSERT(!IsWindow(obj));
229
0
        return true;
230
0
    }
231
0
232
0
    // Invoke the prewrap callback. The prewrap callback is responsible for
233
0
    // doing similar reification as above, but can account for any additional
234
0
    // embedder requirements.
235
0
    //
236
0
    // We're a bit worried about infinite recursion here, so we do a check -
237
0
    // see bug 809295.
238
0
    auto preWrap = cx->runtime()->wrapObjectCallbacks->preWrap;
239
0
    if (!CheckSystemRecursionLimit(cx)) {
240
0
        if (obj->getClass()->isDOMClass()) {
241
0
            MOZ_CRASH("Looks like bug 1488480/1405521, with system recursion limit failing in getNonWrapperObjectForCurrentCompartment");
242
0
        }
243
0
        return false;
244
0
    }
245
0
    if (preWrap) {
246
0
        preWrap(cx, cx->global(), obj, objectPassedToWrap, obj);
247
0
        if (!obj) {
248
0
            if (UncheckedUnwrap(objectPassedToWrap)->getClass()->isDOMClass()) {
249
0
                MOZ_CRASH("Looks like bug 1488480/1405521, with preWrap failing in getNonWrapperObjectForCurrentCompartment");
250
0
            }
251
0
            return false;
252
0
        }
253
0
    }
254
0
    MOZ_ASSERT(!IsWindow(obj));
255
0
256
0
    return true;
257
0
}
258
259
bool
260
Compartment::getOrCreateWrapper(JSContext* cx, HandleObject existing, MutableHandleObject obj)
261
0
{
262
0
    // If we already have a wrapper for this value, use it.
263
0
    RootedValue key(cx, ObjectValue(*obj));
264
0
    if (WrapperMap::Ptr p = crossCompartmentWrappers.lookup(CrossCompartmentKey(key))) {
265
0
        obj.set(&p->value().get().toObject());
266
0
        MOZ_ASSERT(obj->is<CrossCompartmentWrapperObject>());
267
0
        return true;
268
0
    }
269
0
270
0
    // Ensure that the wrappee is exposed in case we are creating a new wrapper
271
0
    // for a gray object.
272
0
    ExposeObjectToActiveJS(obj);
273
0
274
0
    // Create a new wrapper for the object.
275
0
    auto wrap = cx->runtime()->wrapObjectCallbacks->wrap;
276
0
    RootedObject wrapper(cx, wrap(cx, existing, obj));
277
0
    if (!wrapper) {
278
0
        if (key.toObject().getClass()->isDOMClass()) {
279
0
            MOZ_CRASH("Looks like bug 1488480/1405521, with wrap() call failing in Compartment::getOrCreateWrapper");
280
0
        }
281
0
        return false;
282
0
    }
283
0
284
0
    // We maintain the invariant that the key in the cross-compartment wrapper
285
0
    // map is always directly wrapped by the value.
286
0
    MOZ_ASSERT(Wrapper::wrappedObject(wrapper) == &key.get().toObject());
287
0
288
0
    if (!putWrapper(cx, CrossCompartmentKey(key), ObjectValue(*wrapper))) {
289
0
        // Enforce the invariant that all cross-compartment wrapper object are
290
0
        // in the map by nuking the wrapper if we couldn't add it.
291
0
        // Unfortunately it's possible for the wrapper to still be marked if we
292
0
        // took this path, for example if the object metadata callback stashes a
293
0
        // reference to it.
294
0
        if (wrapper->is<CrossCompartmentWrapperObject>()) {
295
0
            NukeCrossCompartmentWrapper(cx, wrapper);
296
0
        }
297
0
        if (obj->getClass()->isDOMClass()) {
298
0
            MOZ_CRASH("Looks like bug 1488480/1405521, with hashtable ops failing in  Compartment::getOrCreateWrapper");
299
0
        }
300
0
        return false;
301
0
    }
302
0
303
0
    obj.set(wrapper);
304
0
    return true;
305
0
}
306
307
bool
308
Compartment::wrap(JSContext* cx, MutableHandleObject obj)
309
8.11M
{
310
8.11M
    MOZ_ASSERT(cx->compartment() == this);
311
8.11M
312
8.11M
    if (!obj) {
313
0
        return true;
314
0
    }
315
8.11M
316
8.11M
    AutoDisableProxyCheck adpc;
317
8.11M
318
8.11M
    // Anything we're wrapping has already escaped into script, so must have
319
8.11M
    // been unmarked-gray at some point in the past.
320
8.11M
    MOZ_ASSERT(JS::ObjectIsNotGray(obj));
321
8.11M
322
8.11M
    // The passed object may already be wrapped, or may fit a number of special
323
8.11M
    // cases that we need to check for and manually correct.
324
8.11M
    if (!getNonWrapperObjectForCurrentCompartment(cx, obj)) {
325
0
        return false;
326
0
    }
327
8.11M
328
8.11M
    // If the reification above did not result in a same-compartment object,
329
8.11M
    // get or create a new wrapper object in this compartment for it.
330
8.11M
    if (obj->compartment() != this) {
331
0
        if (!getOrCreateWrapper(cx, nullptr, obj)) {
332
0
            return false;
333
0
        }
334
8.11M
    }
335
8.11M
336
8.11M
    // Ensure that the wrapper is also exposed.
337
8.11M
    ExposeObjectToActiveJS(obj);
338
8.11M
    return true;
339
8.11M
}
340
341
bool
342
Compartment::rewrap(JSContext* cx, MutableHandleObject obj, HandleObject existingArg)
343
0
{
344
0
    MOZ_ASSERT(cx->compartment() == this);
345
0
    MOZ_ASSERT(obj);
346
0
    MOZ_ASSERT(existingArg);
347
0
    MOZ_ASSERT(existingArg->compartment() == cx->compartment());
348
0
    MOZ_ASSERT(IsDeadProxyObject(existingArg));
349
0
350
0
    AutoDisableProxyCheck adpc;
351
0
352
0
    // It may not be possible to re-use existing; if so, clear it so that we
353
0
    // are forced to create a new wrapper. Note that this cannot call out to
354
0
    // |wrap| because of the different gray unmarking semantics.
355
0
    RootedObject existing(cx, existingArg);
356
0
    if (existing->hasStaticPrototype() ||
357
0
        // Note: Class asserted above, so all that's left to check is callability
358
0
        existing->isCallable() ||
359
0
        obj->isCallable())
360
0
    {
361
0
        existing.set(nullptr);
362
0
    }
363
0
364
0
    // The passed object may already be wrapped, or may fit a number of special
365
0
    // cases that we need to check for and manually correct.
366
0
    if (!getNonWrapperObjectForCurrentCompartment(cx, obj)) {
367
0
        return false;
368
0
    }
369
0
370
0
    // If the reification above resulted in a same-compartment object, we do
371
0
    // not need to create or return an existing wrapper.
372
0
    if (obj->compartment() == this) {
373
0
        return true;
374
0
    }
375
0
376
0
    return getOrCreateWrapper(cx, existing, obj);
377
0
}
378
379
bool
380
Compartment::wrap(JSContext* cx, MutableHandle<JS::PropertyDescriptor> desc)
381
0
{
382
0
    if (!wrap(cx, desc.object())) {
383
0
        return false;
384
0
    }
385
0
386
0
    if (desc.hasGetterObject()) {
387
0
        if (!wrap(cx, desc.getterObject())) {
388
0
            return false;
389
0
        }
390
0
    }
391
0
    if (desc.hasSetterObject()) {
392
0
        if (!wrap(cx, desc.setterObject())) {
393
0
            return false;
394
0
        }
395
0
    }
396
0
397
0
    return wrap(cx, desc.value());
398
0
}
399
400
bool
401
Compartment::wrap(JSContext* cx, MutableHandle<GCVector<Value>> vec)
402
0
{
403
0
    for (size_t i = 0; i < vec.length(); ++i) {
404
0
        if (!wrap(cx, vec[i])) {
405
0
            return false;
406
0
        }
407
0
    }
408
0
    return true;
409
0
}
410
411
void
412
Compartment::traceOutgoingCrossCompartmentWrappers(JSTracer* trc)
413
18
{
414
18
    MOZ_ASSERT(JS::RuntimeHeapIsMajorCollecting());
415
18
    MOZ_ASSERT(!zone()->isCollectingFromAnyThread() || trc->runtime()->gc.isHeapCompacting());
416
18
417
18
    for (NonStringWrapperEnum e(this); !e.empty(); e.popFront()) {
418
0
        if (e.front().key().is<JSObject*>()) {
419
0
            Value v = e.front().value().unbarrieredGet();
420
0
            ProxyObject* wrapper = &v.toObject().as<ProxyObject>();
421
0
422
0
            /*
423
0
             * We have a cross-compartment wrapper. Its private pointer may
424
0
             * point into the compartment being collected, so we should mark it.
425
0
             */
426
0
            ProxyObject::traceEdgeToTarget(trc, wrapper);
427
0
        }
428
0
    }
429
18
}
430
431
/* static */ void
432
Compartment::traceIncomingCrossCompartmentEdgesForZoneGC(JSTracer* trc)
433
18
{
434
18
    gcstats::AutoPhase ap(trc->runtime()->gc.stats(), gcstats::PhaseKind::MARK_CCWS);
435
18
    MOZ_ASSERT(JS::RuntimeHeapIsMajorCollecting());
436
72
    for (CompartmentsIter c(trc->runtime()); !c.done(); c.next()) {
437
54
        if (!c->zone()->isCollecting()) {
438
18
            c->traceOutgoingCrossCompartmentWrappers(trc);
439
18
        }
440
54
    }
441
18
    Debugger::traceIncomingCrossCompartmentEdges(trc);
442
18
}
443
444
void
445
Compartment::sweepAfterMinorGC(JSTracer* trc)
446
3
{
447
3
    crossCompartmentWrappers.sweepAfterMinorGC(trc);
448
3
449
6
    for (RealmsInCompartmentIter r(this); !r.done(); r.next()) {
450
3
        r->sweepAfterMinorGC();
451
3
    }
452
3
}
453
454
/*
455
 * Remove dead wrappers from the table. We must sweep all compartments, since
456
 * string entries in the crossCompartmentWrappers table are not marked during
457
 * markCrossCompartmentWrappers.
458
 */
459
void
460
Compartment::sweepCrossCompartmentWrappers()
461
36
{
462
36
    crossCompartmentWrappers.sweep();
463
36
}
464
465
namespace {
466
struct TraceRootFunctor {
467
    JSTracer* trc;
468
    const char* name;
469
0
    TraceRootFunctor(JSTracer* trc, const char* name) : trc(trc), name(name) {}
470
0
    template <class T> void operator()(T* t) { return TraceRoot(trc, t, name); }
Unexecuted instantiation: Unified_cpp_js_src33.cpp:void (anonymous namespace)::TraceRootFunctor::operator()<JSObject*>(JSObject**)
Unexecuted instantiation: Unified_cpp_js_src33.cpp:void (anonymous namespace)::TraceRootFunctor::operator()<JSString*>(JSString**)
Unexecuted instantiation: Unified_cpp_js_src33.cpp:void (anonymous namespace)::TraceRootFunctor::operator()<JSScript*>(JSScript**)
Unexecuted instantiation: Unified_cpp_js_src33.cpp:void (anonymous namespace)::TraceRootFunctor::operator()<js::LazyScript*>(js::LazyScript**)
Unexecuted instantiation: Unified_cpp_js_src33.cpp:void (anonymous namespace)::TraceRootFunctor::operator()<js::NativeObject*>(js::NativeObject**)
471
};
472
struct NeedsSweepUnbarrieredFunctor {
473
0
    template <class T> bool operator()(T* t) const { return IsAboutToBeFinalizedUnbarriered(t); }
Unexecuted instantiation: Unified_cpp_js_src33.cpp:bool (anonymous namespace)::NeedsSweepUnbarrieredFunctor::operator()<JSObject*>(JSObject**) const
Unexecuted instantiation: Unified_cpp_js_src33.cpp:bool (anonymous namespace)::NeedsSweepUnbarrieredFunctor::operator()<JSString*>(JSString**) const
Unexecuted instantiation: Unified_cpp_js_src33.cpp:bool (anonymous namespace)::NeedsSweepUnbarrieredFunctor::operator()<JSScript*>(JSScript**) const
Unexecuted instantiation: Unified_cpp_js_src33.cpp:bool (anonymous namespace)::NeedsSweepUnbarrieredFunctor::operator()<js::LazyScript*>(js::LazyScript**) const
Unexecuted instantiation: Unified_cpp_js_src33.cpp:bool (anonymous namespace)::NeedsSweepUnbarrieredFunctor::operator()<js::NativeObject*>(js::NativeObject**) const
474
};
475
} // namespace (anonymous)
476
477
void
478
CrossCompartmentKey::trace(JSTracer* trc)
479
0
{
480
0
    applyToWrapped(TraceRootFunctor(trc, "CrossCompartmentKey::wrapped"));
481
0
    applyToDebugger(TraceRootFunctor(trc, "CrossCompartmentKey::debugger"));
482
0
}
483
484
bool
485
CrossCompartmentKey::needsSweep()
486
0
{
487
0
    return applyToWrapped(NeedsSweepUnbarrieredFunctor()) ||
488
0
           applyToDebugger(NeedsSweepUnbarrieredFunctor());
489
0
}
490
491
/* static */ void
492
Compartment::fixupCrossCompartmentWrappersAfterMovingGC(JSTracer* trc)
493
0
{
494
0
    MOZ_ASSERT(trc->runtime()->gc.isHeapCompacting());
495
0
496
0
    for (CompartmentsIter comp(trc->runtime()); !comp.done(); comp.next()) {
497
0
        // Sweep the wrapper map to update keys (wrapped values) in other
498
0
        // compartments that may have been moved.
499
0
        comp->sweepCrossCompartmentWrappers();
500
0
        // Trace the wrappers in the map to update their cross-compartment edges
501
0
        // to wrapped values in other compartments that may have been moved.
502
0
        comp->traceOutgoingCrossCompartmentWrappers(trc);
503
0
    }
504
0
}
505
506
void
507
Compartment::fixupAfterMovingGC()
508
0
{
509
0
    MOZ_ASSERT(zone()->isGCCompacting());
510
0
511
0
    for (RealmsInCompartmentIter r(this); !r.done(); r.next()) {
512
0
        r->fixupAfterMovingGC();
513
0
    }
514
0
515
0
    // Sweep the wrapper map to update values (wrapper objects) in this
516
0
    // compartment that may have been moved.
517
0
    sweepCrossCompartmentWrappers();
518
0
}
519
520
void
521
Compartment::addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf,
522
                                    size_t* compartmentObjects,
523
                                    size_t* crossCompartmentWrappersTables,
524
                                    size_t* compartmentsPrivateData)
525
0
{
526
0
    *compartmentObjects += mallocSizeOf(this);
527
0
    *crossCompartmentWrappersTables += crossCompartmentWrappers.sizeOfExcludingThis(mallocSizeOf);
528
0
529
0
    if (auto callback = runtime_->sizeOfIncludingThisCompartmentCallback) {
530
0
        *compartmentsPrivateData += callback(mallocSizeOf, this);
531
0
    }
532
0
}