diff --git a/content/base/public/nsContentUtils.h b/content/base/public/nsContentUtils.h index 1ba2a603e50a..9a4f3898afbe 100644 --- a/content/base/public/nsContentUtils.h +++ b/content/base/public/nsContentUtils.h @@ -42,7 +42,7 @@ #ifndef nsContentUtils_h___ #define nsContentUtils_h___ -#include "jspubtd.h" +#include "jsprvtd.h" #include "jsnum.h" #include "nsAString.h" #include "nsIStatefulFrame.h" diff --git a/dom/src/json/nsJSON.cpp b/dom/src/json/nsJSON.cpp index 46e6014e6e2b..e5dd6b82e889 100644 --- a/dom/src/json/nsJSON.cpp +++ b/dom/src/json/nsJSON.cpp @@ -39,6 +39,7 @@ #include "jsapi.h" #include "jsdtoa.h" +#include "jsprvtd.h" #include "jsnum.h" #include "jsbool.h" #include "jsarena.h" diff --git a/js/jsd/jsdebug.h b/js/jsd/jsdebug.h index 724d5273f168..c80f29c0566f 100644 --- a/js/jsd/jsdebug.h +++ b/js/jsd/jsdebug.h @@ -54,6 +54,7 @@ extern "C" } #endif +#define WRAPPED_IN_EXTERN_C JS_BEGIN_EXTERN_C #include "jsapi.h" #include "jsdbgapi.h" @@ -61,6 +62,7 @@ JS_BEGIN_EXTERN_C #include "lwdbgapi.h" #endif JS_END_EXTERN_C +#undef POSSIBLY_INCLUDED_FROM_C JS_BEGIN_EXTERN_C diff --git a/js/src/jsarray.cpp b/js/src/jsarray.cpp index 856fdaaaa48b..d4ec15671366 100644 --- a/js/src/jsarray.cpp +++ b/js/src/jsarray.cpp @@ -99,6 +99,7 @@ #include "jsscope.h" #include "jsstr.h" #include "jsstaticcheck.h" +#include "jsvector.h" /* 2^32 - 1 as a number and a string */ #define MAXINDEX 4294967295u @@ -1301,225 +1302,15 @@ js_MakeArraySlow(JSContext *cx, JSObject *obj) return JS_FALSE; } -enum ArrayToStringOp { - TO_STRING, - TO_LOCALE_STRING, - TO_SOURCE -}; - -/* - * When op is TO_STRING or TO_LOCALE_STRING sep indicates a separator to use - * or "," when sep is NULL. - * When op is TO_SOURCE sep must be NULL. - */ +/* Transfer ownership of buffer to returned string. */ static JSBool -array_join_sub(JSContext *cx, JSObject *obj, enum ArrayToStringOp op, - JSString *sep, jsval *rval) +BufferToString(JSContext *cx, JSTempVector &buf, jsval *rval) { - JSBool ok, hole; - jsuint length, index; - jschar *chars, *ochars; - size_t nchars, growth, seplen, tmplen, extratail; - const jschar *sepstr; - JSString *str; - JSHashEntry *he; - JSAtom *atom; - - JS_CHECK_RECURSION(cx, return JS_FALSE); - - ok = js_GetLengthProperty(cx, obj, &length); - if (!ok) - return JS_FALSE; - - he = js_EnterSharpObject(cx, obj, NULL, &chars); - if (!he) - return JS_FALSE; -#ifdef DEBUG - growth = (size_t) -1; -#endif - - /* - * We must check for the sharp bit and skip js_LeaveSharpObject when it is - * set even when op is not TO_SOURCE. A script can overwrite the default - * toSource implementation and trigger a call, for example, to the - * toString method during serialization of the object graph (bug 369696). - */ - if (IS_SHARP(he)) { -#if JS_HAS_SHARP_VARS - nchars = js_strlen(chars); -#else - chars[0] = '['; - chars[1] = ']'; - chars[2] = 0; - nchars = 2; -#endif - goto make_string; - } - - if (op == TO_SOURCE) { - /* - * Always allocate 2 extra chars for closing ']' and terminating 0 - * and then preallocate 1 + extratail to include starting '['. - */ - extratail = 2; - growth = (1 + extratail) * sizeof(jschar); - if (!chars) { - nchars = 0; - chars = (jschar *) malloc(growth); - if (!chars) - goto done; - } else { - MAKE_SHARP(he); - nchars = js_strlen(chars); - growth += nchars * sizeof(jschar); - chars = (jschar *)realloc((ochars = chars), growth); - if (!chars) { - free(ochars); - goto done; - } - } - chars[nchars++] = '['; - JS_ASSERT(sep == NULL); - sepstr = NULL; /* indicates to use ", " as separator */ - seplen = 2; - } else { - /* - * Free any sharp variable definition in chars. Normally, we would - * MAKE_SHARP(he) so that only the first sharp variable annotation is - * a definition, and all the rest are references, but in the current - * case of (op != TO_SOURCE), we don't need chars at all. - */ - if (chars) - JS_free(cx, chars); - chars = NULL; - nchars = 0; - extratail = 1; /* allocate extra char for terminating 0 */ - - /* Return the empty string on a cycle as well as on empty join. */ - if (IS_BUSY(he) || length == 0) { - js_LeaveSharpObject(cx, NULL); - *rval = JS_GetEmptyStringValue(cx); - return ok; - } - - /* Flag he as BUSY so we can distinguish a cycle from a join-point. */ - MAKE_BUSY(he); - - if (sep) { - sep->getCharsAndLength(sepstr, seplen); - } else { - sepstr = NULL; /* indicates to use "," as separator */ - seplen = 1; - } - } - - /* Use rval to locally root each element value as we loop and convert. */ - for (index = 0; index < length; index++) { - ok = (JS_CHECK_OPERATION_LIMIT(cx) && - GetArrayElement(cx, obj, index, &hole, rval)); - if (!ok) - goto done; - if (hole || - (op != TO_SOURCE && - (JSVAL_IS_VOID(*rval) || JSVAL_IS_NULL(*rval)))) { - str = cx->runtime->emptyString; - } else { - if (op == TO_LOCALE_STRING) { - JSObject *robj; - - atom = cx->runtime->atomState.toLocaleStringAtom; - ok = js_ValueToObject(cx, *rval, &robj); - if (ok) { - /* Re-use *rval to protect robj temporarily. */ - *rval = OBJECT_TO_JSVAL(robj); - ok = js_TryMethod(cx, robj, atom, 0, NULL, rval); - } - if (!ok) - goto done; - str = js_ValueToString(cx, *rval); - } else if (op == TO_STRING) { - str = js_ValueToString(cx, *rval); - } else { - JS_ASSERT(op == TO_SOURCE); - str = js_ValueToSource(cx, *rval); - } - if (!str) { - ok = JS_FALSE; - goto done; - } - } - - /* - * Do not append separator after the last element unless it is a hole - * and we are in toSource. In that case we append single ",". - */ - if (index + 1 == length) - seplen = (hole && op == TO_SOURCE) ? 1 : 0; - - /* Allocate 1 at end for closing bracket and zero. */ - tmplen = str->length(); - growth = nchars + tmplen + seplen + extratail; - if (nchars > growth || tmplen > growth || - growth > (size_t)-1 / sizeof(jschar)) { - if (chars) { - free(chars); - chars = NULL; - } - goto done; - } - growth *= sizeof(jschar); - if (!chars) { - chars = (jschar *) malloc(growth); - if (!chars) - goto done; - } else { - chars = (jschar *) realloc((ochars = chars), growth); - if (!chars) { - free(ochars); - goto done; - } - } - - js_strncpy(&chars[nchars], str->chars(), tmplen); - nchars += tmplen; - - if (seplen) { - if (sepstr) { - js_strncpy(&chars[nchars], sepstr, seplen); - } else { - JS_ASSERT(seplen == 1 || seplen == 2); - chars[nchars] = ','; - if (seplen == 2) - chars[nchars + 1] = ' '; - } - nchars += seplen; - } - } - - done: - if (op == TO_SOURCE) { - if (chars) - chars[nchars++] = ']'; - } else { - CLEAR_BUSY(he); - } - js_LeaveSharpObject(cx, NULL); - if (!ok) { - if (chars) - free(chars); - return ok; - } - - make_string: - if (!chars) { - JS_ReportOutOfMemory(cx); - return JS_FALSE; - } - chars[nchars] = 0; - JS_ASSERT(growth == (size_t)-1 || (nchars + 1) * sizeof(jschar) == growth); - str = js_NewString(cx, chars, nchars); + size_t length = buf.size() - 1; + jschar *chars = buf.extractRawBuffer(); + JSString *str = js_NewString(cx, chars, length); if (!str) { - free(chars); + JS_free(cx, chars); return JS_FALSE; } *rval = STRING_TO_JSVAL(str); @@ -1530,28 +1321,254 @@ array_join_sub(JSContext *cx, JSObject *obj, enum ArrayToStringOp op, static JSBool array_toSource(JSContext *cx, uintN argc, jsval *vp) { - JSObject *obj; + JS_CHECK_RECURSION(cx, return JS_FALSE); - obj = JS_THIS_OBJECT(cx, vp); - if (OBJ_GET_CLASS(cx, obj) != &js_SlowArrayClass && - !JS_InstanceOf(cx, obj, &js_ArrayClass, vp + 2)) { + JSObject *obj = JS_THIS_OBJECT(cx, vp); + if (!obj || + (OBJ_GET_CLASS(cx, obj) != &js_SlowArrayClass && + !JS_InstanceOf(cx, obj, &js_ArrayClass, vp + 2))) { return JS_FALSE; } - return array_join_sub(cx, obj, TO_SOURCE, NULL, vp); + + /* Find joins or cycles in the reachable object graph. */ + jschar *sharpchars; + JSHashEntry *he = js_EnterSharpObject(cx, obj, NULL, &sharpchars); + if (!he) + return JS_FALSE; + JSBool initiallySharp = IS_SHARP(he) ? JS_TRUE : JS_FALSE; + + /* After this point, all paths exit through the 'done' label. */ + MUST_FLOW_THROUGH("done"); + JSBool ok = JS_TRUE; + + /* + * This object will take responsibility for the jschar buffer until the + * buffer is transferred to the returned JSString. + */ + JSTempVector buf(cx); + if (!(ok = buf.reserve(3))) + goto done; + + /* Cycles/joins are indicated by sharp objects. */ +#if JS_HAS_SHARP_VARS + if (IS_SHARP(he)) { + JS_ASSERT(sharpchars != 0); + /* +1 to include the trailing '\0' */ + buf.replaceRawBuffer(sharpchars, js_strlen(sharpchars) + 1); + goto make_string; + } else if (sharpchars) { + MAKE_SHARP(he); + buf.replaceRawBuffer(sharpchars, js_strlen(sharpchars)); + } +#else + if (IS_SHARP(he)) { + static const jschar arr[] = { '[', ']', 0 }; + if (!(ok = buf.pushBack(arr, arr + 3))) + goto done; + if (sharpchars) + JS_free(cx, sharpchars); + goto make_string; + } +#endif + + if (!(ok = buf.pushBack('['))) + goto done; + + jsuint length; + ok = js_GetLengthProperty(cx, obj, &length); + if (!ok) + goto done; + + for (jsuint index = 0; index < length; index++) { + /* Use vp to locally root each element value. */ + JSBool hole; + ok = (JS_CHECK_OPERATION_LIMIT(cx) && + GetArrayElement(cx, obj, index, &hole, vp)); + if (!ok) + goto done; + + /* Get element's character string. */ + JSString *str; + if (hole) { + str = cx->runtime->emptyString; + } else { + str = js_ValueToSource(cx, *vp); + if (!str) { + ok = JS_FALSE; + goto done; + } + } + *vp = STRING_TO_JSVAL(str); + const jschar *chars; + size_t charlen; + str->getCharsAndLength(chars, charlen); + + /* Append element to buffer. */ + if (!(ok = buf.pushBack(chars, chars + charlen))) + goto done; + if (index + 1 != length) { + if (!(ok = buf.pushBack(',')) || !(ok = buf.pushBack(' '))) + goto done; + } else if (hole) { + if (!(ok = buf.pushBack(','))) + goto done; + } + } + + /* Finalize the buffer. */ + if (!(ok = buf.pushBack(']')) || !(ok = buf.pushBack(0))) + goto done; + + make_string: + if (!(ok = BufferToString(cx, buf, vp))) + goto done; + + done: + if (!initiallySharp) + js_LeaveSharpObject(cx, NULL); + return ok; } #endif +static JSHashNumber +js_hash_array(const void *key) +{ + return (JSHashNumber)JS_PTR_TO_UINT32(key) >> JSVAL_TAGBITS; +} + +JSBool +js_InitContextBusyArrayTable(JSContext *cx) +{ + cx->busyArrayTable = JS_NewHashTable(4, js_hash_array, JS_CompareValues, + JS_CompareValues, NULL, NULL); + return cx->busyArrayTable != NULL; +} + +static JSBool +array_toString_sub(JSContext *cx, JSObject *obj, JSBool locale, + JSString *sepstr, jsval *rval) +{ + JS_CHECK_RECURSION(cx, return JS_FALSE); + + /* + * This hash table is shared between toString invocations and must be empty + * after the root invocation completes. + */ + JSHashTable *table = cx->busyArrayTable; + + /* + * Use HashTable entry as the cycle indicator. On first visit, create the + * entry, and, when leaving, remove the entry. + */ + JSHashNumber hash = js_hash_array(obj); + JSHashEntry **hep = JS_HashTableRawLookup(table, hash, obj); + JSHashEntry *he = *hep; + if (!he) { + /* Not in hash table, so not a cycle. */ + he = JS_HashTableRawAdd(table, hep, hash, obj, NULL); + if (!he) { + JS_ReportOutOfMemory(cx); + return JS_FALSE; + } + } else { + /* Cycle, so return empty string. */ + *rval = ATOM_KEY(cx->runtime->atomState.emptyAtom); + return JS_TRUE; + } + + JSAutoTempValueRooter tvr(cx, obj); + + /* After this point, all paths exit through the 'done' label. */ + MUST_FLOW_THROUGH("done"); + JSBool ok = JS_TRUE; + + /* Get characters to use for the separator. */ + static const jschar comma = ','; + const jschar *sep; + size_t seplen; + if (sepstr) { + sepstr->getCharsAndLength(sep, seplen); + } else { + sep = , + seplen = 1; + } + + /* + * This object will take responsibility for the jschar buffer until the + * buffer is transferred to the returned JSString. + */ + JSTempVector buf(cx); + + jsuint length; + ok = js_GetLengthProperty(cx, obj, &length); + if (!ok) + goto done; + + for (jsuint index = 0; index < length; index++) { + /* Use rval to locally root each element value. */ + JSBool hole; + ok = JS_CHECK_OPERATION_LIMIT(cx) && + GetArrayElement(cx, obj, index, &hole, rval); + if (!ok) + goto done; + + /* Get element's character string. */ + if (!(hole || JSVAL_IS_VOID(*rval) || JSVAL_IS_NULL(*rval))) { + if (locale) { + JSObject *robj; + + JSAtom *atom = cx->runtime->atomState.toLocaleStringAtom; + ok = js_ValueToObject(cx, *rval, &robj); + if (ok) { + /* Re-use *rval to protect robj temporarily. */ + *rval = OBJECT_TO_JSVAL(robj); + ok = js_TryMethod(cx, robj, atom, 0, NULL, rval); + } + if (!ok) + goto done; + } + + ok = js_ValueToStringBuffer(cx, *rval, buf); + if (!ok) + goto done; + } + + /* Append the separator. */ + if (index + 1 != length) { + if (!(ok = buf.pushBack(sep, sep + seplen))) + goto done; + } + } + + /* Finalize the buffer. */ + if (buf.empty()) { + *rval = ATOM_KEY(cx->runtime->atomState.emptyAtom); + goto done; + } + + ok = buf.pushBack(0) && + BufferToString(cx, buf, rval); + if (!ok) + goto done; + + done: + JS_HashTableRawRemove(table, hep, he); + return ok; +} + static JSBool array_toString(JSContext *cx, uintN argc, jsval *vp) { JSObject *obj; obj = JS_THIS_OBJECT(cx, vp); - if (OBJ_GET_CLASS(cx, obj) != &js_SlowArrayClass && - !JS_InstanceOf(cx, obj, &js_ArrayClass, vp + 2)) { + if (!obj || + (OBJ_GET_CLASS(cx, obj) != &js_SlowArrayClass && + !JS_InstanceOf(cx, obj, &js_ArrayClass, vp + 2))) { return JS_FALSE; } - return array_join_sub(cx, obj, TO_STRING, NULL, vp); + + return array_toString_sub(cx, obj, JS_FALSE, NULL, vp); } static JSBool @@ -1560,8 +1577,9 @@ array_toLocaleString(JSContext *cx, uintN argc, jsval *vp) JSObject *obj; obj = JS_THIS_OBJECT(cx, vp); - if (OBJ_GET_CLASS(cx, obj) != &js_SlowArrayClass && - !JS_InstanceOf(cx, obj, &js_ArrayClass, vp + 2)) { + if (!obj || + (OBJ_GET_CLASS(cx, obj) != &js_SlowArrayClass && + !JS_InstanceOf(cx, obj, &js_ArrayClass, vp + 2))) { return JS_FALSE; } @@ -1569,7 +1587,7 @@ array_toLocaleString(JSContext *cx, uintN argc, jsval *vp) * Passing comma here as the separator. Need a way to get a * locale-specific version. */ - return array_join_sub(cx, obj, TO_LOCALE_STRING, NULL, vp); + return array_toString_sub(cx, obj, JS_TRUE, NULL, vp); } enum TargetElementsType { @@ -1716,7 +1734,7 @@ static JSString* FASTCALL Array_p_join(JSContext* cx, JSObject* obj, JSString *str) { JSAutoTempValueRooter tvr(cx); - if (!array_join_sub(cx, obj, TO_STRING, str, tvr.addr())) { + if (!array_toString_sub(cx, obj, JS_FALSE, str, tvr.addr())) { js_SetBuiltinError(cx); return NULL; } @@ -1727,7 +1745,7 @@ static JSString* FASTCALL Array_p_toString(JSContext* cx, JSObject* obj) { JSAutoTempValueRooter tvr(cx); - if (!array_join_sub(cx, obj, TO_STRING, NULL, tvr.addr())) { + if (!array_toString_sub(cx, obj, JS_FALSE, NULL, tvr.addr())) { js_SetBuiltinError(cx); return NULL; } @@ -1753,7 +1771,7 @@ array_join(JSContext *cx, uintN argc, jsval *vp) vp[2] = STRING_TO_JSVAL(str); } obj = JS_THIS_OBJECT(cx, vp); - return obj && array_join_sub(cx, obj, TO_STRING, str, vp); + return obj && array_toString_sub(cx, obj, JS_FALSE, str, vp); } static JSBool diff --git a/js/src/jsarray.h b/js/src/jsarray.h index 5ed42158ca91..4244a6017ad4 100644 --- a/js/src/jsarray.h +++ b/js/src/jsarray.h @@ -97,6 +97,9 @@ js_GetProtoIfDenseArray(JSContext *cx, JSObject *obj) extern JSObject * js_InitArrayClass(JSContext *cx, JSObject *obj); +extern JSBool +js_InitContextBusyArrayTable(JSContext *); + extern JSObject * js_NewArrayObject(JSContext *cx, jsuint length, jsval *vector, JSBool holey = JS_FALSE); diff --git a/js/src/jsbool.cpp b/js/src/jsbool.cpp index f7829e6ceebd..9dd68c893e81 100644 --- a/js/src/jsbool.cpp +++ b/js/src/jsbool.cpp @@ -52,6 +52,7 @@ #include "jsnum.h" #include "jsobj.h" #include "jsstr.h" +#include "jsvector.h" /* Check pseudo-booleans values. */ JS_STATIC_ASSERT(!(JSVAL_TRUE & JSVAL_HOLE_FLAG)); @@ -162,6 +163,16 @@ js_BooleanToString(JSContext *cx, JSBool b) return ATOM_TO_STRING(cx->runtime->atomState.booleanAtoms[b ? 1 : 0]); } +/* This function implements E-262-3 section 9.8, toString. */ +JSBool +js_BooleanToStringBuffer(JSContext *cx, JSBool b, JSTempVector &buf) +{ + static const jschar trueChars[] = { 't', 'r', 'u', 'e' }, + falseChars[] = { 'f', 'a', 'l', 's', 'e' }; + return b ? buf.pushBack(trueChars, trueChars + JS_ARRAY_LENGTH(trueChars)) + : buf.pushBack(falseChars, falseChars + JS_ARRAY_LENGTH(falseChars)); +} + JSBool js_ValueToBoolean(jsval v) { diff --git a/js/src/jsbool.h b/js/src/jsbool.h index 627c4a0d69db..20d6db999625 100644 --- a/js/src/jsbool.h +++ b/js/src/jsbool.h @@ -80,6 +80,9 @@ js_InitBooleanClass(JSContext *cx, JSObject *obj); extern JSString * js_BooleanToString(JSContext *cx, JSBool b); +extern JSBool +js_BooleanToStringBuffer(JSContext *cx, JSBool b, JSTempVector &buf); + extern JSBool js_ValueToBoolean(jsval v); diff --git a/js/src/jscntxt.cpp b/js/src/jscntxt.cpp index 4ef7dd7fdc05..cc87e3b3b385 100644 --- a/js/src/jscntxt.cpp +++ b/js/src/jscntxt.cpp @@ -387,6 +387,11 @@ js_NewContext(JSRuntime *rt, size_t stackChunkSize) js_InitRegExpStatics(cx); JS_ASSERT(cx->resolveFlags == 0); + if (!js_InitContextBusyArrayTable(cx)) { + FreeContext(cx); + return NULL; + } + #ifdef JS_THREADSAFE if (!js_InitContextThread(cx)) { FreeContext(cx); @@ -743,6 +748,12 @@ FreeContext(JSContext *cx) JS_free(cx, temp); } + /* Destroy the busy array table. */ + if (cx->busyArrayTable) { + JS_HashTableDestroy(cx->busyArrayTable); + cx->busyArrayTable = NULL; + } + /* Destroy the resolve recursion damper. */ if (cx->resolvingTable) { JS_DHashTableDestroy(cx->resolvingTable); diff --git a/js/src/jscntxt.h b/js/src/jscntxt.h index 551fc28c93d9..b2ee966d3e9a 100644 --- a/js/src/jscntxt.h +++ b/js/src/jscntxt.h @@ -56,6 +56,7 @@ #include "jspubtd.h" #include "jsregexp.h" #include "jsutil.h" +#include "jsarray.h" JS_BEGIN_EXTERN_C @@ -955,6 +956,7 @@ struct JSContext { /* State for object and array toSource conversion. */ JSSharpObjectMap sharpObjectMap; + JSHashTable *busyArrayTable; /* Argument formatter support for JS_{Convert,Push}Arguments{,VA}. */ JSArgumentFormatMap *argumentFormatMap; diff --git a/js/src/jsdtoa.cpp b/js/src/jsdtoa.cpp index 7ce4932fd734..7790dab463a6 100644 --- a/js/src/jsdtoa.cpp +++ b/js/src/jsdtoa.cpp @@ -46,7 +46,7 @@ #include "jsdtoa.h" #include "jsprf.h" #include "jsutil.h" /* Added by JSIFY */ -#include "jspubtd.h" +#include "jsprvtd.h" #include "jsnum.h" #include "jsbit.h" diff --git a/js/src/jsnum.cpp b/js/src/jsnum.cpp index 2a6f2ed6b3bc..5932b1483fd3 100644 --- a/js/src/jsnum.cpp +++ b/js/src/jsnum.cpp @@ -71,6 +71,7 @@ #include "jsprf.h" #include "jsscope.h" #include "jsstr.h" +#include "jsvector.h" static JSBool num_isNaN(JSContext *cx, uintN argc, jsval *vp) @@ -861,6 +862,41 @@ js_NumberToString(JSContext *cx, jsdouble d) return NumberToStringWithBase(cx, d, 10); } +JSBool JS_FASTCALL +js_NumberValueToStringBuffer(JSContext *cx, jsval v, JSTempVector &buf) +{ + /* Convert to C-string. */ + static const size_t arrSize = DTOSTR_STANDARD_BUFFER_SIZE; + char arr[arrSize]; + const char *cstr; + if (JSVAL_IS_INT(v)) { + cstr = IntToCString(JSVAL_TO_INT(v), 10, arr, arrSize); + } else { + JS_ASSERT(JSVAL_IS_DOUBLE(v)); + cstr = JS_dtostr(arr, arrSize, DTOSTR_STANDARD, 0, *JSVAL_TO_DOUBLE(v)); + } + if (!cstr) + return JS_FALSE; + + /* + * Inflate to jschar string. The input C-string characters are < 127, so + * even if jschars are UTF-8, all chars should map to one jschar. + */ + size_t cstrlen = strlen(cstr); + JS_ASSERT(cstrlen < arrSize); + size_t sizeBefore = buf.size(); + if (!buf.growBy(cstrlen)) + return JS_FALSE; + jschar *appendBegin = buf.begin() + sizeBefore; +#ifdef DEBUG + size_t oldcstrlen = cstrlen; + JSBool ok = +#endif + js_InflateStringToBuffer(cx, cstr, cstrlen, appendBegin, &cstrlen); + JS_ASSERT(ok && cstrlen == oldcstrlen); + return JS_TRUE; +} + jsdouble js_ValueToNumber(JSContext *cx, jsval *vp) { diff --git a/js/src/jsnum.h b/js/src/jsnum.h index 08dec279a011..5914ce9b2178 100644 --- a/js/src/jsnum.h +++ b/js/src/jsnum.h @@ -189,6 +189,13 @@ js_NewWeaklyRootedNumber(JSContext *cx, jsdouble d, jsval *vp); extern JSString * JS_FASTCALL js_NumberToString(JSContext *cx, jsdouble d); +/* + * Convert an integer or double (contained in the given jsval) to a string and + * append to the given buffer. + */ +extern JSBool JS_FASTCALL +js_NumberValueToStringBuffer(JSContext *, jsval, JSTempVector &); + /* * Convert a value to a number. On exit JSVAL_IS_NULL(*vp) iff there was an * error. If on exit JSVAL_IS_NUMBER(*vp), then *vp holds the jsval that diff --git a/js/src/jsprvtd.h b/js/src/jsprvtd.h index 377c68cb92f6..521979bdc183 100644 --- a/js/src/jsprvtd.h +++ b/js/src/jsprvtd.h @@ -134,6 +134,27 @@ typedef struct JSXML JSXML; typedef struct JSXMLArray JSXMLArray; typedef struct JSXMLArrayCursor JSXMLArrayCursor; +/* + * Template declarations. + * + * jsprvtd.h can be included in both C and C++ translation units. For C++, it + * may possibly be wrapped in an extern "C" block which does not agree with + * templates. Since there is no way to auto-detect whether or not we are in an + * extern block, we rely on the C++/extern user to #define WRAPPED_IN_EXTERN_C + * to let us know to temporarily exit the block. + */ +#ifdef __cplusplus +# ifdef WRAPPED_IN_EXTERN_C +} +# endif + +template class JSTempVector; + +# ifdef WRAPPED_IN_EXTERN_C +extern "C" { +# endif +#endif /* __cplusplus */ + /* "Friend" types used by jscntxt.h and jsdbgapi.h. */ typedef enum JSTrapStatus { JSTRAP_ERROR, diff --git a/js/src/jsstr.cpp b/js/src/jsstr.cpp index 5fa799248b4d..936bc963fbb9 100644 --- a/js/src/jsstr.cpp +++ b/js/src/jsstr.cpp @@ -73,6 +73,7 @@ #include "jsstaticcheck.h" #include "jsstr.h" #include "jsbit.h" +#include "jsvector.h" #define JSSTRDEP_RECURSION_LIMIT 100 @@ -246,35 +247,36 @@ js_MakeStringImmutable(JSContext *cx, JSString *str) static JSString * ArgToRootedString(JSContext *cx, uintN argc, jsval *vp, uintN arg) { - JSObject *obj; - JSString *str; - if (arg >= argc) return ATOM_TO_STRING(cx->runtime->atomState.typeAtoms[JSTYPE_VOID]); vp += 2 + arg; - if (JSVAL_IS_OBJECT(*vp)) { - obj = JSVAL_TO_OBJECT(*vp); - if (!obj) - return ATOM_TO_STRING(cx->runtime->atomState.nullAtom); - if (!OBJ_DEFAULT_VALUE(cx, obj, JSTYPE_STRING, vp)) - return NULL; + if (!JSVAL_IS_PRIMITIVE(*vp) && + !OBJ_DEFAULT_VALUE(cx, JSVAL_TO_OBJECT(*vp), JSTYPE_STRING, vp)) { + return NULL; } - if (JSVAL_IS_STRING(*vp)) - return JSVAL_TO_STRING(*vp); - if (JSVAL_IS_INT(*vp)) { - str = js_NumberToString(cx, JSVAL_TO_INT(*vp)); - } else if (JSVAL_IS_DOUBLE(*vp)) { - str = js_NumberToString(cx, *JSVAL_TO_DOUBLE(*vp)); + + JSString *str; + if (JSVAL_IS_STRING(*vp)) { + str = JSVAL_TO_STRING(*vp); } else if (JSVAL_IS_BOOLEAN(*vp)) { - return ATOM_TO_STRING(cx->runtime->atomState.booleanAtoms[ + str = ATOM_TO_STRING(cx->runtime->atomState.booleanAtoms[ JSVAL_TO_BOOLEAN(*vp)? 1 : 0]); - } else { - JS_ASSERT(JSVAL_IS_VOID(*vp)); - return ATOM_TO_STRING(cx->runtime->atomState.typeAtoms[JSTYPE_VOID]); + } else if (JSVAL_IS_NULL(*vp)) { + str = ATOM_TO_STRING(cx->runtime->atomState.nullAtom); + } else if (JSVAL_IS_VOID(*vp)) { + str = ATOM_TO_STRING(cx->runtime->atomState.typeAtoms[JSTYPE_VOID]); + } + else { + if (JSVAL_IS_INT(*vp)) { + str = js_NumberToString(cx, JSVAL_TO_INT(*vp)); + } else { + JS_ASSERT(JSVAL_IS_DOUBLE(*vp)); + str = js_NumberToString(cx, *JSVAL_TO_DOUBLE(*vp)); + } + if (str) + *vp = STRING_TO_JSVAL(str); } - if (str) - *vp = STRING_TO_JSVAL(str); return str; } @@ -2966,16 +2968,13 @@ js_ValueToPrintable(JSContext *cx, jsval v, JSValueToStringFun v2sfun) JS_FRIEND_API(JSString *) js_ValueToString(JSContext *cx, jsval v) { - JSObject *obj; JSString *str; - if (JSVAL_IS_OBJECT(v)) { - obj = JSVAL_TO_OBJECT(v); - if (!obj) - return ATOM_TO_STRING(cx->runtime->atomState.nullAtom); - if (!OBJ_DEFAULT_VALUE(cx, obj, JSTYPE_STRING, &v)) - return NULL; + if (!JSVAL_IS_PRIMITIVE(v) && + !OBJ_DEFAULT_VALUE(cx, JSVAL_TO_OBJECT(v), JSTYPE_STRING, &v)) { + return NULL; } + if (JSVAL_IS_STRING(v)) { str = JSVAL_TO_STRING(v); } else if (JSVAL_IS_INT(v)) { @@ -2984,12 +2983,50 @@ js_ValueToString(JSContext *cx, jsval v) str = js_NumberToString(cx, *JSVAL_TO_DOUBLE(v)); } else if (JSVAL_IS_BOOLEAN(v)) { str = js_BooleanToString(cx, JSVAL_TO_BOOLEAN(v)); + } else if (JSVAL_IS_NULL(v)) { + str = ATOM_TO_STRING(cx->runtime->atomState.nullAtom); } else { str = ATOM_TO_STRING(cx->runtime->atomState.typeAtoms[JSTYPE_VOID]); } return str; } +static inline JSBool +pushAtom(JSAtom *atom, JSTempVector &buf) +{ + JSString *str = ATOM_TO_STRING(atom); + const jschar *chars; + size_t length; + str->getCharsAndLength(chars, length); + return buf.pushBack(chars, chars + length); +} + +/* This function implements E-262-3 section 9.8, toString. */ +JS_FRIEND_API(JSBool) +js_ValueToStringBuffer(JSContext *cx, jsval v, JSTempVector &buf) +{ + if (!JSVAL_IS_PRIMITIVE(v) && + !OBJ_DEFAULT_VALUE(cx, JSVAL_TO_OBJECT(v), JSTYPE_STRING, &v)) { + return JS_FALSE; + } + + if (JSVAL_IS_STRING(v)) { + JSString *str = JSVAL_TO_STRING(v); + const jschar *chars; + size_t length; + str->getCharsAndLength(chars, length); + return buf.pushBack(chars, chars + length); + } + if (JSVAL_IS_NUMBER(v)) + return js_NumberValueToStringBuffer(cx, v, buf); + if (JSVAL_IS_BOOLEAN(v)) + return js_BooleanToStringBuffer(cx, JSVAL_TO_BOOLEAN(v), buf); + if (JSVAL_IS_NULL(v)) + return pushAtom(cx->runtime->atomState.nullAtom, buf); + JS_ASSERT(JSVAL_IS_VOID(v)); + return pushAtom(cx->runtime->atomState.typeAtoms[JSTYPE_VOID], buf); +} + JS_FRIEND_API(JSString *) js_ValueToSource(JSContext *cx, jsval v) { diff --git a/js/src/jsstr.h b/js/src/jsstr.h index aca27883250f..57f75db9e53f 100644 --- a/js/src/jsstr.h +++ b/js/src/jsstr.h @@ -602,6 +602,14 @@ js_ValueToPrintable(JSContext *cx, jsval v, JSValueToStringFun v2sfun); extern JS_FRIEND_API(JSString *) js_ValueToString(JSContext *cx, jsval v); +/* + * This function implements E-262-3 section 9.8, toString. Convert the given + * value to a string of jschars appended to the given buffer. On error, the + * passed buffer may have partial results appended. + */ +extern JS_FRIEND_API(JSBool) +js_ValueToStringBuffer(JSContext *, jsval, JSTempVector &); + /* * Convert a value to its source expression, returning null after reporting * an error, otherwise returning a new string reference. diff --git a/js/src/jsvector.h b/js/src/jsvector.h new file mode 100644 index 000000000000..47fad6f8e30f --- /dev/null +++ b/js/src/jsvector.h @@ -0,0 +1,410 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sw=4 et tw=99 ft=cpp: + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla SpiderMonkey JavaScript 1.9 code, released + * June 12, 2009. + * + * The Initial Developer of the Original Code is + * the Mozilla Corporation. + * + * Contributor(s): + * Luke Wagner + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef jsvector_h_ +#define jsvector_h_ + +#include "jscntxt.h" + +#include +#include + +/* + * Traits class for identifying POD types. Until C++0x, there is no automatic + * way to detect PODs, so for the moment it is done manually. + */ +template struct IsPodType { static const bool result = false; }; +template <> struct IsPodType { static const bool result = true; }; +template <> struct IsPodType { static const bool result = true; }; +template <> struct IsPodType { static const bool result = true; }; +template <> struct IsPodType { static const bool result = true; }; +template <> struct IsPodType { static const bool result = true; }; +template <> struct IsPodType { static const bool result = true; }; +template <> struct IsPodType { static const bool result = true; }; + +/* + * This template class provides a default implementation for vector operations + * when the element type is not known to be a POD, as judged by IsPodType. + */ +template +struct JSTempVectorImpl +{ + /* Destroys constructed objects in the range [begin, end). */ + static inline void destroy(T *begin, T *end) { + for (T *p = begin; p != end; ++p) + p->~T(); + } + + /* Constructs objects in the uninitialized range [begin, end). */ + static inline void initialize(T *begin, T *end) { + for (T *p = begin; p != end; ++p) + new(p) T(); + } + + /* + * Copy-constructs objects in the uninitialized range + * [dst, dst+(srcend-srcbeg)) from the range [srcbeg, srcend). + */ + template + static inline void copyInitialize(T *dst, const U *srcbeg, const U *srcend) { + for (const U *p = srcbeg; p != srcend; ++p, ++dst) + new(dst) T(*p); + } + + /* + * Grows the given buffer to have capacity newcap, preserving the objects + * constructed in the range [begin, end) and updating vec. + */ + static inline bool growTo(JSTempVector &vec, size_t newcap) { + size_t bytes = sizeof(T) * newcap; + T *newbuf = reinterpret_cast(malloc(bytes)); + if (!newbuf) { + js_ReportOutOfMemory(vec.mCx); + return false; + } + for (T *dst = newbuf, *src = vec.mBegin; src != vec.mEnd; ++dst, ++src) + new(dst) T(*src); + JSTempVectorImpl::destroy(vec.mBegin, vec.mEnd); + free(vec.mBegin); + vec.mEnd = newbuf + (vec.mEnd - vec.mBegin); + vec.mBegin = newbuf; + vec.mCapacity = newbuf + newcap; + return true; + } +}; + +/* + * This partial template specialization provides a default implementation for + * vector operations when the element type is known to be a POD, as judged by + * IsPodType. + */ +template +struct JSTempVectorImpl +{ + static inline void destroy(T *, T *) {} + + static inline void initialize(T *begin, T *end) { + //memset(begin, 0, sizeof(T) * (end-begin)); //SLOWER + for (T *p = begin; p != end; ++p) + *p = 0; + } + + static inline void copyInitialize(T *dst, const T *srcbeg, const T *srcend) { + //memcpy(dst, srcbeg, sizeof(T) * (srcend-srcbeg)); //SLOWER + for (const T *p = srcbeg; p != srcend; ++p, ++dst) + *dst = *p; + } + + static inline bool growTo(JSTempVector &vec, size_t newcap) { + size_t bytes = sizeof(T) * newcap; + T *newbuf = reinterpret_cast(realloc(vec.mBegin, bytes)); + if (!newbuf) { + js_ReportOutOfMemory(vec.mCx); + free(vec.mBegin); + return false; + } + vec.mEnd = newbuf + (vec.mEnd - vec.mBegin); + vec.mBegin = newbuf; + vec.mCapacity = newbuf + newcap; + return true; + } +}; + +/* + * JS-friendly, STL-like container providing a short-lived, dynamic buffer. + * JSTempVector calls the constructors/destructors of all elements stored in + * its internal buffer, so non-PODs may be safely used. + * + * T requirements: + * - default and copy constructible, assignable, destructible + * - operations do not throw + * + * N.B: JSTempVector is not reentrant: T member functions called during + * JSTempVector member functions must not call back into the same + * JSTempVector. + */ +template +class JSTempVector +{ +#ifdef DEBUG + bool mInProgress; +#endif + + class ReentrancyGuard { + JSTempVector &mVec; + public: + ReentrancyGuard(JSTempVector &v) + : mVec(v) + { +#ifdef DEBUG + JS_ASSERT(!mVec.mInProgress); + mVec.mInProgress = true; +#endif + } + ~ReentrancyGuard() + { +#ifdef DEBUG + mVec.mInProgress = false; +#endif + } + }; + + public: + JSTempVector(JSContext *cx) + : +#ifdef DEBUG + mInProgress(false), +#endif + mCx(cx), mBegin(0), mEnd(0), mCapacity(0) + {} + ~JSTempVector(); + + JSTempVector(const JSTempVector &); + JSTempVector &operator=(const JSTempVector &); + + /* accessors */ + + size_t size() const { return mEnd - mBegin; } + size_t capacity() const { return mCapacity - mBegin; } + bool empty() const { return mBegin == mEnd; } + + T &operator[](int i) { + JS_ASSERT(!mInProgress && i < size()); + return mBegin[i]; + } + + const T &operator[](int i) const { + JS_ASSERT(!mInProgress && i < size()); + return mBegin[i]; + } + + T *begin() { + JS_ASSERT(!mInProgress); + return mBegin; + } + + const T *begin() const { + JS_ASSERT(!mInProgress); + return mBegin; + } + + T *end() { + JS_ASSERT(!mInProgress); + return mEnd; + } + + const T *end() const { + JS_ASSERT(!mInProgress); + return mEnd; + } + + T &back() { + JS_ASSERT(!mInProgress); + return *(mEnd - 1); + } + + const T &back() const { + JS_ASSERT(!mInProgress && !empty()); + return *(mEnd - 1); + } + + /* mutators */ + + bool reserve(size_t); + bool growBy(size_t); + void clear(); + + bool pushBack(const T &); + template bool pushBack(const U *begin, const U *end); + + /* + * Transfers ownership of the internal buffer used by JSTempVector to the + * caller. After this call, the JSTempVector is empty. + * N.B. Although a T*, only the range [0, size()) is constructed. + */ + T *extractRawBuffer(); + + /* + * Transfer ownership of an array of objects into the JSTempVector. + * N.B. This call assumes that there are no uninitialized elements in the + * passed array. + */ + void replaceRawBuffer(T *, size_t length); + + private: + typedef JSTempVectorImpl::result> Impl; + friend class JSTempVectorImpl::result>; + + static const int sGrowthFactor = 3; + + bool checkOverflow(size_t newval, size_t oldval, size_t diff) const; + + JSContext *mCx; + T *mBegin, *mEnd, *mCapacity; +}; + +template +inline +JSTempVector::~JSTempVector() +{ + ReentrancyGuard g(*this); + Impl::destroy(mBegin, mEnd); + free(mBegin); +} + +template +inline bool +JSTempVector::reserve(size_t newsz) +{ + ReentrancyGuard g(*this); + size_t oldcap = capacity(); + if (newsz > oldcap) { + size_t diff = newsz - oldcap; + size_t newcap = diff + oldcap * sGrowthFactor; + return checkOverflow(newcap, oldcap, diff) && + Impl::growTo(*this, newcap); + } + return true; +} + +template +inline bool +JSTempVector::growBy(size_t amount) +{ + /* grow if needed */ + size_t oldsize = size(), newsize = oldsize + amount; + if (!checkOverflow(newsize, oldsize, amount) || + (newsize > capacity() && !reserve(newsize))) + return false; + + /* initialize new elements */ + ReentrancyGuard g(*this); + JS_ASSERT(mCapacity - (mBegin + newsize) >= 0); + T *newend = mBegin + newsize; + Impl::initialize(mEnd, newend); + mEnd = newend; + return true; +} + +template +inline void +JSTempVector::clear() +{ + ReentrancyGuard g(*this); + Impl::destroy(mBegin, mEnd); + mEnd = mBegin; +} + +/* + * Check for overflow of an increased size or capacity (generically, 'value'). + * 'diff' is how much greater newval should be compared to oldval. + */ +template +inline bool +JSTempVector::checkOverflow(size_t newval, size_t oldval, size_t diff) const +{ + size_t newbytes = newval * sizeof(T), + oldbytes = oldval * sizeof(T), + diffbytes = diff * sizeof(T); + bool ok = newbytes >= oldbytes && (newbytes - oldbytes) >= diffbytes; + if (!ok) + js_ReportAllocationOverflow(mCx); + return ok; +} + +template +inline bool +JSTempVector::pushBack(const T &t) +{ + ReentrancyGuard g(*this); + if (mEnd == mCapacity) { + /* reallocate, doubling size */ + size_t oldcap = capacity(); + size_t newcap = empty() ? 1 : oldcap * sGrowthFactor; + if (!checkOverflow(newcap, oldcap, 1) || + !Impl::growTo(*this, newcap)) + return false; + } + JS_ASSERT(mEnd != mCapacity); + new(mEnd++) T(t); + return true; +} + +template +template +inline bool +JSTempVector::pushBack(const U *begin, const U *end) +{ + ReentrancyGuard g(*this); + size_t space = mCapacity - mEnd, needed = end - begin; + if (space < needed) { + /* reallocate, doubling size */ + size_t oldcap = capacity(); + size_t newcap = empty() ? needed : (needed + oldcap * sGrowthFactor); + if (!checkOverflow(newcap, oldcap, needed) || + !Impl::growTo(*this, newcap)) + return false; + } + JS_ASSERT((mCapacity - mEnd) >= (end - begin)); + Impl::copyInitialize(mEnd, begin, end); + mEnd += needed; + return true; +} + +template +inline T * +JSTempVector::extractRawBuffer() +{ + T *ret = mBegin; + mBegin = mEnd = mCapacity = 0; + return ret; +} + +template +inline void +JSTempVector::replaceRawBuffer(T *p, size_t length) +{ + ReentrancyGuard g(*this); + Impl::destroy(mBegin, mEnd); + free(mBegin); + mBegin = p; + mCapacity = mEnd = mBegin + length; +} + +#endif /* jsvector_h_ */