js/src/jsatom.cpp

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

     1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
     2  * vim: set ts=8 sts=4 et sw=4 tw=99:
     3  * This Source Code Form is subject to the terms of the Mozilla Public
     4  * License, v. 2.0. If a copy of the MPL was not distributed with this
     5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     7 /*
     8  * JS atom table.
     9  */
    11 #include "jsatominlines.h"
    13 #include "mozilla/ArrayUtils.h"
    14 #include "mozilla/RangedPtr.h"
    16 #include <string.h>
    18 #include "jscntxt.h"
    19 #include "jsstr.h"
    20 #include "jstypes.h"
    22 #include "gc/Marking.h"
    23 #include "vm/Xdr.h"
    25 #include "jscntxtinlines.h"
    26 #include "jscompartmentinlines.h"
    27 #include "jsobjinlines.h"
    29 #include "vm/String-inl.h"
    31 using namespace js;
    32 using namespace js::gc;
    34 using mozilla::ArrayEnd;
    35 using mozilla::ArrayLength;
    36 using mozilla::RangedPtr;
    38 const char *
    39 js::AtomToPrintableString(ExclusiveContext *cx, JSAtom *atom, JSAutoByteString *bytes)
    40 {
    41     JSString *str = js_QuoteString(cx, atom, 0);
    42     if (!str)
    43         return nullptr;
    44     return bytes->encodeLatin1(cx, str);
    45 }
    47 const char * const js::TypeStrings[] = {
    48     js_undefined_str,
    49     js_object_str,
    50     js_function_str,
    51     js_string_str,
    52     js_number_str,
    53     js_boolean_str,
    54     js_null_str,
    55 };
    57 #define DEFINE_PROTO_STRING(name,code,init,clasp) const char js_##name##_str[] = #name;
    58 JS_FOR_EACH_PROTOTYPE(DEFINE_PROTO_STRING)
    59 #undef DEFINE_PROTO_STRING
    61 #define CONST_CHAR_STR(idpart, id, text) const char js_##idpart##_str[] = text;
    62 FOR_EACH_COMMON_PROPERTYNAME(CONST_CHAR_STR)
    63 #undef CONST_CHAR_STR
    65 /* Constant strings that are not atomized. */
    66 const char js_break_str[]           = "break";
    67 const char js_case_str[]            = "case";
    68 const char js_catch_str[]           = "catch";
    69 const char js_class_str[]           = "class";
    70 const char js_close_str[]           = "close";
    71 const char js_const_str[]           = "const";
    72 const char js_continue_str[]        = "continue";
    73 const char js_debugger_str[]        = "debugger";
    74 const char js_default_str[]         = "default";
    75 const char js_do_str[]              = "do";
    76 const char js_else_str[]            = "else";
    77 const char js_enum_str[]            = "enum";
    78 const char js_export_str[]          = "export";
    79 const char js_extends_str[]         = "extends";
    80 const char js_finally_str[]         = "finally";
    81 const char js_for_str[]             = "for";
    82 const char js_getter_str[]          = "getter";
    83 const char js_if_str[]              = "if";
    84 const char js_implements_str[]      = "implements";
    85 const char js_import_str[]          = "import";
    86 const char js_in_str[]              = "in";
    87 const char js_instanceof_str[]      = "instanceof";
    88 const char js_interface_str[]       = "interface";
    89 const char js_new_str[]             = "new";
    90 const char js_package_str[]         = "package";
    91 const char js_private_str[]         = "private";
    92 const char js_protected_str[]       = "protected";
    93 const char js_public_str[]          = "public";
    94 const char js_send_str[]            = "send";
    95 const char js_setter_str[]          = "setter";
    96 const char js_static_str[]          = "static";
    97 const char js_super_str[]           = "super";
    98 const char js_switch_str[]          = "switch";
    99 const char js_this_str[]            = "this";
   100 const char js_try_str[]             = "try";
   101 const char js_typeof_str[]          = "typeof";
   102 const char js_void_str[]            = "void";
   103 const char js_while_str[]           = "while";
   104 const char js_with_str[]            = "with";
   106 // Use a low initial capacity for atom hash tables to avoid penalizing runtimes
   107 // which create a small number of atoms.
   108 static const uint32_t JS_STRING_HASH_COUNT = 64;
   110 struct CommonNameInfo
   111 {
   112     const char *str;
   113     size_t length;
   114 };
   116 bool
   117 JSRuntime::initializeAtoms(JSContext *cx)
   118 {
   119     atoms_ = cx->new_<AtomSet>();
   120     if (!atoms_ || !atoms_->init(JS_STRING_HASH_COUNT))
   121         return false;
   123     if (parentRuntime) {
   124         staticStrings = parentRuntime->staticStrings;
   125         commonNames = parentRuntime->commonNames;
   126         emptyString = parentRuntime->emptyString;
   127         permanentAtoms = parentRuntime->permanentAtoms;
   128         return true;
   129     }
   131     permanentAtoms = cx->new_<AtomSet>();
   132     if (!permanentAtoms || !permanentAtoms->init(JS_STRING_HASH_COUNT))
   133         return false;
   135     staticStrings = cx->new_<StaticStrings>();
   136     if (!staticStrings || !staticStrings->init(cx))
   137         return false;
   139     static const CommonNameInfo cachedNames[] = {
   140 #define COMMON_NAME_INFO(idpart, id, text) { js_##idpart##_str, sizeof(text) - 1 },
   141         FOR_EACH_COMMON_PROPERTYNAME(COMMON_NAME_INFO)
   142 #undef COMMON_NAME_INFO
   143 #define COMMON_NAME_INFO(name, code, init, clasp) { js_##name##_str, sizeof(#name) - 1 },
   144         JS_FOR_EACH_PROTOTYPE(COMMON_NAME_INFO)
   145 #undef COMMON_NAME_INFO
   146     };
   148     commonNames = cx->new_<JSAtomState>();
   149     if (!commonNames)
   150         return false;
   152     FixedHeapPtr<PropertyName> *names = reinterpret_cast<FixedHeapPtr<PropertyName> *>(commonNames);
   153     for (size_t i = 0; i < ArrayLength(cachedNames); i++, names++) {
   154         JSAtom *atom = Atomize(cx, cachedNames[i].str, cachedNames[i].length, InternAtom);
   155         if (!atom)
   156             return false;
   157         names->init(atom->asPropertyName());
   158     }
   159     JS_ASSERT(uintptr_t(names) == uintptr_t(commonNames + 1));
   161     emptyString = commonNames->empty;
   162     return true;
   163 }
   165 void
   166 JSRuntime::finishAtoms()
   167 {
   168     if (atoms_)
   169         js_delete(atoms_);
   171     if (!parentRuntime) {
   172         if (staticStrings)
   173             js_delete(staticStrings);
   175         if (commonNames)
   176             js_delete(commonNames);
   178         if (permanentAtoms)
   179             js_delete(permanentAtoms);
   180     }
   182     atoms_ = nullptr;
   183     staticStrings = nullptr;
   184     commonNames = nullptr;
   185     permanentAtoms = nullptr;
   186     emptyString = nullptr;
   187 }
   189 void
   190 js::MarkAtoms(JSTracer *trc)
   191 {
   192     JSRuntime *rt = trc->runtime();
   193     for (AtomSet::Enum e(rt->atoms()); !e.empty(); e.popFront()) {
   194         const AtomStateEntry &entry = e.front();
   195         if (!entry.isTagged())
   196             continue;
   198         JSAtom *atom = entry.asPtr();
   199         bool tagged = entry.isTagged();
   200         MarkStringRoot(trc, &atom, "interned_atom");
   201         if (entry.asPtr() != atom)
   202             e.rekeyFront(AtomHasher::Lookup(atom), AtomStateEntry(atom, tagged));
   203     }
   204 }
   206 void
   207 js::MarkPermanentAtoms(JSTracer *trc)
   208 {
   209     JSRuntime *rt = trc->runtime();
   211     // Permanent atoms only need to be marked in the runtime which owns them.
   212     if (rt->parentRuntime)
   213         return;
   215     // Static strings are not included in the permanent atoms table.
   216     if (rt->staticStrings)
   217         rt->staticStrings->trace(trc);
   219     if (rt->permanentAtoms) {
   220         for (AtomSet::Enum e(*rt->permanentAtoms); !e.empty(); e.popFront()) {
   221             const AtomStateEntry &entry = e.front();
   223             JSAtom *atom = entry.asPtr();
   224             MarkPermanentAtom(trc, atom, "permanent_table");
   225         }
   226     }
   227 }
   229 void
   230 JSRuntime::sweepAtoms()
   231 {
   232     if (!atoms_)
   233         return;
   235     for (AtomSet::Enum e(*atoms_); !e.empty(); e.popFront()) {
   236         AtomStateEntry entry = e.front();
   237         JSAtom *atom = entry.asPtr();
   238         bool isDying = IsStringAboutToBeFinalized(&atom);
   240         /* Pinned or interned key cannot be finalized. */
   241         JS_ASSERT_IF(hasContexts() && entry.isTagged(), !isDying);
   243         if (isDying)
   244             e.removeFront();
   245     }
   246 }
   248 bool
   249 JSRuntime::transformToPermanentAtoms()
   250 {
   251     JS_ASSERT(!parentRuntime);
   253     // All static strings were created as permanent atoms, now move the contents
   254     // of the atoms table into permanentAtoms and mark each as permanent.
   256     JS_ASSERT(permanentAtoms && permanentAtoms->empty());
   258     AtomSet *temp = atoms_;
   259     atoms_ = permanentAtoms;
   260     permanentAtoms = temp;
   262     for (AtomSet::Enum e(*permanentAtoms); !e.empty(); e.popFront()) {
   263         AtomStateEntry entry = e.front();
   264         JSAtom *atom = entry.asPtr();
   265         atom->morphIntoPermanentAtom();
   266     }
   268     return true;
   269 }
   271 bool
   272 AtomIsInterned(JSContext *cx, JSAtom *atom)
   273 {
   274     /* We treat static strings as interned because they're never collected. */
   275     if (StaticStrings::isStatic(atom))
   276         return true;
   278     AtomHasher::Lookup lookup(atom);
   280     /* Likewise, permanent strings are considered to be interned. */
   281     AtomSet::Ptr p = cx->permanentAtoms().readonlyThreadsafeLookup(lookup);
   282     if (p)
   283         return true;
   285     AutoLockForExclusiveAccess lock(cx);
   287     p = cx->runtime()->atoms().lookup(lookup);
   288     if (!p)
   289         return false;
   291     return p->isTagged();
   292 }
   294 /*
   295  * When the jschars reside in a freshly allocated buffer the memory can be used
   296  * as a new JSAtom's storage without copying. The contract is that the caller no
   297  * longer owns the memory and this method is responsible for freeing the memory.
   298  */
   299 MOZ_ALWAYS_INLINE
   300 static JSAtom *
   301 AtomizeAndtake(ExclusiveContext *cx, jschar *tbchars, size_t length, InternBehavior ib)
   302 {
   303     JS_ASSERT(tbchars[length] == 0);
   305     if (JSAtom *s = cx->staticStrings().lookup(tbchars, length)) {
   306         js_free(tbchars);
   307         return s;
   308     }
   310     AtomHasher::Lookup lookup(tbchars, length);
   312     AtomSet::Ptr pp = cx->permanentAtoms().readonlyThreadsafeLookup(lookup);
   313     if (pp) {
   314         js_free(tbchars);
   315         return pp->asPtr();
   316     }
   318     AutoLockForExclusiveAccess lock(cx);
   320     /*
   321      * If a GC occurs at js_NewStringCopy then |p| will still have the correct
   322      * hash, allowing us to avoid rehashing it. Even though the hash is
   323      * unchanged, we need to re-lookup the table position because a last-ditch
   324      * GC will potentially free some table entries.
   325      */
   326     AtomSet& atoms = cx->atoms();
   327     AtomSet::AddPtr p = atoms.lookupForAdd(lookup);
   328     if (p) {
   329         JSAtom *atom = p->asPtr();
   330         p->setTagged(bool(ib));
   331         js_free(tbchars);
   332         return atom;
   333     }
   335     AutoCompartment ac(cx, cx->atomsCompartment());
   337     JSFlatString *flat = js_NewString<NoGC>(cx, tbchars, length);
   338     if (!flat) {
   339         js_free(tbchars);
   340         js_ReportOutOfMemory(cx);
   341         return nullptr;
   342     }
   344     JSAtom *atom = flat->morphAtomizedStringIntoAtom();
   346     if (!atoms.relookupOrAdd(p, lookup, AtomStateEntry(atom, bool(ib)))) {
   347         js_ReportOutOfMemory(cx); /* SystemAllocPolicy does not report OOM. */
   348         return nullptr;
   349     }
   351     return atom;
   352 }
   354 /* |tbchars| must not point into an inline or short string. */
   355 MOZ_ALWAYS_INLINE
   356 static JSAtom *
   357 AtomizeAndCopyChars(ExclusiveContext *cx, const jschar *tbchars, size_t length, InternBehavior ib)
   358 {
   359     if (JSAtom *s = cx->staticStrings().lookup(tbchars, length))
   360          return s;
   362     AtomHasher::Lookup lookup(tbchars, length);
   364     AtomSet::Ptr pp = cx->permanentAtoms().readonlyThreadsafeLookup(lookup);
   365     if (pp)
   366         return pp->asPtr();
   368     /*
   369      * If a GC occurs at js_NewStringCopy then |p| will still have the correct
   370      * hash, allowing us to avoid rehashing it. Even though the hash is
   371      * unchanged, we need to re-lookup the table position because a last-ditch
   372      * GC will potentially free some table entries.
   373      */
   375     AutoLockForExclusiveAccess lock(cx);
   377     AtomSet& atoms = cx->atoms();
   378     AtomSet::AddPtr p = atoms.lookupForAdd(lookup);
   379     if (p) {
   380         JSAtom *atom = p->asPtr();
   381         p->setTagged(bool(ib));
   382         return atom;
   383     }
   385     AutoCompartment ac(cx, cx->atomsCompartment());
   387     JSFlatString *flat = js_NewStringCopyN<NoGC>(cx, tbchars, length);
   388     if (!flat) {
   389         js_ReportOutOfMemory(cx);
   390         return nullptr;
   391     }
   393     JSAtom *atom = flat->morphAtomizedStringIntoAtom();
   395     if (!atoms.relookupOrAdd(p, lookup, AtomStateEntry(atom, bool(ib)))) {
   396         js_ReportOutOfMemory(cx); /* SystemAllocPolicy does not report OOM. */
   397         return nullptr;
   398     }
   400     return atom;
   401 }
   403 JSAtom *
   404 js::AtomizeString(ExclusiveContext *cx, JSString *str,
   405                   js::InternBehavior ib /* = js::DoNotInternAtom */)
   406 {
   407     if (str->isAtom()) {
   408         JSAtom &atom = str->asAtom();
   409         /* N.B. static atoms are effectively always interned. */
   410         if (ib != InternAtom || js::StaticStrings::isStatic(&atom))
   411             return &atom;
   413         AtomHasher::Lookup lookup(&atom);
   415         /* Likewise, permanent atoms are always interned. */
   416         AtomSet::Ptr p = cx->permanentAtoms().readonlyThreadsafeLookup(lookup);
   417         if (p)
   418             return &atom;
   420         AutoLockForExclusiveAccess lock(cx);
   422         p = cx->atoms().lookup(lookup);
   423         JS_ASSERT(p); /* Non-static atom must exist in atom state set. */
   424         JS_ASSERT(p->asPtr() == &atom);
   425         JS_ASSERT(ib == InternAtom);
   426         p->setTagged(bool(ib));
   427         return &atom;
   428     }
   430     const jschar *chars = str->getChars(cx);
   431     if (!chars)
   432         return nullptr;
   434     return AtomizeAndCopyChars(cx, chars, str->length(), ib);
   435 }
   437 JSAtom *
   438 js::Atomize(ExclusiveContext *cx, const char *bytes, size_t length, InternBehavior ib)
   439 {
   440     CHECK_REQUEST(cx);
   442     if (!JSString::validateLength(cx, length))
   443         return nullptr;
   445     static const unsigned ATOMIZE_BUF_MAX = 32;
   446     if (length < ATOMIZE_BUF_MAX) {
   447         /*
   448          * Avoiding the malloc in InflateString on shorter strings saves us
   449          * over 20,000 malloc calls on mozilla browser startup. This compares to
   450          * only 131 calls where the string is longer than a 31 char (net) buffer.
   451          * The vast majority of atomized strings are already in the hashtable. So
   452          * js::AtomizeString rarely has to copy the temp string we make.
   453          */
   454         jschar inflated[ATOMIZE_BUF_MAX];
   455         InflateStringToBuffer(bytes, length, inflated);
   456         return AtomizeAndCopyChars(cx, inflated, length, ib);
   457     }
   459     jschar *tbcharsZ = InflateString(cx, bytes, &length);
   460     if (!tbcharsZ)
   461         return nullptr;
   462     return AtomizeAndtake(cx, tbcharsZ, length, ib);
   463 }
   465 JSAtom *
   466 js::AtomizeChars(ExclusiveContext *cx, const jschar *chars, size_t length, InternBehavior ib)
   467 {
   468     CHECK_REQUEST(cx);
   470     if (!JSString::validateLength(cx, length))
   471         return nullptr;
   473     return AtomizeAndCopyChars(cx, chars, length, ib);
   474 }
   476 bool
   477 js::IndexToIdSlow(ExclusiveContext *cx, uint32_t index, MutableHandleId idp)
   478 {
   479     JS_ASSERT(index > JSID_INT_MAX);
   481     jschar buf[UINT32_CHAR_BUFFER_LENGTH];
   482     RangedPtr<jschar> end(ArrayEnd(buf), buf, ArrayEnd(buf));
   483     RangedPtr<jschar> start = BackfillIndexInCharBuffer(index, end);
   485     JSAtom *atom = AtomizeChars(cx, start.get(), end - start);
   486     if (!atom)
   487         return false;
   489     idp.set(JSID_FROM_BITS((size_t)atom));
   490     return true;
   491 }
   493 template <AllowGC allowGC>
   494 static JSAtom *
   495 ToAtomSlow(ExclusiveContext *cx, typename MaybeRooted<Value, allowGC>::HandleType arg)
   496 {
   497     JS_ASSERT(!arg.isString());
   499     Value v = arg;
   500     if (!v.isPrimitive()) {
   501         if (!cx->shouldBeJSContext() || !allowGC)
   502             return nullptr;
   503         RootedValue v2(cx, v);
   504         if (!ToPrimitive(cx->asJSContext(), JSTYPE_STRING, &v2))
   505             return nullptr;
   506         v = v2;
   507     }
   509     if (v.isString())
   510         return AtomizeString(cx, v.toString());
   511     if (v.isInt32())
   512         return Int32ToAtom(cx, v.toInt32());
   513     if (v.isDouble())
   514         return NumberToAtom(cx, v.toDouble());
   515     if (v.isBoolean())
   516         return v.toBoolean() ? cx->names().true_ : cx->names().false_;
   517     if (v.isNull())
   518         return cx->names().null;
   519     return cx->names().undefined;
   520 }
   522 template <AllowGC allowGC>
   523 JSAtom *
   524 js::ToAtom(ExclusiveContext *cx, typename MaybeRooted<Value, allowGC>::HandleType v)
   525 {
   526     if (!v.isString())
   527         return ToAtomSlow<allowGC>(cx, v);
   529     JSString *str = v.toString();
   530     if (str->isAtom())
   531         return &str->asAtom();
   533     return AtomizeString(cx, str);
   534 }
   536 template JSAtom *
   537 js::ToAtom<CanGC>(ExclusiveContext *cx, HandleValue v);
   539 template JSAtom *
   540 js::ToAtom<NoGC>(ExclusiveContext *cx, Value v);
   542 template<XDRMode mode>
   543 bool
   544 js::XDRAtom(XDRState<mode> *xdr, MutableHandleAtom atomp)
   545 {
   546     if (mode == XDR_ENCODE) {
   547         uint32_t nchars = atomp->length();
   548         if (!xdr->codeUint32(&nchars))
   549             return false;
   551         jschar *chars = const_cast<jschar *>(atomp->getChars(xdr->cx()));
   552         if (!chars)
   553             return false;
   555         return xdr->codeChars(chars, nchars);
   556     }
   558     /* Avoid JSString allocation for already existing atoms. See bug 321985. */
   559     uint32_t nchars;
   560     if (!xdr->codeUint32(&nchars))
   561         return false;
   563     JSContext *cx = xdr->cx();
   564     JSAtom *atom;
   565 #if IS_LITTLE_ENDIAN
   566     /* Directly access the little endian chars in the XDR buffer. */
   567     const jschar *chars = reinterpret_cast<const jschar *>(xdr->buf.read(nchars * sizeof(jschar)));
   568     atom = AtomizeChars(cx, chars, nchars);
   569 #else
   570     /*
   571      * We must copy chars to a temporary buffer to convert between little and
   572      * big endian data.
   573      */
   574     jschar *chars;
   575     jschar stackChars[256];
   576     if (nchars <= ArrayLength(stackChars)) {
   577         chars = stackChars;
   578     } else {
   579         /*
   580          * This is very uncommon. Don't use the tempLifoAlloc arena for this as
   581          * most allocations here will be bigger than tempLifoAlloc's default
   582          * chunk size.
   583          */
   584         chars = cx->runtime()->pod_malloc<jschar>(nchars);
   585         if (!chars)
   586             return false;
   587     }
   589     JS_ALWAYS_TRUE(xdr->codeChars(chars, nchars));
   590     atom = AtomizeChars(cx, chars, nchars);
   591     if (chars != stackChars)
   592         js_free(chars);
   593 #endif /* !IS_LITTLE_ENDIAN */
   595     if (!atom)
   596         return false;
   597     atomp.set(atom);
   598     return true;
   599 }
   601 template bool
   602 js::XDRAtom(XDRState<XDR_ENCODE> *xdr, MutableHandleAtom atomp);
   604 template bool
   605 js::XDRAtom(XDRState<XDR_DECODE> *xdr, MutableHandleAtom atomp);

mercurial