JSLinearString* js::NewDependentString(JSContext* cx, JSString* baseArg, size_t start, size_t length) { if (length == 0) return cx->emptyString(); JSLinearString* base = baseArg->ensureLinear(cx); if (!base) return nullptr; if (start == 0 && length == base->length()) return base; if (base->hasTwoByteChars()) { AutoCheckCannotGC nogc; const char16_t* chars = base->twoByteChars(nogc) + start; if (JSLinearString* staticStr = cx->staticStrings().lookup(chars, length)) return staticStr; } else { AutoCheckCannotGC nogc; const Latin1Char* chars = base->latin1Chars(nogc) + start; if (JSLinearString* staticStr = cx->staticStrings().lookup(chars, length)) return staticStr; } return JSDependentString::new_(cx, base, start, length); }
bool js::ParseRegExpFlags(JSContext* cx, JSString* flagStr, RegExpFlag* flagsOut) { JSLinearString* linear = flagStr->ensureLinear(cx); if (!linear) return false; size_t len = linear->length(); bool ok; char16_t lastParsed; if (linear->hasLatin1Chars()) { AutoCheckCannotGC nogc; ok = ::ParseRegExpFlags(linear->latin1Chars(nogc), len, flagsOut, &lastParsed); } else { AutoCheckCannotGC nogc; ok = ::ParseRegExpFlags(linear->twoByteChars(nogc), len, flagsOut, &lastParsed); } if (!ok) { char charBuf[2]; charBuf[0] = char(lastParsed); charBuf[1] = '\0'; JS_ReportErrorFlagsAndNumber(cx, JSREPORT_ERROR, GetErrorMessage, nullptr, JSMSG_BAD_REGEXP_FLAG, charBuf); return false; } return true; }
JSAtom * js::AtomizeString(JSContext *cx, JSString *str, js::InternBehavior ib /* = js::DoNotInternAtom */) { if (str->isAtom()) { JSAtom &atom = str->asAtom(); /* N.B. static atoms are effectively always interned. */ if (ib != InternAtom || js::StaticStrings::isStatic(&atom)) return &atom; AtomSet::Ptr p = cx->runtime->atoms.lookup(AtomHasher::Lookup(&atom)); JS_ASSERT(p); /* Non-static atom must exist in atom state set. */ JS_ASSERT(p->asPtr() == &atom); JS_ASSERT(ib == InternAtom); p->setTagged(bool(ib)); return &atom; } const jschar *chars = str->getChars(cx); if (!chars) return NULL; if (JSAtom *atom = AtomizeAndCopyChars<NoGC>(cx, chars, str->length(), ib)) return atom; if (!allowGC) return NULL; JSLinearString *linear = str->ensureLinear(cx); if (!linear) return NULL; JS_ASSERT(linear->length() <= JSString::MAX_LENGTH); return AtomizeAndCopyChars<CanGC>(cx, linear->chars(), linear->length(), ib); }
JSBool js_json_parse(JSContext *cx, uintN argc, Value *vp) { JSString *s = NULL; Value *argv = vp + 2; AutoValueRooter reviver(cx); if (!JS_ConvertArguments(cx, argc, Jsvalify(argv), "S / v", &s, reviver.addr())) return JS_FALSE; JSLinearString *linearStr = s->ensureLinear(cx); if (!linearStr) return JS_FALSE; JSONParser *jp = js_BeginJSONParse(cx, vp); JSBool ok = jp != NULL; if (ok) { const jschar *chars = linearStr->chars(); size_t length = linearStr->length(); ok = js_ConsumeJSONText(cx, jp, chars, length); ok &= !!js_FinishJSONParse(cx, jp, reviver.value()); } return ok; }
JSFlatString * RegExpObject::toString(JSContext *cx) const { JSLinearString *src = getSource(); StringBuffer sb(cx); if (size_t len = src->length()) { if (!sb.reserve(len + 2)) return NULL; sb.infallibleAppend('/'); sb.infallibleAppend(src->chars(), len); sb.infallibleAppend('/'); } else { if (!sb.append("/(?:)/")) return NULL; } if (global() && !sb.append('g')) return NULL; if (ignoreCase() && !sb.append('i')) return NULL; if (multiline() && !sb.append('m')) return NULL; if (sticky() && !sb.append('y')) return NULL; return sb.finishString(); }
bool ScopedThreadSafeStringInspector::ensureChars(ThreadSafeContext *cx, const AutoCheckCannotGC &nogc) { if (state_ != Uninitialized) return true; if (cx->isExclusiveContext()) { JSLinearString *linear = str_->ensureLinear(cx->asExclusiveContext()); if (!linear) return false; if (linear->hasTwoByteChars()) { state_ = TwoByte; twoByteChars_ = linear->twoByteChars(nogc); } else { state_ = Latin1; latin1Chars_ = linear->latin1Chars(nogc); } } else { if (str_->hasPureChars()) { state_ = TwoByte; twoByteChars_ = str_->pureChars(); } else { if (!str_->copyNonPureChars(cx, scopedChars_)) return false; state_ = TwoByte; twoByteChars_ = scopedChars_; } } MOZ_ASSERT(state_ != Uninitialized); return true; }
void CopyChars(jschar *dest, const JSLinearString &str) { AutoCheckCannotGC nogc; if (str.hasTwoByteChars()) PodCopy(dest, str.twoByteChars(nogc), str.length()); else CopyAndInflateChars(dest, str.latin1Chars(nogc), str.length()); }
static bool Quote(JSContext* cx, StringBuffer& sb, JSString* str) { JSLinearString* linear = str->ensureLinear(cx); if (!linear) return false; return linear->hasLatin1Chars() ? Quote<Latin1Char>(sb, linear) : Quote<char16_t>(sb, linear); }
bool ScopedThreadSafeStringInspector::ensureChars(ThreadSafeContext *cx, const AutoCheckCannotGC &nogc) { if (state_ != Uninitialized) return true; if (cx->isExclusiveContext()) { JSLinearString *linear = str_->ensureLinear(cx->asExclusiveContext()); if (!linear) return false; if (linear->hasTwoByteChars()) { state_ = TwoByte; twoByteChars_ = linear->twoByteChars(nogc); } else { state_ = Latin1; latin1Chars_ = linear->latin1Chars(nogc); } } else { if (str_->isLinear()) { if (str_->hasLatin1Chars()) { state_ = Latin1; latin1Chars_ = str_->asLinear().latin1Chars(nogc); } else { state_ = TwoByte; twoByteChars_ = str_->asLinear().twoByteChars(nogc); } } else { if (str_->hasLatin1Chars()) { ScopedJSFreePtr<Latin1Char> chars; if (!str_->asRope().copyLatin1Chars(cx, chars)) return false; state_ = Latin1; latin1Chars_ = chars; scopedChars_ = chars.forget(); } else { ScopedJSFreePtr<jschar> chars; if (!str_->asRope().copyTwoByteChars(cx, chars)) return false; state_ = TwoByte; twoByteChars_ = chars; scopedChars_ = chars.forget(); } } } MOZ_ASSERT(state_ != Uninitialized); return true; }
/* ES5 15.12.2. */ JSBool js_json_parse(JSContext *cx, unsigned argc, Value *vp) { /* Step 1. */ JSLinearString *linear; if (argc >= 1) { JSString *str = ToString(cx, vp[2]); if (!str) return false; linear = str->ensureLinear(cx); if (!linear) return false; } else { linear = cx->runtime->atomState.typeAtoms[JSTYPE_VOID]; } JS::Anchor<JSString *> anchor(linear); Value reviver = (argc >= 2) ? vp[3] : UndefinedValue(); /* Steps 2-5. */ return ParseJSONWithReviver(cx, linear->chars(), linear->length(), reviver, vp); }
void CopyChars(Latin1Char* dest, const JSLinearString& str) { AutoCheckCannotGC nogc; if (str.hasLatin1Chars()) { PodCopy(dest, str.latin1Chars(nogc), str.length()); } else { /* * When we flatten a TwoByte rope, we turn child ropes (including Latin1 * ropes) into TwoByte dependent strings. If one of these strings is * also part of another Latin1 rope tree, we can have a Latin1 rope with * a TwoByte descendent and we end up here when we flatten it. Although * the chars are stored as TwoByte, we know they must be in the Latin1 * range, so we can safely deflate here. */ size_t len = str.length(); const char16_t* chars = str.twoByteChars(nogc); for (size_t i = 0; i < len; i++) { MOZ_ASSERT(chars[i] <= JSString::MAX_LATIN1_CHAR); dest[i] = chars[i]; } } }
bool ScopedThreadSafeStringInspector::ensureChars(ThreadSafeContext *cx) { if (chars_) return true; if (cx->isExclusiveContext()) { JSLinearString *linear = str_->ensureLinear(cx->asExclusiveContext()); if (!linear) return false; chars_ = linear->chars(); } else { if (str_->hasPureChars()) { chars_ = str_->pureChars(); } else { if (!str_->copyNonPureChars(cx, scopedChars_)) return false; chars_ = scopedChars_; } } JS_ASSERT(chars_); return true; }
/* ES5 15.12.2. */ JSBool js_json_parse(JSContext *cx, unsigned argc, Value *vp) { CallArgs args = CallArgsFromVp(argc, vp); /* Step 1. */ JSLinearString *linear; if (argc >= 1) { JSString *str = ToString(cx, args[0]); if (!str) return false; linear = str->ensureLinear(cx); if (!linear) return false; } else { linear = cx->names().undefined; } JS::Anchor<JSString *> anchor(linear); RootedValue reviver(cx, (argc >= 2) ? args[1] : UndefinedValue()); /* Steps 2-5. */ return ParseJSONWithReviver(cx, linear->chars(), linear->length(), reviver, args.rval()); }
bool js::intl_isDefaultTimeZone(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); MOZ_ASSERT(args.length() == 1); MOZ_ASSERT(args[0].isString() || args[0].isUndefined()); // |undefined| is the default value when the Intl runtime caches haven't // yet been initialized. Handle it the same way as a cache miss. if (args[0].isUndefined()) { args.rval().setBoolean(false); return true; } // The current default might be stale, because JS::ResetTimeZone() doesn't // immediately update ICU's default time zone. So perform an update if // needed. js::ResyncICUDefaultTimeZone(); Vector<char16_t, INITIAL_CHAR_BUFFER_SIZE> chars(cx); int32_t size = CallICU(cx, ucal_getDefaultTimeZone, chars); if (size < 0) return false; JSLinearString* str = args[0].toString()->ensureLinear(cx); if (!str) return false; bool equals; if (str->length() == size_t(size)) { JS::AutoCheckCannotGC nogc; equals = str->hasLatin1Chars() ? EqualChars(str->latin1Chars(nogc), chars.begin(), str->length()) : EqualChars(str->twoByteChars(nogc), chars.begin(), str->length()); } else { equals = false; } args.rval().setBoolean(equals); return true; }
JSAtom * js::AtomizeString(ExclusiveContext *cx, JSString *str, js::InternBehavior ib /* = js::DoNotInternAtom */) { if (str->isAtom()) { JSAtom &atom = str->asAtom(); /* N.B. static atoms are effectively always interned. */ if (ib != InternAtom || js::StaticStrings::isStatic(&atom)) return &atom; AtomHasher::Lookup lookup(&atom); /* Likewise, permanent atoms are always interned. */ MOZ_ASSERT(cx->isPermanentAtomsInitialized()); AtomSet::Ptr p = cx->permanentAtoms().readonlyThreadsafeLookup(lookup); if (p) return &atom; AutoLockForExclusiveAccess lock(cx); p = cx->atoms().lookup(lookup); MOZ_ASSERT(p); /* Non-static atom must exist in atom state set. */ MOZ_ASSERT(p->asPtr() == &atom); MOZ_ASSERT(ib == InternAtom); p->setTagged(bool(ib)); return &atom; } JSLinearString *linear = str->ensureLinear(cx); if (!linear) return nullptr; JS::AutoCheckCannotGC nogc; return linear->hasLatin1Chars() ? AtomizeAndCopyChars(cx, linear->latin1Chars(nogc), linear->length(), ib) : AtomizeAndCopyChars(cx, linear->twoByteChars(nogc), linear->length(), ib); }
/* ES6 24.3.2. */ bool js::Stringify(JSContext* cx, MutableHandleValue vp, JSObject* replacer_, const Value& space_, StringBuffer& sb, StringifyBehavior stringifyBehavior) { RootedObject replacer(cx, replacer_); RootedValue space(cx, space_); MOZ_ASSERT_IF(stringifyBehavior == StringifyBehavior::RestrictedSafe, space.isNull()); MOZ_ASSERT_IF(stringifyBehavior == StringifyBehavior::RestrictedSafe, vp.isObject()); /** * This uses MOZ_ASSERT, since it's actually asserting something jsapi * consumers could get wrong, so needs a better error message. */ MOZ_ASSERT(stringifyBehavior == StringifyBehavior::Normal || vp.toObject().is<PlainObject>() || vp.toObject().is<ArrayObject>(), "input to JS::ToJSONMaybeSafely must be a plain object or array"); /* Step 4. */ AutoIdVector propertyList(cx); if (replacer) { bool isArray; if (replacer->isCallable()) { /* Step 4a(i): use replacer to transform values. */ } else if (!IsArray(cx, replacer, &isArray)) { return false; } else if (isArray) { /* Step 4b(iii). */ /* Step 4b(iii)(2-3). */ uint32_t len; if (!GetLengthProperty(cx, replacer, &len)) return false; // Cap the initial size to a moderately small value. This avoids // ridiculous over-allocation if an array with bogusly-huge length // is passed in. If we end up having to add elements past this // size, the set will naturally resize to accommodate them. const uint32_t MaxInitialSize = 32; Rooted<GCHashSet<jsid>> idSet(cx, GCHashSet<jsid>(cx)); if (!idSet.init(Min(len, MaxInitialSize))) return false; /* Step 4b(iii)(4). */ uint32_t k = 0; /* Step 4b(iii)(5). */ RootedValue item(cx); for (; k < len; k++) { if (!CheckForInterrupt(cx)) return false; /* Step 4b(iii)(5)(a-b). */ if (!GetElement(cx, replacer, replacer, k, &item)) return false; RootedId id(cx); /* Step 4b(iii)(5)(c-f). */ if (item.isNumber()) { /* Step 4b(iii)(5)(e). */ int32_t n; if (ValueFitsInInt32(item, &n) && INT_FITS_IN_JSID(n)) { id = INT_TO_JSID(n); } else { if (!ValueToId<CanGC>(cx, item, &id)) return false; } } else { bool shouldAdd = item.isString(); if (!shouldAdd) { ESClass cls; if (!GetClassOfValue(cx, item, &cls)) return false; shouldAdd = cls == ESClass::String || cls == ESClass::Number; } if (shouldAdd) { /* Step 4b(iii)(5)(f). */ if (!ValueToId<CanGC>(cx, item, &id)) return false; } else { /* Step 4b(iii)(5)(g). */ continue; } } /* Step 4b(iii)(5)(g). */ auto p = idSet.lookupForAdd(id); if (!p) { /* Step 4b(iii)(5)(g)(i). */ if (!idSet.add(p, id) || !propertyList.append(id)) return false; } } } else { replacer = nullptr; } } /* Step 5. */ if (space.isObject()) { RootedObject spaceObj(cx, &space.toObject()); ESClass cls; if (!GetBuiltinClass(cx, spaceObj, &cls)) return false; if (cls == ESClass::Number) { double d; if (!ToNumber(cx, space, &d)) return false; space = NumberValue(d); } else if (cls == ESClass::String) { JSString* str = ToStringSlow<CanGC>(cx, space); if (!str) return false; space = StringValue(str); } } StringBuffer gap(cx); if (space.isNumber()) { /* Step 6. */ double d; MOZ_ALWAYS_TRUE(ToInteger(cx, space, &d)); d = Min(10.0, d); if (d >= 1 && !gap.appendN(' ', uint32_t(d))) return false; } else if (space.isString()) { /* Step 7. */ JSLinearString* str = space.toString()->ensureLinear(cx); if (!str) return false; size_t len = Min(size_t(10), str->length()); if (!gap.appendSubstring(str, 0, len)) return false; } else { /* Step 8. */ MOZ_ASSERT(gap.empty()); } /* Step 9. */ RootedPlainObject wrapper(cx, NewBuiltinClassInstance<PlainObject>(cx)); if (!wrapper) return false; /* Steps 10-11. */ RootedId emptyId(cx, NameToId(cx->names().empty)); if (!NativeDefineProperty(cx, wrapper, emptyId, vp, nullptr, nullptr, JSPROP_ENUMERATE)) return false; /* Step 12. */ StringifyContext scx(cx, sb, gap, replacer, propertyList, stringifyBehavior == StringifyBehavior::RestrictedSafe); if (!scx.init()) return false; if (!PreprocessValue(cx, wrapper, HandleId(emptyId), vp, &scx)) return false; if (IsFilteredValue(vp)) return true; return Str(cx, vp, &scx); }
JSAtom * js_AtomizeString(JSContext *cx, JSString *strArg, uintN flags) { JS_ASSERT(!(flags & ~(ATOM_PINNED|ATOM_INTERNED|ATOM_TMPSTR|ATOM_NOCOPY))); JS_ASSERT_IF(flags & ATOM_NOCOPY, flags & ATOM_TMPSTR); if (strArg->isAtomized()) return STRING_TO_ATOM(strArg); JSLinearString *str = strArg->ensureLinear(cx); if (!str) return NULL; const jschar *chars = str->chars(); size_t length = str->length(); JSString *staticStr = JSString::lookupStaticString(chars, length); if (staticStr) return STRING_TO_ATOM(staticStr); JSAtomState *state = &cx->runtime->atomState; AtomSet &atoms = state->atoms; AutoLockAtomsCompartment lock(cx); AtomSet::AddPtr p = atoms.lookupForAdd(str); /* Hashing the string should have flattened it if it was a rope. */ JS_ASSERT(str->isFlat() || str->isDependent()); JSLinearString *key; if (p) { key = AtomEntryToKey(*p); } else { /* * Ensure that any atomized string lives only in the default * compartment. */ bool needNewString = !!(flags & ATOM_TMPSTR) || str->asCell()->compartment() != cx->runtime->atomsCompartment; /* * Unless str is already comes from the default compartment and flat, * we have to relookup the key as the last ditch GC invoked from the * string allocation or OOM handling may unlock the default * compartment lock. */ if (!needNewString && str->isFlat()) { str->flatClearExtensible(); key = str; atoms.add(p, StringToInitialAtomEntry(key)); } else { if (needNewString) { SwitchToCompartment sc(cx, cx->runtime->atomsCompartment); if (flags & ATOM_NOCOPY) { key = js_NewString(cx, const_cast<jschar *>(str->flatChars()), length); if (!key) return NULL; /* Finish handing off chars to the GC'ed key string. */ JS_ASSERT(flags & ATOM_TMPSTR); str->u.chars = NULL; } else { key = js_NewStringCopyN(cx, chars, length); if (!key) return NULL; } } else { JS_ASSERT(str->isDependent()); if (!str->undepend(cx)) return NULL; key = str; } if (!atoms.relookupOrAdd(p, key, StringToInitialAtomEntry(key))) { JS_ReportOutOfMemory(cx); /* SystemAllocPolicy does not report */ return NULL; } } key->flatSetAtomized(); } AddAtomEntryFlags(*p, flags & (ATOM_PINNED | ATOM_INTERNED)); JSAtom *atom = STRING_TO_ATOM(key); return atom; }
void CopyChars(Latin1Char *dest, const JSLinearString &str) { AutoCheckCannotGC nogc; PodCopy(dest, str.latin1Chars(nogc), str.length()); }
bool JSCompartment::wrap(JSContext *cx, Value *vp, JSObject *existing) { JS_ASSERT(cx->compartment == this); JS_ASSERT_IF(existing, existing->compartment() == cx->compartment); JS_ASSERT_IF(existing, vp->isObject()); JS_ASSERT_IF(existing, IsDeadProxyObject(existing)); unsigned flags = 0; JS_CHECK_CHROME_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()); JS_ASSERT(obj->getParent() == global); } return true; } if (vp->isString()) { RootedValue orig(cx, *vp); JSLinearString *str = vp->toString()->ensureLinear(cx); if (!str) return false; JSString *wrapped = js_NewStringCopyN(cx, str->chars(), str->length()); if (!wrapped) return false; vp->setString(wrapped); if (!putWrapper(orig, *vp)) return false; if (str->compartment()->isGCMarking()) { /* * All string wrappers are dropped when collection starts, but we * just created a new one. Mark the wrapped string to stop it being * finalized, because if it was then the pointer in this * compartment's wrapper map would be left dangling. */ JSString *tmp = str; MarkStringUnbarriered(&rt->gcMarker, &tmp, "wrapped string"); JS_ASSERT(tmp == str); } return true; } RootedObject obj(cx, &vp->toObject()); JSObject *proto = Proxy::LazyProto; if (existing) { /* Is it possible to reuse |existing|? */ if (!existing->getTaggedProto().isLazy() || existing->getClass() != &ObjectProxyClass || existing->getParent() != global || obj->isCallable()) { existing = NULL; } } /* * 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); wrapper = cx->runtime->wrapObjectCallback(cx, existing, 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 (!putWrapper(key, *vp)) return false; return true; }
JSString* js::ConcatStrings(ExclusiveContext* cx, typename MaybeRooted<JSString*, allowGC>::HandleType left, typename MaybeRooted<JSString*, allowGC>::HandleType right) { MOZ_ASSERT_IF(!left->isAtom(), cx->isInsideCurrentZone(left)); MOZ_ASSERT_IF(!right->isAtom(), cx->isInsideCurrentZone(right)); size_t leftLen = left->length(); if (leftLen == 0) return right; size_t rightLen = right->length(); if (rightLen == 0) return left; size_t wholeLength = leftLen + rightLen; if (!JSString::validateLength(cx, wholeLength)) return nullptr; bool isLatin1 = left->hasLatin1Chars() && right->hasLatin1Chars(); bool canUseInline = isLatin1 ? JSInlineString::lengthFits<Latin1Char>(wholeLength) : JSInlineString::lengthFits<char16_t>(wholeLength); if (canUseInline && cx->isJSContext()) { Latin1Char* latin1Buf = nullptr; // initialize to silence GCC warning char16_t* twoByteBuf = nullptr; // initialize to silence GCC warning JSInlineString* str = isLatin1 ? AllocateInlineString<allowGC>(cx, wholeLength, &latin1Buf) : AllocateInlineString<allowGC>(cx, wholeLength, &twoByteBuf); if (!str) return nullptr; AutoCheckCannotGC nogc; JSLinearString* leftLinear = left->ensureLinear(cx); if (!leftLinear) return nullptr; JSLinearString* rightLinear = right->ensureLinear(cx); if (!rightLinear) return nullptr; if (isLatin1) { PodCopy(latin1Buf, leftLinear->latin1Chars(nogc), leftLen); PodCopy(latin1Buf + leftLen, rightLinear->latin1Chars(nogc), rightLen); latin1Buf[wholeLength] = 0; } else { if (leftLinear->hasTwoByteChars()) PodCopy(twoByteBuf, leftLinear->twoByteChars(nogc), leftLen); else CopyAndInflateChars(twoByteBuf, leftLinear->latin1Chars(nogc), leftLen); if (rightLinear->hasTwoByteChars()) PodCopy(twoByteBuf + leftLen, rightLinear->twoByteChars(nogc), rightLen); else CopyAndInflateChars(twoByteBuf + leftLen, rightLinear->latin1Chars(nogc), rightLen); twoByteBuf[wholeLength] = 0; } return str; } return JSRope::new_<allowGC>(cx, left, right, wholeLength); }
/* ES5 15.12.3. */ bool js::Stringify(JSContext* cx, MutableHandleValue vp, JSObject* replacer_, Value space_, StringBuffer& sb) { RootedObject replacer(cx, replacer_); RootedValue space(cx, space_); /* Step 4. */ AutoIdVector propertyList(cx); if (replacer) { if (replacer->isCallable()) { /* Step 4a(i): use replacer to transform values. */ } else if (IsArray(replacer, cx)) { /* * Step 4b: The spec algorithm is unhelpfully vague about the exact * steps taken when the replacer is an array, regarding the exact * sequence of [[Get]] calls for the array's elements, when its * overall length is calculated, whether own or own plus inherited * properties are considered, and so on. A rewrite was proposed in * <https://mail.mozilla.org/pipermail/es5-discuss/2011-April/003976.html>, * whose steps are copied below, and which are implemented here. * * i. Let PropertyList be an empty internal List. * ii. Let len be the result of calling the [[Get]] internal * method of replacer with the argument "length". * iii. Let i be 0. * iv. While i < len: * 1. Let item be undefined. * 2. Let v be the result of calling the [[Get]] internal * method of replacer with the argument ToString(i). * 3. If Type(v) is String then let item be v. * 4. Else if Type(v) is Number then let item be ToString(v). * 5. Else if Type(v) is Object then * a. If the [[Class]] internal property of v is "String" * or "Number" then let item be ToString(v). * 6. If item is not undefined and item is not currently an * element of PropertyList then, * a. Append item to the end of PropertyList. * 7. Let i be i + 1. */ /* Step 4b(ii). */ uint32_t len; if (!GetLengthProperty(cx, replacer, &len)) return false; if (replacer->is<ArrayObject>() && !replacer->isIndexed()) len = Min(len, replacer->as<ArrayObject>().getDenseInitializedLength()); // Cap the initial size to a moderately small value. This avoids // ridiculous over-allocation if an array with bogusly-huge length // is passed in. If we end up having to add elements past this // size, the set will naturally resize to accommodate them. const uint32_t MaxInitialSize = 1024; HashSet<jsid, JsidHasher> idSet(cx); if (!idSet.init(Min(len, MaxInitialSize))) return false; /* Step 4b(iii). */ uint32_t i = 0; /* Step 4b(iv). */ RootedValue v(cx); for (; i < len; i++) { if (!CheckForInterrupt(cx)) return false; /* Step 4b(iv)(2). */ if (!GetElement(cx, replacer, replacer, i, &v)) return false; RootedId id(cx); if (v.isNumber()) { /* Step 4b(iv)(4). */ int32_t n; if (v.isNumber() && ValueFitsInInt32(v, &n) && INT_FITS_IN_JSID(n)) { id = INT_TO_JSID(n); } else { if (!ValueToId<CanGC>(cx, v, &id)) return false; } } else if (v.isString() || IsObjectWithClass(v, ESClass_String, cx) || IsObjectWithClass(v, ESClass_Number, cx)) { /* Step 4b(iv)(3), 4b(iv)(5). */ if (!ValueToId<CanGC>(cx, v, &id)) return false; } else { continue; } /* Step 4b(iv)(6). */ HashSet<jsid, JsidHasher>::AddPtr p = idSet.lookupForAdd(id); if (!p) { /* Step 4b(iv)(6)(a). */ if (!idSet.add(p, id) || !propertyList.append(id)) return false; } } } else { replacer = nullptr; } } /* Step 5. */ if (space.isObject()) { RootedObject spaceObj(cx, &space.toObject()); if (ObjectClassIs(spaceObj, ESClass_Number, cx)) { double d; if (!ToNumber(cx, space, &d)) return false; space = NumberValue(d); } else if (ObjectClassIs(spaceObj, ESClass_String, cx)) { JSString* str = ToStringSlow<CanGC>(cx, space); if (!str) return false; space = StringValue(str); } } StringBuffer gap(cx); if (space.isNumber()) { /* Step 6. */ double d; JS_ALWAYS_TRUE(ToInteger(cx, space, &d)); d = Min(10.0, d); if (d >= 1 && !gap.appendN(' ', uint32_t(d))) return false; } else if (space.isString()) { /* Step 7. */ JSLinearString* str = space.toString()->ensureLinear(cx); if (!str) return false; size_t len = Min(size_t(10), str->length()); if (!gap.appendSubstring(str, 0, len)) return false; } else { /* Step 8. */ MOZ_ASSERT(gap.empty()); } /* Step 9. */ RootedPlainObject wrapper(cx, NewBuiltinClassInstance<PlainObject>(cx)); if (!wrapper) return false; /* Step 10. */ RootedId emptyId(cx, NameToId(cx->names().empty)); if (!NativeDefineProperty(cx, wrapper, emptyId, vp, nullptr, nullptr, JSPROP_ENUMERATE)) return false; /* Step 11. */ StringifyContext scx(cx, sb, gap, replacer, propertyList); if (!PreprocessValue(cx, wrapper, HandleId(emptyId), vp, &scx)) return false; if (IsFilteredValue(vp)) return true; return Str(cx, vp, &scx); }
/* ES5 15.12.3. */ JSBool js_Stringify(JSContext *cx, Value *vp, JSObject *replacer_, Value space, StringBuffer &sb) { RootedVarObject replacer(cx, replacer_); RootValue spaceRoot(cx, &space); /* Step 4. */ AutoIdVector propertyList(cx); if (replacer) { if (replacer->isCallable()) { /* Step 4a(i): use replacer to transform values. */ } else if (ObjectClassIs(*replacer, ESClass_Array, cx)) { /* * Step 4b: The spec algorithm is unhelpfully vague about the exact * steps taken when the replacer is an array, regarding the exact * sequence of [[Get]] calls for the array's elements, when its * overall length is calculated, whether own or own plus inherited * properties are considered, and so on. A rewrite was proposed in * <https://mail.mozilla.org/pipermail/es5-discuss/2011-April/003976.html>, * whose steps are copied below, and which are implemented here. * * i. Let PropertyList be an empty internal List. * ii. Let len be the result of calling the [[Get]] internal * method of replacer with the argument "length". * iii. Let i be 0. * iv. While i < len: * 1. Let item be undefined. * 2. Let v be the result of calling the [[Get]] internal * method of replacer with the argument ToString(i). * 3. If Type(v) is String then let item be v. * 4. Else if Type(v) is Number then let item be ToString(v). * 5. Else if Type(v) is Object then * a. If the [[Class]] internal property of v is "String" * or "Number" then let item be ToString(v). * 6. If item is not undefined and item is not currently an * element of PropertyList then, * a. Append item to the end of PropertyList. * 7. Let i be i + 1. */ /* Step 4b(ii). */ uint32_t len; JS_ALWAYS_TRUE(js_GetLengthProperty(cx, replacer, &len)); if (replacer->isDenseArray()) len = JS_MIN(len, replacer->getDenseArrayCapacity()); HashSet<jsid> idSet(cx); if (!idSet.init(len)) return false; /* Step 4b(iii). */ uint32_t i = 0; /* Step 4b(iv). */ for (; i < len; i++) { /* Step 4b(iv)(2). */ Value v; if (!replacer->getElement(cx, i, &v)) return false; jsid id; if (v.isNumber()) { /* Step 4b(iv)(4). */ int32_t n; if (v.isNumber() && ValueFitsInInt32(v, &n) && INT_FITS_IN_JSID(n)) { id = INT_TO_JSID(n); } else { if (!ValueToId(cx, v, &id)) return false; } } else if (v.isString() || (v.isObject() && (ObjectClassIs(v.toObject(), ESClass_String, cx) || ObjectClassIs(v.toObject(), ESClass_Number, cx)))) { /* Step 4b(iv)(3), 4b(iv)(5). */ if (!ValueToId(cx, v, &id)) return false; } else { continue; } /* Step 4b(iv)(6). */ HashSet<jsid>::AddPtr p = idSet.lookupForAdd(id); if (!p) { /* Step 4b(iv)(6)(a). */ if (!idSet.add(p, id) || !propertyList.append(id)) return false; } } } else { replacer = NULL; } } /* Step 5. */ if (space.isObject()) { JSObject &spaceObj = space.toObject(); if (ObjectClassIs(spaceObj, ESClass_Number, cx)) { double d; if (!ToNumber(cx, space, &d)) return false; space = NumberValue(d); } else if (ObjectClassIs(spaceObj, ESClass_String, cx)) { JSString *str = ToStringSlow(cx, space); if (!str) return false; space = StringValue(str); } } StringBuffer gap(cx); if (space.isNumber()) { /* Step 6. */ double d; JS_ALWAYS_TRUE(ToInteger(cx, space, &d)); d = JS_MIN(10, d); if (d >= 1 && !gap.appendN(' ', uint32_t(d))) return false; } else if (space.isString()) { /* Step 7. */ JSLinearString *str = space.toString()->ensureLinear(cx); if (!str) return false; JS::Anchor<JSString *> anchor(str); size_t len = JS_MIN(10, space.toString()->length()); if (!gap.append(str->chars(), len)) return false; } else { /* Step 8. */ JS_ASSERT(gap.empty()); } /* Step 9. */ RootedVarObject wrapper(cx, NewBuiltinClassInstance(cx, &ObjectClass)); if (!wrapper) return false; /* Step 10. */ jsid emptyId = NameToId(cx->runtime->atomState.emptyAtom); if (!DefineNativeProperty(cx, wrapper, emptyId, *vp, JS_PropertyStub, JS_StrictPropertyStub, JSPROP_ENUMERATE, 0, 0)) { return false; } /* Step 11. */ StringifyContext scx(cx, sb, gap, replacer, propertyList); if (!scx.init()) return false; if (!PreprocessValue(cx, wrapper, emptyId, vp, &scx)) return false; if (IsFilteredValue(*vp)) return true; return Str(cx, *vp, &scx); }