void ErrorReport::ReportAddonExceptionToTelementry(JSContext* cx) { MOZ_ASSERT(exnObject); RootedObject unwrapped(cx, UncheckedUnwrap(exnObject)); MOZ_ASSERT(unwrapped, "UncheckedUnwrap failed?"); // There is not much we can report if the exception is not an ErrorObject, let's ignore those. if (!unwrapped->is<ErrorObject>()) return; Rooted<ErrorObject*> errObj(cx, &unwrapped->as<ErrorObject>()); RootedObject stack(cx, errObj->stack()); // Let's ignore TOP level exceptions. For regular add-ons those will not be reported anyway, // for SDK based once it should not be a valid case either. // At this point the frame stack is unwound but the exception object stored the stack so let's // use that for getting the function name. if (!stack) return; JSCompartment* comp = stack->compartment(); JSAddonId* addonId = comp->creationOptions().addonIdOrNull(); // We only want to send the report if the scope that just have thrown belongs to an add-on. // Let's check the compartment of the youngest function on the stack, to determine that. if (!addonId) return; RootedString funnameString(cx); JS::SavedFrameResult result = GetSavedFrameFunctionDisplayName(cx, stack, &funnameString); // AccessDenied should never be the case here for add-ons but let's not risk it. JSAutoByteString bytes; const char* funname = nullptr; bool denied = result == JS::SavedFrameResult::AccessDenied; funname = denied ? "unknown" : funnameString ? AtomToPrintableString(cx, &funnameString->asAtom(), &bytes) : "anonymous"; UniqueChars addonIdChars(JS_EncodeString(cx, addonId)); const char* filename = nullptr; if (reportp && reportp->filename) { filename = strrchr(reportp->filename, '/'); if (filename) filename++; } if (!filename) { filename = "FILE_NOT_FOUND"; } char histogramKey[64]; SprintfLiteral(histogramKey, "%s %s %s %u", addonIdChars.get(), funname, filename, (reportp ? reportp->lineno : 0) ); cx->runtime()->addTelemetry(JS_TELEMETRY_ADDON_EXCEPTIONS, 1, histogramKey); }
IonBailoutIterator::IonBailoutIterator(const IonActivationIterator &activations, BailoutStack *bailout) : IonFrameIterator(activations), machine_(bailout->machine()) { uint8_t *sp = bailout->parentStackPointer(); uint8_t *fp = sp + bailout->frameSize(); current_ = fp; type_ = IonFrame_OptimizedJS; topFrameSize_ = current_ - sp; topIonScript_ = script()->ion; if (bailout->frameClass() == FrameSizeClass::None()) { snapshotOffset_ = bailout->snapshotOffset(); return; } // Compute the snapshot offset from the bailout ID. IonActivation *activation = activations.activation(); JSCompartment *jsCompartment = activation->compartment(); IonCompartment *ionCompartment = jsCompartment->ionCompartment(); IonCode *code = ionCompartment->getBailoutTable(bailout->frameClass()); uintptr_t tableOffset = bailout->tableOffset(); uintptr_t tableStart = reinterpret_cast<uintptr_t>(code->raw()); JS_ASSERT(tableOffset >= tableStart && tableOffset < tableStart + code->instructionsSize()); JS_ASSERT((tableOffset - tableStart) % BAILOUT_TABLE_ENTRY_SIZE == 0); uint32_t bailoutId = ((tableOffset - tableStart) / BAILOUT_TABLE_ENTRY_SIZE) - 1; JS_ASSERT(bailoutId < BAILOUT_TABLE_SIZE); snapshotOffset_ = topIonScript_->bailoutToSnapshot(bailoutId); }
/* * Rewrap *idp and the fields of *desc for the current compartment. Also: * defining a property on a proxy requires pd_ to contain a descriptor object, * so reconstitute desc->pd_ if needed. */ bool PropDesc::wrapInto(JSContext *cx, HandleObject obj, const jsid &id, jsid *wrappedId, PropDesc *desc) const { MOZ_ASSERT(!isUndefined()); JSCompartment *comp = cx->compartment; *wrappedId = id; if (!comp->wrapId(cx, wrappedId)) return false; *desc = *this; RootedValue value(cx, desc->value_); RootedValue get(cx, desc->get_); RootedValue set(cx, desc->set_); if (!comp->wrap(cx, &value) || !comp->wrap(cx, &get) || !comp->wrap(cx, &set)) return false; desc->value_ = value; desc->get_ = get; desc->set_ = set; return !obj->isProxy() || desc->makeObject(cx); }
bool JSCompartment::wrap(JSContext *cx, MutableHandle<PropDesc> desc) { if (desc.isUndefined()) return true; JSCompartment *comp = cx->compartment(); if (desc.hasValue()) { RootedValue value(cx, desc.value()); if (!comp->wrap(cx, &value)) return false; desc.setValue(value); } if (desc.hasGet()) { RootedValue get(cx, desc.getterValue()); if (!comp->wrap(cx, &get)) return false; desc.setGetter(get); } if (desc.hasSet()) { RootedValue set(cx, desc.setterValue()); if (!comp->wrap(cx, &set)) return false; desc.setSetter(set); } return true; }
/* * Move an IonBuilder for which compilation has either finished, failed, or * been cancelled into the Ion compartment's finished compilations list. * All off thread compilations which are started must eventually be finished. */ static void FinishOffThreadIonCompile(ion::IonBuilder *builder) { JSCompartment *compartment = builder->script->compartment(); JS_ASSERT(compartment->rt->workerThreadState->isLocked()); compartment->ionCompartment()->finishedOffThreadCompilations().append(builder); }
js::NukeCrossCompartmentWrapper(JSContext* cx, JSObject* wrapper) { JSCompartment* comp = wrapper->compartment(); auto ptr = comp->lookupWrapper(ObjectValue(*Wrapper::wrappedObject(wrapper))); if (ptr) comp->removeWrapper(ptr); NukeRemovedCrossCompartmentWrapper(cx, wrapper); }
static bool IsMarked(T **thingp) { JS_ASSERT(thingp); JS_ASSERT(*thingp); JSCompartment *c = (*thingp)->compartment(); if (!c->isCollecting() || c->isGCFinished()) return true; return (*thingp)->isMarked(); }
// Given a cross-compartment wrapper |wobj|, update it to point to // |newTarget|. This recomputes the wrapper with JS_WrapValue, and thus can be // useful even if wrapper already points to newTarget. bool js::RemapWrapper(JSContext *cx, JSObject *wobj, JSObject *newTarget) { JS_ASSERT(IsCrossCompartmentWrapper(wobj)); JS_ASSERT(!IsCrossCompartmentWrapper(newTarget)); JSObject *origTarget = Wrapper::wrappedObject(wobj); JS_ASSERT(origTarget); Value origv = ObjectValue(*origTarget); JSCompartment *wcompartment = wobj->compartment(); WrapperMap &pmap = wcompartment->crossCompartmentWrappers; // If we're mapping to a different target (as opposed to just recomputing // for the same target), we must not have an existing wrapper for the new // target, otherwise this will break. JS_ASSERT_IF(origTarget != newTarget, !pmap.has(ObjectValue(*newTarget))); // The old value should still be in the cross-compartment wrapper map, and // the lookup should return wobj. JS_ASSERT(&pmap.lookup(origv)->value.unsafeGet()->toObject() == wobj); pmap.remove(origv); // When we remove origv from the wrapper map, its wrapper, wobj, must // immediately cease to be a cross-compartment wrapper. Neuter it. NukeCrossCompartmentWrapper(cx, wobj); // First, we wrap it in the new compartment. We try to use the existing // wrapper, |wobj|, since it's been nuked anyway. The wrap() function has // the choice to reuse |wobj| or not. JSObject *tobj = newTarget; AutoCompartment ac(cx, wobj); if (!wcompartment->wrap(cx, &tobj, wobj)) MOZ_CRASH(); // If wrap() reused |wobj|, it will have overwritten it and returned with // |tobj == wobj|. Otherwise, |tobj| will point to a new wrapper and |wobj| // will still be nuked. In the latter case, we replace |wobj| with the // contents of the new wrapper in |tobj|. if (tobj != wobj) { // Now, because we need to maintain object identity, we do a brain // transplant on the old object so that it contains the contents of the // new one. if (!wobj->swap(cx, tobj)) MOZ_CRASH(); } // Before swapping, this wrapper came out of wrap(), which enforces the // invariant that the wrapper in the map points directly to the key. JS_ASSERT(Wrapper::wrappedObject(wobj) == newTarget); // Update the entry in the compartment's wrapper map to point to the old // wrapper, which has now been updated (via reuse or swap). pmap.put(ObjectValue(*newTarget), ObjectValue(*wobj)); return true; }
void ForkJoinShared::transferArenasToCompartmentAndProcessGCRequests() { JSCompartment *comp = cx_->compartment; for (unsigned i = 0; i < numSlices_; i++) comp->adoptWorkerAllocator(allocators_[i]); if (gcRequested_) { if (!gcZone_) TriggerGC(cx_->runtime, gcReason_); else TriggerZoneGC(gcZone_, gcReason_); gcRequested_ = false; gcZone_ = NULL; } }
// Given a cross-compartment wrapper |wobj|, update it to point to // |newTarget|. This recomputes the wrapper with JS_WrapValue, and thus can be // useful even if wrapper already points to newTarget. bool js::RemapWrapper(JSContext *cx, JSObject *wobj, JSObject *newTarget) { JS_ASSERT(IsCrossCompartmentWrapper(wobj)); JS_ASSERT(!IsCrossCompartmentWrapper(newTarget)); JSObject *origTarget = Wrapper::wrappedObject(wobj); JS_ASSERT(origTarget); Value origv = ObjectValue(*origTarget); JSCompartment *wcompartment = wobj->compartment(); WrapperMap &pmap = wcompartment->crossCompartmentWrappers; // If we're mapping to a different target (as opposed to just recomputing // for the same target), we must not have an existing wrapper for the new // target, otherwise this will break. JS_ASSERT_IF(origTarget != newTarget, !pmap.has(ObjectValue(*newTarget))); // The old value should still be in the cross-compartment wrapper map, and // the lookup should return wobj. JS_ASSERT(&pmap.lookup(origv)->value.toObject() == wobj); pmap.remove(origv); // When we remove origv from the wrapper map, its wrapper, wobj, must // immediately cease to be a cross-compartment wrapper. Neuter it. NukeCrossCompartmentWrapper(wobj); // First, we wrap it in the new compartment. This will return // a new wrapper. AutoCompartment ac(cx, wobj); JSObject *tobj = newTarget; if (!ac.enter() || !wcompartment->wrap(cx, &tobj)) return false; // Now, because we need to maintain object identity, we do a // brain transplant on the old object. At the same time, we // update the entry in the compartment's wrapper map to point // to the old wrapper. JS_ASSERT(tobj != wobj); if (!wobj->swap(cx, tobj)) return false; // Before swapping, this wrapper came out of wrap(), which enforces the // invariant that the wrapper in the map points directly to the key. JS_ASSERT(Wrapper::wrappedObject(wobj) == newTarget); pmap.put(ObjectValue(*newTarget), ObjectValue(*wobj)); return true; }
GlobalObject* GlobalObject::new_(JSContext* cx, const Class* clasp, JSPrincipals* principals, JS::OnNewGlobalHookOption hookOption, const JS::CompartmentOptions& options) { MOZ_ASSERT(!cx->isExceptionPending()); MOZ_ASSERT(!cx->runtime()->isAtomsCompartment(cx->compartment())); JSRuntime* rt = cx->runtime(); auto zoneSpecifier = options.creationOptions().zoneSpecifier(); Zone* zone; if (zoneSpecifier == JS::SystemZone) zone = rt->gc.systemZone; else if (zoneSpecifier == JS::FreshZone) zone = nullptr; else zone = static_cast<Zone*>(options.creationOptions().zonePointer()); JSCompartment* compartment = NewCompartment(cx, zone, principals, options); if (!compartment) return nullptr; // Lazily create the system zone. if (!rt->gc.systemZone && zoneSpecifier == JS::SystemZone) { rt->gc.systemZone = compartment->zone(); rt->gc.systemZone->isSystem = true; } Rooted<GlobalObject*> global(cx); { AutoCompartment ac(cx, compartment); global = GlobalObject::createInternal(cx, clasp); if (!global) return nullptr; } if (hookOption == JS::FireOnNewGlobalHook) JS_FireOnNewGlobalObject(cx, global); return global; }
/* static */ bool DebuggerMemory::setTrackingAllocationSites(JSContext *cx, unsigned argc, Value *vp) { THIS_DEBUGGER_MEMORY(cx, argc, vp, "(set trackingAllocationSites)", args, memory); if (!args.requireAtLeast(cx, "(set trackingAllocationSites)", 1)) return false; Debugger *dbg = memory->getDebugger(); bool enabling = ToBoolean(args[0]); if (enabling == dbg->trackingAllocationSites) { // Nothing to do here... args.rval().setUndefined(); return true; } if (enabling) { for (GlobalObjectSet::Range r = dbg->debuggees.all(); !r.empty(); r.popFront()) { JSCompartment *compartment = r.front()->compartment(); if (compartment->hasObjectMetadataCallback()) { JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_OBJECT_METADATA_CALLBACK_ALREADY_SET); return false; } } } for (GlobalObjectSet::Range r = dbg->debuggees.all(); !r.empty(); r.popFront()) { if (enabling) { r.front()->compartment()->setObjectMetadataCallback(SavedStacksMetadataCallback); } else { r.front()->compartment()->forgetObjectMetadataCallback(); } } if (!enabling) dbg->emptyAllocationsLog(); dbg->trackingAllocationSites = enabling; args.rval().setUndefined(); return true; }
/* * Rewrap *idp and the fields of *desc for the current compartment. Also: * defining a property on a proxy requires pd_ to contain a descriptor object, * so reconstitute desc->pd_ if needed. */ bool PropDesc::wrapInto(JSContext *cx, JSObject *obj, const jsid &id, jsid *wrappedId, PropDesc *desc) const { MOZ_ASSERT(!isUndefined()); JSCompartment *comp = cx->compartment; *wrappedId = id; if (!comp->wrapId(cx, wrappedId)) return false; *desc = *this; if (!comp->wrap(cx, &desc->value_)) return false; if (!comp->wrap(cx, &desc->get_)) return false; if (!comp->wrap(cx, &desc->set_)) return false; return !obj->isProxy() || desc->makeObject(cx); }
// Given a cross-compartment wrapper |wobj|, update it to point to // |newTarget|. This recomputes the wrapper with JS_WrapValue, and thus can be // useful even if wrapper already points to newTarget. // This operation crashes on failure rather than leaving the heap in an // inconsistent state. void js::RemapWrapper(JSContext* cx, JSObject* wobjArg, JSObject* newTargetArg) { RootedObject wobj(cx, wobjArg); RootedObject newTarget(cx, newTargetArg); MOZ_ASSERT(wobj->is<CrossCompartmentWrapperObject>()); MOZ_ASSERT(!newTarget->is<CrossCompartmentWrapperObject>()); JSObject* origTarget = Wrapper::wrappedObject(wobj); MOZ_ASSERT(origTarget); Value origv = ObjectValue(*origTarget); JSCompartment* wcompartment = wobj->compartment(); AutoDisableProxyCheck adpc(cx->runtime()); // If we're mapping to a different target (as opposed to just recomputing // for the same target), we must not have an existing wrapper for the new // target, otherwise this will break. MOZ_ASSERT_IF(origTarget != newTarget, !wcompartment->lookupWrapper(ObjectValue(*newTarget))); // The old value should still be in the cross-compartment wrapper map, and // the lookup should return wobj. WrapperMap::Ptr p = wcompartment->lookupWrapper(origv); MOZ_ASSERT(&p->value().unsafeGet()->toObject() == wobj); wcompartment->removeWrapper(p); // When we remove origv from the wrapper map, its wrapper, wobj, must // immediately cease to be a cross-compartment wrapper. Nuke it. NukeCrossCompartmentWrapper(cx, wobj); // First, we wrap it in the new compartment. We try to use the existing // wrapper, |wobj|, since it's been nuked anyway. The wrap() function has // the choice to reuse |wobj| or not. RootedObject tobj(cx, newTarget); AutoCompartment ac(cx, wobj); if (!wcompartment->wrap(cx, &tobj, wobj)) MOZ_CRASH(); // If wrap() reused |wobj|, it will have overwritten it and returned with // |tobj == wobj|. Otherwise, |tobj| will point to a new wrapper and |wobj| // will still be nuked. In the latter case, we replace |wobj| with the // contents of the new wrapper in |tobj|. if (tobj != wobj) { // Now, because we need to maintain object identity, we do a brain // transplant on the old object so that it contains the contents of the // new one. if (!JSObject::swap(cx, wobj, tobj)) MOZ_CRASH(); } // Before swapping, this wrapper came out of wrap(), which enforces the // invariant that the wrapper in the map points directly to the key. MOZ_ASSERT(Wrapper::wrappedObject(wobj) == newTarget); // Update the entry in the compartment's wrapper map to point to the old // wrapper, which has now been updated (via reuse or swap). MOZ_ASSERT(wobj->is<WrapperObject>()); if (!wcompartment->putWrapper(cx, CrossCompartmentKey(newTarget), ObjectValue(*wobj))) MOZ_CRASH(); }
/* * The given JSErrorReport object have been zeroed and must not outlive * cx->fp() (otherwise owned fields may become invalid). */ static void PopulateReportBlame(JSContext* cx, JSErrorReport* report) { JSCompartment* compartment = cx->compartment(); if (!compartment) return; /* * Walk stack until we find a frame that is associated with a non-builtin * rather than a builtin frame and which we're allowed to know about. */ NonBuiltinFrameIter iter(cx, compartment->principals()); if (iter.done()) return; report->filename = iter.filename(); report->lineno = iter.computeLine(&report->column); // XXX: Make the column 1-based as in other browsers, instead of 0-based // which is how SpiderMonkey stores it internally. This will be // unnecessary once bug 1144340 is fixed. report->column++; report->isMuted = iter.mutedErrors(); }
static bool ShouldMarkCrossCompartment(JSTracer *trc, RawObject src, Cell *cell) { if (!IS_GC_MARKING_TRACER(trc)) return true; JSCompartment *c = cell->compartment(); uint32_t color = AsGCMarker(trc)->getMarkColor(); JS_ASSERT(color == BLACK || color == GRAY); if (color == BLACK) { /* * Having black->gray edges violates our promise to the cycle * collector. This can happen if we're collecting a compartment and it * has an edge to an uncollected compartment: it's possible that the * source and destination of the cross-compartment edge should be gray, * but the source was marked black by the conservative scanner. */ if (cell->isMarked(GRAY)) { JS_ASSERT(!cell->compartment()->isCollecting()); trc->runtime->gcFoundBlackGrayEdges = true; } return c->isGCMarking(); } else { if (c->isGCMarkingBlack()) { /* * The destination compartment is being not being marked gray now, * but it will be later, so record the cell so it can be marked gray * at the appropriate time. */ if (!cell->isMarked()) DelayCrossCompartmentGrayMarking(src); return false; } return c->isGCMarkingGray(); } }
Shape * PropertyTree::getChild(JSContext *cx, Shape *parent_, uint32_t nfixed, const StackShape &child) { Shape *shape = NULL; JS_ASSERT(parent_); /* * The property tree has extremely low fan-out below its root in * popular embeddings with real-world workloads. Patterns such as * defining closures that capture a constructor's environment as * getters or setters on the new object that is passed in as * |this| can significantly increase fan-out below the property * tree root -- see bug 335700 for details. */ KidsPointer *kidp = &parent_->kids; if (kidp->isShape()) { Shape *kid = kidp->toShape(); if (kid->matches(child)) shape = kid; } else if (kidp->isHash()) { shape = *kidp->toHash()->lookup(child); } else { /* If kidp->isNull(), we always insert. */ } #ifdef JSGC_INCREMENTAL if (shape) { JSCompartment *comp = shape->compartment(); if (comp->needsBarrier()) { /* * We need a read barrier for the shape tree, since these are weak * pointers. */ Shape *tmp = shape; MarkShapeUnbarriered(comp->barrierTracer(), &tmp, "read barrier"); JS_ASSERT(tmp == shape); } else if (comp->isGCSweeping() && !shape->isMarked() && !shape->arenaHeader()->allocatedDuringIncremental) { /* * The shape we've found is unreachable and due to be finalized, so * remove our weak reference to it and don't use it. */ JS_ASSERT(parent_->isMarked()); parent_->removeChild(shape); shape = NULL; } } #endif if (shape) return shape; StackShape::AutoRooter childRoot(cx, &child); RootedShape parent(cx, parent_); shape = newShape(cx); if (!shape) return NULL; new (shape) Shape(child, nfixed); if (!insertChild(cx, parent, shape)) return NULL; return shape; }