bool JSCompartment::wrap(JSContext *cx, JSString **strp) { JS_ASSERT(!cx->runtime()->isAtomsCompartment(this)); JS_ASSERT(cx->compartment() == this); /* If the string is already in this compartment, we are done. */ JSString *str = *strp; if (str->zone() == zone()) return true; /* If the string is an atom, we don't have to copy. */ if (str->isAtom()) { JS_ASSERT(cx->runtime()->isAtomsZone(str->zone())); return true; } /* Check the cache. */ RootedValue key(cx, StringValue(str)); if (WrapperMap::Ptr p = crossCompartmentWrappers.lookup(key)) { *strp = p->value().get().toString(); return true; } /* * No dice. Make a copy, and cache it. Directly allocate the copy in the * destination compartment, rather than first flattening it (and possibly * allocating in source compartment), because we don't know whether the * flattening will pay off later. */ JSString *copy; if (str->hasPureChars()) { copy = js_NewStringCopyN<CanGC>(cx, str->pureChars(), str->length()); } else { ScopedJSFreePtr<jschar> copiedChars; if (!str->copyNonPureCharsZ(cx, copiedChars)) return false; copy = js_NewString<CanGC>(cx, copiedChars.forget(), str->length()); } if (!copy) return false; if (!putWrapper(cx, key, StringValue(copy))) return false; *strp = copy; return true; }
void JSString::visitChildren(JSCell* cell, SlotVisitor& visitor) { JSString* thisObject = jsCast<JSString*>(cell); Base::visitChildren(thisObject, visitor); MARK_LOG_MESSAGE1("[%u]: ", thisObject->length()); #if ENABLE(OBJECT_MARK_LOGGING) if (!thisObject->isRope()) { WTF::StringImpl* ourImpl = thisObject->m_value.impl(); if (ourImpl->is8Bit()) MARK_LOG_MESSAGE1("[8 %p]", ourImpl->characters8()); else MARK_LOG_MESSAGE1("[16 %p]", ourImpl->characters16()); } else MARK_LOG_MESSAGE0("[rope]: "); #endif if (thisObject->isRope()) static_cast<JSRopeString*>(thisObject)->visitFibers(visitor); else { StringImpl* impl = thisObject->m_value.impl(); ASSERT(impl); visitor.reportExtraMemoryUsage(impl->costDuringGC()); } }
// Overview: These functions convert a JSString from holding a string in rope form // down to a simple String representation. It does so by building up the string // backwards, since we want to avoid recursion, we expect that the tree structure // representing the rope is likely imbalanced with more nodes down the left side // (since appending to the string is likely more common) - and as such resolving // in this fashion should minimize work queue size. (If we built the queue forwards // we would likely have to place all of the constituent StringImpls into the // Vector before performing any concatenation, but by working backwards we likely // only fill the queue with the number of substrings at any given level in a // rope-of-ropes.) void JSRopeString::resolveRopeSlowCase8(LChar* buffer) const { LChar* position = buffer + length(); // We will be working backwards over the rope. Vector<JSString*, 32, UnsafeVectorOverflow> workQueue; // Putting strings into a Vector is only OK because there are no GC points in this method. for (size_t i = 0; i < s_maxInternalRopeLength && fiber(i); ++i) workQueue.append(fiber(i).get()); while (!workQueue.isEmpty()) { JSString* currentFiber = workQueue.last(); workQueue.removeLast(); const LChar* characters; if (currentFiber->isRope()) { JSRopeString* currentFiberAsRope = static_cast<JSRopeString*>(currentFiber); if (!currentFiberAsRope->isSubstring()) { for (size_t i = 0; i < s_maxInternalRopeLength && currentFiberAsRope->fiber(i); ++i) workQueue.append(currentFiberAsRope->fiber(i).get()); continue; } ASSERT(!currentFiberAsRope->substringBase()->isRope()); characters = currentFiberAsRope->substringBase()->m_value.characters8() + currentFiberAsRope->substringOffset(); } else characters = currentFiber->m_value.characters8(); unsigned length = currentFiber->length(); position -= length; StringImpl::copyChars(position, characters, length); } ASSERT(buffer == position); }
JSBool EvalScriptVersion16(JSContext *cx, uintN argc, jsval *vp) { JS_ASSERT(argc == 1); jsval *argv = JS_ARGV(cx, vp); JS_ASSERT(JSVAL_IS_STRING(argv[0])); JSString *str = JSVAL_TO_STRING(argv[0]); const jschar *chars = str->getChars(cx); JS_ASSERT(chars); size_t len = str->length(); return callbackData->evalVersion(chars, len, JSVERSION_1_6); }
static TriState equalToSingleCharacter(JSValue value, UChar character) { if (!value.isString()) return FalseTriState; JSString* jsString = asString(value); if (jsString->length() != 1) return FalseTriState; const StringImpl* string = jsString->tryGetValueImpl(); if (!string) return MixedTriState; return triState(string->at(0) == character); }
JSObjectRef JSFunction::MakeFunction(const JSContext& js_context, const JSString& body, const std::vector<JSString>& parameter_names, const JSString& function_name, const JSString& source_url, int starting_line_number) { JSValueRef exception { nullptr }; JSStringRef source_url_ref = (source_url.length() > 0) ? static_cast<JSStringRef>(source_url) : nullptr; JSObjectRef js_object_ref = nullptr; if (!parameter_names.empty()) { std::vector<JSStringRef> parameter_name_array = detail::to_vector(parameter_names); js_object_ref = JSObjectMakeFunction(static_cast<JSContextRef>(js_context), static_cast<JSStringRef>(function_name), static_cast<unsigned>(parameter_name_array.size()), ¶meter_name_array[0], static_cast<JSStringRef>(body), source_url_ref, starting_line_number, &exception); } else { js_object_ref = JSObjectMakeFunction(static_cast<JSContextRef>(js_context), static_cast<JSStringRef>(function_name), 0, nullptr, static_cast<JSStringRef>(body), source_url_ref, starting_line_number, &exception); } if (exception) { // If this assert fails then we need to JSValueUnprotect // js_object_ref. assert(!js_object_ref); detail::ThrowRuntimeError("JSFunction", JSValue(js_context, exception)); } return js_object_ref; }
/** * Handles an assertion failure in self-hosted code just like an assertion * failure in C++ code. Information about the failure can be provided in args[0]. */ static bool intrinsic_AssertionFailed(JSContext *cx, unsigned argc, Value *vp) { #ifdef DEBUG CallArgs args = CallArgsFromVp(argc, vp); if (args.length() > 0) { // try to dump the informative string JSString *str = ToString<CanGC>(cx, args[0]); if (str) { const jschar *chars = str->getChars(cx); if (chars) { fprintf(stderr, "Self-hosted JavaScript assertion info: "); JSString::dumpChars(chars, str->length()); fputc('\n', stderr); } } } #endif JS_ASSERT(false); return false; }
bool GetPropIRGenerator::tryAttachStringChar(ValOperandId valId, ValOperandId indexId) { MOZ_ASSERT(idVal_.isInt32()); if (!val_.isString()) return false; JSString* str = val_.toString(); int32_t index = idVal_.toInt32(); if (size_t(index) >= str->length() || !str->isLinear() || str->asLinear().latin1OrTwoByteChar(index) >= StaticStrings::UNIT_STATIC_LIMIT) { return false; } StringOperandId strId = writer.guardIsString(valId); Int32OperandId int32IndexId = writer.guardIsInt32(indexId); writer.loadStringCharResult(strId, int32IndexId); writer.returnFromIC(); return true; }
static JSBool JO(JSContext *cx, Value *vp, StringifyContext *scx) { JSObject *obj = &vp->toObject(); CycleDetector detect(scx, obj); if (!detect.init(cx)) return JS_FALSE; if (!scx->sb.append('{')) return JS_FALSE; Value vec[3] = { NullValue(), NullValue(), NullValue() }; AutoArrayRooter tvr(cx, JS_ARRAY_LENGTH(vec), vec); Value& outputValue = vec[0]; Value& whitelistElement = vec[1]; AutoIdRooter idr(cx); jsid& id = *idr.addr(); Value *keySource = vp; bool usingWhitelist = false; // if the replacer is an array, we use the keys from it if (scx->replacer && JS_IsArrayObject(cx, scx->replacer)) { usingWhitelist = true; vec[2].setObject(*scx->replacer); keySource = &vec[2]; } JSBool memberWritten = JS_FALSE; AutoIdVector props(cx); if (!GetPropertyNames(cx, &keySource->toObject(), JSITER_OWNONLY, &props)) return JS_FALSE; for (size_t i = 0, len = props.length(); i < len; i++) { outputValue.setUndefined(); if (!usingWhitelist) { if (!js_ValueToStringId(cx, IdToValue(props[i]), &id)) return JS_FALSE; } else { // skip non-index properties jsuint index = 0; if (!js_IdIsIndex(props[i], &index)) continue; if (!scx->replacer->getProperty(cx, props[i], &whitelistElement)) return JS_FALSE; if (!js_ValueToStringId(cx, whitelistElement, &id)) return JS_FALSE; } // We should have a string id by this point. Either from // JS_Enumerate's id array, or by converting an element // of the whitelist. JS_ASSERT(JSID_IS_ATOM(id)); if (!JS_GetPropertyById(cx, obj, id, Jsvalify(&outputValue))) return JS_FALSE; if (outputValue.isObjectOrNull() && !js_TryJSON(cx, &outputValue)) return JS_FALSE; // call this here, so we don't write out keys if the replacer function // wants to elide the value. if (!CallReplacerFunction(cx, id, obj, scx, &outputValue)) return JS_FALSE; JSType type = JS_TypeOfValue(cx, Jsvalify(outputValue)); // elide undefined values and functions and XML if (outputValue.isUndefined() || type == JSTYPE_FUNCTION || type == JSTYPE_XML) continue; // output a comma unless this is the first member to write if (memberWritten && !scx->sb.append(',')) return JS_FALSE; memberWritten = JS_TRUE; if (!WriteIndent(cx, scx, scx->depth)) return JS_FALSE; // Be careful below, this string is weakly rooted JSString *s = js_ValueToString(cx, IdToValue(id)); if (!s) return JS_FALSE; JS::Anchor<JSString *> anchor(s); size_t length = s->length(); const jschar *chars = s->getChars(cx); if (!chars) return JS_FALSE; if (!write_string(cx, scx->sb, chars, length) || !scx->sb.append(':') || !(scx->gap.empty() || scx->sb.append(' ')) || !Str(cx, id, obj, scx, &outputValue, true)) { return JS_FALSE; } } if (memberWritten && !WriteIndent(cx, scx, scx->depth - 1)) return JS_FALSE; return scx->sb.append('}'); }
JS_GetTraceThingInfo(char* buf, size_t bufsize, JSTracer* trc, void* thing, JS::TraceKind kind, bool details) { const char* name = nullptr; /* silence uninitialized warning */ size_t n; if (bufsize == 0) return; switch (kind) { case JS::TraceKind::Object: { name = static_cast<JSObject*>(thing)->getClass()->name; break; } case JS::TraceKind::Script: name = "script"; break; case JS::TraceKind::String: name = ((JSString*)thing)->isDependent() ? "substring" : "string"; break; case JS::TraceKind::Symbol: name = "symbol"; break; case JS::TraceKind::BaseShape: name = "base_shape"; break; case JS::TraceKind::JitCode: name = "jitcode"; break; case JS::TraceKind::LazyScript: name = "lazyscript"; break; case JS::TraceKind::Shape: name = "shape"; break; case JS::TraceKind::ObjectGroup: name = "object_group"; break; default: name = "INVALID"; break; } n = strlen(name); if (n > bufsize - 1) n = bufsize - 1; js_memcpy(buf, name, n + 1); buf += n; bufsize -= n; *buf = '\0'; if (details && bufsize > 2) { switch (kind) { case JS::TraceKind::Object: { JSObject* obj = (JSObject*)thing; if (obj->is<JSFunction>()) { JSFunction* fun = &obj->as<JSFunction>(); if (fun->displayAtom()) { *buf++ = ' '; bufsize--; PutEscapedString(buf, bufsize, fun->displayAtom(), 0); } } else if (obj->getClass()->flags & JSCLASS_HAS_PRIVATE) { JS_snprintf(buf, bufsize, " %p", obj->as<NativeObject>().getPrivate()); } else { JS_snprintf(buf, bufsize, " <no private>"); } break; } case JS::TraceKind::Script: { JSScript* script = static_cast<JSScript*>(thing); JS_snprintf(buf, bufsize, " %s:%" PRIuSIZE, script->filename(), script->lineno()); break; } case JS::TraceKind::String: { *buf++ = ' '; bufsize--; JSString* str = (JSString*)thing; if (str->isLinear()) { bool willFit = str->length() + strlen("<length > ") + CountDecimalDigits(str->length()) < bufsize; n = JS_snprintf(buf, bufsize, "<length %d%s> ", (int)str->length(), willFit ? "" : " (truncated)"); buf += n; bufsize -= n; PutEscapedString(buf, bufsize, &str->asLinear(), 0); } else { JS_snprintf(buf, bufsize, "<rope: length %d>", (int)str->length()); } break; } case JS::TraceKind::Symbol: { JS::Symbol* sym = static_cast<JS::Symbol*>(thing); if (JSString* desc = sym->description()) { if (desc->isLinear()) { *buf++ = ' '; bufsize--; PutEscapedString(buf, bufsize, &desc->asLinear(), 0); } else { JS_snprintf(buf, bufsize, "<nonlinear desc>"); } } else { JS_snprintf(buf, bufsize, "<null>"); } break; } default: break; } } buf[bufsize - 1] = '\0'; }
bool JSCompartment::wrap(JSContext *cx, Value *vp) { JS_ASSERT(cx->compartment == this); unsigned flags = 0; JS_CHECK_RECURSION(cx, return false); #ifdef DEBUG struct AutoDisableProxyCheck { JSRuntime *runtime; AutoDisableProxyCheck(JSRuntime *rt) : runtime(rt) { runtime->gcDisableStrictProxyCheckingCount++; } ~AutoDisableProxyCheck() { runtime->gcDisableStrictProxyCheckingCount--; } } adpc(rt); #endif /* Only GC things have to be wrapped or copied. */ if (!vp->isMarkable()) return true; if (vp->isString()) { JSString *str = vp->toString(); /* If the string is already in this compartment, we are done. */ if (str->compartment() == this) return true; /* If the string is an atom, we don't have to copy. */ if (str->isAtom()) { JS_ASSERT(str->compartment() == cx->runtime->atomsCompartment); return true; } } /* * Wrappers should really be parented to the wrapped parent of the wrapped * object, but in that case a wrapped global object would have a NULL * parent without being a proper global object (JSCLASS_IS_GLOBAL). Instead, * we parent all wrappers to the global object in their home compartment. * This loses us some transparency, and is generally very cheesy. */ HandleObject global = cx->global(); /* Unwrap incoming objects. */ if (vp->isObject()) { Rooted<JSObject*> obj(cx, &vp->toObject()); if (obj->compartment() == this) return WrapForSameCompartment(cx, obj, vp); /* Translate StopIteration singleton. */ if (obj->isStopIteration()) { RootedValue vvp(cx, *vp); bool result = js_FindClassObject(cx, JSProto_StopIteration, &vvp); *vp = vvp; return result; } /* Unwrap the object, but don't unwrap outer windows. */ obj = UnwrapObject(&vp->toObject(), /* stopAtOuter = */ true, &flags); if (obj->compartment() == this) return WrapForSameCompartment(cx, obj, vp); if (cx->runtime->preWrapObjectCallback) { obj = cx->runtime->preWrapObjectCallback(cx, global, obj, flags); if (!obj) return false; } if (obj->compartment() == this) return WrapForSameCompartment(cx, obj, vp); vp->setObject(*obj); #ifdef DEBUG { JSObject *outer = GetOuterObject(cx, obj); JS_ASSERT(outer && outer == obj); } #endif } RootedValue key(cx, *vp); /* If we already have a wrapper for this value, use it. */ if (WrapperMap::Ptr p = crossCompartmentWrappers.lookup(key)) { *vp = p->value; if (vp->isObject()) { RootedObject obj(cx, &vp->toObject()); JS_ASSERT(obj->isCrossCompartmentWrapper()); if (obj->getParent() != global) { do { if (!JSObject::setParent(cx, obj, global)) return false; obj = obj->getProto(); } while (obj && obj->isCrossCompartmentWrapper()); } } return true; } if (vp->isString()) { RootedValue orig(cx, *vp); JSString *str = vp->toString(); const jschar *chars = str->getChars(cx); if (!chars) return false; JSString *wrapped = js_NewStringCopyN(cx, chars, str->length()); if (!wrapped) return false; vp->setString(wrapped); return crossCompartmentWrappers.put(orig, *vp); } RootedObject obj(cx, &vp->toObject()); /* * Recurse to wrap the prototype. Long prototype chains will run out of * stack, causing an error in CHECK_RECURSE. * * Wrapping the proto before creating the new wrapper and adding it to the * cache helps avoid leaving a bad entry in the cache on OOM. But note that * if we wrapped both proto and parent, we would get infinite recursion * here (since Object.prototype->parent->proto leads to Object.prototype * itself). */ RootedObject proto(cx, obj->getProto()); if (!wrap(cx, proto.address())) return false; /* * We hand in the original wrapped object into the wrap hook to allow * the wrap hook to reason over what wrappers are currently applied * to the object. */ RootedObject wrapper(cx, cx->runtime->wrapObjectCallback(cx, obj, proto, global, flags)); if (!wrapper) return false; // We maintain the invariant that the key in the cross-compartment wrapper // map is always directly wrapped by the value. JS_ASSERT(Wrapper::wrappedObject(wrapper) == &key.get().toObject()); vp->setObject(*wrapper); if (!crossCompartmentWrappers.put(key, *vp)) return false; return true; }
static void StatsCellCallback(JSRuntime *rt, void *data, void *thing, JSGCTraceKind traceKind, size_t thingSize) { IteratorClosure *closure = static_cast<IteratorClosure *>(data); RuntimeStats *rtStats = closure->rtStats; ZoneStats *zStats = rtStats->currZoneStats; switch (traceKind) { case JSTRACE_OBJECT: { JSObject *obj = static_cast<JSObject *>(thing); CompartmentStats *cStats = GetCompartmentStats(obj->compartment()); if (obj->is<JSFunction>()) cStats->gcHeapObjectsFunction += thingSize; else if (obj->is<ArrayObject>()) cStats->gcHeapObjectsDenseArray += thingSize; else if (obj->isCrossCompartmentWrapper()) cStats->gcHeapObjectsCrossCompartmentWrapper += thingSize; else cStats->gcHeapObjectsOrdinary += thingSize; JS::ObjectsExtraSizes objectsExtra; obj->sizeOfExcludingThis(rtStats->mallocSizeOf_, &objectsExtra); cStats->objectsExtra.add(objectsExtra); // JSObject::sizeOfExcludingThis() doesn't measure objectsExtraPrivate, // so we do it here. if (ObjectPrivateVisitor *opv = closure->opv) { nsISupports *iface; if (opv->getISupports_(obj, &iface) && iface) { cStats->objectsExtra.private_ += opv->sizeOfIncludingThis(iface); } } break; } case JSTRACE_STRING: { JSString *str = static_cast<JSString *>(thing); size_t strSize = str->sizeOfExcludingThis(rtStats->mallocSizeOf_); // If we can't grow hugeStrings, let's just call this string non-huge. // We're probably about to OOM anyway. if (strSize >= JS::HugeStringInfo::MinSize() && zStats->hugeStrings.growBy(1)) { zStats->gcHeapStringsNormal += thingSize; JS::HugeStringInfo &info = zStats->hugeStrings.back(); info.length = str->length(); info.size = strSize; PutEscapedString(info.buffer, sizeof(info.buffer), &str->asLinear(), 0); } else if (str->isShort()) { MOZ_ASSERT(strSize == 0); zStats->gcHeapStringsShort += thingSize; } else { zStats->gcHeapStringsNormal += thingSize; zStats->stringCharsNonHuge += strSize; } break; } case JSTRACE_SHAPE: { Shape *shape = static_cast<Shape *>(thing); CompartmentStats *cStats = GetCompartmentStats(shape->compartment()); size_t propTableSize, kidsSize; shape->sizeOfExcludingThis(rtStats->mallocSizeOf_, &propTableSize, &kidsSize); if (shape->inDictionary()) { cStats->gcHeapShapesDict += thingSize; cStats->shapesExtraDictTables += propTableSize; JS_ASSERT(kidsSize == 0); } else { if (shape->base()->getObjectParent() == shape->compartment()->maybeGlobal()) { cStats->gcHeapShapesTreeGlobalParented += thingSize; } else { cStats->gcHeapShapesTreeNonGlobalParented += thingSize; } cStats->shapesExtraTreeTables += propTableSize; cStats->shapesExtraTreeShapeKids += kidsSize; } break; } case JSTRACE_BASE_SHAPE: { BaseShape *base = static_cast<BaseShape *>(thing); CompartmentStats *cStats = GetCompartmentStats(base->compartment()); cStats->gcHeapShapesBase += thingSize; break; } case JSTRACE_SCRIPT: { JSScript *script = static_cast<JSScript *>(thing); CompartmentStats *cStats = GetCompartmentStats(script->compartment()); cStats->gcHeapScripts += thingSize; cStats->scriptData += script->sizeOfData(rtStats->mallocSizeOf_); #ifdef JS_ION size_t baselineData = 0, baselineStubsFallback = 0; ion::SizeOfBaselineData(script, rtStats->mallocSizeOf_, &baselineData, &baselineStubsFallback); cStats->baselineData += baselineData; cStats->baselineStubsFallback += baselineStubsFallback; cStats->ionData += ion::SizeOfIonData(script, rtStats->mallocSizeOf_); #endif ScriptSource *ss = script->scriptSource(); SourceSet::AddPtr entry = closure->seenSources.lookupForAdd(ss); if (!entry) { closure->seenSources.add(entry, ss); // Not much to be done on failure. rtStats->runtime.scriptSources += ss->sizeOfIncludingThis(rtStats->mallocSizeOf_); } break; } case JSTRACE_LAZY_SCRIPT: { LazyScript *lazy = static_cast<LazyScript *>(thing); zStats->gcHeapLazyScripts += thingSize; zStats->lazyScripts += lazy->sizeOfExcludingThis(rtStats->mallocSizeOf_); break; } case JSTRACE_IONCODE: { #ifdef JS_ION zStats->gcHeapIonCodes += thingSize; // The code for a script is counted in ExecutableAllocator::sizeOfCode(). #endif break; } case JSTRACE_TYPE_OBJECT: { types::TypeObject *obj = static_cast<types::TypeObject *>(thing); zStats->gcHeapTypeObjects += thingSize; zStats->typeObjects += obj->sizeOfExcludingThis(rtStats->mallocSizeOf_); break; } } // Yes, this is a subtraction: see StatsArenaCallback() for details. zStats->gcHeapUnusedGcThings -= thingSize; }
bool JSCompartment::wrap(JSContext *cx, Value *vp) { JS_ASSERT(cx->compartment == this); uintN flags = 0; JS_CHECK_RECURSION(cx, return false); /* Only GC things have to be wrapped or copied. */ if (!vp->isMarkable()) return true; /* Static strings do not have to be wrapped. */ if (vp->isString() && JSString::isStatic(vp->toString())) return true; /* Unwrap incoming objects. */ if (vp->isObject()) { JSObject *obj = &vp->toObject(); /* If the object is already in this compartment, we are done. */ if (obj->getCompartment(cx) == this) return true; /* Don't unwrap an outer window proxy. */ if (!obj->getClass()->ext.innerObject) { obj = vp->toObject().unwrap(&flags); OBJ_TO_OUTER_OBJECT(cx, obj); if (!obj) return false; vp->setObject(*obj); } /* If the wrapped object is already in this compartment, we are done. */ if (obj->getCompartment(cx) == this) return true; } /* If we already have a wrapper for this value, use it. */ if (WrapperMap::Ptr p = crossCompartmentWrappers.lookup(*vp)) { *vp = p->value; return true; } if (vp->isString()) { Value orig = *vp; JSString *str = vp->toString(); JSString *wrapped = js_NewStringCopyN(cx, str->chars(), str->length()); if (!wrapped) return false; vp->setString(wrapped); return crossCompartmentWrappers.put(orig, *vp); } JSObject *obj = &vp->toObject(); /* * Recurse to wrap the prototype. Long prototype chains will run out of * stack, causing an error in CHECK_RECURSE. * * Wrapping the proto before creating the new wrapper and adding it to the * cache helps avoid leaving a bad entry in the cache on OOM. But note that * if we wrapped both proto and parent, we would get infinite recursion * here (since Object.prototype->parent->proto leads to Object.prototype * itself). */ JSObject *proto = obj->getProto(); if (!wrap(cx, &proto)) return false; /* * We hand in the original wrapped object into the wrap hook to allow * the wrap hook to reason over what wrappers are currently applied * to the object. */ JSObject *wrapper = cx->runtime->wrapObjectCallback(cx, obj, proto, flags); if (!wrapper) return false; wrapper->setProto(proto); vp->setObject(*wrapper); if (!crossCompartmentWrappers.put(wrapper->getProxyPrivate(), *vp)) return false; /* * Wrappers should really be parented to the wrapped parent of the wrapped * object, but in that case a wrapped global object would have a NULL * parent without being a proper global object (JSCLASS_IS_GLOBAL). Instead, * we parent all wrappers to the global object in their home compartment. * This loses us some transparency, and is generally very cheesy. */ JSObject *global; if (cx->hasfp()) { global = cx->fp()->scopeChain().getGlobal(); } else { global = cx->globalObject; OBJ_TO_INNER_OBJECT(cx, global); if (!global) return false; } wrapper->setParent(global); return true; }
JS_GetTraceThingInfo(char *buf, size_t bufsize, JSTracer *trc, void *thing, JSGCTraceKind kind, bool details) { const char *name = nullptr; /* silence uninitialized warning */ size_t n; if (bufsize == 0) return; switch (kind) { case JSTRACE_OBJECT: { name = static_cast<JSObject *>(thing)->getClass()->name; break; } case JSTRACE_STRING: name = ((JSString *)thing)->isDependent() ? "substring" : "string"; break; case JSTRACE_SCRIPT: name = "script"; break; case JSTRACE_LAZY_SCRIPT: name = "lazyscript"; break; case JSTRACE_JITCODE: name = "jitcode"; break; case JSTRACE_SHAPE: name = "shape"; break; case JSTRACE_BASE_SHAPE: name = "base_shape"; break; case JSTRACE_TYPE_OBJECT: name = "type_object"; break; } n = strlen(name); if (n > bufsize - 1) n = bufsize - 1; js_memcpy(buf, name, n + 1); buf += n; bufsize -= n; *buf = '\0'; if (details && bufsize > 2) { switch (kind) { case JSTRACE_OBJECT: { JSObject *obj = (JSObject *)thing; if (obj->is<JSFunction>()) { JSFunction *fun = &obj->as<JSFunction>(); if (fun->displayAtom()) { *buf++ = ' '; bufsize--; PutEscapedString(buf, bufsize, fun->displayAtom(), 0); } } else if (obj->getClass()->flags & JSCLASS_HAS_PRIVATE) { JS_snprintf(buf, bufsize, " %p", obj->getPrivate()); } else { JS_snprintf(buf, bufsize, " <no private>"); } break; } case JSTRACE_STRING: { *buf++ = ' '; bufsize--; JSString *str = (JSString *)thing; if (str->isLinear()) { bool willFit = str->length() + strlen("<length > ") + CountDecimalDigits(str->length()) < bufsize; n = JS_snprintf(buf, bufsize, "<length %d%s> ", (int)str->length(), willFit ? "" : " (truncated)"); buf += n; bufsize -= n; PutEscapedString(buf, bufsize, &str->asLinear(), 0); } else JS_snprintf(buf, bufsize, "<rope: length %d>", (int)str->length()); break; } case JSTRACE_SCRIPT: { JSScript *script = static_cast<JSScript *>(thing); JS_snprintf(buf, bufsize, " %s:%u", script->filename(), unsigned(script->lineno())); break; } case JSTRACE_LAZY_SCRIPT: case JSTRACE_JITCODE: case JSTRACE_SHAPE: case JSTRACE_BASE_SHAPE: case JSTRACE_TYPE_OBJECT: break; } } buf[bufsize - 1] = '\0'; }
static void StatsCellCallback(JSRuntime *rt, void *data, void *thing, JSGCTraceKind traceKind, size_t thingSize) { IteratorClosure *closure = static_cast<IteratorClosure *>(data); RuntimeStats *rtStats = closure->rtStats; CompartmentStats *cStats = rtStats->currCompartmentStats; switch (traceKind) { case JSTRACE_OBJECT: { JSObject *obj = static_cast<JSObject *>(thing); if (obj->isFunction()) { cStats->gcHeapObjectsFunction += thingSize; } else if (obj->isDenseArray()) { cStats->gcHeapObjectsDenseArray += thingSize; } else if (obj->isSlowArray()) { cStats->gcHeapObjectsSlowArray += thingSize; } else if (obj->isCrossCompartmentWrapper()) { cStats->gcHeapObjectsCrossCompartmentWrapper += thingSize; } else { cStats->gcHeapObjectsOrdinary += thingSize; } size_t slotsSize, elementsSize, argumentsDataSize, regExpStaticsSize, propertyIteratorDataSize; obj->sizeOfExcludingThis(rtStats->mallocSizeOf, &slotsSize, &elementsSize, &argumentsDataSize, ®ExpStaticsSize, &propertyIteratorDataSize); cStats->objectsExtraSlots += slotsSize; cStats->objectsExtraElements += elementsSize; cStats->objectsExtraArgumentsData += argumentsDataSize; cStats->objectsExtraRegExpStatics += regExpStaticsSize; cStats->objectsExtraPropertyIteratorData += propertyIteratorDataSize; if (ObjectPrivateVisitor *opv = closure->opv) { js::Class *clazz = js::GetObjectClass(obj); if (clazz->flags & JSCLASS_HAS_PRIVATE && clazz->flags & JSCLASS_PRIVATE_IS_NSISUPPORTS) { cStats->objectsExtraPrivate += opv->sizeOfIncludingThis(GetObjectPrivate(obj)); } } break; } case JSTRACE_STRING: { JSString *str = static_cast<JSString *>(thing); size_t strSize = str->sizeOfExcludingThis(rtStats->mallocSizeOf); // If we can't grow hugeStrings, let's just call this string non-huge. // We're probably about to OOM anyway. if (strSize >= HugeStringInfo::MinSize() && cStats->hugeStrings.growBy(1)) { cStats->gcHeapStringsNormal += thingSize; HugeStringInfo &info = cStats->hugeStrings.back(); info.length = str->length(); info.size = strSize; PutEscapedString(info.buffer, sizeof(info.buffer), &str->asLinear(), 0); } else if (str->isShort()) { MOZ_ASSERT(strSize == 0); cStats->gcHeapStringsShort += thingSize; } else { cStats->gcHeapStringsNormal += thingSize; cStats->stringCharsNonHuge += strSize; } break; } case JSTRACE_SHAPE: { Shape *shape = static_cast<Shape*>(thing); size_t propTableSize, kidsSize; shape->sizeOfExcludingThis(rtStats->mallocSizeOf, &propTableSize, &kidsSize); if (shape->inDictionary()) { cStats->gcHeapShapesDict += thingSize; cStats->shapesExtraDictTables += propTableSize; JS_ASSERT(kidsSize == 0); } else { if (shape->base()->getObjectParent() == shape->compartment()->maybeGlobal()) { cStats->gcHeapShapesTreeGlobalParented += thingSize; } else { cStats->gcHeapShapesTreeNonGlobalParented += thingSize; } cStats->shapesExtraTreeTables += propTableSize; cStats->shapesExtraTreeShapeKids += kidsSize; } break; } case JSTRACE_BASE_SHAPE: { cStats->gcHeapShapesBase += thingSize; break; } case JSTRACE_SCRIPT: { JSScript *script = static_cast<JSScript *>(thing); cStats->gcHeapScripts += thingSize; cStats->scriptData += script->sizeOfData(rtStats->mallocSizeOf); #ifdef JS_METHODJIT cStats->jaegerData += script->sizeOfJitScripts(rtStats->mallocSizeOf); # ifdef JS_ION cStats->ionData += ion::MemoryUsed(script, rtStats->mallocSizeOf); # endif #endif ScriptSource *ss = script->scriptSource(); SourceSet::AddPtr entry = closure->seenSources.lookupForAdd(ss); if (!entry) { closure->seenSources.add(entry, ss); // Not much to be done on failure. rtStats->runtime.scriptSources += ss->sizeOfIncludingThis(rtStats->mallocSizeOf); } break; } case JSTRACE_IONCODE: { #ifdef JS_METHODJIT # ifdef JS_ION cStats->gcHeapIonCodes += thingSize; // The code for a script is counted in ExecutableAllocator::sizeOfCode(). # endif #endif break; } case JSTRACE_TYPE_OBJECT: { types::TypeObject *obj = static_cast<types::TypeObject *>(thing); cStats->gcHeapTypeObjects += thingSize; obj->sizeOfExcludingThis(&cStats->typeInferenceSizes, rtStats->mallocSizeOf); break; } #if JS_HAS_XML_SUPPORT case JSTRACE_XML: { cStats->gcHeapXML += thingSize; break; } #endif } // Yes, this is a subtraction: see StatsArenaCallback() for details. cStats->gcHeapUnusedGcThings -= thingSize; }
void GCMarker::dumpConservativeRoots() { if (!conservativeDumpFileName) return; FILE *fp; if (!strcmp(conservativeDumpFileName, "stdout")) { fp = stdout; } else if (!strcmp(conservativeDumpFileName, "stderr")) { fp = stderr; } else if (!(fp = fopen(conservativeDumpFileName, "aw"))) { fprintf(stderr, "Warning: cannot open %s to dump the conservative roots\n", conservativeDumpFileName); return; } conservativeStats.dump(fp); for (void **thingp = conservativeRoots.begin(); thingp != conservativeRoots.end(); ++thingp) { void *thing = thingp; fprintf(fp, " %p: ", thing); switch (GetGCThingTraceKind(thing)) { default: JS_NOT_REACHED("Unknown trace kind"); case JSTRACE_OBJECT: { JSObject *obj = (JSObject *) thing; fprintf(fp, "object %s", obj->getClass()->name); break; } case JSTRACE_SHAPE: { fprintf(fp, "shape"); break; } case JSTRACE_STRING: { JSString *str = (JSString *) thing; if (str->isLinear()) { char buf[50]; PutEscapedString(buf, sizeof buf, &str->asLinear(), '"'); fprintf(fp, "string %s", buf); } else { fprintf(fp, "rope: length %d", (int)str->length()); } break; } # if JS_HAS_XML_SUPPORT case JSTRACE_XML: { JSXML *xml = (JSXML *) thing; fprintf(fp, "xml %u", (unsigned)xml->xml_class); break; } # endif } fputc('\n', fp); } fputc('\n', fp); if (fp != stdout && fp != stderr) fclose(fp); }
bool JSCompartment::wrap(JSContext *cx, Value *vp) { JS_ASSERT(cx->compartment == this); unsigned flags = 0; JS_CHECK_RECURSION(cx, return false); /* Only GC things have to be wrapped or copied. */ if (!vp->isMarkable()) return true; if (vp->isString()) { JSString *str = vp->toString(); /* If the string is already in this compartment, we are done. */ if (str->compartment() == this) return true; /* If the string is an atom, we don't have to copy. */ if (str->isAtom()) { JS_ASSERT(str->compartment() == cx->runtime->atomsCompartment); return true; } } /* * Wrappers should really be parented to the wrapped parent of the wrapped * object, but in that case a wrapped global object would have a NULL * parent without being a proper global object (JSCLASS_IS_GLOBAL). Instead, * we parent all wrappers to the global object in their home compartment. * This loses us some transparency, and is generally very cheesy. */ JSObject *global; if (cx->hasfp()) { global = &cx->fp()->scopeChain().global(); } else { global = JS_ObjectToInnerObject(cx, cx->globalObject); if (!global) return false; } /* Unwrap incoming objects. */ if (vp->isObject()) { JSObject *obj = &vp->toObject(); /* If the object is already in this compartment, we are done. */ if (obj->compartment() == this) return true; /* Translate StopIteration singleton. */ if (obj->isStopIteration()) return js_FindClassObject(cx, NULL, JSProto_StopIteration, vp); /* Don't unwrap an outer window proxy. */ if (!obj->getClass()->ext.innerObject) { obj = UnwrapObject(&vp->toObject(), true, &flags); vp->setObject(*obj); if (obj->compartment() == this) return true; if (cx->runtime->preWrapObjectCallback) { obj = cx->runtime->preWrapObjectCallback(cx, global, obj, flags); if (!obj) return false; } vp->setObject(*obj); if (obj->compartment() == this) return true; } else { if (cx->runtime->preWrapObjectCallback) { obj = cx->runtime->preWrapObjectCallback(cx, global, obj, flags); if (!obj) return false; } JS_ASSERT(!obj->isWrapper() || obj->getClass()->ext.innerObject); vp->setObject(*obj); } #ifdef DEBUG { JSObject *outer = obj; OBJ_TO_OUTER_OBJECT(cx, outer); JS_ASSERT(outer && outer == obj); } #endif } /* If we already have a wrapper for this value, use it. */ if (WrapperMap::Ptr p = crossCompartmentWrappers.lookup(*vp)) { *vp = p->value; if (vp->isObject()) { JSObject *obj = &vp->toObject(); JS_ASSERT(obj->isCrossCompartmentWrapper()); if (global->getClass() != &dummy_class && obj->getParent() != global) { do { if (!obj->setParent(cx, global)) return false; obj = obj->getProto(); } while (obj && obj->isCrossCompartmentWrapper()); } } return true; } if (vp->isString()) { Value orig = *vp; JSString *str = vp->toString(); const jschar *chars = str->getChars(cx); if (!chars) return false; JSString *wrapped = js_NewStringCopyN(cx, chars, str->length()); if (!wrapped) return false; vp->setString(wrapped); return crossCompartmentWrappers.put(orig, *vp); } JSObject *obj = &vp->toObject(); /* * Recurse to wrap the prototype. Long prototype chains will run out of * stack, causing an error in CHECK_RECURSE. * * Wrapping the proto before creating the new wrapper and adding it to the * cache helps avoid leaving a bad entry in the cache on OOM. But note that * if we wrapped both proto and parent, we would get infinite recursion * here (since Object.prototype->parent->proto leads to Object.prototype * itself). */ JSObject *proto = obj->getProto(); if (!wrap(cx, &proto)) return false; /* * We hand in the original wrapped object into the wrap hook to allow * the wrap hook to reason over what wrappers are currently applied * to the object. */ JSObject *wrapper = cx->runtime->wrapObjectCallback(cx, obj, proto, global, flags); if (!wrapper) return false; vp->setObject(*wrapper); if (wrapper->getProto() != proto && !SetProto(cx, wrapper, proto, false)) return false; if (!crossCompartmentWrappers.put(GetProxyPrivate(wrapper), *vp)) return false; if (!wrapper->setParent(cx, global)) return false; return true; }
JS_PUBLIC_API void JS_GetTraceThingInfo(char* buf, size_t bufsize, JSTracer* trc, void* thing, JS::TraceKind kind, bool details) { const char* name = nullptr; /* silence uninitialized warning */ size_t n; if (bufsize == 0) { return; } switch (kind) { case JS::TraceKind::BaseShape: name = "base_shape"; break; case JS::TraceKind::JitCode: name = "jitcode"; break; case JS::TraceKind::LazyScript: name = "lazyscript"; break; case JS::TraceKind::Null: name = "null_pointer"; break; case JS::TraceKind::Object: { name = static_cast<JSObject*>(thing)->getClass()->name; break; } case JS::TraceKind::ObjectGroup: name = "object_group"; break; case JS::TraceKind::RegExpShared: name = "reg_exp_shared"; break; case JS::TraceKind::Scope: name = "scope"; break; case JS::TraceKind::Script: name = "script"; break; case JS::TraceKind::Shape: name = "shape"; break; case JS::TraceKind::String: name = ((JSString*)thing)->isDependent() ? "substring" : "string"; break; case JS::TraceKind::Symbol: name = "symbol"; break; #ifdef ENABLE_BIGINT case JS::TraceKind::BigInt: name = "BigInt"; break; #endif default: name = "INVALID"; break; } n = strlen(name); if (n > bufsize - 1) { n = bufsize - 1; } js_memcpy(buf, name, n + 1); buf += n; bufsize -= n; *buf = '\0'; if (details && bufsize > 2) { switch (kind) { case JS::TraceKind::Object: { JSObject* obj = (JSObject*)thing; if (obj->is<JSFunction>()) { JSFunction* fun = &obj->as<JSFunction>(); if (fun->displayAtom()) { *buf++ = ' '; bufsize--; PutEscapedString(buf, bufsize, fun->displayAtom(), 0); } } else if (obj->getClass()->flags & JSCLASS_HAS_PRIVATE) { snprintf(buf, bufsize, " %p", obj->as<NativeObject>().getPrivate()); } else { snprintf(buf, bufsize, " <no private>"); } break; } case JS::TraceKind::Script: { JSScript* script = static_cast<JSScript*>(thing); snprintf(buf, bufsize, " %s:%u", script->filename(), script->lineno()); break; } case JS::TraceKind::LazyScript: { LazyScript* script = static_cast<LazyScript*>(thing); snprintf(buf, bufsize, " %s:%u", script->filename(), script->lineno()); break; } case JS::TraceKind::String: { *buf++ = ' '; bufsize--; JSString* str = (JSString*)thing; if (str->isLinear()) { const char* header = StringKindHeader(str); bool willFit = str->length() + strlen("<length > ") + strlen(header) + CountDecimalDigits(str->length()) < bufsize; n = snprintf(buf, bufsize, "<%slength %zu%s> ", header, str->length(), willFit ? "" : " (truncated)"); buf += n; bufsize -= n; PutEscapedString(buf, bufsize, &str->asLinear(), 0); } else { snprintf(buf, bufsize, "<rope: length %zu>", str->length()); } break; } case JS::TraceKind::Symbol: { JS::Symbol* sym = static_cast<JS::Symbol*>(thing); if (JSString* desc = sym->description()) { if (desc->isLinear()) { *buf++ = ' '; bufsize--; PutEscapedString(buf, bufsize, &desc->asLinear(), 0); } else { snprintf(buf, bufsize, "<nonlinear desc>"); } } else { snprintf(buf, bufsize, "<null>"); } break; } case JS::TraceKind::Scope: { js::Scope* scope = static_cast<js::Scope*>(thing); snprintf(buf, bufsize, " %s", js::ScopeKindString(scope->kind())); break; } default: break; } } buf[bufsize - 1] = '\0'; }