| /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- |
| * vim: set ts=8 sts=4 et sw=4 tw=99: |
| * This Source Code Form is subject to the terms of the Mozilla Public |
| * License, v. 2.0. If a copy of the MPL was not distributed with this |
| * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
| |
| /* |
| * JS atom table. |
| */ |
| #include "jsatom.h" |
| |
| #include "mozilla/RangedPtr.h" |
| #include "mozilla/Util.h" |
| |
| #include <string.h> |
| |
| #include "jsapi.h" |
| #include "jscntxt.h" |
| #include "jsstr.h" |
| #include "jstypes.h" |
| |
| #include "gc/Marking.h" |
| #include "vm/Xdr.h" |
| |
| #include "jsatominlines.h" |
| #include "jscompartmentinlines.h" |
| |
| #include "vm/String-inl.h" |
| |
| #include "nb/memory_scope.h" |
| |
| using namespace js; |
| using namespace js::gc; |
| |
| using mozilla::ArrayEnd; |
| using mozilla::ArrayLength; |
| using mozilla::RangedPtr; |
| |
| const char * |
| js_AtomToPrintableString(JSContext *cx, JSAtom *atom, JSAutoByteString *bytes) |
| { |
| TRACK_MEMORY_SCOPE("Javascript"); |
| return js_ValueToPrintable(cx, StringValue(atom), bytes); |
| } |
| |
| const char * const js::TypeStrings[] = { |
| js_undefined_str, |
| js_object_str, |
| js_function_str, |
| js_string_str, |
| js_number_str, |
| js_boolean_str, |
| js_null_str, |
| }; |
| |
| #define DEFINE_PROTO_STRING(name,code,init) const char js_##name##_str[] = #name; |
| JS_FOR_EACH_PROTOTYPE(DEFINE_PROTO_STRING) |
| #undef DEFINE_PROTO_STRING |
| |
| #define CONST_CHAR_STR(idpart, id, text) const char js_##idpart##_str[] = text; |
| FOR_EACH_COMMON_PROPERTYNAME(CONST_CHAR_STR) |
| #undef CONST_CHAR_STR |
| |
| /* Constant strings that are not atomized. */ |
| const char js_break_str[] = "break"; |
| const char js_case_str[] = "case"; |
| const char js_catch_str[] = "catch"; |
| const char js_class_str[] = "class"; |
| const char js_const_str[] = "const"; |
| const char js_continue_str[] = "continue"; |
| const char js_debugger_str[] = "debugger"; |
| const char js_default_str[] = "default"; |
| const char js_do_str[] = "do"; |
| const char js_else_str[] = "else"; |
| const char js_enum_str[] = "enum"; |
| const char js_export_str[] = "export"; |
| const char js_extends_str[] = "extends"; |
| const char js_finally_str[] = "finally"; |
| const char js_for_str[] = "for"; |
| const char js_getter_str[] = "getter"; |
| const char js_if_str[] = "if"; |
| const char js_implements_str[] = "implements"; |
| const char js_import_str[] = "import"; |
| const char js_in_str[] = "in"; |
| const char js_instanceof_str[] = "instanceof"; |
| const char js_interface_str[] = "interface"; |
| const char js_let_str[] = "let"; |
| const char js_new_str[] = "new"; |
| const char js_package_str[] = "package"; |
| const char js_private_str[] = "private"; |
| const char js_protected_str[] = "protected"; |
| const char js_public_str[] = "public"; |
| const char js_setter_str[] = "setter"; |
| const char js_static_str[] = "static"; |
| const char js_super_str[] = "super"; |
| const char js_switch_str[] = "switch"; |
| const char js_this_str[] = "this"; |
| const char js_try_str[] = "try"; |
| const char js_typeof_str[] = "typeof"; |
| const char js_void_str[] = "void"; |
| const char js_while_str[] = "while"; |
| const char js_with_str[] = "with"; |
| const char js_yield_str[] = "yield"; |
| #if JS_HAS_GENERATORS |
| const char js_close_str[] = "close"; |
| const char js_send_str[] = "send"; |
| #endif |
| |
| /* |
| * For a browser build from 2007-08-09 after the browser starts up there are |
| * just 55 double atoms, but over 15000 string atoms. Not to penalize more |
| * economical embeddings allocating too much memory initially we initialize |
| * atomized strings with just 1K entries. |
| */ |
| #define JS_STRING_HASH_COUNT 1024 |
| |
| JSBool |
| js::InitAtoms(JSRuntime *rt) |
| { |
| TRACK_MEMORY_SCOPE("Javascript"); |
| return rt->atoms.init(JS_STRING_HASH_COUNT); |
| } |
| |
| void |
| js::FinishAtoms(JSRuntime *rt) |
| { |
| TRACK_MEMORY_SCOPE("Javascript"); |
| AtomSet &atoms = rt->atoms; |
| if (!atoms.initialized()) { |
| /* |
| * We are called with uninitialized state when JS_NewRuntime fails and |
| * calls JS_DestroyRuntime on a partially initialized runtime. |
| */ |
| return; |
| } |
| |
| FreeOp fop(rt, false); |
| for (AtomSet::Range r = atoms.all(); !r.empty(); r.popFront()) |
| r.front().asPtr()->finalize(&fop); |
| } |
| |
| struct CommonNameInfo |
| { |
| const char *str; |
| size_t length; |
| }; |
| |
| bool |
| js::InitCommonNames(JSContext *cx) |
| { |
| TRACK_MEMORY_SCOPE("Javascript"); |
| static const CommonNameInfo cachedNames[] = { |
| #define COMMON_NAME_INFO(idpart, id, text) { js_##idpart##_str, sizeof(text) - 1 }, |
| FOR_EACH_COMMON_PROPERTYNAME(COMMON_NAME_INFO) |
| #undef COMMON_NAME_INFO |
| #define COMMON_NAME_INFO(name, code, init) { js_##name##_str, sizeof(#name) - 1 }, |
| JS_FOR_EACH_PROTOTYPE(COMMON_NAME_INFO) |
| #undef COMMON_NAME_INFO |
| }; |
| |
| FixedHeapPtr<PropertyName> *names = &cx->runtime()->firstCachedName; |
| for (size_t i = 0; i < ArrayLength(cachedNames); i++, names++) { |
| JSAtom *atom = Atomize(cx, cachedNames[i].str, cachedNames[i].length, InternAtom); |
| if (!atom) |
| return false; |
| names->init(atom->asPropertyName()); |
| } |
| JS_ASSERT(uintptr_t(names) == uintptr_t(&cx->runtime()->atomState + 1)); |
| |
| cx->runtime()->emptyString = cx->names().empty; |
| return true; |
| } |
| |
| void |
| js::FinishCommonNames(JSRuntime *rt) |
| { |
| TRACK_MEMORY_SCOPE("Javascript"); |
| rt->emptyString = NULL; |
| #ifdef DEBUG |
| memset(&rt->atomState, JS_FREE_PATTERN, sizeof(JSAtomState)); |
| #endif |
| } |
| |
| void |
| js::MarkAtoms(JSTracer *trc) |
| { |
| TRACK_MEMORY_SCOPE("Javascript"); |
| JSRuntime *rt = trc->runtime; |
| for (AtomSet::Range r = rt->atoms.all(); !r.empty(); r.popFront()) { |
| AtomStateEntry entry = r.front(); |
| if (!entry.isTagged()) |
| continue; |
| |
| JSAtom *tmp = entry.asPtr(); |
| MarkStringRoot(trc, &tmp, "interned_atom"); |
| JS_ASSERT(tmp == entry.asPtr()); |
| } |
| } |
| |
| void |
| js::SweepAtoms(JSRuntime *rt) |
| { |
| TRACK_MEMORY_SCOPE("Javascript"); |
| for (AtomSet::Enum e(rt->atoms); !e.empty(); e.popFront()) { |
| AtomStateEntry entry = e.front(); |
| JSAtom *atom = entry.asPtr(); |
| bool isDying = IsStringAboutToBeFinalized(&atom); |
| |
| /* Pinned or interned key cannot be finalized. */ |
| JS_ASSERT_IF(entry.isTagged(), !isDying); |
| |
| if (isDying) |
| e.removeFront(); |
| } |
| } |
| |
| bool |
| AtomIsInterned(JSContext *cx, JSAtom *atom) |
| { |
| /* We treat static strings as interned because they're never collected. */ |
| if (StaticStrings::isStatic(atom)) |
| return true; |
| |
| AtomSet::Ptr p = cx->runtime()->atoms.lookup(atom); |
| if (!p) |
| return false; |
| |
| return p->isTagged(); |
| } |
| |
| enum OwnCharsBehavior |
| { |
| CopyChars, /* in other words, do not take ownership */ |
| TakeCharOwnership |
| }; |
| |
| /* |
| * When the jschars reside in a freshly allocated buffer the memory can be used |
| * as a new JSAtom's storage without copying. The contract is that the caller no |
| * longer owns the memory and this method is responsible for freeing the memory. |
| */ |
| JS_ALWAYS_INLINE |
| static JSAtom * |
| AtomizeAndTakeOwnership(JSContext *cx, jschar *tbchars, size_t length, InternBehavior ib) |
| { |
| TRACK_MEMORY_SCOPE("Javascript"); |
| JS_ASSERT(tbchars[length] == 0); |
| |
| if (JSAtom *s = cx->runtime()->staticStrings.lookup(tbchars, length)) { |
| js_free(tbchars); |
| return s; |
| } |
| |
| /* |
| * If a GC occurs at js_NewStringCopy then |p| will still have the correct |
| * hash, allowing us to avoid rehashing it. Even though the hash is |
| * unchanged, we need to re-lookup the table position because a last-ditch |
| * GC will potentially free some table entries. |
| */ |
| AtomSet& atoms = cx->runtime()->atoms; |
| AtomSet::AddPtr p = atoms.lookupForAdd(AtomHasher::Lookup(tbchars, length)); |
| SkipRoot skipHash(cx, &p); /* Prevent the hash from being poisoned. */ |
| if (p) { |
| JSAtom *atom = p->asPtr(); |
| p->setTagged(bool(ib)); |
| js_free(tbchars); |
| return atom; |
| } |
| |
| AutoCompartment ac(cx, cx->runtime()->atomsCompartment); |
| |
| JSFlatString *flat = js_NewString<CanGC>(cx, tbchars, length); |
| if (!flat) { |
| js_free(tbchars); |
| return NULL; |
| } |
| |
| JSAtom *atom = flat->morphAtomizedStringIntoAtom(); |
| |
| if (!atoms.relookupOrAdd(p, AtomHasher::Lookup(tbchars, length), |
| AtomStateEntry(atom, bool(ib)))) { |
| JS_ReportOutOfMemory(cx); /* SystemAllocPolicy does not report OOM. */ |
| return NULL; |
| } |
| |
| return atom; |
| } |
| |
| /* |tbchars| must not point into an inline or short string. */ |
| template <AllowGC allowGC> |
| JS_ALWAYS_INLINE |
| static JSAtom * |
| AtomizeAndCopyChars(JSContext *cx, const jschar *tbchars, size_t length, InternBehavior ib) |
| { |
| TRACK_MEMORY_SCOPE("Javascript"); |
| if (JSAtom *s = cx->runtime()->staticStrings.lookup(tbchars, length)) |
| return s; |
| |
| /* |
| * If a GC occurs at js_NewStringCopy then |p| will still have the correct |
| * hash, allowing us to avoid rehashing it. Even though the hash is |
| * unchanged, we need to re-lookup the table position because a last-ditch |
| * GC will potentially free some table entries. |
| */ |
| |
| AtomSet& atoms = cx->runtime()->atoms; |
| AtomSet::AddPtr p = atoms.lookupForAdd(AtomHasher::Lookup(tbchars, length)); |
| SkipRoot skipHash(cx, &p); /* Prevent the hash from being poisoned. */ |
| if (p) { |
| JSAtom *atom = p->asPtr(); |
| p->setTagged(bool(ib)); |
| return atom; |
| } |
| |
| AutoCompartment ac(cx, cx->runtime()->atomsCompartment); |
| |
| JSFlatString *flat = js_NewStringCopyN<allowGC>(cx, tbchars, length); |
| if (!flat) |
| return NULL; |
| |
| JSAtom *atom = flat->morphAtomizedStringIntoAtom(); |
| |
| if (!atoms.relookupOrAdd(p, AtomHasher::Lookup(tbchars, length), |
| AtomStateEntry(atom, bool(ib)))) { |
| JS_ReportOutOfMemory(cx); /* SystemAllocPolicy does not report OOM. */ |
| return NULL; |
| } |
| |
| return atom; |
| } |
| |
| template <AllowGC allowGC> |
| JSAtom * |
| js::AtomizeString(JSContext *cx, JSString *str, js::InternBehavior ib /* = js::DoNotInternAtom */) |
| { |
| TRACK_MEMORY_SCOPE("Javascript"); |
| 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); |
| } |
| |
| template JSAtom * |
| js::AtomizeString<CanGC>(JSContext *cx, JSString *str, js::InternBehavior ib); |
| |
| template JSAtom * |
| js::AtomizeString<NoGC>(JSContext *cx, JSString *str, js::InternBehavior ib); |
| |
| JSAtom * |
| js::Atomize(JSContext *cx, const char *bytes, size_t length, InternBehavior ib) |
| { |
| TRACK_MEMORY_SCOPE("Javascript"); |
| CHECK_REQUEST(cx); |
| |
| if (!JSString::validateLength(cx, length)) |
| return NULL; |
| |
| static const unsigned ATOMIZE_BUF_MAX = 32; |
| if (length < ATOMIZE_BUF_MAX) { |
| /* |
| * Avoiding the malloc in InflateString on shorter strings saves us |
| * over 20,000 malloc calls on mozilla browser startup. This compares to |
| * only 131 calls where the string is longer than a 31 char (net) buffer. |
| * The vast majority of atomized strings are already in the hashtable. So |
| * js::AtomizeString rarely has to copy the temp string we make. |
| */ |
| jschar inflated[ATOMIZE_BUF_MAX]; |
| size_t inflatedLength = ATOMIZE_BUF_MAX - 1; |
| InflateStringToBuffer(cx, bytes, length, inflated, &inflatedLength); |
| return AtomizeAndCopyChars<CanGC>(cx, inflated, inflatedLength, ib); |
| } |
| |
| jschar *tbcharsZ = InflateString(cx, bytes, &length); |
| if (!tbcharsZ) |
| return NULL; |
| return AtomizeAndTakeOwnership(cx, tbcharsZ, length, ib); |
| } |
| |
| template <AllowGC allowGC> |
| JSAtom * |
| js::AtomizeChars(JSContext *cx, const jschar *chars, size_t length, InternBehavior ib) |
| { |
| TRACK_MEMORY_SCOPE("Javascript"); |
| CHECK_REQUEST(cx); |
| |
| if (!JSString::validateLength(cx, length)) |
| return NULL; |
| |
| return AtomizeAndCopyChars<allowGC>(cx, chars, length, ib); |
| } |
| |
| template JSAtom * |
| js::AtomizeChars<CanGC>(JSContext *cx, const jschar *chars, size_t length, InternBehavior ib); |
| |
| template JSAtom * |
| js::AtomizeChars<NoGC>(JSContext *cx, const jschar *chars, size_t length, InternBehavior ib); |
| |
| template <AllowGC allowGC> |
| bool |
| js::IndexToIdSlow(JSContext *cx, uint32_t index, |
| typename MaybeRooted<jsid, allowGC>::MutableHandleType idp) |
| { |
| TRACK_MEMORY_SCOPE("Javascript"); |
| JS_ASSERT(index > JSID_INT_MAX); |
| |
| jschar buf[UINT32_CHAR_BUFFER_LENGTH]; |
| RangedPtr<jschar> end(ArrayEnd(buf), buf, ArrayEnd(buf)); |
| RangedPtr<jschar> start = BackfillIndexInCharBuffer(index, end); |
| |
| JSAtom *atom = AtomizeChars<allowGC>(cx, start.get(), end - start); |
| if (!atom) |
| return false; |
| |
| idp.set(JSID_FROM_BITS((size_t)atom)); |
| return true; |
| } |
| |
| template bool |
| js::IndexToIdSlow<CanGC>(JSContext *cx, uint32_t index, MutableHandleId idp); |
| |
| template bool |
| js::IndexToIdSlow<NoGC>(JSContext *cx, uint32_t index, FakeMutableHandle<jsid> idp); |
| |
| template <AllowGC allowGC> |
| JSAtom * |
| js::ToAtom(JSContext *cx, typename MaybeRooted<Value, allowGC>::HandleType v) |
| { |
| TRACK_MEMORY_SCOPE("Javascript"); |
| if (!v.isString()) { |
| JSString *str = js::ToStringSlow<allowGC>(cx, v); |
| if (!str) |
| return NULL; |
| JS::Anchor<JSString *> anchor(str); |
| return AtomizeString<allowGC>(cx, str); |
| } |
| |
| JSString *str = v.toString(); |
| if (str->isAtom()) |
| return &str->asAtom(); |
| |
| JS::Anchor<JSString *> anchor(str); |
| return AtomizeString<allowGC>(cx, str); |
| } |
| |
| template JSAtom * |
| js::ToAtom<CanGC>(JSContext *cx, HandleValue v); |
| |
| template JSAtom * |
| js::ToAtom<NoGC>(JSContext *cx, Value v); |
| |
| template<XDRMode mode> |
| bool |
| js::XDRAtom(XDRState<mode> *xdr, MutableHandleAtom atomp) |
| { |
| TRACK_MEMORY_SCOPE("Javascript"); |
| if (mode == XDR_ENCODE) { |
| uint32_t nchars = atomp->length(); |
| if (!xdr->codeUint32(&nchars)) |
| return false; |
| |
| jschar *chars = const_cast<jschar *>(atomp->getChars(xdr->cx())); |
| if (!chars) |
| return false; |
| |
| return xdr->codeChars(chars, nchars); |
| } |
| |
| /* Avoid JSString allocation for already existing atoms. See bug 321985. */ |
| uint32_t nchars; |
| if (!xdr->codeUint32(&nchars)) |
| return false; |
| |
| JSContext *cx = xdr->cx(); |
| JSAtom *atom; |
| #if IS_LITTLE_ENDIAN |
| /* Directly access the little endian chars in the XDR buffer. */ |
| const jschar *chars = reinterpret_cast<const jschar *>(xdr->buf.read(nchars * sizeof(jschar))); |
| atom = AtomizeChars<CanGC>(cx, chars, nchars); |
| #else |
| /* |
| * We must copy chars to a temporary buffer to convert between little and |
| * big endian data. |
| */ |
| jschar *chars; |
| jschar stackChars[256]; |
| if (nchars <= ArrayLength(stackChars)) { |
| chars = stackChars; |
| } else { |
| /* |
| * This is very uncommon. Don't use the tempLifoAlloc arena for this as |
| * most allocations here will be bigger than tempLifoAlloc's default |
| * chunk size. |
| */ |
| chars = cx->runtime()->pod_malloc<jschar>(nchars); |
| if (!chars) |
| return false; |
| } |
| |
| JS_ALWAYS_TRUE(xdr->codeChars(chars, nchars)); |
| atom = AtomizeChars<CanGC>(cx, chars, nchars); |
| if (chars != stackChars) |
| js_free(chars); |
| #endif /* !IS_LITTLE_ENDIAN */ |
| |
| if (!atom) |
| return false; |
| atomp.set(atom); |
| return true; |
| } |
| |
| template bool |
| js::XDRAtom(XDRState<XDR_ENCODE> *xdr, MutableHandleAtom atomp); |
| |
| template bool |
| js::XDRAtom(XDRState<XDR_DECODE> *xdr, MutableHandleAtom atomp); |
| |