1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/js/src/jsstr.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,4943 @@ 1.4 +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- 1.5 + * vim: set ts=8 sts=4 et sw=4 tw=99: 1.6 + * This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 + 1.10 +/* 1.11 + * JS string type implementation. 1.12 + * 1.13 + * In order to avoid unnecessary js_LockGCThing/js_UnlockGCThing calls, these 1.14 + * native methods store strings (possibly newborn) converted from their 'this' 1.15 + * parameter and arguments on the stack: 'this' conversions at argv[-1], arg 1.16 + * conversions at their index (argv[0], argv[1]). This is a legitimate method 1.17 + * of rooting things that might lose their newborn root due to subsequent GC 1.18 + * allocations in the same native method. 1.19 + */ 1.20 + 1.21 +#include "jsstr.h" 1.22 + 1.23 +#include "mozilla/Attributes.h" 1.24 +#include "mozilla/Casting.h" 1.25 +#include "mozilla/CheckedInt.h" 1.26 +#include "mozilla/FloatingPoint.h" 1.27 +#include "mozilla/PodOperations.h" 1.28 + 1.29 +#include <ctype.h> 1.30 +#include <string.h> 1.31 + 1.32 +#include "jsapi.h" 1.33 +#include "jsarray.h" 1.34 +#include "jsatom.h" 1.35 +#include "jsbool.h" 1.36 +#include "jscntxt.h" 1.37 +#include "jsgc.h" 1.38 +#include "jsnum.h" 1.39 +#include "jsobj.h" 1.40 +#include "jsopcode.h" 1.41 +#include "jstypes.h" 1.42 +#include "jsutil.h" 1.43 + 1.44 +#include "builtin/Intl.h" 1.45 +#include "builtin/RegExp.h" 1.46 +#if ENABLE_INTL_API 1.47 +#include "unicode/unorm.h" 1.48 +#endif 1.49 +#include "vm/GlobalObject.h" 1.50 +#include "vm/Interpreter.h" 1.51 +#include "vm/NumericConversions.h" 1.52 +#include "vm/Opcodes.h" 1.53 +#include "vm/RegExpObject.h" 1.54 +#include "vm/RegExpStatics.h" 1.55 +#include "vm/ScopeObject.h" 1.56 +#include "vm/StringBuffer.h" 1.57 + 1.58 +#include "jsinferinlines.h" 1.59 + 1.60 +#include "vm/Interpreter-inl.h" 1.61 +#include "vm/String-inl.h" 1.62 +#include "vm/StringObject-inl.h" 1.63 + 1.64 +using namespace js; 1.65 +using namespace js::gc; 1.66 +using namespace js::types; 1.67 +using namespace js::unicode; 1.68 + 1.69 +using mozilla::CheckedInt; 1.70 +using mozilla::IsNaN; 1.71 +using mozilla::IsNegativeZero; 1.72 +using mozilla::PodCopy; 1.73 +using mozilla::PodEqual; 1.74 +using mozilla::SafeCast; 1.75 + 1.76 +typedef Handle<JSLinearString*> HandleLinearString; 1.77 + 1.78 +static JSLinearString * 1.79 +ArgToRootedString(JSContext *cx, CallArgs &args, unsigned argno) 1.80 +{ 1.81 + if (argno >= args.length()) 1.82 + return cx->names().undefined; 1.83 + 1.84 + JSString *str = ToString<CanGC>(cx, args[argno]); 1.85 + if (!str) 1.86 + return nullptr; 1.87 + 1.88 + args[argno].setString(str); 1.89 + return str->ensureLinear(cx); 1.90 +} 1.91 + 1.92 +/* 1.93 + * Forward declarations for URI encode/decode and helper routines 1.94 + */ 1.95 +static bool 1.96 +str_decodeURI(JSContext *cx, unsigned argc, Value *vp); 1.97 + 1.98 +static bool 1.99 +str_decodeURI_Component(JSContext *cx, unsigned argc, Value *vp); 1.100 + 1.101 +static bool 1.102 +str_encodeURI(JSContext *cx, unsigned argc, Value *vp); 1.103 + 1.104 +static bool 1.105 +str_encodeURI_Component(JSContext *cx, unsigned argc, Value *vp); 1.106 + 1.107 +/* 1.108 + * Global string methods 1.109 + */ 1.110 + 1.111 + 1.112 +/* ES5 B.2.1 */ 1.113 +static bool 1.114 +str_escape(JSContext *cx, unsigned argc, Value *vp) 1.115 +{ 1.116 + CallArgs args = CallArgsFromVp(argc, vp); 1.117 + 1.118 + static const char digits[] = {'0', '1', '2', '3', '4', '5', '6', '7', 1.119 + '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; 1.120 + 1.121 + JSLinearString *str = ArgToRootedString(cx, args, 0); 1.122 + if (!str) 1.123 + return false; 1.124 + 1.125 + size_t length = str->length(); 1.126 + const jschar *chars = str->chars(); 1.127 + 1.128 + static const uint8_t shouldPassThrough[256] = { 1.129 + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 1.130 + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 1.131 + 0,0,0,0,0,0,0,0,0,0,1,1,0,1,1,1, /* !"#$%&'()*+,-./ */ 1.132 + 1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0, /* 0123456789:;<=>? */ 1.133 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, /* @ABCDEFGHIJKLMNO */ 1.134 + 1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,1, /* PQRSTUVWXYZ[\]^_ */ 1.135 + 0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, /* `abcdefghijklmno */ 1.136 + 1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0, /* pqrstuvwxyz{\}~ DEL */ 1.137 + }; 1.138 + 1.139 + /* In step 7, exactly 69 characters should pass through unencoded. */ 1.140 +#ifdef DEBUG 1.141 + size_t count = 0; 1.142 + for (size_t i = 0; i < sizeof(shouldPassThrough); i++) { 1.143 + if (shouldPassThrough[i]) { 1.144 + count++; 1.145 + } 1.146 + } 1.147 + JS_ASSERT(count == 69); 1.148 +#endif 1.149 + 1.150 + 1.151 + /* Take a first pass and see how big the result string will need to be. */ 1.152 + size_t newlength = length; 1.153 + for (size_t i = 0; i < length; i++) { 1.154 + jschar ch = chars[i]; 1.155 + if (ch < 128 && shouldPassThrough[ch]) 1.156 + continue; 1.157 + 1.158 + /* The character will be encoded as %XX or %uXXXX. */ 1.159 + newlength += (ch < 256) ? 2 : 5; 1.160 + 1.161 + /* 1.162 + * This overflow test works because newlength is incremented by at 1.163 + * most 5 on each iteration. 1.164 + */ 1.165 + if (newlength < length) { 1.166 + js_ReportAllocationOverflow(cx); 1.167 + return false; 1.168 + } 1.169 + } 1.170 + 1.171 + if (newlength >= ~(size_t)0 / sizeof(jschar)) { 1.172 + js_ReportAllocationOverflow(cx); 1.173 + return false; 1.174 + } 1.175 + 1.176 + jschar *newchars = cx->pod_malloc<jschar>(newlength + 1); 1.177 + if (!newchars) 1.178 + return false; 1.179 + size_t i, ni; 1.180 + for (i = 0, ni = 0; i < length; i++) { 1.181 + jschar ch = chars[i]; 1.182 + if (ch < 128 && shouldPassThrough[ch]) { 1.183 + newchars[ni++] = ch; 1.184 + } else if (ch < 256) { 1.185 + newchars[ni++] = '%'; 1.186 + newchars[ni++] = digits[ch >> 4]; 1.187 + newchars[ni++] = digits[ch & 0xF]; 1.188 + } else { 1.189 + newchars[ni++] = '%'; 1.190 + newchars[ni++] = 'u'; 1.191 + newchars[ni++] = digits[ch >> 12]; 1.192 + newchars[ni++] = digits[(ch & 0xF00) >> 8]; 1.193 + newchars[ni++] = digits[(ch & 0xF0) >> 4]; 1.194 + newchars[ni++] = digits[ch & 0xF]; 1.195 + } 1.196 + } 1.197 + JS_ASSERT(ni == newlength); 1.198 + newchars[newlength] = 0; 1.199 + 1.200 + JSString *retstr = js_NewString<CanGC>(cx, newchars, newlength); 1.201 + if (!retstr) { 1.202 + js_free(newchars); 1.203 + return false; 1.204 + } 1.205 + 1.206 + args.rval().setString(retstr); 1.207 + return true; 1.208 +} 1.209 + 1.210 +static inline bool 1.211 +Unhex4(const jschar *chars, jschar *result) 1.212 +{ 1.213 + jschar a = chars[0], 1.214 + b = chars[1], 1.215 + c = chars[2], 1.216 + d = chars[3]; 1.217 + 1.218 + if (!(JS7_ISHEX(a) && JS7_ISHEX(b) && JS7_ISHEX(c) && JS7_ISHEX(d))) 1.219 + return false; 1.220 + 1.221 + *result = (((((JS7_UNHEX(a) << 4) + JS7_UNHEX(b)) << 4) + JS7_UNHEX(c)) << 4) + JS7_UNHEX(d); 1.222 + return true; 1.223 +} 1.224 + 1.225 +static inline bool 1.226 +Unhex2(const jschar *chars, jschar *result) 1.227 +{ 1.228 + jschar a = chars[0], 1.229 + b = chars[1]; 1.230 + 1.231 + if (!(JS7_ISHEX(a) && JS7_ISHEX(b))) 1.232 + return false; 1.233 + 1.234 + *result = (JS7_UNHEX(a) << 4) + JS7_UNHEX(b); 1.235 + return true; 1.236 +} 1.237 + 1.238 +/* ES5 B.2.2 */ 1.239 +static bool 1.240 +str_unescape(JSContext *cx, unsigned argc, Value *vp) 1.241 +{ 1.242 + CallArgs args = CallArgsFromVp(argc, vp); 1.243 + 1.244 + /* Step 1. */ 1.245 + JSLinearString *str = ArgToRootedString(cx, args, 0); 1.246 + if (!str) 1.247 + return false; 1.248 + 1.249 + /* 1.250 + * NB: use signed integers for length/index to allow simple length 1.251 + * comparisons without unsigned-underflow hazards. 1.252 + */ 1.253 + JS_STATIC_ASSERT(JSString::MAX_LENGTH <= INT_MAX); 1.254 + 1.255 + /* Step 2. */ 1.256 + int length = str->length(); 1.257 + const jschar *chars = str->chars(); 1.258 + 1.259 + /* Step 3. */ 1.260 + StringBuffer sb(cx); 1.261 + 1.262 + /* 1.263 + * Note that the spec algorithm has been optimized to avoid building 1.264 + * a string in the case where no escapes are present. 1.265 + */ 1.266 + 1.267 + /* Step 4. */ 1.268 + int k = 0; 1.269 + bool building = false; 1.270 + 1.271 + while (true) { 1.272 + /* Step 5. */ 1.273 + if (k == length) { 1.274 + JSLinearString *result; 1.275 + if (building) { 1.276 + result = sb.finishString(); 1.277 + if (!result) 1.278 + return false; 1.279 + } else { 1.280 + result = str; 1.281 + } 1.282 + 1.283 + args.rval().setString(result); 1.284 + return true; 1.285 + } 1.286 + 1.287 + /* Step 6. */ 1.288 + jschar c = chars[k]; 1.289 + 1.290 + /* Step 7. */ 1.291 + if (c != '%') 1.292 + goto step_18; 1.293 + 1.294 + /* Step 8. */ 1.295 + if (k > length - 6) 1.296 + goto step_14; 1.297 + 1.298 + /* Step 9. */ 1.299 + if (chars[k + 1] != 'u') 1.300 + goto step_14; 1.301 + 1.302 +#define ENSURE_BUILDING \ 1.303 + JS_BEGIN_MACRO \ 1.304 + if (!building) { \ 1.305 + building = true; \ 1.306 + if (!sb.reserve(length)) \ 1.307 + return false; \ 1.308 + sb.infallibleAppend(chars, chars + k); \ 1.309 + } \ 1.310 + JS_END_MACRO 1.311 + 1.312 + /* Step 10-13. */ 1.313 + if (Unhex4(&chars[k + 2], &c)) { 1.314 + ENSURE_BUILDING; 1.315 + k += 5; 1.316 + goto step_18; 1.317 + } 1.318 + 1.319 + step_14: 1.320 + /* Step 14. */ 1.321 + if (k > length - 3) 1.322 + goto step_18; 1.323 + 1.324 + /* Step 15-17. */ 1.325 + if (Unhex2(&chars[k + 1], &c)) { 1.326 + ENSURE_BUILDING; 1.327 + k += 2; 1.328 + } 1.329 + 1.330 + step_18: 1.331 + if (building) 1.332 + sb.infallibleAppend(c); 1.333 + 1.334 + /* Step 19. */ 1.335 + k += 1; 1.336 + } 1.337 +#undef ENSURE_BUILDING 1.338 +} 1.339 + 1.340 +#if JS_HAS_UNEVAL 1.341 +static bool 1.342 +str_uneval(JSContext *cx, unsigned argc, Value *vp) 1.343 +{ 1.344 + CallArgs args = CallArgsFromVp(argc, vp); 1.345 + JSString *str = ValueToSource(cx, args.get(0)); 1.346 + if (!str) 1.347 + return false; 1.348 + 1.349 + args.rval().setString(str); 1.350 + return true; 1.351 +} 1.352 +#endif 1.353 + 1.354 +static const JSFunctionSpec string_functions[] = { 1.355 + JS_FN(js_escape_str, str_escape, 1,0), 1.356 + JS_FN(js_unescape_str, str_unescape, 1,0), 1.357 +#if JS_HAS_UNEVAL 1.358 + JS_FN(js_uneval_str, str_uneval, 1,0), 1.359 +#endif 1.360 + JS_FN(js_decodeURI_str, str_decodeURI, 1,0), 1.361 + JS_FN(js_encodeURI_str, str_encodeURI, 1,0), 1.362 + JS_FN(js_decodeURIComponent_str, str_decodeURI_Component, 1,0), 1.363 + JS_FN(js_encodeURIComponent_str, str_encodeURI_Component, 1,0), 1.364 + 1.365 + JS_FS_END 1.366 +}; 1.367 + 1.368 +const jschar js_empty_ucstr[] = {0}; 1.369 +const JSSubString js_EmptySubString = {0, js_empty_ucstr}; 1.370 + 1.371 +static const unsigned STRING_ELEMENT_ATTRS = JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT; 1.372 + 1.373 +static bool 1.374 +str_enumerate(JSContext *cx, HandleObject obj) 1.375 +{ 1.376 + RootedString str(cx, obj->as<StringObject>().unbox()); 1.377 + RootedValue value(cx); 1.378 + for (size_t i = 0, length = str->length(); i < length; i++) { 1.379 + JSString *str1 = js_NewDependentString(cx, str, i, 1); 1.380 + if (!str1) 1.381 + return false; 1.382 + value.setString(str1); 1.383 + if (!JSObject::defineElement(cx, obj, i, value, 1.384 + JS_PropertyStub, JS_StrictPropertyStub, 1.385 + STRING_ELEMENT_ATTRS)) 1.386 + { 1.387 + return false; 1.388 + } 1.389 + } 1.390 + 1.391 + return true; 1.392 +} 1.393 + 1.394 +bool 1.395 +js::str_resolve(JSContext *cx, HandleObject obj, HandleId id, MutableHandleObject objp) 1.396 +{ 1.397 + if (!JSID_IS_INT(id)) 1.398 + return true; 1.399 + 1.400 + RootedString str(cx, obj->as<StringObject>().unbox()); 1.401 + 1.402 + int32_t slot = JSID_TO_INT(id); 1.403 + if ((size_t)slot < str->length()) { 1.404 + JSString *str1 = cx->staticStrings().getUnitStringForElement(cx, str, size_t(slot)); 1.405 + if (!str1) 1.406 + return false; 1.407 + RootedValue value(cx, StringValue(str1)); 1.408 + if (!JSObject::defineElement(cx, obj, uint32_t(slot), value, nullptr, nullptr, 1.409 + STRING_ELEMENT_ATTRS)) 1.410 + { 1.411 + return false; 1.412 + } 1.413 + objp.set(obj); 1.414 + } 1.415 + return true; 1.416 +} 1.417 + 1.418 +const Class StringObject::class_ = { 1.419 + js_String_str, 1.420 + JSCLASS_HAS_RESERVED_SLOTS(StringObject::RESERVED_SLOTS) | 1.421 + JSCLASS_NEW_RESOLVE | JSCLASS_HAS_CACHED_PROTO(JSProto_String), 1.422 + JS_PropertyStub, /* addProperty */ 1.423 + JS_DeletePropertyStub, /* delProperty */ 1.424 + JS_PropertyStub, /* getProperty */ 1.425 + JS_StrictPropertyStub, /* setProperty */ 1.426 + str_enumerate, 1.427 + (JSResolveOp)str_resolve, 1.428 + JS_ConvertStub 1.429 +}; 1.430 + 1.431 +/* 1.432 + * Returns a JSString * for the |this| value associated with 'call', or throws 1.433 + * a TypeError if |this| is null or undefined. This algorithm is the same as 1.434 + * calling CheckObjectCoercible(this), then returning ToString(this), as all 1.435 + * String.prototype.* methods do (other than toString and valueOf). 1.436 + */ 1.437 +static MOZ_ALWAYS_INLINE JSString * 1.438 +ThisToStringForStringProto(JSContext *cx, CallReceiver call) 1.439 +{ 1.440 + JS_CHECK_RECURSION(cx, return nullptr); 1.441 + 1.442 + if (call.thisv().isString()) 1.443 + return call.thisv().toString(); 1.444 + 1.445 + if (call.thisv().isObject()) { 1.446 + RootedObject obj(cx, &call.thisv().toObject()); 1.447 + if (obj->is<StringObject>()) { 1.448 + Rooted<jsid> id(cx, NameToId(cx->names().toString)); 1.449 + if (ClassMethodIsNative(cx, obj, &StringObject::class_, id, js_str_toString)) { 1.450 + JSString *str = obj->as<StringObject>().unbox(); 1.451 + call.setThis(StringValue(str)); 1.452 + return str; 1.453 + } 1.454 + } 1.455 + } else if (call.thisv().isNullOrUndefined()) { 1.456 + JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_CANT_CONVERT_TO, 1.457 + call.thisv().isNull() ? "null" : "undefined", "object"); 1.458 + return nullptr; 1.459 + } 1.460 + 1.461 + JSString *str = ToStringSlow<CanGC>(cx, call.thisv()); 1.462 + if (!str) 1.463 + return nullptr; 1.464 + 1.465 + call.setThis(StringValue(str)); 1.466 + return str; 1.467 +} 1.468 + 1.469 +MOZ_ALWAYS_INLINE bool 1.470 +IsString(HandleValue v) 1.471 +{ 1.472 + return v.isString() || (v.isObject() && v.toObject().is<StringObject>()); 1.473 +} 1.474 + 1.475 +#if JS_HAS_TOSOURCE 1.476 + 1.477 +/* 1.478 + * String.prototype.quote is generic (as are most string methods), unlike 1.479 + * toSource, toString, and valueOf. 1.480 + */ 1.481 +static bool 1.482 +str_quote(JSContext *cx, unsigned argc, Value *vp) 1.483 +{ 1.484 + CallArgs args = CallArgsFromVp(argc, vp); 1.485 + RootedString str(cx, ThisToStringForStringProto(cx, args)); 1.486 + if (!str) 1.487 + return false; 1.488 + str = js_QuoteString(cx, str, '"'); 1.489 + if (!str) 1.490 + return false; 1.491 + args.rval().setString(str); 1.492 + return true; 1.493 +} 1.494 + 1.495 +MOZ_ALWAYS_INLINE bool 1.496 +str_toSource_impl(JSContext *cx, CallArgs args) 1.497 +{ 1.498 + JS_ASSERT(IsString(args.thisv())); 1.499 + 1.500 + Rooted<JSString*> str(cx, ToString<CanGC>(cx, args.thisv())); 1.501 + if (!str) 1.502 + return false; 1.503 + 1.504 + str = js_QuoteString(cx, str, '"'); 1.505 + if (!str) 1.506 + return false; 1.507 + 1.508 + StringBuffer sb(cx); 1.509 + if (!sb.append("(new String(") || !sb.append(str) || !sb.append("))")) 1.510 + return false; 1.511 + 1.512 + str = sb.finishString(); 1.513 + if (!str) 1.514 + return false; 1.515 + args.rval().setString(str); 1.516 + return true; 1.517 +} 1.518 + 1.519 +static bool 1.520 +str_toSource(JSContext *cx, unsigned argc, Value *vp) 1.521 +{ 1.522 + CallArgs args = CallArgsFromVp(argc, vp); 1.523 + return CallNonGenericMethod<IsString, str_toSource_impl>(cx, args); 1.524 +} 1.525 + 1.526 +#endif /* JS_HAS_TOSOURCE */ 1.527 + 1.528 +MOZ_ALWAYS_INLINE bool 1.529 +str_toString_impl(JSContext *cx, CallArgs args) 1.530 +{ 1.531 + JS_ASSERT(IsString(args.thisv())); 1.532 + 1.533 + args.rval().setString(args.thisv().isString() 1.534 + ? args.thisv().toString() 1.535 + : args.thisv().toObject().as<StringObject>().unbox()); 1.536 + return true; 1.537 +} 1.538 + 1.539 +bool 1.540 +js_str_toString(JSContext *cx, unsigned argc, Value *vp) 1.541 +{ 1.542 + CallArgs args = CallArgsFromVp(argc, vp); 1.543 + return CallNonGenericMethod<IsString, str_toString_impl>(cx, args); 1.544 +} 1.545 + 1.546 +/* 1.547 + * Java-like string native methods. 1.548 + */ 1.549 + 1.550 +static MOZ_ALWAYS_INLINE bool 1.551 +ValueToIntegerRange(JSContext *cx, HandleValue v, int32_t *out) 1.552 +{ 1.553 + if (v.isInt32()) { 1.554 + *out = v.toInt32(); 1.555 + } else { 1.556 + double d; 1.557 + if (!ToInteger(cx, v, &d)) 1.558 + return false; 1.559 + if (d > INT32_MAX) 1.560 + *out = INT32_MAX; 1.561 + else if (d < INT32_MIN) 1.562 + *out = INT32_MIN; 1.563 + else 1.564 + *out = int32_t(d); 1.565 + } 1.566 + 1.567 + return true; 1.568 +} 1.569 + 1.570 +static JSString * 1.571 +DoSubstr(JSContext *cx, JSString *str, size_t begin, size_t len) 1.572 +{ 1.573 + /* 1.574 + * Optimization for one level deep ropes. 1.575 + * This is common for the following pattern: 1.576 + * 1.577 + * while() { 1.578 + * text = text.substr(0, x) + "bla" + text.substr(x) 1.579 + * test.charCodeAt(x + 1) 1.580 + * } 1.581 + */ 1.582 + if (str->isRope()) { 1.583 + JSRope *rope = &str->asRope(); 1.584 + 1.585 + /* Substring is totally in leftChild of rope. */ 1.586 + if (begin + len <= rope->leftChild()->length()) { 1.587 + str = rope->leftChild(); 1.588 + return js_NewDependentString(cx, str, begin, len); 1.589 + } 1.590 + 1.591 + /* Substring is totally in rightChild of rope. */ 1.592 + if (begin >= rope->leftChild()->length()) { 1.593 + str = rope->rightChild(); 1.594 + begin -= rope->leftChild()->length(); 1.595 + return js_NewDependentString(cx, str, begin, len); 1.596 + } 1.597 + 1.598 + /* 1.599 + * Requested substring is partly in the left and partly in right child. 1.600 + * Create a rope of substrings for both childs. 1.601 + */ 1.602 + JS_ASSERT (begin < rope->leftChild()->length() && 1.603 + begin + len > rope->leftChild()->length()); 1.604 + 1.605 + size_t lhsLength = rope->leftChild()->length() - begin; 1.606 + size_t rhsLength = begin + len - rope->leftChild()->length(); 1.607 + 1.608 + Rooted<JSRope *> ropeRoot(cx, rope); 1.609 + RootedString lhs(cx, js_NewDependentString(cx, ropeRoot->leftChild(), 1.610 + begin, lhsLength)); 1.611 + if (!lhs) 1.612 + return nullptr; 1.613 + 1.614 + RootedString rhs(cx, js_NewDependentString(cx, ropeRoot->rightChild(), 0, rhsLength)); 1.615 + if (!rhs) 1.616 + return nullptr; 1.617 + 1.618 + return JSRope::new_<CanGC>(cx, lhs, rhs, len); 1.619 + } 1.620 + 1.621 + return js_NewDependentString(cx, str, begin, len); 1.622 +} 1.623 + 1.624 +static bool 1.625 +str_substring(JSContext *cx, unsigned argc, Value *vp) 1.626 +{ 1.627 + CallArgs args = CallArgsFromVp(argc, vp); 1.628 + 1.629 + JSString *str = ThisToStringForStringProto(cx, args); 1.630 + if (!str) 1.631 + return false; 1.632 + 1.633 + int32_t length, begin, end; 1.634 + if (args.length() > 0) { 1.635 + end = length = int32_t(str->length()); 1.636 + 1.637 + if (args[0].isInt32()) { 1.638 + begin = args[0].toInt32(); 1.639 + } else { 1.640 + RootedString strRoot(cx, str); 1.641 + if (!ValueToIntegerRange(cx, args[0], &begin)) 1.642 + return false; 1.643 + str = strRoot; 1.644 + } 1.645 + 1.646 + if (begin < 0) 1.647 + begin = 0; 1.648 + else if (begin > length) 1.649 + begin = length; 1.650 + 1.651 + if (args.hasDefined(1)) { 1.652 + if (args[1].isInt32()) { 1.653 + end = args[1].toInt32(); 1.654 + } else { 1.655 + RootedString strRoot(cx, str); 1.656 + if (!ValueToIntegerRange(cx, args[1], &end)) 1.657 + return false; 1.658 + str = strRoot; 1.659 + } 1.660 + 1.661 + if (end > length) { 1.662 + end = length; 1.663 + } else { 1.664 + if (end < 0) 1.665 + end = 0; 1.666 + if (end < begin) { 1.667 + int32_t tmp = begin; 1.668 + begin = end; 1.669 + end = tmp; 1.670 + } 1.671 + } 1.672 + } 1.673 + 1.674 + str = DoSubstr(cx, str, size_t(begin), size_t(end - begin)); 1.675 + if (!str) 1.676 + return false; 1.677 + } 1.678 + 1.679 + args.rval().setString(str); 1.680 + return true; 1.681 +} 1.682 + 1.683 +JSString* JS_FASTCALL 1.684 +js_toLowerCase(JSContext *cx, JSString *str) 1.685 +{ 1.686 + size_t n = str->length(); 1.687 + const jschar *s = str->getChars(cx); 1.688 + if (!s) 1.689 + return nullptr; 1.690 + 1.691 + jschar *news = cx->pod_malloc<jschar>(n + 1); 1.692 + if (!news) 1.693 + return nullptr; 1.694 + for (size_t i = 0; i < n; i++) 1.695 + news[i] = unicode::ToLowerCase(s[i]); 1.696 + news[n] = 0; 1.697 + str = js_NewString<CanGC>(cx, news, n); 1.698 + if (!str) { 1.699 + js_free(news); 1.700 + return nullptr; 1.701 + } 1.702 + return str; 1.703 +} 1.704 + 1.705 +static inline bool 1.706 +ToLowerCaseHelper(JSContext *cx, CallReceiver call) 1.707 +{ 1.708 + RootedString str(cx, ThisToStringForStringProto(cx, call)); 1.709 + if (!str) 1.710 + return false; 1.711 + 1.712 + str = js_toLowerCase(cx, str); 1.713 + if (!str) 1.714 + return false; 1.715 + 1.716 + call.rval().setString(str); 1.717 + return true; 1.718 +} 1.719 + 1.720 +static bool 1.721 +str_toLowerCase(JSContext *cx, unsigned argc, Value *vp) 1.722 +{ 1.723 + return ToLowerCaseHelper(cx, CallArgsFromVp(argc, vp)); 1.724 +} 1.725 + 1.726 +static bool 1.727 +str_toLocaleLowerCase(JSContext *cx, unsigned argc, Value *vp) 1.728 +{ 1.729 + CallArgs args = CallArgsFromVp(argc, vp); 1.730 + 1.731 + /* 1.732 + * Forcefully ignore the first (or any) argument and return toLowerCase(), 1.733 + * ECMA has reserved that argument, presumably for defining the locale. 1.734 + */ 1.735 + if (cx->runtime()->localeCallbacks && cx->runtime()->localeCallbacks->localeToLowerCase) { 1.736 + RootedString str(cx, ThisToStringForStringProto(cx, args)); 1.737 + if (!str) 1.738 + return false; 1.739 + 1.740 + RootedValue result(cx); 1.741 + if (!cx->runtime()->localeCallbacks->localeToLowerCase(cx, str, &result)) 1.742 + return false; 1.743 + 1.744 + args.rval().set(result); 1.745 + return true; 1.746 + } 1.747 + 1.748 + return ToLowerCaseHelper(cx, args); 1.749 +} 1.750 + 1.751 +JSString* JS_FASTCALL 1.752 +js_toUpperCase(JSContext *cx, JSString *str) 1.753 +{ 1.754 + size_t n = str->length(); 1.755 + const jschar *s = str->getChars(cx); 1.756 + if (!s) 1.757 + return nullptr; 1.758 + jschar *news = cx->pod_malloc<jschar>(n + 1); 1.759 + if (!news) 1.760 + return nullptr; 1.761 + for (size_t i = 0; i < n; i++) 1.762 + news[i] = unicode::ToUpperCase(s[i]); 1.763 + news[n] = 0; 1.764 + str = js_NewString<CanGC>(cx, news, n); 1.765 + if (!str) { 1.766 + js_free(news); 1.767 + return nullptr; 1.768 + } 1.769 + return str; 1.770 +} 1.771 + 1.772 +static bool 1.773 +ToUpperCaseHelper(JSContext *cx, CallReceiver call) 1.774 +{ 1.775 + RootedString str(cx, ThisToStringForStringProto(cx, call)); 1.776 + if (!str) 1.777 + return false; 1.778 + 1.779 + str = js_toUpperCase(cx, str); 1.780 + if (!str) 1.781 + return false; 1.782 + 1.783 + call.rval().setString(str); 1.784 + return true; 1.785 +} 1.786 + 1.787 +static bool 1.788 +str_toUpperCase(JSContext *cx, unsigned argc, Value *vp) 1.789 +{ 1.790 + return ToUpperCaseHelper(cx, CallArgsFromVp(argc, vp)); 1.791 +} 1.792 + 1.793 +static bool 1.794 +str_toLocaleUpperCase(JSContext *cx, unsigned argc, Value *vp) 1.795 +{ 1.796 + CallArgs args = CallArgsFromVp(argc, vp); 1.797 + 1.798 + /* 1.799 + * Forcefully ignore the first (or any) argument and return toUpperCase(), 1.800 + * ECMA has reserved that argument, presumably for defining the locale. 1.801 + */ 1.802 + if (cx->runtime()->localeCallbacks && cx->runtime()->localeCallbacks->localeToUpperCase) { 1.803 + RootedString str(cx, ThisToStringForStringProto(cx, args)); 1.804 + if (!str) 1.805 + return false; 1.806 + 1.807 + RootedValue result(cx); 1.808 + if (!cx->runtime()->localeCallbacks->localeToUpperCase(cx, str, &result)) 1.809 + return false; 1.810 + 1.811 + args.rval().set(result); 1.812 + return true; 1.813 + } 1.814 + 1.815 + return ToUpperCaseHelper(cx, args); 1.816 +} 1.817 + 1.818 +#if !EXPOSE_INTL_API 1.819 +static bool 1.820 +str_localeCompare(JSContext *cx, unsigned argc, Value *vp) 1.821 +{ 1.822 + CallArgs args = CallArgsFromVp(argc, vp); 1.823 + RootedString str(cx, ThisToStringForStringProto(cx, args)); 1.824 + if (!str) 1.825 + return false; 1.826 + 1.827 + RootedString thatStr(cx, ToString<CanGC>(cx, args.get(0))); 1.828 + if (!thatStr) 1.829 + return false; 1.830 + 1.831 + if (cx->runtime()->localeCallbacks && cx->runtime()->localeCallbacks->localeCompare) { 1.832 + RootedValue result(cx); 1.833 + if (!cx->runtime()->localeCallbacks->localeCompare(cx, str, thatStr, &result)) 1.834 + return false; 1.835 + 1.836 + args.rval().set(result); 1.837 + return true; 1.838 + } 1.839 + 1.840 + int32_t result; 1.841 + if (!CompareStrings(cx, str, thatStr, &result)) 1.842 + return false; 1.843 + 1.844 + args.rval().setInt32(result); 1.845 + return true; 1.846 +} 1.847 +#endif 1.848 + 1.849 +#if EXPOSE_INTL_API 1.850 +static const size_t SB_LENGTH = 32; 1.851 + 1.852 +/* ES6 20140210 draft 21.1.3.12. */ 1.853 +static bool 1.854 +str_normalize(JSContext *cx, unsigned argc, Value *vp) 1.855 +{ 1.856 + CallArgs args = CallArgsFromVp(argc, vp); 1.857 + 1.858 + // Steps 1-3. 1.859 + RootedString str(cx, ThisToStringForStringProto(cx, args)); 1.860 + if (!str) 1.861 + return false; 1.862 + 1.863 + // Step 4. 1.864 + UNormalizationMode form; 1.865 + if (!args.hasDefined(0)) { 1.866 + form = UNORM_NFC; 1.867 + } else { 1.868 + // Steps 5-6. 1.869 + Rooted<JSLinearString*> formStr(cx, ArgToRootedString(cx, args, 0)); 1.870 + if (!formStr) 1.871 + return false; 1.872 + 1.873 + // Step 7. 1.874 + if (formStr == cx->names().NFC) { 1.875 + form = UNORM_NFC; 1.876 + } else if (formStr == cx->names().NFD) { 1.877 + form = UNORM_NFD; 1.878 + } else if (formStr == cx->names().NFKC) { 1.879 + form = UNORM_NFKC; 1.880 + } else if (formStr == cx->names().NFKD) { 1.881 + form = UNORM_NFKD; 1.882 + } else { 1.883 + JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, 1.884 + JSMSG_INVALID_NORMALIZE_FORM); 1.885 + return false; 1.886 + } 1.887 + } 1.888 + 1.889 + // Step 8. 1.890 + Rooted<JSFlatString*> flatStr(cx, str->ensureFlat(cx)); 1.891 + if (!flatStr) 1.892 + return false; 1.893 + const UChar *srcChars = JSCharToUChar(flatStr->chars()); 1.894 + int32_t srcLen = SafeCast<int32_t>(flatStr->length()); 1.895 + StringBuffer chars(cx); 1.896 + if (!chars.resize(SB_LENGTH)) 1.897 + return false; 1.898 + UErrorCode status = U_ZERO_ERROR; 1.899 + int32_t size = unorm_normalize(srcChars, srcLen, form, 0, 1.900 + JSCharToUChar(chars.begin()), SB_LENGTH, 1.901 + &status); 1.902 + if (status == U_BUFFER_OVERFLOW_ERROR) { 1.903 + if (!chars.resize(size)) 1.904 + return false; 1.905 + status = U_ZERO_ERROR; 1.906 +#ifdef DEBUG 1.907 + int32_t finalSize = 1.908 +#endif 1.909 + unorm_normalize(srcChars, srcLen, form, 0, 1.910 + JSCharToUChar(chars.begin()), size, 1.911 + &status); 1.912 + MOZ_ASSERT(size == finalSize || U_FAILURE(status), "unorm_normalize behaved inconsistently"); 1.913 + } 1.914 + if (U_FAILURE(status)) 1.915 + return false; 1.916 + // Trim any unused characters. 1.917 + if (!chars.resize(size)) 1.918 + return false; 1.919 + RootedString ns(cx, chars.finishString()); 1.920 + if (!ns) 1.921 + return false; 1.922 + 1.923 + // Step 9. 1.924 + args.rval().setString(ns); 1.925 + return true; 1.926 +} 1.927 +#endif 1.928 + 1.929 +bool 1.930 +js_str_charAt(JSContext *cx, unsigned argc, Value *vp) 1.931 +{ 1.932 + CallArgs args = CallArgsFromVp(argc, vp); 1.933 + 1.934 + RootedString str(cx); 1.935 + size_t i; 1.936 + if (args.thisv().isString() && args.length() != 0 && args[0].isInt32()) { 1.937 + str = args.thisv().toString(); 1.938 + i = size_t(args[0].toInt32()); 1.939 + if (i >= str->length()) 1.940 + goto out_of_range; 1.941 + } else { 1.942 + str = ThisToStringForStringProto(cx, args); 1.943 + if (!str) 1.944 + return false; 1.945 + 1.946 + double d = 0.0; 1.947 + if (args.length() > 0 && !ToInteger(cx, args[0], &d)) 1.948 + return false; 1.949 + 1.950 + if (d < 0 || str->length() <= d) 1.951 + goto out_of_range; 1.952 + i = size_t(d); 1.953 + } 1.954 + 1.955 + str = cx->staticStrings().getUnitStringForElement(cx, str, i); 1.956 + if (!str) 1.957 + return false; 1.958 + args.rval().setString(str); 1.959 + return true; 1.960 + 1.961 + out_of_range: 1.962 + args.rval().setString(cx->runtime()->emptyString); 1.963 + return true; 1.964 +} 1.965 + 1.966 +bool 1.967 +js_str_charCodeAt(JSContext *cx, unsigned argc, Value *vp) 1.968 +{ 1.969 + CallArgs args = CallArgsFromVp(argc, vp); 1.970 + 1.971 + RootedString str(cx); 1.972 + size_t i; 1.973 + if (args.thisv().isString() && args.length() != 0 && args[0].isInt32()) { 1.974 + str = args.thisv().toString(); 1.975 + i = size_t(args[0].toInt32()); 1.976 + if (i >= str->length()) 1.977 + goto out_of_range; 1.978 + } else { 1.979 + str = ThisToStringForStringProto(cx, args); 1.980 + if (!str) 1.981 + return false; 1.982 + 1.983 + double d = 0.0; 1.984 + if (args.length() > 0 && !ToInteger(cx, args[0], &d)) 1.985 + return false; 1.986 + 1.987 + if (d < 0 || str->length() <= d) 1.988 + goto out_of_range; 1.989 + i = size_t(d); 1.990 + } 1.991 + 1.992 + jschar c; 1.993 + if (!str->getChar(cx, i, &c)) 1.994 + return false; 1.995 + args.rval().setInt32(c); 1.996 + return true; 1.997 + 1.998 +out_of_range: 1.999 + args.rval().setNaN(); 1.1000 + return true; 1.1001 +} 1.1002 + 1.1003 +/* 1.1004 + * Boyer-Moore-Horspool superlinear search for pat:patlen in text:textlen. 1.1005 + * The patlen argument must be positive and no greater than sBMHPatLenMax. 1.1006 + * 1.1007 + * Return the index of pat in text, or -1 if not found. 1.1008 + */ 1.1009 +static const uint32_t sBMHCharSetSize = 256; /* ISO-Latin-1 */ 1.1010 +static const uint32_t sBMHPatLenMax = 255; /* skip table element is uint8_t */ 1.1011 +static const int sBMHBadPattern = -2; /* return value if pat is not ISO-Latin-1 */ 1.1012 + 1.1013 +int 1.1014 +js_BoyerMooreHorspool(const jschar *text, uint32_t textlen, 1.1015 + const jschar *pat, uint32_t patlen) 1.1016 +{ 1.1017 + uint8_t skip[sBMHCharSetSize]; 1.1018 + 1.1019 + JS_ASSERT(0 < patlen && patlen <= sBMHPatLenMax); 1.1020 + for (uint32_t i = 0; i < sBMHCharSetSize; i++) 1.1021 + skip[i] = (uint8_t)patlen; 1.1022 + uint32_t m = patlen - 1; 1.1023 + for (uint32_t i = 0; i < m; i++) { 1.1024 + jschar c = pat[i]; 1.1025 + if (c >= sBMHCharSetSize) 1.1026 + return sBMHBadPattern; 1.1027 + skip[c] = (uint8_t)(m - i); 1.1028 + } 1.1029 + jschar c; 1.1030 + for (uint32_t k = m; 1.1031 + k < textlen; 1.1032 + k += ((c = text[k]) >= sBMHCharSetSize) ? patlen : skip[c]) { 1.1033 + for (uint32_t i = k, j = m; ; i--, j--) { 1.1034 + if (text[i] != pat[j]) 1.1035 + break; 1.1036 + if (j == 0) 1.1037 + return static_cast<int>(i); /* safe: max string size */ 1.1038 + } 1.1039 + } 1.1040 + return -1; 1.1041 +} 1.1042 + 1.1043 +struct MemCmp { 1.1044 + typedef uint32_t Extent; 1.1045 + static MOZ_ALWAYS_INLINE Extent computeExtent(const jschar *, uint32_t patlen) { 1.1046 + return (patlen - 1) * sizeof(jschar); 1.1047 + } 1.1048 + static MOZ_ALWAYS_INLINE bool match(const jschar *p, const jschar *t, Extent extent) { 1.1049 + return memcmp(p, t, extent) == 0; 1.1050 + } 1.1051 +}; 1.1052 + 1.1053 +struct ManualCmp { 1.1054 + typedef const jschar *Extent; 1.1055 + static MOZ_ALWAYS_INLINE Extent computeExtent(const jschar *pat, uint32_t patlen) { 1.1056 + return pat + patlen; 1.1057 + } 1.1058 + static MOZ_ALWAYS_INLINE bool match(const jschar *p, const jschar *t, Extent extent) { 1.1059 + for (; p != extent; ++p, ++t) { 1.1060 + if (*p != *t) 1.1061 + return false; 1.1062 + } 1.1063 + return true; 1.1064 + } 1.1065 +}; 1.1066 + 1.1067 +template <class InnerMatch> 1.1068 +static int 1.1069 +UnrolledMatch(const jschar *text, uint32_t textlen, const jschar *pat, uint32_t patlen) 1.1070 +{ 1.1071 + JS_ASSERT(patlen > 0 && textlen > 0); 1.1072 + const jschar *textend = text + textlen - (patlen - 1); 1.1073 + const jschar p0 = *pat; 1.1074 + const jschar *const patNext = pat + 1; 1.1075 + const typename InnerMatch::Extent extent = InnerMatch::computeExtent(pat, patlen); 1.1076 + uint8_t fixup; 1.1077 + 1.1078 + const jschar *t = text; 1.1079 + switch ((textend - t) & 7) { 1.1080 + case 0: if (*t++ == p0) { fixup = 8; goto match; } 1.1081 + case 7: if (*t++ == p0) { fixup = 7; goto match; } 1.1082 + case 6: if (*t++ == p0) { fixup = 6; goto match; } 1.1083 + case 5: if (*t++ == p0) { fixup = 5; goto match; } 1.1084 + case 4: if (*t++ == p0) { fixup = 4; goto match; } 1.1085 + case 3: if (*t++ == p0) { fixup = 3; goto match; } 1.1086 + case 2: if (*t++ == p0) { fixup = 2; goto match; } 1.1087 + case 1: if (*t++ == p0) { fixup = 1; goto match; } 1.1088 + } 1.1089 + while (t != textend) { 1.1090 + if (t[0] == p0) { t += 1; fixup = 8; goto match; } 1.1091 + if (t[1] == p0) { t += 2; fixup = 7; goto match; } 1.1092 + if (t[2] == p0) { t += 3; fixup = 6; goto match; } 1.1093 + if (t[3] == p0) { t += 4; fixup = 5; goto match; } 1.1094 + if (t[4] == p0) { t += 5; fixup = 4; goto match; } 1.1095 + if (t[5] == p0) { t += 6; fixup = 3; goto match; } 1.1096 + if (t[6] == p0) { t += 7; fixup = 2; goto match; } 1.1097 + if (t[7] == p0) { t += 8; fixup = 1; goto match; } 1.1098 + t += 8; 1.1099 + continue; 1.1100 + do { 1.1101 + if (*t++ == p0) { 1.1102 + match: 1.1103 + if (!InnerMatch::match(patNext, t, extent)) 1.1104 + goto failed_match; 1.1105 + return t - text - 1; 1.1106 + } 1.1107 + failed_match:; 1.1108 + } while (--fixup > 0); 1.1109 + } 1.1110 + return -1; 1.1111 +} 1.1112 + 1.1113 +static MOZ_ALWAYS_INLINE int 1.1114 +StringMatch(const jschar *text, uint32_t textlen, 1.1115 + const jschar *pat, uint32_t patlen) 1.1116 +{ 1.1117 + if (patlen == 0) 1.1118 + return 0; 1.1119 + if (textlen < patlen) 1.1120 + return -1; 1.1121 + 1.1122 +#if defined(__i386__) || defined(_M_IX86) || defined(__i386) 1.1123 + /* 1.1124 + * Given enough registers, the unrolled loop below is faster than the 1.1125 + * following loop. 32-bit x86 does not have enough registers. 1.1126 + */ 1.1127 + if (patlen == 1) { 1.1128 + const jschar p0 = *pat; 1.1129 + for (const jschar *c = text, *end = text + textlen; c != end; ++c) { 1.1130 + if (*c == p0) 1.1131 + return c - text; 1.1132 + } 1.1133 + return -1; 1.1134 + } 1.1135 +#endif 1.1136 + 1.1137 + /* 1.1138 + * If the text or pattern string is short, BMH will be more expensive than 1.1139 + * the basic linear scan due to initialization cost and a more complex loop 1.1140 + * body. While the correct threshold is input-dependent, we can make a few 1.1141 + * conservative observations: 1.1142 + * - When |textlen| is "big enough", the initialization time will be 1.1143 + * proportionally small, so the worst-case slowdown is minimized. 1.1144 + * - When |patlen| is "too small", even the best case for BMH will be 1.1145 + * slower than a simple scan for large |textlen| due to the more complex 1.1146 + * loop body of BMH. 1.1147 + * From this, the values for "big enough" and "too small" are determined 1.1148 + * empirically. See bug 526348. 1.1149 + */ 1.1150 + if (textlen >= 512 && patlen >= 11 && patlen <= sBMHPatLenMax) { 1.1151 + int index = js_BoyerMooreHorspool(text, textlen, pat, patlen); 1.1152 + if (index != sBMHBadPattern) 1.1153 + return index; 1.1154 + } 1.1155 + 1.1156 + /* 1.1157 + * For big patterns with large potential overlap we want the SIMD-optimized 1.1158 + * speed of memcmp. For small patterns, a simple loop is faster. 1.1159 + * 1.1160 + * FIXME: Linux memcmp performance is sad and the manual loop is faster. 1.1161 + */ 1.1162 + return 1.1163 +#if !defined(__linux__) 1.1164 + patlen > 128 ? UnrolledMatch<MemCmp>(text, textlen, pat, patlen) 1.1165 + : 1.1166 +#endif 1.1167 + UnrolledMatch<ManualCmp>(text, textlen, pat, patlen); 1.1168 +} 1.1169 + 1.1170 +static const size_t sRopeMatchThresholdRatioLog2 = 5; 1.1171 + 1.1172 +bool 1.1173 +js::StringHasPattern(const jschar *text, uint32_t textlen, 1.1174 + const jschar *pat, uint32_t patlen) 1.1175 +{ 1.1176 + return StringMatch(text, textlen, pat, patlen) != -1; 1.1177 +} 1.1178 + 1.1179 +// When an algorithm does not need a string represented as a single linear 1.1180 +// array of characters, this range utility may be used to traverse the string a 1.1181 +// sequence of linear arrays of characters. This avoids flattening ropes. 1.1182 +class StringSegmentRange 1.1183 +{ 1.1184 + // If malloc() shows up in any profiles from this vector, we can add a new 1.1185 + // StackAllocPolicy which stashes a reusable freed-at-gc buffer in the cx. 1.1186 + AutoStringVector stack; 1.1187 + Rooted<JSLinearString*> cur; 1.1188 + 1.1189 + bool settle(JSString *str) { 1.1190 + while (str->isRope()) { 1.1191 + JSRope &rope = str->asRope(); 1.1192 + if (!stack.append(rope.rightChild())) 1.1193 + return false; 1.1194 + str = rope.leftChild(); 1.1195 + } 1.1196 + cur = &str->asLinear(); 1.1197 + return true; 1.1198 + } 1.1199 + 1.1200 + public: 1.1201 + StringSegmentRange(JSContext *cx) 1.1202 + : stack(cx), cur(cx) 1.1203 + {} 1.1204 + 1.1205 + MOZ_WARN_UNUSED_RESULT bool init(JSString *str) { 1.1206 + JS_ASSERT(stack.empty()); 1.1207 + return settle(str); 1.1208 + } 1.1209 + 1.1210 + bool empty() const { 1.1211 + return cur == nullptr; 1.1212 + } 1.1213 + 1.1214 + JSLinearString *front() const { 1.1215 + JS_ASSERT(!cur->isRope()); 1.1216 + return cur; 1.1217 + } 1.1218 + 1.1219 + MOZ_WARN_UNUSED_RESULT bool popFront() { 1.1220 + JS_ASSERT(!empty()); 1.1221 + if (stack.empty()) { 1.1222 + cur = nullptr; 1.1223 + return true; 1.1224 + } 1.1225 + return settle(stack.popCopy()); 1.1226 + } 1.1227 +}; 1.1228 + 1.1229 +/* 1.1230 + * RopeMatch takes the text to search and the pattern to search for in the text. 1.1231 + * RopeMatch returns false on OOM and otherwise returns the match index through 1.1232 + * the 'match' outparam (-1 for not found). 1.1233 + */ 1.1234 +static bool 1.1235 +RopeMatch(JSContext *cx, JSString *textstr, const jschar *pat, uint32_t patlen, int *match) 1.1236 +{ 1.1237 + JS_ASSERT(textstr->isRope()); 1.1238 + 1.1239 + if (patlen == 0) { 1.1240 + *match = 0; 1.1241 + return true; 1.1242 + } 1.1243 + if (textstr->length() < patlen) { 1.1244 + *match = -1; 1.1245 + return true; 1.1246 + } 1.1247 + 1.1248 + /* 1.1249 + * List of leaf nodes in the rope. If we run out of memory when trying to 1.1250 + * append to this list, we can still fall back to StringMatch, so use the 1.1251 + * system allocator so we don't report OOM in that case. 1.1252 + */ 1.1253 + Vector<JSLinearString *, 16, SystemAllocPolicy> strs; 1.1254 + 1.1255 + /* 1.1256 + * We don't want to do rope matching if there is a poor node-to-char ratio, 1.1257 + * since this means spending a lot of time in the match loop below. We also 1.1258 + * need to build the list of leaf nodes. Do both here: iterate over the 1.1259 + * nodes so long as there are not too many. 1.1260 + */ 1.1261 + { 1.1262 + size_t textstrlen = textstr->length(); 1.1263 + size_t threshold = textstrlen >> sRopeMatchThresholdRatioLog2; 1.1264 + StringSegmentRange r(cx); 1.1265 + if (!r.init(textstr)) 1.1266 + return false; 1.1267 + while (!r.empty()) { 1.1268 + if (threshold-- == 0 || !strs.append(r.front())) { 1.1269 + const jschar *chars = textstr->getChars(cx); 1.1270 + if (!chars) 1.1271 + return false; 1.1272 + *match = StringMatch(chars, textstrlen, pat, patlen); 1.1273 + return true; 1.1274 + } 1.1275 + if (!r.popFront()) 1.1276 + return false; 1.1277 + } 1.1278 + } 1.1279 + 1.1280 + /* Absolute offset from the beginning of the logical string textstr. */ 1.1281 + int pos = 0; 1.1282 + 1.1283 + for (JSLinearString **outerp = strs.begin(); outerp != strs.end(); ++outerp) { 1.1284 + /* Try to find a match within 'outer'. */ 1.1285 + JSLinearString *outer = *outerp; 1.1286 + const jschar *chars = outer->chars(); 1.1287 + size_t len = outer->length(); 1.1288 + int matchResult = StringMatch(chars, len, pat, patlen); 1.1289 + if (matchResult != -1) { 1.1290 + /* Matched! */ 1.1291 + *match = pos + matchResult; 1.1292 + return true; 1.1293 + } 1.1294 + 1.1295 + /* Try to find a match starting in 'outer' and running into other nodes. */ 1.1296 + const jschar *const text = chars + (patlen > len ? 0 : len - patlen + 1); 1.1297 + const jschar *const textend = chars + len; 1.1298 + const jschar p0 = *pat; 1.1299 + const jschar *const p1 = pat + 1; 1.1300 + const jschar *const patend = pat + patlen; 1.1301 + for (const jschar *t = text; t != textend; ) { 1.1302 + if (*t++ != p0) 1.1303 + continue; 1.1304 + JSLinearString **innerp = outerp; 1.1305 + const jschar *ttend = textend; 1.1306 + for (const jschar *pp = p1, *tt = t; pp != patend; ++pp, ++tt) { 1.1307 + while (tt == ttend) { 1.1308 + if (++innerp == strs.end()) { 1.1309 + *match = -1; 1.1310 + return true; 1.1311 + } 1.1312 + JSLinearString *inner = *innerp; 1.1313 + tt = inner->chars(); 1.1314 + ttend = tt + inner->length(); 1.1315 + } 1.1316 + if (*pp != *tt) 1.1317 + goto break_continue; 1.1318 + } 1.1319 + 1.1320 + /* Matched! */ 1.1321 + *match = pos + (t - chars) - 1; /* -1 because of *t++ above */ 1.1322 + return true; 1.1323 + 1.1324 + break_continue:; 1.1325 + } 1.1326 + 1.1327 + pos += len; 1.1328 + } 1.1329 + 1.1330 + *match = -1; 1.1331 + return true; 1.1332 +} 1.1333 + 1.1334 +/* ES6 20121026 draft 15.5.4.24. */ 1.1335 +static bool 1.1336 +str_contains(JSContext *cx, unsigned argc, Value *vp) 1.1337 +{ 1.1338 + CallArgs args = CallArgsFromVp(argc, vp); 1.1339 + 1.1340 + // Steps 1, 2, and 3 1.1341 + RootedString str(cx, ThisToStringForStringProto(cx, args)); 1.1342 + if (!str) 1.1343 + return false; 1.1344 + 1.1345 + // Steps 4 and 5 1.1346 + Rooted<JSLinearString*> searchStr(cx, ArgToRootedString(cx, args, 0)); 1.1347 + if (!searchStr) 1.1348 + return false; 1.1349 + 1.1350 + // Steps 6 and 7 1.1351 + uint32_t pos = 0; 1.1352 + if (args.hasDefined(1)) { 1.1353 + if (args[1].isInt32()) { 1.1354 + int i = args[1].toInt32(); 1.1355 + pos = (i < 0) ? 0U : uint32_t(i); 1.1356 + } else { 1.1357 + double d; 1.1358 + if (!ToInteger(cx, args[1], &d)) 1.1359 + return false; 1.1360 + pos = uint32_t(Min(Max(d, 0.0), double(UINT32_MAX))); 1.1361 + } 1.1362 + } 1.1363 + 1.1364 + // Step 8 1.1365 + uint32_t textLen = str->length(); 1.1366 + const jschar *textChars = str->getChars(cx); 1.1367 + if (!textChars) 1.1368 + return false; 1.1369 + 1.1370 + // Step 9 1.1371 + uint32_t start = Min(Max(pos, 0U), textLen); 1.1372 + 1.1373 + // Step 10 1.1374 + uint32_t searchLen = searchStr->length(); 1.1375 + const jschar *searchChars = searchStr->chars(); 1.1376 + 1.1377 + // Step 11 1.1378 + textChars += start; 1.1379 + textLen -= start; 1.1380 + int match = StringMatch(textChars, textLen, searchChars, searchLen); 1.1381 + args.rval().setBoolean(match != -1); 1.1382 + return true; 1.1383 +} 1.1384 + 1.1385 +/* ES6 20120927 draft 15.5.4.7. */ 1.1386 +static bool 1.1387 +str_indexOf(JSContext *cx, unsigned argc, Value *vp) 1.1388 +{ 1.1389 + CallArgs args = CallArgsFromVp(argc, vp); 1.1390 + 1.1391 + // Steps 1, 2, and 3 1.1392 + RootedString str(cx, ThisToStringForStringProto(cx, args)); 1.1393 + if (!str) 1.1394 + return false; 1.1395 + 1.1396 + // Steps 4 and 5 1.1397 + Rooted<JSLinearString*> searchStr(cx, ArgToRootedString(cx, args, 0)); 1.1398 + if (!searchStr) 1.1399 + return false; 1.1400 + 1.1401 + // Steps 6 and 7 1.1402 + uint32_t pos = 0; 1.1403 + if (args.hasDefined(1)) { 1.1404 + if (args[1].isInt32()) { 1.1405 + int i = args[1].toInt32(); 1.1406 + pos = (i < 0) ? 0U : uint32_t(i); 1.1407 + } else { 1.1408 + double d; 1.1409 + if (!ToInteger(cx, args[1], &d)) 1.1410 + return false; 1.1411 + pos = uint32_t(Min(Max(d, 0.0), double(UINT32_MAX))); 1.1412 + } 1.1413 + } 1.1414 + 1.1415 + // Step 8 1.1416 + uint32_t textLen = str->length(); 1.1417 + const jschar *textChars = str->getChars(cx); 1.1418 + if (!textChars) 1.1419 + return false; 1.1420 + 1.1421 + // Step 9 1.1422 + uint32_t start = Min(Max(pos, 0U), textLen); 1.1423 + 1.1424 + // Step 10 1.1425 + uint32_t searchLen = searchStr->length(); 1.1426 + const jschar *searchChars = searchStr->chars(); 1.1427 + 1.1428 + // Step 11 1.1429 + textChars += start; 1.1430 + textLen -= start; 1.1431 + int match = StringMatch(textChars, textLen, searchChars, searchLen); 1.1432 + args.rval().setInt32((match == -1) ? -1 : start + match); 1.1433 + return true; 1.1434 +} 1.1435 + 1.1436 +static bool 1.1437 +str_lastIndexOf(JSContext *cx, unsigned argc, Value *vp) 1.1438 +{ 1.1439 + CallArgs args = CallArgsFromVp(argc, vp); 1.1440 + RootedString textstr(cx, ThisToStringForStringProto(cx, args)); 1.1441 + if (!textstr) 1.1442 + return false; 1.1443 + 1.1444 + size_t textlen = textstr->length(); 1.1445 + 1.1446 + Rooted<JSLinearString*> patstr(cx, ArgToRootedString(cx, args, 0)); 1.1447 + if (!patstr) 1.1448 + return false; 1.1449 + 1.1450 + size_t patlen = patstr->length(); 1.1451 + 1.1452 + int i = textlen - patlen; // Start searching here 1.1453 + if (i < 0) { 1.1454 + args.rval().setInt32(-1); 1.1455 + return true; 1.1456 + } 1.1457 + 1.1458 + if (args.length() > 1) { 1.1459 + if (args[1].isInt32()) { 1.1460 + int j = args[1].toInt32(); 1.1461 + if (j <= 0) 1.1462 + i = 0; 1.1463 + else if (j < i) 1.1464 + i = j; 1.1465 + } else { 1.1466 + double d; 1.1467 + if (!ToNumber(cx, args[1], &d)) 1.1468 + return false; 1.1469 + if (!IsNaN(d)) { 1.1470 + d = ToInteger(d); 1.1471 + if (d <= 0) 1.1472 + i = 0; 1.1473 + else if (d < i) 1.1474 + i = (int)d; 1.1475 + } 1.1476 + } 1.1477 + } 1.1478 + 1.1479 + if (patlen == 0) { 1.1480 + args.rval().setInt32(i); 1.1481 + return true; 1.1482 + } 1.1483 + 1.1484 + const jschar *text = textstr->getChars(cx); 1.1485 + if (!text) 1.1486 + return false; 1.1487 + 1.1488 + const jschar *pat = patstr->chars(); 1.1489 + 1.1490 + const jschar *t = text + i; 1.1491 + const jschar *textend = text - 1; 1.1492 + const jschar p0 = *pat; 1.1493 + const jschar *patNext = pat + 1; 1.1494 + const jschar *patEnd = pat + patlen; 1.1495 + 1.1496 + for (; t != textend; --t) { 1.1497 + if (*t == p0) { 1.1498 + const jschar *t1 = t + 1; 1.1499 + for (const jschar *p1 = patNext; p1 != patEnd; ++p1, ++t1) { 1.1500 + if (*t1 != *p1) 1.1501 + goto break_continue; 1.1502 + } 1.1503 + args.rval().setInt32(t - text); 1.1504 + return true; 1.1505 + } 1.1506 + break_continue:; 1.1507 + } 1.1508 + 1.1509 + args.rval().setInt32(-1); 1.1510 + return true; 1.1511 +} 1.1512 + 1.1513 +/* ES6 20131108 draft 21.1.3.18. */ 1.1514 +static bool 1.1515 +str_startsWith(JSContext *cx, unsigned argc, Value *vp) 1.1516 +{ 1.1517 + CallArgs args = CallArgsFromVp(argc, vp); 1.1518 + 1.1519 + // Steps 1, 2, and 3 1.1520 + RootedString str(cx, ThisToStringForStringProto(cx, args)); 1.1521 + if (!str) 1.1522 + return false; 1.1523 + 1.1524 + // Step 4 1.1525 + if (args.get(0).isObject() && IsObjectWithClass(args[0], ESClass_RegExp, cx)) { 1.1526 + JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INVALID_ARG_TYPE, 1.1527 + "first", "", "Regular Expression"); 1.1528 + return false; 1.1529 + } 1.1530 + 1.1531 + // Steps 5 and 6 1.1532 + Rooted<JSLinearString*> searchStr(cx, ArgToRootedString(cx, args, 0)); 1.1533 + if (!searchStr) 1.1534 + return false; 1.1535 + 1.1536 + // Steps 7 and 8 1.1537 + uint32_t pos = 0; 1.1538 + if (args.hasDefined(1)) { 1.1539 + if (args[1].isInt32()) { 1.1540 + int i = args[1].toInt32(); 1.1541 + pos = (i < 0) ? 0U : uint32_t(i); 1.1542 + } else { 1.1543 + double d; 1.1544 + if (!ToInteger(cx, args[1], &d)) 1.1545 + return false; 1.1546 + pos = uint32_t(Min(Max(d, 0.0), double(UINT32_MAX))); 1.1547 + } 1.1548 + } 1.1549 + 1.1550 + // Step 9 1.1551 + uint32_t textLen = str->length(); 1.1552 + const jschar *textChars = str->getChars(cx); 1.1553 + if (!textChars) 1.1554 + return false; 1.1555 + 1.1556 + // Step 10 1.1557 + uint32_t start = Min(Max(pos, 0U), textLen); 1.1558 + 1.1559 + // Step 11 1.1560 + uint32_t searchLen = searchStr->length(); 1.1561 + const jschar *searchChars = searchStr->chars(); 1.1562 + 1.1563 + // Step 12 1.1564 + if (searchLen + start < searchLen || searchLen + start > textLen) { 1.1565 + args.rval().setBoolean(false); 1.1566 + return true; 1.1567 + } 1.1568 + 1.1569 + // Steps 13 and 14 1.1570 + args.rval().setBoolean(PodEqual(textChars + start, searchChars, searchLen)); 1.1571 + return true; 1.1572 +} 1.1573 + 1.1574 +/* ES6 20131108 draft 21.1.3.7. */ 1.1575 +static bool 1.1576 +str_endsWith(JSContext *cx, unsigned argc, Value *vp) 1.1577 +{ 1.1578 + CallArgs args = CallArgsFromVp(argc, vp); 1.1579 + 1.1580 + // Steps 1, 2, and 3 1.1581 + RootedString str(cx, ThisToStringForStringProto(cx, args)); 1.1582 + if (!str) 1.1583 + return false; 1.1584 + 1.1585 + // Step 4 1.1586 + if (args.get(0).isObject() && IsObjectWithClass(args[0], ESClass_RegExp, cx)) { 1.1587 + JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INVALID_ARG_TYPE, 1.1588 + "first", "", "Regular Expression"); 1.1589 + return false; 1.1590 + } 1.1591 + 1.1592 + // Steps 5 and 6 1.1593 + Rooted<JSLinearString *> searchStr(cx, ArgToRootedString(cx, args, 0)); 1.1594 + if (!searchStr) 1.1595 + return false; 1.1596 + 1.1597 + // Step 7 1.1598 + uint32_t textLen = str->length(); 1.1599 + const jschar *textChars = str->getChars(cx); 1.1600 + if (!textChars) 1.1601 + return false; 1.1602 + 1.1603 + // Steps 8 and 9 1.1604 + uint32_t pos = textLen; 1.1605 + if (args.hasDefined(1)) { 1.1606 + if (args[1].isInt32()) { 1.1607 + int i = args[1].toInt32(); 1.1608 + pos = (i < 0) ? 0U : uint32_t(i); 1.1609 + } else { 1.1610 + double d; 1.1611 + if (!ToInteger(cx, args[1], &d)) 1.1612 + return false; 1.1613 + pos = uint32_t(Min(Max(d, 0.0), double(UINT32_MAX))); 1.1614 + } 1.1615 + } 1.1616 + 1.1617 + // Step 10 1.1618 + uint32_t end = Min(Max(pos, 0U), textLen); 1.1619 + 1.1620 + // Step 11 1.1621 + uint32_t searchLen = searchStr->length(); 1.1622 + const jschar *searchChars = searchStr->chars(); 1.1623 + 1.1624 + // Step 13 (reordered) 1.1625 + if (searchLen > end) { 1.1626 + args.rval().setBoolean(false); 1.1627 + return true; 1.1628 + } 1.1629 + 1.1630 + // Step 12 1.1631 + uint32_t start = end - searchLen; 1.1632 + 1.1633 + // Steps 14 and 15 1.1634 + args.rval().setBoolean(PodEqual(textChars + start, searchChars, searchLen)); 1.1635 + return true; 1.1636 +} 1.1637 + 1.1638 +static bool 1.1639 +js_TrimString(JSContext *cx, Value *vp, bool trimLeft, bool trimRight) 1.1640 +{ 1.1641 + CallReceiver call = CallReceiverFromVp(vp); 1.1642 + RootedString str(cx, ThisToStringForStringProto(cx, call)); 1.1643 + if (!str) 1.1644 + return false; 1.1645 + size_t length = str->length(); 1.1646 + const jschar *chars = str->getChars(cx); 1.1647 + if (!chars) 1.1648 + return false; 1.1649 + 1.1650 + size_t begin = 0; 1.1651 + size_t end = length; 1.1652 + 1.1653 + if (trimLeft) { 1.1654 + while (begin < length && unicode::IsSpace(chars[begin])) 1.1655 + ++begin; 1.1656 + } 1.1657 + 1.1658 + if (trimRight) { 1.1659 + while (end > begin && unicode::IsSpace(chars[end - 1])) 1.1660 + --end; 1.1661 + } 1.1662 + 1.1663 + str = js_NewDependentString(cx, str, begin, end - begin); 1.1664 + if (!str) 1.1665 + return false; 1.1666 + 1.1667 + call.rval().setString(str); 1.1668 + return true; 1.1669 +} 1.1670 + 1.1671 +static bool 1.1672 +str_trim(JSContext *cx, unsigned argc, Value *vp) 1.1673 +{ 1.1674 + return js_TrimString(cx, vp, true, true); 1.1675 +} 1.1676 + 1.1677 +static bool 1.1678 +str_trimLeft(JSContext *cx, unsigned argc, Value *vp) 1.1679 +{ 1.1680 + return js_TrimString(cx, vp, true, false); 1.1681 +} 1.1682 + 1.1683 +static bool 1.1684 +str_trimRight(JSContext *cx, unsigned argc, Value *vp) 1.1685 +{ 1.1686 + return js_TrimString(cx, vp, false, true); 1.1687 +} 1.1688 + 1.1689 +/* 1.1690 + * Perl-inspired string functions. 1.1691 + */ 1.1692 + 1.1693 +namespace { 1.1694 + 1.1695 +/* Result of a successfully performed flat match. */ 1.1696 +class FlatMatch 1.1697 +{ 1.1698 + RootedAtom patstr; 1.1699 + const jschar *pat; 1.1700 + size_t patlen; 1.1701 + int32_t match_; 1.1702 + 1.1703 + friend class StringRegExpGuard; 1.1704 + 1.1705 + public: 1.1706 + FlatMatch(JSContext *cx) : patstr(cx) {} 1.1707 + JSLinearString *pattern() const { return patstr; } 1.1708 + size_t patternLength() const { return patlen; } 1.1709 + 1.1710 + /* 1.1711 + * Note: The match is -1 when the match is performed successfully, 1.1712 + * but no match is found. 1.1713 + */ 1.1714 + int32_t match() const { return match_; } 1.1715 +}; 1.1716 + 1.1717 +} /* anonymous namespace */ 1.1718 + 1.1719 +static inline bool 1.1720 +IsRegExpMetaChar(jschar c) 1.1721 +{ 1.1722 + switch (c) { 1.1723 + /* Taken from the PatternCharacter production in 15.10.1. */ 1.1724 + case '^': case '$': case '\\': case '.': case '*': case '+': 1.1725 + case '?': case '(': case ')': case '[': case ']': case '{': 1.1726 + case '}': case '|': 1.1727 + return true; 1.1728 + default: 1.1729 + return false; 1.1730 + } 1.1731 +} 1.1732 + 1.1733 +static inline bool 1.1734 +HasRegExpMetaChars(const jschar *chars, size_t length) 1.1735 +{ 1.1736 + for (size_t i = 0; i < length; ++i) { 1.1737 + if (IsRegExpMetaChar(chars[i])) 1.1738 + return true; 1.1739 + } 1.1740 + return false; 1.1741 +} 1.1742 + 1.1743 +namespace { 1.1744 + 1.1745 +/* 1.1746 + * StringRegExpGuard factors logic out of String regexp operations. 1.1747 + * 1.1748 + * |optarg| indicates in which argument position RegExp flags will be found, if 1.1749 + * present. This is a Mozilla extension and not part of any ECMA spec. 1.1750 + */ 1.1751 +class MOZ_STACK_CLASS StringRegExpGuard 1.1752 +{ 1.1753 + RegExpGuard re_; 1.1754 + FlatMatch fm; 1.1755 + RootedObject obj_; 1.1756 + 1.1757 + /* 1.1758 + * Upper bound on the number of characters we are willing to potentially 1.1759 + * waste on searching for RegExp meta-characters. 1.1760 + */ 1.1761 + static const size_t MAX_FLAT_PAT_LEN = 256; 1.1762 + 1.1763 + static JSAtom * 1.1764 + flattenPattern(JSContext *cx, JSAtom *patstr) 1.1765 + { 1.1766 + StringBuffer sb(cx); 1.1767 + if (!sb.reserve(patstr->length())) 1.1768 + return nullptr; 1.1769 + 1.1770 + static const jschar ESCAPE_CHAR = '\\'; 1.1771 + const jschar *chars = patstr->chars(); 1.1772 + size_t len = patstr->length(); 1.1773 + for (const jschar *it = chars; it != chars + len; ++it) { 1.1774 + if (IsRegExpMetaChar(*it)) { 1.1775 + if (!sb.append(ESCAPE_CHAR) || !sb.append(*it)) 1.1776 + return nullptr; 1.1777 + } else { 1.1778 + if (!sb.append(*it)) 1.1779 + return nullptr; 1.1780 + } 1.1781 + } 1.1782 + return sb.finishAtom(); 1.1783 + } 1.1784 + 1.1785 + public: 1.1786 + StringRegExpGuard(JSContext *cx) 1.1787 + : re_(cx), fm(cx), obj_(cx) 1.1788 + { } 1.1789 + 1.1790 + /* init must succeed in order to call tryFlatMatch or normalizeRegExp. */ 1.1791 + bool init(JSContext *cx, CallArgs args, bool convertVoid = false) 1.1792 + { 1.1793 + if (args.length() != 0 && IsObjectWithClass(args[0], ESClass_RegExp, cx)) 1.1794 + return init(cx, &args[0].toObject()); 1.1795 + 1.1796 + if (convertVoid && !args.hasDefined(0)) { 1.1797 + fm.patstr = cx->runtime()->emptyString; 1.1798 + return true; 1.1799 + } 1.1800 + 1.1801 + JSString *arg = ArgToRootedString(cx, args, 0); 1.1802 + if (!arg) 1.1803 + return false; 1.1804 + 1.1805 + fm.patstr = AtomizeString(cx, arg); 1.1806 + if (!fm.patstr) 1.1807 + return false; 1.1808 + 1.1809 + return true; 1.1810 + } 1.1811 + 1.1812 + bool init(JSContext *cx, JSObject *regexp) { 1.1813 + obj_ = regexp; 1.1814 + 1.1815 + JS_ASSERT(ObjectClassIs(obj_, ESClass_RegExp, cx)); 1.1816 + 1.1817 + if (!RegExpToShared(cx, obj_, &re_)) 1.1818 + return false; 1.1819 + return true; 1.1820 + } 1.1821 + 1.1822 + bool init(JSContext *cx, HandleString pattern) { 1.1823 + fm.patstr = AtomizeString(cx, pattern); 1.1824 + if (!fm.patstr) 1.1825 + return false; 1.1826 + return true; 1.1827 + } 1.1828 + 1.1829 + /* 1.1830 + * Attempt to match |patstr| to |textstr|. A flags argument, metachars in 1.1831 + * the pattern string, or a lengthy pattern string can thwart this process. 1.1832 + * 1.1833 + * |checkMetaChars| looks for regexp metachars in the pattern string. 1.1834 + * 1.1835 + * Return whether flat matching could be used. 1.1836 + * 1.1837 + * N.B. tryFlatMatch returns nullptr on OOM, so the caller must check 1.1838 + * cx->isExceptionPending(). 1.1839 + */ 1.1840 + const FlatMatch * 1.1841 + tryFlatMatch(JSContext *cx, JSString *textstr, unsigned optarg, unsigned argc, 1.1842 + bool checkMetaChars = true) 1.1843 + { 1.1844 + if (re_.initialized()) 1.1845 + return nullptr; 1.1846 + 1.1847 + fm.pat = fm.patstr->chars(); 1.1848 + fm.patlen = fm.patstr->length(); 1.1849 + 1.1850 + if (optarg < argc) 1.1851 + return nullptr; 1.1852 + 1.1853 + if (checkMetaChars && 1.1854 + (fm.patlen > MAX_FLAT_PAT_LEN || HasRegExpMetaChars(fm.pat, fm.patlen))) { 1.1855 + return nullptr; 1.1856 + } 1.1857 + 1.1858 + /* 1.1859 + * textstr could be a rope, so we want to avoid flattening it for as 1.1860 + * long as possible. 1.1861 + */ 1.1862 + if (textstr->isRope()) { 1.1863 + if (!RopeMatch(cx, textstr, fm.pat, fm.patlen, &fm.match_)) 1.1864 + return nullptr; 1.1865 + } else { 1.1866 + const jschar *text = textstr->asLinear().chars(); 1.1867 + size_t textlen = textstr->length(); 1.1868 + fm.match_ = StringMatch(text, textlen, fm.pat, fm.patlen); 1.1869 + } 1.1870 + return &fm; 1.1871 + } 1.1872 + 1.1873 + /* If the pattern is not already a regular expression, make it so. */ 1.1874 + bool normalizeRegExp(JSContext *cx, bool flat, unsigned optarg, CallArgs args) 1.1875 + { 1.1876 + if (re_.initialized()) 1.1877 + return true; 1.1878 + 1.1879 + /* Build RegExp from pattern string. */ 1.1880 + RootedString opt(cx); 1.1881 + if (optarg < args.length()) { 1.1882 + opt = ToString<CanGC>(cx, args[optarg]); 1.1883 + if (!opt) 1.1884 + return false; 1.1885 + } else { 1.1886 + opt = nullptr; 1.1887 + } 1.1888 + 1.1889 + Rooted<JSAtom *> patstr(cx); 1.1890 + if (flat) { 1.1891 + patstr = flattenPattern(cx, fm.patstr); 1.1892 + if (!patstr) 1.1893 + return false; 1.1894 + } else { 1.1895 + patstr = fm.patstr; 1.1896 + } 1.1897 + JS_ASSERT(patstr); 1.1898 + 1.1899 + return cx->compartment()->regExps.get(cx, patstr, opt, &re_); 1.1900 + } 1.1901 + 1.1902 + bool zeroLastIndex(JSContext *cx) { 1.1903 + if (!regExpIsObject()) 1.1904 + return true; 1.1905 + 1.1906 + // Use a fast path for same-global RegExp objects with writable 1.1907 + // lastIndex. 1.1908 + if (obj_->is<RegExpObject>() && obj_->nativeLookup(cx, cx->names().lastIndex)->writable()) { 1.1909 + obj_->as<RegExpObject>().zeroLastIndex(); 1.1910 + return true; 1.1911 + } 1.1912 + 1.1913 + // Handle everything else generically (including throwing if .lastIndex is non-writable). 1.1914 + RootedValue zero(cx, Int32Value(0)); 1.1915 + return JSObject::setProperty(cx, obj_, obj_, cx->names().lastIndex, &zero, true); 1.1916 + } 1.1917 + 1.1918 + RegExpShared ®Exp() { return *re_; } 1.1919 + 1.1920 + bool regExpIsObject() { return obj_ != nullptr; } 1.1921 + HandleObject regExpObject() { 1.1922 + JS_ASSERT(regExpIsObject()); 1.1923 + return obj_; 1.1924 + } 1.1925 + 1.1926 + private: 1.1927 + StringRegExpGuard(const StringRegExpGuard &) MOZ_DELETE; 1.1928 + void operator=(const StringRegExpGuard &) MOZ_DELETE; 1.1929 +}; 1.1930 + 1.1931 +} /* anonymous namespace */ 1.1932 + 1.1933 +static bool 1.1934 +DoMatchLocal(JSContext *cx, CallArgs args, RegExpStatics *res, Handle<JSLinearString*> input, 1.1935 + RegExpShared &re) 1.1936 +{ 1.1937 + size_t charsLen = input->length(); 1.1938 + const jschar *chars = input->chars(); 1.1939 + 1.1940 + size_t i = 0; 1.1941 + ScopedMatchPairs matches(&cx->tempLifoAlloc()); 1.1942 + RegExpRunStatus status = re.execute(cx, chars, charsLen, &i, matches); 1.1943 + if (status == RegExpRunStatus_Error) 1.1944 + return false; 1.1945 + 1.1946 + if (status == RegExpRunStatus_Success_NotFound) { 1.1947 + args.rval().setNull(); 1.1948 + return true; 1.1949 + } 1.1950 + 1.1951 + if (!res->updateFromMatchPairs(cx, input, matches)) 1.1952 + return false; 1.1953 + 1.1954 + RootedValue rval(cx); 1.1955 + if (!CreateRegExpMatchResult(cx, input, matches, &rval)) 1.1956 + return false; 1.1957 + 1.1958 + args.rval().set(rval); 1.1959 + return true; 1.1960 +} 1.1961 + 1.1962 +/* ES5 15.5.4.10 step 8. */ 1.1963 +static bool 1.1964 +DoMatchGlobal(JSContext *cx, CallArgs args, RegExpStatics *res, Handle<JSLinearString*> input, 1.1965 + StringRegExpGuard &g) 1.1966 +{ 1.1967 + // Step 8a. 1.1968 + // 1.1969 + // This single zeroing of "lastIndex" covers all "lastIndex" changes in the 1.1970 + // rest of String.prototype.match, particularly in steps 8f(i) and 1.1971 + // 8f(iii)(2)(a). Here's why. 1.1972 + // 1.1973 + // The inputs to the calls to RegExp.prototype.exec are a RegExp object 1.1974 + // whose .global is true and a string. The only side effect of a call in 1.1975 + // these circumstances is that the RegExp's .lastIndex will be modified to 1.1976 + // the next starting index after the discovered match (or to 0 if there's 1.1977 + // no remaining match). Because .lastIndex is a non-configurable data 1.1978 + // property and no script-controllable code executes after step 8a, passing 1.1979 + // step 8a implies *every* .lastIndex set succeeds. String.prototype.match 1.1980 + // calls RegExp.prototype.exec repeatedly, and the last call doesn't match, 1.1981 + // so the final value of .lastIndex is 0: exactly the state after step 8a 1.1982 + // succeeds. No spec step lets script observe intermediate .lastIndex 1.1983 + // values. 1.1984 + // 1.1985 + // The arrays returned by RegExp.prototype.exec always have a string at 1.1986 + // index 0, for which [[Get]]s have no side effects. 1.1987 + // 1.1988 + // Filling in a new array using [[DefineOwnProperty]] is unobservable. 1.1989 + // 1.1990 + // This is a tricky point, because after this set, our implementation *can* 1.1991 + // fail. The key is that script can't distinguish these failure modes from 1.1992 + // one where, in spec terms, we fail immediately after step 8a. That *in 1.1993 + // reality* we might have done extra matching work, or created a partial 1.1994 + // results array to return, or hit an interrupt, is irrelevant. The 1.1995 + // script can't tell we did any of those things but didn't update 1.1996 + // .lastIndex. Thus we can optimize steps 8b onward however we want, 1.1997 + // including eliminating intermediate .lastIndex sets, as long as we don't 1.1998 + // add ways for script to observe the intermediate states. 1.1999 + // 1.2000 + // In short: it's okay to cheat (by setting .lastIndex to 0, once) because 1.2001 + // we can't get caught. 1.2002 + if (!g.zeroLastIndex(cx)) 1.2003 + return false; 1.2004 + 1.2005 + // Step 8b. 1.2006 + AutoValueVector elements(cx); 1.2007 + 1.2008 + size_t lastSuccessfulStart = 0; 1.2009 + 1.2010 + // The loop variables from steps 8c-e aren't needed, as we use different 1.2011 + // techniques from the spec to implement step 8f's loop. 1.2012 + 1.2013 + // Step 8f. 1.2014 + MatchPair match; 1.2015 + size_t charsLen = input->length(); 1.2016 + const jschar *chars = input->chars(); 1.2017 + RegExpShared &re = g.regExp(); 1.2018 + for (size_t searchIndex = 0; searchIndex <= charsLen; ) { 1.2019 + if (!CheckForInterrupt(cx)) 1.2020 + return false; 1.2021 + 1.2022 + // Steps 8f(i-ii), minus "lastIndex" updates (see above). 1.2023 + size_t nextSearchIndex = searchIndex; 1.2024 + RegExpRunStatus status = re.executeMatchOnly(cx, chars, charsLen, &nextSearchIndex, match); 1.2025 + if (status == RegExpRunStatus_Error) 1.2026 + return false; 1.2027 + 1.2028 + // Step 8f(ii). 1.2029 + if (status == RegExpRunStatus_Success_NotFound) 1.2030 + break; 1.2031 + 1.2032 + lastSuccessfulStart = searchIndex; 1.2033 + 1.2034 + // Steps 8f(iii)(1-3). 1.2035 + searchIndex = match.isEmpty() ? nextSearchIndex + 1 : nextSearchIndex; 1.2036 + 1.2037 + // Step 8f(iii)(4-5). 1.2038 + JSLinearString *str = js_NewDependentString(cx, input, match.start, match.length()); 1.2039 + if (!str) 1.2040 + return false; 1.2041 + if (!elements.append(StringValue(str))) 1.2042 + return false; 1.2043 + } 1.2044 + 1.2045 + // Step 8g. 1.2046 + if (elements.empty()) { 1.2047 + args.rval().setNull(); 1.2048 + return true; 1.2049 + } 1.2050 + 1.2051 + // The last *successful* match updates the RegExpStatics. (Interestingly, 1.2052 + // this implies that String.prototype.match's semantics aren't those 1.2053 + // implied by the RegExp.prototype.exec calls in the ES5 algorithm.) 1.2054 + res->updateLazily(cx, input, &re, lastSuccessfulStart); 1.2055 + 1.2056 + // Steps 8b, 8f(iii)(5-6), 8h. 1.2057 + JSObject *array = NewDenseCopiedArray(cx, elements.length(), elements.begin()); 1.2058 + if (!array) 1.2059 + return false; 1.2060 + 1.2061 + args.rval().setObject(*array); 1.2062 + return true; 1.2063 +} 1.2064 + 1.2065 +static bool 1.2066 +BuildFlatMatchArray(JSContext *cx, HandleString textstr, const FlatMatch &fm, CallArgs *args) 1.2067 +{ 1.2068 + if (fm.match() < 0) { 1.2069 + args->rval().setNull(); 1.2070 + return true; 1.2071 + } 1.2072 + 1.2073 + /* For this non-global match, produce a RegExp.exec-style array. */ 1.2074 + RootedObject obj(cx, NewDenseEmptyArray(cx)); 1.2075 + if (!obj) 1.2076 + return false; 1.2077 + 1.2078 + RootedValue patternVal(cx, StringValue(fm.pattern())); 1.2079 + RootedValue matchVal(cx, Int32Value(fm.match())); 1.2080 + RootedValue textVal(cx, StringValue(textstr)); 1.2081 + 1.2082 + if (!JSObject::defineElement(cx, obj, 0, patternVal) || 1.2083 + !JSObject::defineProperty(cx, obj, cx->names().index, matchVal) || 1.2084 + !JSObject::defineProperty(cx, obj, cx->names().input, textVal)) 1.2085 + { 1.2086 + return false; 1.2087 + } 1.2088 + 1.2089 + args->rval().setObject(*obj); 1.2090 + return true; 1.2091 +} 1.2092 + 1.2093 +/* ES5 15.5.4.10. */ 1.2094 +bool 1.2095 +js::str_match(JSContext *cx, unsigned argc, Value *vp) 1.2096 +{ 1.2097 + CallArgs args = CallArgsFromVp(argc, vp); 1.2098 + 1.2099 + /* Steps 1-2. */ 1.2100 + RootedString str(cx, ThisToStringForStringProto(cx, args)); 1.2101 + if (!str) 1.2102 + return false; 1.2103 + 1.2104 + /* Steps 3-4, plus the trailing-argument "flags" extension. */ 1.2105 + StringRegExpGuard g(cx); 1.2106 + if (!g.init(cx, args, true)) 1.2107 + return false; 1.2108 + 1.2109 + /* Fast path when the search pattern can be searched for as a string. */ 1.2110 + if (const FlatMatch *fm = g.tryFlatMatch(cx, str, 1, args.length())) 1.2111 + return BuildFlatMatchArray(cx, str, *fm, &args); 1.2112 + 1.2113 + /* Return if there was an error in tryFlatMatch. */ 1.2114 + if (cx->isExceptionPending()) 1.2115 + return false; 1.2116 + 1.2117 + /* Create regular-expression internals as needed to perform the match. */ 1.2118 + if (!g.normalizeRegExp(cx, false, 1, args)) 1.2119 + return false; 1.2120 + 1.2121 + RegExpStatics *res = cx->global()->getRegExpStatics(); 1.2122 + Rooted<JSLinearString*> linearStr(cx, str->ensureLinear(cx)); 1.2123 + if (!linearStr) 1.2124 + return false; 1.2125 + 1.2126 + /* Steps 5-6, 7. */ 1.2127 + if (!g.regExp().global()) 1.2128 + return DoMatchLocal(cx, args, res, linearStr, g.regExp()); 1.2129 + 1.2130 + /* Steps 6, 8. */ 1.2131 + return DoMatchGlobal(cx, args, res, linearStr, g); 1.2132 +} 1.2133 + 1.2134 +bool 1.2135 +js::str_search(JSContext *cx, unsigned argc, Value *vp) 1.2136 +{ 1.2137 + CallArgs args = CallArgsFromVp(argc, vp); 1.2138 + RootedString str(cx, ThisToStringForStringProto(cx, args)); 1.2139 + if (!str) 1.2140 + return false; 1.2141 + 1.2142 + StringRegExpGuard g(cx); 1.2143 + if (!g.init(cx, args, true)) 1.2144 + return false; 1.2145 + if (const FlatMatch *fm = g.tryFlatMatch(cx, str, 1, args.length())) { 1.2146 + args.rval().setInt32(fm->match()); 1.2147 + return true; 1.2148 + } 1.2149 + 1.2150 + if (cx->isExceptionPending()) /* from tryFlatMatch */ 1.2151 + return false; 1.2152 + 1.2153 + if (!g.normalizeRegExp(cx, false, 1, args)) 1.2154 + return false; 1.2155 + 1.2156 + Rooted<JSLinearString*> linearStr(cx, str->ensureLinear(cx)); 1.2157 + if (!linearStr) 1.2158 + return false; 1.2159 + 1.2160 + const jschar *chars = linearStr->chars(); 1.2161 + size_t length = linearStr->length(); 1.2162 + RegExpStatics *res = cx->global()->getRegExpStatics(); 1.2163 + 1.2164 + /* Per ECMAv5 15.5.4.12 (5) The last index property is ignored and left unchanged. */ 1.2165 + size_t i = 0; 1.2166 + MatchPair match; 1.2167 + 1.2168 + RegExpRunStatus status = g.regExp().executeMatchOnly(cx, chars, length, &i, match); 1.2169 + if (status == RegExpRunStatus_Error) 1.2170 + return false; 1.2171 + 1.2172 + if (status == RegExpRunStatus_Success) 1.2173 + res->updateLazily(cx, linearStr, &g.regExp(), 0); 1.2174 + 1.2175 + JS_ASSERT_IF(status == RegExpRunStatus_Success_NotFound, match.start == -1); 1.2176 + args.rval().setInt32(match.start); 1.2177 + return true; 1.2178 +} 1.2179 + 1.2180 +// Utility for building a rope (lazy concatenation) of strings. 1.2181 +class RopeBuilder { 1.2182 + JSContext *cx; 1.2183 + RootedString res; 1.2184 + 1.2185 + RopeBuilder(const RopeBuilder &other) MOZ_DELETE; 1.2186 + void operator=(const RopeBuilder &other) MOZ_DELETE; 1.2187 + 1.2188 + public: 1.2189 + RopeBuilder(JSContext *cx) 1.2190 + : cx(cx), res(cx, cx->runtime()->emptyString) 1.2191 + {} 1.2192 + 1.2193 + inline bool append(HandleString str) { 1.2194 + res = ConcatStrings<CanGC>(cx, res, str); 1.2195 + return !!res; 1.2196 + } 1.2197 + 1.2198 + inline JSString *result() { 1.2199 + return res; 1.2200 + } 1.2201 +}; 1.2202 + 1.2203 +namespace { 1.2204 + 1.2205 +struct ReplaceData 1.2206 +{ 1.2207 + ReplaceData(JSContext *cx) 1.2208 + : str(cx), g(cx), lambda(cx), elembase(cx), repstr(cx), 1.2209 + fig(cx, NullValue()), sb(cx) 1.2210 + {} 1.2211 + 1.2212 + inline void setReplacementString(JSLinearString *string) { 1.2213 + JS_ASSERT(string); 1.2214 + lambda = nullptr; 1.2215 + elembase = nullptr; 1.2216 + repstr = string; 1.2217 + 1.2218 + /* We're about to store pointers into the middle of our string. */ 1.2219 + dollarEnd = repstr->chars() + repstr->length(); 1.2220 + dollar = js_strchr_limit(repstr->chars(), '$', dollarEnd); 1.2221 + } 1.2222 + 1.2223 + inline void setReplacementFunction(JSObject *func) { 1.2224 + JS_ASSERT(func); 1.2225 + lambda = func; 1.2226 + elembase = nullptr; 1.2227 + repstr = nullptr; 1.2228 + dollar = dollarEnd = nullptr; 1.2229 + } 1.2230 + 1.2231 + RootedString str; /* 'this' parameter object as a string */ 1.2232 + StringRegExpGuard g; /* regexp parameter object and private data */ 1.2233 + RootedObject lambda; /* replacement function object or null */ 1.2234 + RootedObject elembase; /* object for function(a){return b[a]} replace */ 1.2235 + Rooted<JSLinearString*> repstr; /* replacement string */ 1.2236 + const jschar *dollar; /* null or pointer to first $ in repstr */ 1.2237 + const jschar *dollarEnd; /* limit pointer for js_strchr_limit */ 1.2238 + int leftIndex; /* left context index in str->chars */ 1.2239 + JSSubString dollarStr; /* for "$$" InterpretDollar result */ 1.2240 + bool calledBack; /* record whether callback has been called */ 1.2241 + FastInvokeGuard fig; /* used for lambda calls, also holds arguments */ 1.2242 + StringBuffer sb; /* buffer built during DoMatch */ 1.2243 +}; 1.2244 + 1.2245 +} /* anonymous namespace */ 1.2246 + 1.2247 +static bool 1.2248 +ReplaceRegExp(JSContext *cx, RegExpStatics *res, ReplaceData &rdata); 1.2249 + 1.2250 +static bool 1.2251 +DoMatchForReplaceLocal(JSContext *cx, RegExpStatics *res, Handle<JSLinearString*> linearStr, 1.2252 + RegExpShared &re, ReplaceData &rdata) 1.2253 +{ 1.2254 + size_t charsLen = linearStr->length(); 1.2255 + size_t i = 0; 1.2256 + ScopedMatchPairs matches(&cx->tempLifoAlloc()); 1.2257 + RegExpRunStatus status = re.execute(cx, linearStr->chars(), charsLen, &i, matches); 1.2258 + if (status == RegExpRunStatus_Error) 1.2259 + return false; 1.2260 + 1.2261 + if (status == RegExpRunStatus_Success_NotFound) 1.2262 + return true; 1.2263 + 1.2264 + if (!res->updateFromMatchPairs(cx, linearStr, matches)) 1.2265 + return false; 1.2266 + 1.2267 + return ReplaceRegExp(cx, res, rdata); 1.2268 +} 1.2269 + 1.2270 +static bool 1.2271 +DoMatchForReplaceGlobal(JSContext *cx, RegExpStatics *res, Handle<JSLinearString*> linearStr, 1.2272 + RegExpShared &re, ReplaceData &rdata) 1.2273 +{ 1.2274 + size_t charsLen = linearStr->length(); 1.2275 + ScopedMatchPairs matches(&cx->tempLifoAlloc()); 1.2276 + for (size_t count = 0, i = 0; i <= charsLen; ++count) { 1.2277 + if (!CheckForInterrupt(cx)) 1.2278 + return false; 1.2279 + 1.2280 + RegExpRunStatus status = re.execute(cx, linearStr->chars(), charsLen, &i, matches); 1.2281 + if (status == RegExpRunStatus_Error) 1.2282 + return false; 1.2283 + 1.2284 + if (status == RegExpRunStatus_Success_NotFound) 1.2285 + break; 1.2286 + 1.2287 + if (!res->updateFromMatchPairs(cx, linearStr, matches)) 1.2288 + return false; 1.2289 + 1.2290 + if (!ReplaceRegExp(cx, res, rdata)) 1.2291 + return false; 1.2292 + if (!res->matched()) 1.2293 + ++i; 1.2294 + } 1.2295 + 1.2296 + return true; 1.2297 +} 1.2298 + 1.2299 +static bool 1.2300 +InterpretDollar(RegExpStatics *res, const jschar *dp, const jschar *ep, 1.2301 + ReplaceData &rdata, JSSubString *out, size_t *skip) 1.2302 +{ 1.2303 + JS_ASSERT(*dp == '$'); 1.2304 + 1.2305 + /* If there is only a dollar, bail now */ 1.2306 + if (dp + 1 >= ep) 1.2307 + return false; 1.2308 + 1.2309 + /* Interpret all Perl match-induced dollar variables. */ 1.2310 + jschar dc = dp[1]; 1.2311 + if (JS7_ISDEC(dc)) { 1.2312 + /* ECMA-262 Edition 3: 1-9 or 01-99 */ 1.2313 + unsigned num = JS7_UNDEC(dc); 1.2314 + if (num > res->getMatches().parenCount()) 1.2315 + return false; 1.2316 + 1.2317 + const jschar *cp = dp + 2; 1.2318 + if (cp < ep && (dc = *cp, JS7_ISDEC(dc))) { 1.2319 + unsigned tmp = 10 * num + JS7_UNDEC(dc); 1.2320 + if (tmp <= res->getMatches().parenCount()) { 1.2321 + cp++; 1.2322 + num = tmp; 1.2323 + } 1.2324 + } 1.2325 + if (num == 0) 1.2326 + return false; 1.2327 + 1.2328 + *skip = cp - dp; 1.2329 + 1.2330 + JS_ASSERT(num <= res->getMatches().parenCount()); 1.2331 + 1.2332 + /* 1.2333 + * Note: we index to get the paren with the (1-indexed) pair 1.2334 + * number, as opposed to a (0-indexed) paren number. 1.2335 + */ 1.2336 + res->getParen(num, out); 1.2337 + return true; 1.2338 + } 1.2339 + 1.2340 + *skip = 2; 1.2341 + switch (dc) { 1.2342 + case '$': 1.2343 + rdata.dollarStr.chars = dp; 1.2344 + rdata.dollarStr.length = 1; 1.2345 + *out = rdata.dollarStr; 1.2346 + return true; 1.2347 + case '&': 1.2348 + res->getLastMatch(out); 1.2349 + return true; 1.2350 + case '+': 1.2351 + res->getLastParen(out); 1.2352 + return true; 1.2353 + case '`': 1.2354 + res->getLeftContext(out); 1.2355 + return true; 1.2356 + case '\'': 1.2357 + res->getRightContext(out); 1.2358 + return true; 1.2359 + } 1.2360 + return false; 1.2361 +} 1.2362 + 1.2363 +static bool 1.2364 +FindReplaceLength(JSContext *cx, RegExpStatics *res, ReplaceData &rdata, size_t *sizep) 1.2365 +{ 1.2366 + if (rdata.elembase) { 1.2367 + /* 1.2368 + * The base object is used when replace was passed a lambda which looks like 1.2369 + * 'function(a) { return b[a]; }' for the base object b. b will not change 1.2370 + * in the course of the replace unless we end up making a scripted call due 1.2371 + * to accessing a scripted getter or a value with a scripted toString. 1.2372 + */ 1.2373 + JS_ASSERT(rdata.lambda); 1.2374 + JS_ASSERT(!rdata.elembase->getOps()->lookupProperty); 1.2375 + JS_ASSERT(!rdata.elembase->getOps()->getProperty); 1.2376 + 1.2377 + RootedValue match(cx); 1.2378 + if (!res->createLastMatch(cx, &match)) 1.2379 + return false; 1.2380 + JSAtom *atom = ToAtom<CanGC>(cx, match); 1.2381 + if (!atom) 1.2382 + return false; 1.2383 + 1.2384 + RootedValue v(cx); 1.2385 + if (HasDataProperty(cx, rdata.elembase, AtomToId(atom), v.address()) && v.isString()) { 1.2386 + rdata.repstr = v.toString()->ensureLinear(cx); 1.2387 + if (!rdata.repstr) 1.2388 + return false; 1.2389 + *sizep = rdata.repstr->length(); 1.2390 + return true; 1.2391 + } 1.2392 + 1.2393 + /* 1.2394 + * Couldn't handle this property, fall through and despecialize to the 1.2395 + * general lambda case. 1.2396 + */ 1.2397 + rdata.elembase = nullptr; 1.2398 + } 1.2399 + 1.2400 + if (rdata.lambda) { 1.2401 + RootedObject lambda(cx, rdata.lambda); 1.2402 + PreserveRegExpStatics staticsGuard(cx, res); 1.2403 + if (!staticsGuard.init(cx)) 1.2404 + return false; 1.2405 + 1.2406 + /* 1.2407 + * In the lambda case, not only do we find the replacement string's 1.2408 + * length, we compute repstr and return it via rdata for use within 1.2409 + * DoReplace. The lambda is called with arguments ($&, $1, $2, ..., 1.2410 + * index, input), i.e., all the properties of a regexp match array. 1.2411 + * For $&, etc., we must create string jsvals from cx->regExpStatics. 1.2412 + * We grab up stack space to keep the newborn strings GC-rooted. 1.2413 + */ 1.2414 + unsigned p = res->getMatches().parenCount(); 1.2415 + unsigned argc = 1 + p + 2; 1.2416 + 1.2417 + InvokeArgs &args = rdata.fig.args(); 1.2418 + if (!args.init(argc)) 1.2419 + return false; 1.2420 + 1.2421 + args.setCallee(ObjectValue(*lambda)); 1.2422 + args.setThis(UndefinedValue()); 1.2423 + 1.2424 + /* Push $&, $1, $2, ... */ 1.2425 + unsigned argi = 0; 1.2426 + if (!res->createLastMatch(cx, args[argi++])) 1.2427 + return false; 1.2428 + 1.2429 + for (size_t i = 0; i < res->getMatches().parenCount(); ++i) { 1.2430 + if (!res->createParen(cx, i + 1, args[argi++])) 1.2431 + return false; 1.2432 + } 1.2433 + 1.2434 + /* Push match index and input string. */ 1.2435 + args[argi++].setInt32(res->getMatches()[0].start); 1.2436 + args[argi].setString(rdata.str); 1.2437 + 1.2438 + if (!rdata.fig.invoke(cx)) 1.2439 + return false; 1.2440 + 1.2441 + /* root repstr: rdata is on the stack, so scanned by conservative gc. */ 1.2442 + JSString *repstr = ToString<CanGC>(cx, args.rval()); 1.2443 + if (!repstr) 1.2444 + return false; 1.2445 + rdata.repstr = repstr->ensureLinear(cx); 1.2446 + if (!rdata.repstr) 1.2447 + return false; 1.2448 + *sizep = rdata.repstr->length(); 1.2449 + return true; 1.2450 + } 1.2451 + 1.2452 + JSString *repstr = rdata.repstr; 1.2453 + CheckedInt<uint32_t> replen = repstr->length(); 1.2454 + for (const jschar *dp = rdata.dollar, *ep = rdata.dollarEnd; dp; 1.2455 + dp = js_strchr_limit(dp, '$', ep)) { 1.2456 + JSSubString sub; 1.2457 + size_t skip; 1.2458 + if (InterpretDollar(res, dp, ep, rdata, &sub, &skip)) { 1.2459 + if (sub.length > skip) 1.2460 + replen += sub.length - skip; 1.2461 + else 1.2462 + replen -= skip - sub.length; 1.2463 + dp += skip; 1.2464 + } else { 1.2465 + dp++; 1.2466 + } 1.2467 + } 1.2468 + 1.2469 + if (!replen.isValid()) { 1.2470 + js_ReportAllocationOverflow(cx); 1.2471 + return false; 1.2472 + } 1.2473 + 1.2474 + *sizep = replen.value(); 1.2475 + return true; 1.2476 +} 1.2477 + 1.2478 +/* 1.2479 + * Precondition: |rdata.sb| already has necessary growth space reserved (as 1.2480 + * derived from FindReplaceLength). 1.2481 + */ 1.2482 +static void 1.2483 +DoReplace(RegExpStatics *res, ReplaceData &rdata) 1.2484 +{ 1.2485 + JSLinearString *repstr = rdata.repstr; 1.2486 + const jschar *cp; 1.2487 + const jschar *bp = cp = repstr->chars(); 1.2488 + 1.2489 + const jschar *dp = rdata.dollar; 1.2490 + const jschar *ep = rdata.dollarEnd; 1.2491 + for (; dp; dp = js_strchr_limit(dp, '$', ep)) { 1.2492 + /* Move one of the constant portions of the replacement value. */ 1.2493 + size_t len = dp - cp; 1.2494 + rdata.sb.infallibleAppend(cp, len); 1.2495 + cp = dp; 1.2496 + 1.2497 + JSSubString sub; 1.2498 + size_t skip; 1.2499 + if (InterpretDollar(res, dp, ep, rdata, &sub, &skip)) { 1.2500 + len = sub.length; 1.2501 + rdata.sb.infallibleAppend(sub.chars, len); 1.2502 + cp += skip; 1.2503 + dp += skip; 1.2504 + } else { 1.2505 + dp++; 1.2506 + } 1.2507 + } 1.2508 + rdata.sb.infallibleAppend(cp, repstr->length() - (cp - bp)); 1.2509 +} 1.2510 + 1.2511 +static bool 1.2512 +ReplaceRegExp(JSContext *cx, RegExpStatics *res, ReplaceData &rdata) 1.2513 +{ 1.2514 + 1.2515 + const MatchPair &match = res->getMatches()[0]; 1.2516 + JS_ASSERT(!match.isUndefined()); 1.2517 + JS_ASSERT(match.limit >= match.start && match.limit >= 0); 1.2518 + 1.2519 + rdata.calledBack = true; 1.2520 + size_t leftoff = rdata.leftIndex; 1.2521 + size_t leftlen = match.start - leftoff; 1.2522 + rdata.leftIndex = match.limit; 1.2523 + 1.2524 + size_t replen = 0; /* silence 'unused' warning */ 1.2525 + if (!FindReplaceLength(cx, res, rdata, &replen)) 1.2526 + return false; 1.2527 + 1.2528 + CheckedInt<uint32_t> newlen(rdata.sb.length()); 1.2529 + newlen += leftlen; 1.2530 + newlen += replen; 1.2531 + if (!newlen.isValid()) { 1.2532 + js_ReportAllocationOverflow(cx); 1.2533 + return false; 1.2534 + } 1.2535 + if (!rdata.sb.reserve(newlen.value())) 1.2536 + return false; 1.2537 + 1.2538 + JSLinearString &str = rdata.str->asLinear(); /* flattened for regexp */ 1.2539 + const jschar *left = str.chars() + leftoff; 1.2540 + 1.2541 + rdata.sb.infallibleAppend(left, leftlen); /* skipped-over portion of the search value */ 1.2542 + DoReplace(res, rdata); 1.2543 + return true; 1.2544 +} 1.2545 + 1.2546 +static bool 1.2547 +BuildFlatReplacement(JSContext *cx, HandleString textstr, HandleString repstr, 1.2548 + const FlatMatch &fm, MutableHandleValue rval) 1.2549 +{ 1.2550 + RopeBuilder builder(cx); 1.2551 + size_t match = fm.match(); 1.2552 + size_t matchEnd = match + fm.patternLength(); 1.2553 + 1.2554 + if (textstr->isRope()) { 1.2555 + /* 1.2556 + * If we are replacing over a rope, avoid flattening it by iterating 1.2557 + * through it, building a new rope. 1.2558 + */ 1.2559 + StringSegmentRange r(cx); 1.2560 + if (!r.init(textstr)) 1.2561 + return false; 1.2562 + size_t pos = 0; 1.2563 + while (!r.empty()) { 1.2564 + RootedString str(cx, r.front()); 1.2565 + size_t len = str->length(); 1.2566 + size_t strEnd = pos + len; 1.2567 + if (pos < matchEnd && strEnd > match) { 1.2568 + /* 1.2569 + * We need to special-case any part of the rope that overlaps 1.2570 + * with the replacement string. 1.2571 + */ 1.2572 + if (match >= pos) { 1.2573 + /* 1.2574 + * If this part of the rope overlaps with the left side of 1.2575 + * the pattern, then it must be the only one to overlap with 1.2576 + * the first character in the pattern, so we include the 1.2577 + * replacement string here. 1.2578 + */ 1.2579 + RootedString leftSide(cx, js_NewDependentString(cx, str, 0, match - pos)); 1.2580 + if (!leftSide || 1.2581 + !builder.append(leftSide) || 1.2582 + !builder.append(repstr)) { 1.2583 + return false; 1.2584 + } 1.2585 + } 1.2586 + 1.2587 + /* 1.2588 + * If str runs off the end of the matched string, append the 1.2589 + * last part of str. 1.2590 + */ 1.2591 + if (strEnd > matchEnd) { 1.2592 + RootedString rightSide(cx, js_NewDependentString(cx, str, matchEnd - pos, 1.2593 + strEnd - matchEnd)); 1.2594 + if (!rightSide || !builder.append(rightSide)) 1.2595 + return false; 1.2596 + } 1.2597 + } else { 1.2598 + if (!builder.append(str)) 1.2599 + return false; 1.2600 + } 1.2601 + pos += str->length(); 1.2602 + if (!r.popFront()) 1.2603 + return false; 1.2604 + } 1.2605 + } else { 1.2606 + RootedString leftSide(cx, js_NewDependentString(cx, textstr, 0, match)); 1.2607 + if (!leftSide) 1.2608 + return false; 1.2609 + RootedString rightSide(cx); 1.2610 + rightSide = js_NewDependentString(cx, textstr, match + fm.patternLength(), 1.2611 + textstr->length() - match - fm.patternLength()); 1.2612 + if (!rightSide || 1.2613 + !builder.append(leftSide) || 1.2614 + !builder.append(repstr) || 1.2615 + !builder.append(rightSide)) { 1.2616 + return false; 1.2617 + } 1.2618 + } 1.2619 + 1.2620 + rval.setString(builder.result()); 1.2621 + return true; 1.2622 +} 1.2623 + 1.2624 +/* 1.2625 + * Perform a linear-scan dollar substitution on the replacement text, 1.2626 + * constructing a result string that looks like: 1.2627 + * 1.2628 + * newstring = string[:matchStart] + dollarSub(replaceValue) + string[matchLimit:] 1.2629 + */ 1.2630 +static inline bool 1.2631 +BuildDollarReplacement(JSContext *cx, JSString *textstrArg, JSLinearString *repstr, 1.2632 + const jschar *firstDollar, const FlatMatch &fm, MutableHandleValue rval) 1.2633 +{ 1.2634 + Rooted<JSLinearString*> textstr(cx, textstrArg->ensureLinear(cx)); 1.2635 + if (!textstr) 1.2636 + return false; 1.2637 + 1.2638 + JS_ASSERT(repstr->chars() <= firstDollar && firstDollar < repstr->chars() + repstr->length()); 1.2639 + size_t matchStart = fm.match(); 1.2640 + size_t matchLimit = matchStart + fm.patternLength(); 1.2641 + 1.2642 + /* 1.2643 + * Most probably: 1.2644 + * 1.2645 + * len(newstr) >= len(orig) - len(match) + len(replacement) 1.2646 + * 1.2647 + * Note that dollar vars _could_ make the resulting text smaller than this. 1.2648 + */ 1.2649 + StringBuffer newReplaceChars(cx); 1.2650 + if (!newReplaceChars.reserve(textstr->length() - fm.patternLength() + repstr->length())) 1.2651 + return false; 1.2652 + 1.2653 + /* Move the pre-dollar chunk in bulk. */ 1.2654 + newReplaceChars.infallibleAppend(repstr->chars(), firstDollar); 1.2655 + 1.2656 + /* Move the rest char-by-char, interpreting dollars as we encounter them. */ 1.2657 +#define ENSURE(__cond) if (!(__cond)) return false; 1.2658 + const jschar *repstrLimit = repstr->chars() + repstr->length(); 1.2659 + for (const jschar *it = firstDollar; it < repstrLimit; ++it) { 1.2660 + if (*it != '$' || it == repstrLimit - 1) { 1.2661 + ENSURE(newReplaceChars.append(*it)); 1.2662 + continue; 1.2663 + } 1.2664 + 1.2665 + switch (*(it + 1)) { 1.2666 + case '$': /* Eat one of the dollars. */ 1.2667 + ENSURE(newReplaceChars.append(*it)); 1.2668 + break; 1.2669 + case '&': 1.2670 + ENSURE(newReplaceChars.append(textstr->chars() + matchStart, 1.2671 + textstr->chars() + matchLimit)); 1.2672 + break; 1.2673 + case '`': 1.2674 + ENSURE(newReplaceChars.append(textstr->chars(), textstr->chars() + matchStart)); 1.2675 + break; 1.2676 + case '\'': 1.2677 + ENSURE(newReplaceChars.append(textstr->chars() + matchLimit, 1.2678 + textstr->chars() + textstr->length())); 1.2679 + break; 1.2680 + default: /* The dollar we saw was not special (no matter what its mother told it). */ 1.2681 + ENSURE(newReplaceChars.append(*it)); 1.2682 + continue; 1.2683 + } 1.2684 + ++it; /* We always eat an extra char in the above switch. */ 1.2685 + } 1.2686 + 1.2687 + RootedString leftSide(cx, js_NewDependentString(cx, textstr, 0, matchStart)); 1.2688 + ENSURE(leftSide); 1.2689 + 1.2690 + RootedString newReplace(cx, newReplaceChars.finishString()); 1.2691 + ENSURE(newReplace); 1.2692 + 1.2693 + JS_ASSERT(textstr->length() >= matchLimit); 1.2694 + RootedString rightSide(cx, js_NewDependentString(cx, textstr, matchLimit, 1.2695 + textstr->length() - matchLimit)); 1.2696 + ENSURE(rightSide); 1.2697 + 1.2698 + RopeBuilder builder(cx); 1.2699 + ENSURE(builder.append(leftSide) && 1.2700 + builder.append(newReplace) && 1.2701 + builder.append(rightSide)); 1.2702 +#undef ENSURE 1.2703 + 1.2704 + rval.setString(builder.result()); 1.2705 + return true; 1.2706 +} 1.2707 + 1.2708 +struct StringRange 1.2709 +{ 1.2710 + size_t start; 1.2711 + size_t length; 1.2712 + 1.2713 + StringRange(size_t s, size_t l) 1.2714 + : start(s), length(l) 1.2715 + { } 1.2716 +}; 1.2717 + 1.2718 +static inline JSFatInlineString * 1.2719 +FlattenSubstrings(JSContext *cx, const jschar *chars, 1.2720 + const StringRange *ranges, size_t rangesLen, size_t outputLen) 1.2721 +{ 1.2722 + JS_ASSERT(JSFatInlineString::lengthFits(outputLen)); 1.2723 + 1.2724 + JSFatInlineString *str = js_NewGCFatInlineString<CanGC>(cx); 1.2725 + if (!str) 1.2726 + return nullptr; 1.2727 + jschar *buf = str->init(outputLen); 1.2728 + 1.2729 + size_t pos = 0; 1.2730 + for (size_t i = 0; i < rangesLen; i++) { 1.2731 + PodCopy(buf + pos, chars + ranges[i].start, ranges[i].length); 1.2732 + pos += ranges[i].length; 1.2733 + } 1.2734 + JS_ASSERT(pos == outputLen); 1.2735 + 1.2736 + buf[outputLen] = 0; 1.2737 + return str; 1.2738 +} 1.2739 + 1.2740 +static JSString * 1.2741 +AppendSubstrings(JSContext *cx, Handle<JSFlatString*> flatStr, 1.2742 + const StringRange *ranges, size_t rangesLen) 1.2743 +{ 1.2744 + JS_ASSERT(rangesLen); 1.2745 + 1.2746 + /* For single substrings, construct a dependent string. */ 1.2747 + if (rangesLen == 1) 1.2748 + return js_NewDependentString(cx, flatStr, ranges[0].start, ranges[0].length); 1.2749 + 1.2750 + const jschar *chars = flatStr->getChars(cx); 1.2751 + if (!chars) 1.2752 + return nullptr; 1.2753 + 1.2754 + /* Collect substrings into a rope */ 1.2755 + size_t i = 0; 1.2756 + RopeBuilder rope(cx); 1.2757 + RootedString part(cx, nullptr); 1.2758 + while (i < rangesLen) { 1.2759 + 1.2760 + /* Find maximum range that fits in JSFatInlineString */ 1.2761 + size_t substrLen = 0; 1.2762 + size_t end = i; 1.2763 + for (; end < rangesLen; end++) { 1.2764 + if (substrLen + ranges[end].length > JSFatInlineString::MAX_FAT_INLINE_LENGTH) 1.2765 + break; 1.2766 + substrLen += ranges[end].length; 1.2767 + } 1.2768 + 1.2769 + if (i == end) { 1.2770 + /* Not even one range fits JSFatInlineString, use DependentString */ 1.2771 + const StringRange &sr = ranges[i++]; 1.2772 + part = js_NewDependentString(cx, flatStr, sr.start, sr.length); 1.2773 + } else { 1.2774 + /* Copy the ranges (linearly) into a JSFatInlineString */ 1.2775 + part = FlattenSubstrings(cx, chars, ranges + i, end - i, substrLen); 1.2776 + i = end; 1.2777 + } 1.2778 + 1.2779 + if (!part) 1.2780 + return nullptr; 1.2781 + 1.2782 + /* Appending to the rope permanently roots the substring. */ 1.2783 + if (!rope.append(part)) 1.2784 + return nullptr; 1.2785 + } 1.2786 + 1.2787 + return rope.result(); 1.2788 +} 1.2789 + 1.2790 +static bool 1.2791 +StrReplaceRegexpRemove(JSContext *cx, HandleString str, RegExpShared &re, MutableHandleValue rval) 1.2792 +{ 1.2793 + Rooted<JSFlatString*> flatStr(cx, str->ensureFlat(cx)); 1.2794 + if (!flatStr) 1.2795 + return false; 1.2796 + 1.2797 + Vector<StringRange, 16, SystemAllocPolicy> ranges; 1.2798 + 1.2799 + size_t charsLen = flatStr->length(); 1.2800 + 1.2801 + MatchPair match; 1.2802 + size_t startIndex = 0; /* Index used for iterating through the string. */ 1.2803 + size_t lastIndex = 0; /* Index after last successful match. */ 1.2804 + size_t lazyIndex = 0; /* Index before last successful match. */ 1.2805 + 1.2806 + /* Accumulate StringRanges for unmatched substrings. */ 1.2807 + while (startIndex <= charsLen) { 1.2808 + if (!CheckForInterrupt(cx)) 1.2809 + return false; 1.2810 + 1.2811 + RegExpRunStatus status = 1.2812 + re.executeMatchOnly(cx, flatStr->chars(), charsLen, &startIndex, match); 1.2813 + if (status == RegExpRunStatus_Error) 1.2814 + return false; 1.2815 + if (status == RegExpRunStatus_Success_NotFound) 1.2816 + break; 1.2817 + 1.2818 + /* Include the latest unmatched substring. */ 1.2819 + if (size_t(match.start) > lastIndex) { 1.2820 + if (!ranges.append(StringRange(lastIndex, match.start - lastIndex))) 1.2821 + return false; 1.2822 + } 1.2823 + 1.2824 + lazyIndex = lastIndex; 1.2825 + lastIndex = startIndex; 1.2826 + 1.2827 + if (match.isEmpty()) 1.2828 + startIndex++; 1.2829 + 1.2830 + /* Non-global removal executes at most once. */ 1.2831 + if (!re.global()) 1.2832 + break; 1.2833 + } 1.2834 + 1.2835 + /* If unmatched, return the input string. */ 1.2836 + if (!lastIndex) { 1.2837 + if (startIndex > 0) 1.2838 + cx->global()->getRegExpStatics()->updateLazily(cx, flatStr, &re, lazyIndex); 1.2839 + rval.setString(str); 1.2840 + return true; 1.2841 + } 1.2842 + 1.2843 + /* The last successful match updates the RegExpStatics. */ 1.2844 + cx->global()->getRegExpStatics()->updateLazily(cx, flatStr, &re, lazyIndex); 1.2845 + 1.2846 + /* Include any remaining part of the string. */ 1.2847 + if (lastIndex < charsLen) { 1.2848 + if (!ranges.append(StringRange(lastIndex, charsLen - lastIndex))) 1.2849 + return false; 1.2850 + } 1.2851 + 1.2852 + /* Handle the empty string before calling .begin(). */ 1.2853 + if (ranges.empty()) { 1.2854 + rval.setString(cx->runtime()->emptyString); 1.2855 + return true; 1.2856 + } 1.2857 + 1.2858 + JSString *result = AppendSubstrings(cx, flatStr, ranges.begin(), ranges.length()); 1.2859 + if (!result) 1.2860 + return false; 1.2861 + 1.2862 + rval.setString(result); 1.2863 + return true; 1.2864 +} 1.2865 + 1.2866 +static inline bool 1.2867 +StrReplaceRegExp(JSContext *cx, ReplaceData &rdata, MutableHandleValue rval) 1.2868 +{ 1.2869 + rdata.leftIndex = 0; 1.2870 + rdata.calledBack = false; 1.2871 + 1.2872 + RegExpStatics *res = cx->global()->getRegExpStatics(); 1.2873 + RegExpShared &re = rdata.g.regExp(); 1.2874 + 1.2875 + // The spec doesn't describe this function very clearly, so we go ahead and 1.2876 + // assume that when the input to String.prototype.replace is a global 1.2877 + // RegExp, calling the replacer function (assuming one was provided) takes 1.2878 + // place only after the matching is done. See the comment at the beginning 1.2879 + // of DoMatchGlobal explaining why we can zero the the RegExp object's 1.2880 + // lastIndex property here. 1.2881 + if (re.global() && !rdata.g.zeroLastIndex(cx)) 1.2882 + return false; 1.2883 + 1.2884 + /* Optimize removal. */ 1.2885 + if (rdata.repstr && rdata.repstr->length() == 0) { 1.2886 + JS_ASSERT(!rdata.lambda && !rdata.elembase && !rdata.dollar); 1.2887 + return StrReplaceRegexpRemove(cx, rdata.str, re, rval); 1.2888 + } 1.2889 + 1.2890 + Rooted<JSLinearString*> linearStr(cx, rdata.str->ensureLinear(cx)); 1.2891 + if (!linearStr) 1.2892 + return false; 1.2893 + 1.2894 + if (re.global()) { 1.2895 + if (!DoMatchForReplaceGlobal(cx, res, linearStr, re, rdata)) 1.2896 + return false; 1.2897 + } else { 1.2898 + if (!DoMatchForReplaceLocal(cx, res, linearStr, re, rdata)) 1.2899 + return false; 1.2900 + } 1.2901 + 1.2902 + if (!rdata.calledBack) { 1.2903 + /* Didn't match, so the string is unmodified. */ 1.2904 + rval.setString(rdata.str); 1.2905 + return true; 1.2906 + } 1.2907 + 1.2908 + JSSubString sub; 1.2909 + res->getRightContext(&sub); 1.2910 + if (!rdata.sb.append(sub.chars, sub.length)) 1.2911 + return false; 1.2912 + 1.2913 + JSString *retstr = rdata.sb.finishString(); 1.2914 + if (!retstr) 1.2915 + return false; 1.2916 + 1.2917 + rval.setString(retstr); 1.2918 + return true; 1.2919 +} 1.2920 + 1.2921 +static inline bool 1.2922 +str_replace_regexp(JSContext *cx, CallArgs args, ReplaceData &rdata) 1.2923 +{ 1.2924 + if (!rdata.g.normalizeRegExp(cx, true, 2, args)) 1.2925 + return false; 1.2926 + 1.2927 + return StrReplaceRegExp(cx, rdata, args.rval()); 1.2928 +} 1.2929 + 1.2930 +bool 1.2931 +js::str_replace_regexp_raw(JSContext *cx, HandleString string, HandleObject regexp, 1.2932 + HandleString replacement, MutableHandleValue rval) 1.2933 +{ 1.2934 + /* Optimize removal, so we don't have to create ReplaceData */ 1.2935 + if (replacement->length() == 0) { 1.2936 + StringRegExpGuard guard(cx); 1.2937 + if (!guard.init(cx, regexp)) 1.2938 + return false; 1.2939 + 1.2940 + RegExpShared &re = guard.regExp(); 1.2941 + return StrReplaceRegexpRemove(cx, string, re, rval); 1.2942 + } 1.2943 + 1.2944 + ReplaceData rdata(cx); 1.2945 + rdata.str = string; 1.2946 + 1.2947 + JSLinearString *repl = replacement->ensureLinear(cx); 1.2948 + if (!repl) 1.2949 + return false; 1.2950 + 1.2951 + rdata.setReplacementString(repl); 1.2952 + 1.2953 + if (!rdata.g.init(cx, regexp)) 1.2954 + return false; 1.2955 + 1.2956 + return StrReplaceRegExp(cx, rdata, rval); 1.2957 +} 1.2958 + 1.2959 +static inline bool 1.2960 +StrReplaceString(JSContext *cx, ReplaceData &rdata, const FlatMatch &fm, MutableHandleValue rval) 1.2961 +{ 1.2962 + /* 1.2963 + * Note: we could optimize the text.length == pattern.length case if we wanted, 1.2964 + * even in the presence of dollar metachars. 1.2965 + */ 1.2966 + if (rdata.dollar) 1.2967 + return BuildDollarReplacement(cx, rdata.str, rdata.repstr, rdata.dollar, fm, rval); 1.2968 + return BuildFlatReplacement(cx, rdata.str, rdata.repstr, fm, rval); 1.2969 +} 1.2970 + 1.2971 +static const uint32_t ReplaceOptArg = 2; 1.2972 + 1.2973 +bool 1.2974 +js::str_replace_string_raw(JSContext *cx, HandleString string, HandleString pattern, 1.2975 + HandleString replacement, MutableHandleValue rval) 1.2976 +{ 1.2977 + ReplaceData rdata(cx); 1.2978 + 1.2979 + rdata.str = string; 1.2980 + JSLinearString *repl = replacement->ensureLinear(cx); 1.2981 + if (!repl) 1.2982 + return false; 1.2983 + rdata.setReplacementString(repl); 1.2984 + 1.2985 + if (!rdata.g.init(cx, pattern)) 1.2986 + return false; 1.2987 + const FlatMatch *fm = rdata.g.tryFlatMatch(cx, rdata.str, ReplaceOptArg, ReplaceOptArg, false); 1.2988 + 1.2989 + if (fm->match() < 0) { 1.2990 + rval.setString(string); 1.2991 + return true; 1.2992 + } 1.2993 + 1.2994 + return StrReplaceString(cx, rdata, *fm, rval); 1.2995 +} 1.2996 + 1.2997 +static inline bool 1.2998 +str_replace_flat_lambda(JSContext *cx, CallArgs outerArgs, ReplaceData &rdata, const FlatMatch &fm) 1.2999 +{ 1.3000 + RootedString matchStr(cx, js_NewDependentString(cx, rdata.str, fm.match(), fm.patternLength())); 1.3001 + if (!matchStr) 1.3002 + return false; 1.3003 + 1.3004 + /* lambda(matchStr, matchStart, textstr) */ 1.3005 + static const uint32_t lambdaArgc = 3; 1.3006 + if (!rdata.fig.args().init(lambdaArgc)) 1.3007 + return false; 1.3008 + 1.3009 + CallArgs &args = rdata.fig.args(); 1.3010 + args.setCallee(ObjectValue(*rdata.lambda)); 1.3011 + args.setThis(UndefinedValue()); 1.3012 + 1.3013 + Value *sp = args.array(); 1.3014 + sp[0].setString(matchStr); 1.3015 + sp[1].setInt32(fm.match()); 1.3016 + sp[2].setString(rdata.str); 1.3017 + 1.3018 + if (!rdata.fig.invoke(cx)) 1.3019 + return false; 1.3020 + 1.3021 + RootedString repstr(cx, ToString<CanGC>(cx, args.rval())); 1.3022 + if (!repstr) 1.3023 + return false; 1.3024 + 1.3025 + RootedString leftSide(cx, js_NewDependentString(cx, rdata.str, 0, fm.match())); 1.3026 + if (!leftSide) 1.3027 + return false; 1.3028 + 1.3029 + size_t matchLimit = fm.match() + fm.patternLength(); 1.3030 + RootedString rightSide(cx, js_NewDependentString(cx, rdata.str, matchLimit, 1.3031 + rdata.str->length() - matchLimit)); 1.3032 + if (!rightSide) 1.3033 + return false; 1.3034 + 1.3035 + RopeBuilder builder(cx); 1.3036 + if (!(builder.append(leftSide) && 1.3037 + builder.append(repstr) && 1.3038 + builder.append(rightSide))) { 1.3039 + return false; 1.3040 + } 1.3041 + 1.3042 + outerArgs.rval().setString(builder.result()); 1.3043 + return true; 1.3044 +} 1.3045 + 1.3046 +/* 1.3047 + * Pattern match the script to check if it is is indexing into a particular 1.3048 + * object, e.g. 'function(a) { return b[a]; }'. Avoid calling the script in 1.3049 + * such cases, which are used by javascript packers (particularly the popular 1.3050 + * Dean Edwards packer) to efficiently encode large scripts. We only handle the 1.3051 + * code patterns generated by such packers here. 1.3052 + */ 1.3053 +static bool 1.3054 +LambdaIsGetElem(JSContext *cx, JSObject &lambda, MutableHandleObject pobj) 1.3055 +{ 1.3056 + if (!lambda.is<JSFunction>()) 1.3057 + return true; 1.3058 + 1.3059 + RootedFunction fun(cx, &lambda.as<JSFunction>()); 1.3060 + if (!fun->isInterpreted()) 1.3061 + return true; 1.3062 + 1.3063 + JSScript *script = fun->getOrCreateScript(cx); 1.3064 + if (!script) 1.3065 + return false; 1.3066 + 1.3067 + jsbytecode *pc = script->code(); 1.3068 + 1.3069 + /* 1.3070 + * JSOP_GETALIASEDVAR tells us exactly where to find the base object 'b'. 1.3071 + * Rule out the (unlikely) possibility of a heavyweight function since it 1.3072 + * would make our scope walk off by 1. 1.3073 + */ 1.3074 + if (JSOp(*pc) != JSOP_GETALIASEDVAR || fun->isHeavyweight()) 1.3075 + return true; 1.3076 + ScopeCoordinate sc(pc); 1.3077 + ScopeObject *scope = &fun->environment()->as<ScopeObject>(); 1.3078 + for (unsigned i = 0; i < sc.hops(); ++i) 1.3079 + scope = &scope->enclosingScope().as<ScopeObject>(); 1.3080 + Value b = scope->aliasedVar(sc); 1.3081 + pc += JSOP_GETALIASEDVAR_LENGTH; 1.3082 + 1.3083 + /* Look for 'a' to be the lambda's first argument. */ 1.3084 + if (JSOp(*pc) != JSOP_GETARG || GET_ARGNO(pc) != 0) 1.3085 + return true; 1.3086 + pc += JSOP_GETARG_LENGTH; 1.3087 + 1.3088 + /* 'b[a]' */ 1.3089 + if (JSOp(*pc) != JSOP_GETELEM) 1.3090 + return true; 1.3091 + pc += JSOP_GETELEM_LENGTH; 1.3092 + 1.3093 + /* 'return b[a]' */ 1.3094 + if (JSOp(*pc) != JSOP_RETURN) 1.3095 + return true; 1.3096 + 1.3097 + /* 'b' must behave like a normal object. */ 1.3098 + if (!b.isObject()) 1.3099 + return true; 1.3100 + 1.3101 + JSObject &bobj = b.toObject(); 1.3102 + const Class *clasp = bobj.getClass(); 1.3103 + if (!clasp->isNative() || clasp->ops.lookupProperty || clasp->ops.getProperty) 1.3104 + return true; 1.3105 + 1.3106 + pobj.set(&bobj); 1.3107 + return true; 1.3108 +} 1.3109 + 1.3110 +bool 1.3111 +js::str_replace(JSContext *cx, unsigned argc, Value *vp) 1.3112 +{ 1.3113 + CallArgs args = CallArgsFromVp(argc, vp); 1.3114 + 1.3115 + ReplaceData rdata(cx); 1.3116 + rdata.str = ThisToStringForStringProto(cx, args); 1.3117 + if (!rdata.str) 1.3118 + return false; 1.3119 + 1.3120 + if (!rdata.g.init(cx, args)) 1.3121 + return false; 1.3122 + 1.3123 + /* Extract replacement string/function. */ 1.3124 + if (args.length() >= ReplaceOptArg && js_IsCallable(args[1])) { 1.3125 + rdata.setReplacementFunction(&args[1].toObject()); 1.3126 + 1.3127 + if (!LambdaIsGetElem(cx, *rdata.lambda, &rdata.elembase)) 1.3128 + return false; 1.3129 + } else { 1.3130 + JSLinearString *string = ArgToRootedString(cx, args, 1); 1.3131 + if (!string) 1.3132 + return false; 1.3133 + 1.3134 + rdata.setReplacementString(string); 1.3135 + } 1.3136 + 1.3137 + rdata.fig.initFunction(ObjectOrNullValue(rdata.lambda)); 1.3138 + 1.3139 + /* 1.3140 + * Unlike its |String.prototype| brethren, |replace| doesn't convert 1.3141 + * its input to a regular expression. (Even if it contains metachars.) 1.3142 + * 1.3143 + * However, if the user invokes our (non-standard) |flags| argument 1.3144 + * extension then we revert to creating a regular expression. Note that 1.3145 + * this is observable behavior through the side-effect mutation of the 1.3146 + * |RegExp| statics. 1.3147 + */ 1.3148 + 1.3149 + const FlatMatch *fm = rdata.g.tryFlatMatch(cx, rdata.str, ReplaceOptArg, args.length(), false); 1.3150 + 1.3151 + if (!fm) { 1.3152 + if (cx->isExceptionPending()) /* oom in RopeMatch in tryFlatMatch */ 1.3153 + return false; 1.3154 + return str_replace_regexp(cx, args, rdata); 1.3155 + } 1.3156 + 1.3157 + if (fm->match() < 0) { 1.3158 + args.rval().setString(rdata.str); 1.3159 + return true; 1.3160 + } 1.3161 + 1.3162 + if (rdata.lambda) 1.3163 + return str_replace_flat_lambda(cx, args, rdata, *fm); 1.3164 + return StrReplaceString(cx, rdata, *fm, args.rval()); 1.3165 +} 1.3166 + 1.3167 +namespace { 1.3168 + 1.3169 +class SplitMatchResult { 1.3170 + size_t endIndex_; 1.3171 + size_t length_; 1.3172 + 1.3173 + public: 1.3174 + void setFailure() { 1.3175 + JS_STATIC_ASSERT(SIZE_MAX > JSString::MAX_LENGTH); 1.3176 + endIndex_ = SIZE_MAX; 1.3177 + } 1.3178 + bool isFailure() const { 1.3179 + return endIndex_ == SIZE_MAX; 1.3180 + } 1.3181 + size_t endIndex() const { 1.3182 + JS_ASSERT(!isFailure()); 1.3183 + return endIndex_; 1.3184 + } 1.3185 + size_t length() const { 1.3186 + JS_ASSERT(!isFailure()); 1.3187 + return length_; 1.3188 + } 1.3189 + void setResult(size_t length, size_t endIndex) { 1.3190 + length_ = length; 1.3191 + endIndex_ = endIndex; 1.3192 + } 1.3193 +}; 1.3194 + 1.3195 +} /* anonymous namespace */ 1.3196 + 1.3197 +template<class Matcher> 1.3198 +static ArrayObject * 1.3199 +SplitHelper(JSContext *cx, Handle<JSLinearString*> str, uint32_t limit, const Matcher &splitMatch, 1.3200 + Handle<TypeObject*> type) 1.3201 +{ 1.3202 + size_t strLength = str->length(); 1.3203 + SplitMatchResult result; 1.3204 + 1.3205 + /* Step 11. */ 1.3206 + if (strLength == 0) { 1.3207 + if (!splitMatch(cx, str, 0, &result)) 1.3208 + return nullptr; 1.3209 + 1.3210 + /* 1.3211 + * NB: Unlike in the non-empty string case, it's perfectly fine 1.3212 + * (indeed the spec requires it) if we match at the end of the 1.3213 + * string. Thus these cases should hold: 1.3214 + * 1.3215 + * var a = "".split(""); 1.3216 + * assertEq(a.length, 0); 1.3217 + * var b = "".split(/.?/); 1.3218 + * assertEq(b.length, 0); 1.3219 + */ 1.3220 + if (!result.isFailure()) 1.3221 + return NewDenseEmptyArray(cx); 1.3222 + 1.3223 + RootedValue v(cx, StringValue(str)); 1.3224 + return NewDenseCopiedArray(cx, 1, v.address()); 1.3225 + } 1.3226 + 1.3227 + /* Step 12. */ 1.3228 + size_t lastEndIndex = 0; 1.3229 + size_t index = 0; 1.3230 + 1.3231 + /* Step 13. */ 1.3232 + AutoValueVector splits(cx); 1.3233 + 1.3234 + while (index < strLength) { 1.3235 + /* Step 13(a). */ 1.3236 + if (!splitMatch(cx, str, index, &result)) 1.3237 + return nullptr; 1.3238 + 1.3239 + /* 1.3240 + * Step 13(b). 1.3241 + * 1.3242 + * Our match algorithm differs from the spec in that it returns the 1.3243 + * next index at which a match happens. If no match happens we're 1.3244 + * done. 1.3245 + * 1.3246 + * But what if the match is at the end of the string (and the string is 1.3247 + * not empty)? Per 13(c)(ii) this shouldn't be a match, so we have to 1.3248 + * specially exclude it. Thus this case should hold: 1.3249 + * 1.3250 + * var a = "abc".split(/\b/); 1.3251 + * assertEq(a.length, 1); 1.3252 + * assertEq(a[0], "abc"); 1.3253 + */ 1.3254 + if (result.isFailure()) 1.3255 + break; 1.3256 + 1.3257 + /* Step 13(c)(i). */ 1.3258 + size_t sepLength = result.length(); 1.3259 + size_t endIndex = result.endIndex(); 1.3260 + if (sepLength == 0 && endIndex == strLength) 1.3261 + break; 1.3262 + 1.3263 + /* Step 13(c)(ii). */ 1.3264 + if (endIndex == lastEndIndex) { 1.3265 + index++; 1.3266 + continue; 1.3267 + } 1.3268 + 1.3269 + /* Step 13(c)(iii). */ 1.3270 + JS_ASSERT(lastEndIndex < endIndex); 1.3271 + JS_ASSERT(sepLength <= strLength); 1.3272 + JS_ASSERT(lastEndIndex + sepLength <= endIndex); 1.3273 + 1.3274 + /* Steps 13(c)(iii)(1-3). */ 1.3275 + size_t subLength = size_t(endIndex - sepLength - lastEndIndex); 1.3276 + JSString *sub = js_NewDependentString(cx, str, lastEndIndex, subLength); 1.3277 + if (!sub || !splits.append(StringValue(sub))) 1.3278 + return nullptr; 1.3279 + 1.3280 + /* Step 13(c)(iii)(4). */ 1.3281 + if (splits.length() == limit) 1.3282 + return NewDenseCopiedArray(cx, splits.length(), splits.begin()); 1.3283 + 1.3284 + /* Step 13(c)(iii)(5). */ 1.3285 + lastEndIndex = endIndex; 1.3286 + 1.3287 + /* Step 13(c)(iii)(6-7). */ 1.3288 + if (Matcher::returnsCaptures) { 1.3289 + RegExpStatics *res = cx->global()->getRegExpStatics(); 1.3290 + const MatchPairs &matches = res->getMatches(); 1.3291 + for (size_t i = 0; i < matches.parenCount(); i++) { 1.3292 + /* Steps 13(c)(iii)(7)(a-c). */ 1.3293 + if (!matches[i + 1].isUndefined()) { 1.3294 + JSSubString parsub; 1.3295 + res->getParen(i + 1, &parsub); 1.3296 + sub = js_NewStringCopyN<CanGC>(cx, parsub.chars, parsub.length); 1.3297 + if (!sub || !splits.append(StringValue(sub))) 1.3298 + return nullptr; 1.3299 + } else { 1.3300 + /* Only string entries have been accounted for so far. */ 1.3301 + AddTypePropertyId(cx, type, JSID_VOID, UndefinedValue()); 1.3302 + if (!splits.append(UndefinedValue())) 1.3303 + return nullptr; 1.3304 + } 1.3305 + 1.3306 + /* Step 13(c)(iii)(7)(d). */ 1.3307 + if (splits.length() == limit) 1.3308 + return NewDenseCopiedArray(cx, splits.length(), splits.begin()); 1.3309 + } 1.3310 + } 1.3311 + 1.3312 + /* Step 13(c)(iii)(8). */ 1.3313 + index = lastEndIndex; 1.3314 + } 1.3315 + 1.3316 + /* Steps 14-15. */ 1.3317 + JSString *sub = js_NewDependentString(cx, str, lastEndIndex, strLength - lastEndIndex); 1.3318 + if (!sub || !splits.append(StringValue(sub))) 1.3319 + return nullptr; 1.3320 + 1.3321 + /* Step 16. */ 1.3322 + return NewDenseCopiedArray(cx, splits.length(), splits.begin()); 1.3323 +} 1.3324 + 1.3325 +// Fast-path for splitting a string into a character array via split(""). 1.3326 +static ArrayObject * 1.3327 +CharSplitHelper(JSContext *cx, Handle<JSLinearString*> str, uint32_t limit) 1.3328 +{ 1.3329 + size_t strLength = str->length(); 1.3330 + if (strLength == 0) 1.3331 + return NewDenseEmptyArray(cx); 1.3332 + 1.3333 + js::StaticStrings &staticStrings = cx->staticStrings(); 1.3334 + uint32_t resultlen = (limit < strLength ? limit : strLength); 1.3335 + 1.3336 + AutoValueVector splits(cx); 1.3337 + if (!splits.reserve(resultlen)) 1.3338 + return nullptr; 1.3339 + 1.3340 + for (size_t i = 0; i < resultlen; ++i) { 1.3341 + JSString *sub = staticStrings.getUnitStringForElement(cx, str, i); 1.3342 + if (!sub) 1.3343 + return nullptr; 1.3344 + splits.infallibleAppend(StringValue(sub)); 1.3345 + } 1.3346 + 1.3347 + return NewDenseCopiedArray(cx, splits.length(), splits.begin()); 1.3348 +} 1.3349 + 1.3350 +namespace { 1.3351 + 1.3352 +/* 1.3353 + * The SplitMatch operation from ES5 15.5.4.14 is implemented using different 1.3354 + * paths for regular expression and string separators. 1.3355 + * 1.3356 + * The algorithm differs from the spec in that the we return the next index at 1.3357 + * which a match happens. 1.3358 + */ 1.3359 +class SplitRegExpMatcher 1.3360 +{ 1.3361 + RegExpShared &re; 1.3362 + RegExpStatics *res; 1.3363 + 1.3364 + public: 1.3365 + SplitRegExpMatcher(RegExpShared &re, RegExpStatics *res) : re(re), res(res) {} 1.3366 + 1.3367 + static const bool returnsCaptures = true; 1.3368 + 1.3369 + bool operator()(JSContext *cx, Handle<JSLinearString*> str, size_t index, 1.3370 + SplitMatchResult *result) const 1.3371 + { 1.3372 + const jschar *chars = str->chars(); 1.3373 + size_t length = str->length(); 1.3374 + 1.3375 + ScopedMatchPairs matches(&cx->tempLifoAlloc()); 1.3376 + RegExpRunStatus status = re.execute(cx, chars, length, &index, matches); 1.3377 + if (status == RegExpRunStatus_Error) 1.3378 + return false; 1.3379 + 1.3380 + if (status == RegExpRunStatus_Success_NotFound) { 1.3381 + result->setFailure(); 1.3382 + return true; 1.3383 + } 1.3384 + 1.3385 + if (!res->updateFromMatchPairs(cx, str, matches)) 1.3386 + return false; 1.3387 + 1.3388 + JSSubString sep; 1.3389 + res->getLastMatch(&sep); 1.3390 + 1.3391 + result->setResult(sep.length, index); 1.3392 + return true; 1.3393 + } 1.3394 +}; 1.3395 + 1.3396 +class SplitStringMatcher 1.3397 +{ 1.3398 + Rooted<JSLinearString*> sep; 1.3399 + 1.3400 + public: 1.3401 + SplitStringMatcher(JSContext *cx, HandleLinearString sep) 1.3402 + : sep(cx, sep) 1.3403 + {} 1.3404 + 1.3405 + static const bool returnsCaptures = false; 1.3406 + 1.3407 + bool operator()(JSContext *cx, JSLinearString *str, size_t index, SplitMatchResult *res) const 1.3408 + { 1.3409 + JS_ASSERT(index == 0 || index < str->length()); 1.3410 + const jschar *chars = str->chars(); 1.3411 + int match = StringMatch(chars + index, str->length() - index, 1.3412 + sep->chars(), sep->length()); 1.3413 + if (match == -1) 1.3414 + res->setFailure(); 1.3415 + else 1.3416 + res->setResult(sep->length(), index + match + sep->length()); 1.3417 + return true; 1.3418 + } 1.3419 +}; 1.3420 + 1.3421 +} /* anonymous namespace */ 1.3422 + 1.3423 +/* ES5 15.5.4.14 */ 1.3424 +bool 1.3425 +js::str_split(JSContext *cx, unsigned argc, Value *vp) 1.3426 +{ 1.3427 + CallArgs args = CallArgsFromVp(argc, vp); 1.3428 + 1.3429 + /* Steps 1-2. */ 1.3430 + RootedString str(cx, ThisToStringForStringProto(cx, args)); 1.3431 + if (!str) 1.3432 + return false; 1.3433 + 1.3434 + RootedTypeObject type(cx, GetTypeCallerInitObject(cx, JSProto_Array)); 1.3435 + if (!type) 1.3436 + return false; 1.3437 + AddTypePropertyId(cx, type, JSID_VOID, Type::StringType()); 1.3438 + 1.3439 + /* Step 5: Use the second argument as the split limit, if given. */ 1.3440 + uint32_t limit; 1.3441 + if (args.hasDefined(1)) { 1.3442 + double d; 1.3443 + if (!ToNumber(cx, args[1], &d)) 1.3444 + return false; 1.3445 + limit = ToUint32(d); 1.3446 + } else { 1.3447 + limit = UINT32_MAX; 1.3448 + } 1.3449 + 1.3450 + /* Step 8. */ 1.3451 + RegExpGuard re(cx); 1.3452 + RootedLinearString sepstr(cx); 1.3453 + bool sepDefined = args.hasDefined(0); 1.3454 + if (sepDefined) { 1.3455 + if (IsObjectWithClass(args[0], ESClass_RegExp, cx)) { 1.3456 + RootedObject obj(cx, &args[0].toObject()); 1.3457 + if (!RegExpToShared(cx, obj, &re)) 1.3458 + return false; 1.3459 + } else { 1.3460 + sepstr = ArgToRootedString(cx, args, 0); 1.3461 + if (!sepstr) 1.3462 + return false; 1.3463 + } 1.3464 + } 1.3465 + 1.3466 + /* Step 9. */ 1.3467 + if (limit == 0) { 1.3468 + JSObject *aobj = NewDenseEmptyArray(cx); 1.3469 + if (!aobj) 1.3470 + return false; 1.3471 + aobj->setType(type); 1.3472 + args.rval().setObject(*aobj); 1.3473 + return true; 1.3474 + } 1.3475 + 1.3476 + /* Step 10. */ 1.3477 + if (!sepDefined) { 1.3478 + RootedValue v(cx, StringValue(str)); 1.3479 + JSObject *aobj = NewDenseCopiedArray(cx, 1, v.address()); 1.3480 + if (!aobj) 1.3481 + return false; 1.3482 + aobj->setType(type); 1.3483 + args.rval().setObject(*aobj); 1.3484 + return true; 1.3485 + } 1.3486 + Rooted<JSLinearString*> linearStr(cx, str->ensureLinear(cx)); 1.3487 + if (!linearStr) 1.3488 + return false; 1.3489 + 1.3490 + /* Steps 11-15. */ 1.3491 + RootedObject aobj(cx); 1.3492 + if (!re.initialized()) { 1.3493 + if (sepstr->length() == 0) { 1.3494 + aobj = CharSplitHelper(cx, linearStr, limit); 1.3495 + } else { 1.3496 + SplitStringMatcher matcher(cx, sepstr); 1.3497 + aobj = SplitHelper(cx, linearStr, limit, matcher, type); 1.3498 + } 1.3499 + } else { 1.3500 + SplitRegExpMatcher matcher(*re, cx->global()->getRegExpStatics()); 1.3501 + aobj = SplitHelper(cx, linearStr, limit, matcher, type); 1.3502 + } 1.3503 + if (!aobj) 1.3504 + return false; 1.3505 + 1.3506 + /* Step 16. */ 1.3507 + aobj->setType(type); 1.3508 + args.rval().setObject(*aobj); 1.3509 + return true; 1.3510 +} 1.3511 + 1.3512 +JSObject * 1.3513 +js::str_split_string(JSContext *cx, HandleTypeObject type, HandleString str, HandleString sep) 1.3514 +{ 1.3515 + Rooted<JSLinearString*> linearStr(cx, str->ensureLinear(cx)); 1.3516 + if (!linearStr) 1.3517 + return nullptr; 1.3518 + 1.3519 + Rooted<JSLinearString*> linearSep(cx, sep->ensureLinear(cx)); 1.3520 + if (!linearSep) 1.3521 + return nullptr; 1.3522 + 1.3523 + uint32_t limit = UINT32_MAX; 1.3524 + 1.3525 + RootedObject aobj(cx); 1.3526 + if (linearSep->length() == 0) { 1.3527 + aobj = CharSplitHelper(cx, linearStr, limit); 1.3528 + } else { 1.3529 + SplitStringMatcher matcher(cx, linearSep); 1.3530 + aobj = SplitHelper(cx, linearStr, limit, matcher, type); 1.3531 + } 1.3532 + 1.3533 + if (!aobj) 1.3534 + return nullptr; 1.3535 + 1.3536 + aobj->setType(type); 1.3537 + return aobj; 1.3538 +} 1.3539 + 1.3540 +static bool 1.3541 +str_substr(JSContext *cx, unsigned argc, Value *vp) 1.3542 +{ 1.3543 + CallArgs args = CallArgsFromVp(argc, vp); 1.3544 + RootedString str(cx, ThisToStringForStringProto(cx, args)); 1.3545 + if (!str) 1.3546 + return false; 1.3547 + 1.3548 + int32_t length, len, begin; 1.3549 + if (args.length() > 0) { 1.3550 + length = int32_t(str->length()); 1.3551 + if (!ValueToIntegerRange(cx, args[0], &begin)) 1.3552 + return false; 1.3553 + 1.3554 + if (begin >= length) { 1.3555 + args.rval().setString(cx->runtime()->emptyString); 1.3556 + return true; 1.3557 + } 1.3558 + if (begin < 0) { 1.3559 + begin += length; /* length + INT_MIN will always be less than 0 */ 1.3560 + if (begin < 0) 1.3561 + begin = 0; 1.3562 + } 1.3563 + 1.3564 + if (args.hasDefined(1)) { 1.3565 + if (!ValueToIntegerRange(cx, args[1], &len)) 1.3566 + return false; 1.3567 + 1.3568 + if (len <= 0) { 1.3569 + args.rval().setString(cx->runtime()->emptyString); 1.3570 + return true; 1.3571 + } 1.3572 + 1.3573 + if (uint32_t(length) < uint32_t(begin + len)) 1.3574 + len = length - begin; 1.3575 + } else { 1.3576 + len = length - begin; 1.3577 + } 1.3578 + 1.3579 + str = DoSubstr(cx, str, size_t(begin), size_t(len)); 1.3580 + if (!str) 1.3581 + return false; 1.3582 + } 1.3583 + 1.3584 + args.rval().setString(str); 1.3585 + return true; 1.3586 +} 1.3587 + 1.3588 +/* 1.3589 + * Python-esque sequence operations. 1.3590 + */ 1.3591 +static bool 1.3592 +str_concat(JSContext *cx, unsigned argc, Value *vp) 1.3593 +{ 1.3594 + CallArgs args = CallArgsFromVp(argc, vp); 1.3595 + JSString *str = ThisToStringForStringProto(cx, args); 1.3596 + if (!str) 1.3597 + return false; 1.3598 + 1.3599 + for (unsigned i = 0; i < args.length(); i++) { 1.3600 + JSString *argStr = ToString<NoGC>(cx, args[i]); 1.3601 + if (!argStr) { 1.3602 + RootedString strRoot(cx, str); 1.3603 + argStr = ToString<CanGC>(cx, args[i]); 1.3604 + if (!argStr) 1.3605 + return false; 1.3606 + str = strRoot; 1.3607 + } 1.3608 + 1.3609 + JSString *next = ConcatStrings<NoGC>(cx, str, argStr); 1.3610 + if (next) { 1.3611 + str = next; 1.3612 + } else { 1.3613 + RootedString strRoot(cx, str), argStrRoot(cx, argStr); 1.3614 + str = ConcatStrings<CanGC>(cx, strRoot, argStrRoot); 1.3615 + if (!str) 1.3616 + return false; 1.3617 + } 1.3618 + } 1.3619 + 1.3620 + args.rval().setString(str); 1.3621 + return true; 1.3622 +} 1.3623 + 1.3624 +static bool 1.3625 +str_slice(JSContext *cx, unsigned argc, Value *vp) 1.3626 +{ 1.3627 + CallArgs args = CallArgsFromVp(argc, vp); 1.3628 + 1.3629 + if (args.length() == 1 && args.thisv().isString() && args[0].isInt32()) { 1.3630 + JSString *str = args.thisv().toString(); 1.3631 + size_t begin = args[0].toInt32(); 1.3632 + size_t end = str->length(); 1.3633 + if (begin <= end) { 1.3634 + size_t length = end - begin; 1.3635 + if (length == 0) { 1.3636 + str = cx->runtime()->emptyString; 1.3637 + } else { 1.3638 + str = (length == 1) 1.3639 + ? cx->staticStrings().getUnitStringForElement(cx, str, begin) 1.3640 + : js_NewDependentString(cx, str, begin, length); 1.3641 + if (!str) 1.3642 + return false; 1.3643 + } 1.3644 + args.rval().setString(str); 1.3645 + return true; 1.3646 + } 1.3647 + } 1.3648 + 1.3649 + RootedString str(cx, ThisToStringForStringProto(cx, args)); 1.3650 + if (!str) 1.3651 + return false; 1.3652 + 1.3653 + if (args.length() != 0) { 1.3654 + double begin, end, length; 1.3655 + 1.3656 + if (!ToInteger(cx, args[0], &begin)) 1.3657 + return false; 1.3658 + length = str->length(); 1.3659 + if (begin < 0) { 1.3660 + begin += length; 1.3661 + if (begin < 0) 1.3662 + begin = 0; 1.3663 + } else if (begin > length) { 1.3664 + begin = length; 1.3665 + } 1.3666 + 1.3667 + if (args.hasDefined(1)) { 1.3668 + if (!ToInteger(cx, args[1], &end)) 1.3669 + return false; 1.3670 + if (end < 0) { 1.3671 + end += length; 1.3672 + if (end < 0) 1.3673 + end = 0; 1.3674 + } else if (end > length) { 1.3675 + end = length; 1.3676 + } 1.3677 + if (end < begin) 1.3678 + end = begin; 1.3679 + } else { 1.3680 + end = length; 1.3681 + } 1.3682 + 1.3683 + str = js_NewDependentString(cx, str, 1.3684 + (size_t)begin, 1.3685 + (size_t)(end - begin)); 1.3686 + if (!str) 1.3687 + return false; 1.3688 + } 1.3689 + args.rval().setString(str); 1.3690 + return true; 1.3691 +} 1.3692 + 1.3693 +#if JS_HAS_STR_HTML_HELPERS 1.3694 +/* 1.3695 + * HTML composition aids. 1.3696 + */ 1.3697 +static bool 1.3698 +tagify(JSContext *cx, const char *begin, HandleLinearString param, const char *end, 1.3699 + CallReceiver call) 1.3700 +{ 1.3701 + JSString *thisstr = ThisToStringForStringProto(cx, call); 1.3702 + if (!thisstr) 1.3703 + return false; 1.3704 + 1.3705 + JSLinearString *str = thisstr->ensureLinear(cx); 1.3706 + if (!str) 1.3707 + return false; 1.3708 + 1.3709 + if (!end) 1.3710 + end = begin; 1.3711 + 1.3712 + size_t beglen = strlen(begin); 1.3713 + size_t taglen = 1 + beglen + 1; /* '<begin' + '>' */ 1.3714 + if (param) { 1.3715 + size_t numChars = param->length(); 1.3716 + const jschar *parchars = param->chars(); 1.3717 + for (size_t i = 0, parlen = numChars; i < parlen; ++i) { 1.3718 + if (parchars[i] == '"') 1.3719 + numChars += 5; /* len(") - len(") */ 1.3720 + } 1.3721 + taglen += 2 + numChars + 1; /* '="param"' */ 1.3722 + } 1.3723 + size_t endlen = strlen(end); 1.3724 + taglen += str->length() + 2 + endlen + 1; /* 'str</end>' */ 1.3725 + 1.3726 + 1.3727 + StringBuffer sb(cx); 1.3728 + if (!sb.reserve(taglen)) 1.3729 + return false; 1.3730 + 1.3731 + sb.infallibleAppend('<'); 1.3732 + 1.3733 + MOZ_ALWAYS_TRUE(sb.appendInflated(begin, beglen)); 1.3734 + 1.3735 + if (param) { 1.3736 + sb.infallibleAppend('='); 1.3737 + sb.infallibleAppend('"'); 1.3738 + const jschar *parchars = param->chars(); 1.3739 + for (size_t i = 0, parlen = param->length(); i < parlen; ++i) { 1.3740 + if (parchars[i] != '"') { 1.3741 + sb.infallibleAppend(parchars[i]); 1.3742 + } else { 1.3743 + MOZ_ALWAYS_TRUE(sb.append(""")); 1.3744 + } 1.3745 + } 1.3746 + sb.infallibleAppend('"'); 1.3747 + } 1.3748 + 1.3749 + sb.infallibleAppend('>'); 1.3750 + 1.3751 + MOZ_ALWAYS_TRUE(sb.append(str)); 1.3752 + 1.3753 + sb.infallibleAppend('<'); 1.3754 + sb.infallibleAppend('/'); 1.3755 + 1.3756 + MOZ_ALWAYS_TRUE(sb.appendInflated(end, endlen)); 1.3757 + 1.3758 + sb.infallibleAppend('>'); 1.3759 + 1.3760 + JSFlatString *retstr = sb.finishString(); 1.3761 + if (!retstr) 1.3762 + return false; 1.3763 + 1.3764 + call.rval().setString(retstr); 1.3765 + return true; 1.3766 +} 1.3767 + 1.3768 +static bool 1.3769 +tagify_value(JSContext *cx, CallArgs args, const char *begin, const char *end) 1.3770 +{ 1.3771 + RootedLinearString param(cx, ArgToRootedString(cx, args, 0)); 1.3772 + if (!param) 1.3773 + return false; 1.3774 + 1.3775 + return tagify(cx, begin, param, end, args); 1.3776 +} 1.3777 + 1.3778 +static bool 1.3779 +str_bold(JSContext *cx, unsigned argc, Value *vp) 1.3780 +{ 1.3781 + return tagify(cx, "b", NullPtr(), nullptr, CallReceiverFromVp(vp)); 1.3782 +} 1.3783 + 1.3784 +static bool 1.3785 +str_italics(JSContext *cx, unsigned argc, Value *vp) 1.3786 +{ 1.3787 + return tagify(cx, "i", NullPtr(), nullptr, CallReceiverFromVp(vp)); 1.3788 +} 1.3789 + 1.3790 +static bool 1.3791 +str_fixed(JSContext *cx, unsigned argc, Value *vp) 1.3792 +{ 1.3793 + return tagify(cx, "tt", NullPtr(), nullptr, CallReceiverFromVp(vp)); 1.3794 +} 1.3795 + 1.3796 +static bool 1.3797 +str_fontsize(JSContext *cx, unsigned argc, Value *vp) 1.3798 +{ 1.3799 + return tagify_value(cx, CallArgsFromVp(argc, vp), "font size", "font"); 1.3800 +} 1.3801 + 1.3802 +static bool 1.3803 +str_fontcolor(JSContext *cx, unsigned argc, Value *vp) 1.3804 +{ 1.3805 + return tagify_value(cx, CallArgsFromVp(argc, vp), "font color", "font"); 1.3806 +} 1.3807 + 1.3808 +static bool 1.3809 +str_link(JSContext *cx, unsigned argc, Value *vp) 1.3810 +{ 1.3811 + return tagify_value(cx, CallArgsFromVp(argc, vp), "a href", "a"); 1.3812 +} 1.3813 + 1.3814 +static bool 1.3815 +str_anchor(JSContext *cx, unsigned argc, Value *vp) 1.3816 +{ 1.3817 + return tagify_value(cx, CallArgsFromVp(argc, vp), "a name", "a"); 1.3818 +} 1.3819 + 1.3820 +static bool 1.3821 +str_strike(JSContext *cx, unsigned argc, Value *vp) 1.3822 +{ 1.3823 + return tagify(cx, "strike", NullPtr(), nullptr, CallReceiverFromVp(vp)); 1.3824 +} 1.3825 + 1.3826 +static bool 1.3827 +str_small(JSContext *cx, unsigned argc, Value *vp) 1.3828 +{ 1.3829 + return tagify(cx, "small", NullPtr(), nullptr, CallReceiverFromVp(vp)); 1.3830 +} 1.3831 + 1.3832 +static bool 1.3833 +str_big(JSContext *cx, unsigned argc, Value *vp) 1.3834 +{ 1.3835 + return tagify(cx, "big", NullPtr(), nullptr, CallReceiverFromVp(vp)); 1.3836 +} 1.3837 + 1.3838 +static bool 1.3839 +str_blink(JSContext *cx, unsigned argc, Value *vp) 1.3840 +{ 1.3841 + return tagify(cx, "blink", NullPtr(), nullptr, CallReceiverFromVp(vp)); 1.3842 +} 1.3843 + 1.3844 +static bool 1.3845 +str_sup(JSContext *cx, unsigned argc, Value *vp) 1.3846 +{ 1.3847 + return tagify(cx, "sup", NullPtr(), nullptr, CallReceiverFromVp(vp)); 1.3848 +} 1.3849 + 1.3850 +static bool 1.3851 +str_sub(JSContext *cx, unsigned argc, Value *vp) 1.3852 +{ 1.3853 + return tagify(cx, "sub", NullPtr(), nullptr, CallReceiverFromVp(vp)); 1.3854 +} 1.3855 +#endif /* JS_HAS_STR_HTML_HELPERS */ 1.3856 + 1.3857 +static const JSFunctionSpec string_methods[] = { 1.3858 +#if JS_HAS_TOSOURCE 1.3859 + JS_FN("quote", str_quote, 0,JSFUN_GENERIC_NATIVE), 1.3860 + JS_FN(js_toSource_str, str_toSource, 0,0), 1.3861 +#endif 1.3862 + 1.3863 + /* Java-like methods. */ 1.3864 + JS_FN(js_toString_str, js_str_toString, 0,0), 1.3865 + JS_FN(js_valueOf_str, js_str_toString, 0,0), 1.3866 + JS_FN("substring", str_substring, 2,JSFUN_GENERIC_NATIVE), 1.3867 + JS_FN("toLowerCase", str_toLowerCase, 0,JSFUN_GENERIC_NATIVE), 1.3868 + JS_FN("toUpperCase", str_toUpperCase, 0,JSFUN_GENERIC_NATIVE), 1.3869 + JS_FN("charAt", js_str_charAt, 1,JSFUN_GENERIC_NATIVE), 1.3870 + JS_FN("charCodeAt", js_str_charCodeAt, 1,JSFUN_GENERIC_NATIVE), 1.3871 + JS_SELF_HOSTED_FN("codePointAt", "String_codePointAt", 1,0), 1.3872 + JS_FN("contains", str_contains, 1,JSFUN_GENERIC_NATIVE), 1.3873 + JS_FN("indexOf", str_indexOf, 1,JSFUN_GENERIC_NATIVE), 1.3874 + JS_FN("lastIndexOf", str_lastIndexOf, 1,JSFUN_GENERIC_NATIVE), 1.3875 + JS_FN("startsWith", str_startsWith, 1,JSFUN_GENERIC_NATIVE), 1.3876 + JS_FN("endsWith", str_endsWith, 1,JSFUN_GENERIC_NATIVE), 1.3877 + JS_FN("trim", str_trim, 0,JSFUN_GENERIC_NATIVE), 1.3878 + JS_FN("trimLeft", str_trimLeft, 0,JSFUN_GENERIC_NATIVE), 1.3879 + JS_FN("trimRight", str_trimRight, 0,JSFUN_GENERIC_NATIVE), 1.3880 + JS_FN("toLocaleLowerCase", str_toLocaleLowerCase, 0,JSFUN_GENERIC_NATIVE), 1.3881 + JS_FN("toLocaleUpperCase", str_toLocaleUpperCase, 0,JSFUN_GENERIC_NATIVE), 1.3882 +#if EXPOSE_INTL_API 1.3883 + JS_SELF_HOSTED_FN("localeCompare", "String_localeCompare", 1,0), 1.3884 +#else 1.3885 + JS_FN("localeCompare", str_localeCompare, 1,JSFUN_GENERIC_NATIVE), 1.3886 +#endif 1.3887 + JS_SELF_HOSTED_FN("repeat", "String_repeat", 1,0), 1.3888 +#if EXPOSE_INTL_API 1.3889 + JS_FN("normalize", str_normalize, 0,JSFUN_GENERIC_NATIVE), 1.3890 +#endif 1.3891 + 1.3892 + /* Perl-ish methods (search is actually Python-esque). */ 1.3893 + JS_FN("match", str_match, 1,JSFUN_GENERIC_NATIVE), 1.3894 + JS_FN("search", str_search, 1,JSFUN_GENERIC_NATIVE), 1.3895 + JS_FN("replace", str_replace, 2,JSFUN_GENERIC_NATIVE), 1.3896 + JS_FN("split", str_split, 2,JSFUN_GENERIC_NATIVE), 1.3897 + JS_FN("substr", str_substr, 2,JSFUN_GENERIC_NATIVE), 1.3898 + 1.3899 + /* Python-esque sequence methods. */ 1.3900 + JS_FN("concat", str_concat, 1,JSFUN_GENERIC_NATIVE), 1.3901 + JS_FN("slice", str_slice, 2,JSFUN_GENERIC_NATIVE), 1.3902 + 1.3903 + /* HTML string methods. */ 1.3904 +#if JS_HAS_STR_HTML_HELPERS 1.3905 + JS_FN("bold", str_bold, 0,0), 1.3906 + JS_FN("italics", str_italics, 0,0), 1.3907 + JS_FN("fixed", str_fixed, 0,0), 1.3908 + JS_FN("fontsize", str_fontsize, 1,0), 1.3909 + JS_FN("fontcolor", str_fontcolor, 1,0), 1.3910 + JS_FN("link", str_link, 1,0), 1.3911 + JS_FN("anchor", str_anchor, 1,0), 1.3912 + JS_FN("strike", str_strike, 0,0), 1.3913 + JS_FN("small", str_small, 0,0), 1.3914 + JS_FN("big", str_big, 0,0), 1.3915 + JS_FN("blink", str_blink, 0,0), 1.3916 + JS_FN("sup", str_sup, 0,0), 1.3917 + JS_FN("sub", str_sub, 0,0), 1.3918 +#endif 1.3919 + JS_SELF_HOSTED_FN("@@iterator", "String_iterator", 0,0), 1.3920 + JS_FS_END 1.3921 +}; 1.3922 + 1.3923 +bool 1.3924 +js_String(JSContext *cx, unsigned argc, Value *vp) 1.3925 +{ 1.3926 + CallArgs args = CallArgsFromVp(argc, vp); 1.3927 + 1.3928 + RootedString str(cx); 1.3929 + if (args.length() > 0) { 1.3930 + str = ToString<CanGC>(cx, args[0]); 1.3931 + if (!str) 1.3932 + return false; 1.3933 + } else { 1.3934 + str = cx->runtime()->emptyString; 1.3935 + } 1.3936 + 1.3937 + if (args.isConstructing()) { 1.3938 + StringObject *strobj = StringObject::create(cx, str); 1.3939 + if (!strobj) 1.3940 + return false; 1.3941 + args.rval().setObject(*strobj); 1.3942 + return true; 1.3943 + } 1.3944 + 1.3945 + args.rval().setString(str); 1.3946 + return true; 1.3947 +} 1.3948 + 1.3949 +bool 1.3950 +js::str_fromCharCode(JSContext *cx, unsigned argc, Value *vp) 1.3951 +{ 1.3952 + CallArgs args = CallArgsFromVp(argc, vp); 1.3953 + 1.3954 + JS_ASSERT(args.length() <= ARGS_LENGTH_MAX); 1.3955 + if (args.length() == 1) { 1.3956 + uint16_t code; 1.3957 + if (!ToUint16(cx, args[0], &code)) 1.3958 + return false; 1.3959 + if (StaticStrings::hasUnit(code)) { 1.3960 + args.rval().setString(cx->staticStrings().getUnit(code)); 1.3961 + return true; 1.3962 + } 1.3963 + args[0].setInt32(code); 1.3964 + } 1.3965 + jschar *chars = cx->pod_malloc<jschar>(args.length() + 1); 1.3966 + if (!chars) 1.3967 + return false; 1.3968 + for (unsigned i = 0; i < args.length(); i++) { 1.3969 + uint16_t code; 1.3970 + if (!ToUint16(cx, args[i], &code)) { 1.3971 + js_free(chars); 1.3972 + return false; 1.3973 + } 1.3974 + chars[i] = (jschar)code; 1.3975 + } 1.3976 + chars[args.length()] = 0; 1.3977 + JSString *str = js_NewString<CanGC>(cx, chars, args.length()); 1.3978 + if (!str) { 1.3979 + js_free(chars); 1.3980 + return false; 1.3981 + } 1.3982 + 1.3983 + args.rval().setString(str); 1.3984 + return true; 1.3985 +} 1.3986 + 1.3987 +static const JSFunctionSpec string_static_methods[] = { 1.3988 + JS_FN("fromCharCode", js::str_fromCharCode, 1, 0), 1.3989 + JS_SELF_HOSTED_FN("fromCodePoint", "String_static_fromCodePoint", 0,0), 1.3990 + 1.3991 + // This must be at the end because of bug 853075: functions listed after 1.3992 + // self-hosted methods aren't available in self-hosted code. 1.3993 +#if EXPOSE_INTL_API 1.3994 + JS_SELF_HOSTED_FN("localeCompare", "String_static_localeCompare", 2,0), 1.3995 +#endif 1.3996 + JS_FS_END 1.3997 +}; 1.3998 + 1.3999 +/* static */ Shape * 1.4000 +StringObject::assignInitialShape(ExclusiveContext *cx, Handle<StringObject*> obj) 1.4001 +{ 1.4002 + JS_ASSERT(obj->nativeEmpty()); 1.4003 + 1.4004 + return obj->addDataProperty(cx, cx->names().length, LENGTH_SLOT, 1.4005 + JSPROP_PERMANENT | JSPROP_READONLY); 1.4006 +} 1.4007 + 1.4008 +JSObject * 1.4009 +js_InitStringClass(JSContext *cx, HandleObject obj) 1.4010 +{ 1.4011 + JS_ASSERT(obj->isNative()); 1.4012 + 1.4013 + Rooted<GlobalObject*> global(cx, &obj->as<GlobalObject>()); 1.4014 + 1.4015 + Rooted<JSString*> empty(cx, cx->runtime()->emptyString); 1.4016 + RootedObject proto(cx, global->createBlankPrototype(cx, &StringObject::class_)); 1.4017 + if (!proto || !proto->as<StringObject>().init(cx, empty)) 1.4018 + return nullptr; 1.4019 + 1.4020 + /* Now create the String function. */ 1.4021 + RootedFunction ctor(cx); 1.4022 + ctor = global->createConstructor(cx, js_String, cx->names().String, 1); 1.4023 + if (!ctor) 1.4024 + return nullptr; 1.4025 + 1.4026 + if (!LinkConstructorAndPrototype(cx, ctor, proto)) 1.4027 + return nullptr; 1.4028 + 1.4029 + if (!DefinePropertiesAndBrand(cx, proto, nullptr, string_methods) || 1.4030 + !DefinePropertiesAndBrand(cx, ctor, nullptr, string_static_methods)) 1.4031 + { 1.4032 + return nullptr; 1.4033 + } 1.4034 + 1.4035 + if (!GlobalObject::initBuiltinConstructor(cx, global, JSProto_String, ctor, proto)) 1.4036 + return nullptr; 1.4037 + 1.4038 + /* 1.4039 + * Define escape/unescape, the URI encode/decode functions, and maybe 1.4040 + * uneval on the global object. 1.4041 + */ 1.4042 + if (!JS_DefineFunctions(cx, global, string_functions)) 1.4043 + return nullptr; 1.4044 + 1.4045 + return proto; 1.4046 +} 1.4047 + 1.4048 +template <AllowGC allowGC> 1.4049 +JSFlatString * 1.4050 +js_NewString(ThreadSafeContext *cx, jschar *chars, size_t length) 1.4051 +{ 1.4052 + if (length == 1) { 1.4053 + jschar c = chars[0]; 1.4054 + if (StaticStrings::hasUnit(c)) { 1.4055 + // Free |chars| because we're taking possession of it, but it's no 1.4056 + // longer needed because we use the static string instead. 1.4057 + js_free(chars); 1.4058 + return cx->staticStrings().getUnit(c); 1.4059 + } 1.4060 + } 1.4061 + 1.4062 + return JSFlatString::new_<allowGC>(cx, chars, length); 1.4063 +} 1.4064 + 1.4065 +template JSFlatString * 1.4066 +js_NewString<CanGC>(ThreadSafeContext *cx, jschar *chars, size_t length); 1.4067 + 1.4068 +template JSFlatString * 1.4069 +js_NewString<NoGC>(ThreadSafeContext *cx, jschar *chars, size_t length); 1.4070 + 1.4071 +JSLinearString * 1.4072 +js_NewDependentString(JSContext *cx, JSString *baseArg, size_t start, size_t length) 1.4073 +{ 1.4074 + if (length == 0) 1.4075 + return cx->emptyString(); 1.4076 + 1.4077 + JSLinearString *base = baseArg->ensureLinear(cx); 1.4078 + if (!base) 1.4079 + return nullptr; 1.4080 + 1.4081 + if (start == 0 && length == base->length()) 1.4082 + return base; 1.4083 + 1.4084 + const jschar *chars = base->chars() + start; 1.4085 + 1.4086 + if (JSLinearString *staticStr = cx->staticStrings().lookup(chars, length)) 1.4087 + return staticStr; 1.4088 + 1.4089 + return JSDependentString::new_(cx, base, chars, length); 1.4090 +} 1.4091 + 1.4092 +template <AllowGC allowGC> 1.4093 +JSFlatString * 1.4094 +js_NewStringCopyN(ExclusiveContext *cx, const jschar *s, size_t n) 1.4095 +{ 1.4096 + if (JSFatInlineString::lengthFits(n)) 1.4097 + return NewFatInlineString<allowGC>(cx, TwoByteChars(s, n)); 1.4098 + 1.4099 + jschar *news = cx->pod_malloc<jschar>(n + 1); 1.4100 + if (!news) 1.4101 + return nullptr; 1.4102 + js_strncpy(news, s, n); 1.4103 + news[n] = 0; 1.4104 + JSFlatString *str = js_NewString<allowGC>(cx, news, n); 1.4105 + if (!str) 1.4106 + js_free(news); 1.4107 + return str; 1.4108 +} 1.4109 + 1.4110 +template JSFlatString * 1.4111 +js_NewStringCopyN<CanGC>(ExclusiveContext *cx, const jschar *s, size_t n); 1.4112 + 1.4113 +template JSFlatString * 1.4114 +js_NewStringCopyN<NoGC>(ExclusiveContext *cx, const jschar *s, size_t n); 1.4115 + 1.4116 +template <AllowGC allowGC> 1.4117 +JSFlatString * 1.4118 +js_NewStringCopyN(ThreadSafeContext *cx, const char *s, size_t n) 1.4119 +{ 1.4120 + if (JSFatInlineString::lengthFits(n)) 1.4121 + return NewFatInlineString<allowGC>(cx, JS::Latin1Chars(s, n)); 1.4122 + 1.4123 + jschar *chars = InflateString(cx, s, &n); 1.4124 + if (!chars) 1.4125 + return nullptr; 1.4126 + JSFlatString *str = js_NewString<allowGC>(cx, chars, n); 1.4127 + if (!str) 1.4128 + js_free(chars); 1.4129 + return str; 1.4130 +} 1.4131 + 1.4132 +template JSFlatString * 1.4133 +js_NewStringCopyN<CanGC>(ThreadSafeContext *cx, const char *s, size_t n); 1.4134 + 1.4135 +template JSFlatString * 1.4136 +js_NewStringCopyN<NoGC>(ThreadSafeContext *cx, const char *s, size_t n); 1.4137 + 1.4138 +template <AllowGC allowGC> 1.4139 +JSFlatString * 1.4140 +js_NewStringCopyZ(ExclusiveContext *cx, const jschar *s) 1.4141 +{ 1.4142 + size_t n = js_strlen(s); 1.4143 + if (JSFatInlineString::lengthFits(n)) 1.4144 + return NewFatInlineString<allowGC>(cx, TwoByteChars(s, n)); 1.4145 + 1.4146 + size_t m = (n + 1) * sizeof(jschar); 1.4147 + jschar *news = (jschar *) cx->malloc_(m); 1.4148 + if (!news) 1.4149 + return nullptr; 1.4150 + js_memcpy(news, s, m); 1.4151 + JSFlatString *str = js_NewString<allowGC>(cx, news, n); 1.4152 + if (!str) 1.4153 + js_free(news); 1.4154 + return str; 1.4155 +} 1.4156 + 1.4157 +template JSFlatString * 1.4158 +js_NewStringCopyZ<CanGC>(ExclusiveContext *cx, const jschar *s); 1.4159 + 1.4160 +template JSFlatString * 1.4161 +js_NewStringCopyZ<NoGC>(ExclusiveContext *cx, const jschar *s); 1.4162 + 1.4163 +template <AllowGC allowGC> 1.4164 +JSFlatString * 1.4165 +js_NewStringCopyZ(ThreadSafeContext *cx, const char *s) 1.4166 +{ 1.4167 + return js_NewStringCopyN<allowGC>(cx, s, strlen(s)); 1.4168 +} 1.4169 + 1.4170 +template JSFlatString * 1.4171 +js_NewStringCopyZ<CanGC>(ThreadSafeContext *cx, const char *s); 1.4172 + 1.4173 +template JSFlatString * 1.4174 +js_NewStringCopyZ<NoGC>(ThreadSafeContext *cx, const char *s); 1.4175 + 1.4176 +const char * 1.4177 +js_ValueToPrintable(JSContext *cx, const Value &vArg, JSAutoByteString *bytes, bool asSource) 1.4178 +{ 1.4179 + RootedValue v(cx, vArg); 1.4180 + JSString *str; 1.4181 + if (asSource) 1.4182 + str = ValueToSource(cx, v); 1.4183 + else 1.4184 + str = ToString<CanGC>(cx, v); 1.4185 + if (!str) 1.4186 + return nullptr; 1.4187 + str = js_QuoteString(cx, str, 0); 1.4188 + if (!str) 1.4189 + return nullptr; 1.4190 + return bytes->encodeLatin1(cx, str); 1.4191 +} 1.4192 + 1.4193 +template <AllowGC allowGC> 1.4194 +JSString * 1.4195 +js::ToStringSlow(ExclusiveContext *cx, typename MaybeRooted<Value, allowGC>::HandleType arg) 1.4196 +{ 1.4197 + /* As with ToObjectSlow, callers must verify that |arg| isn't a string. */ 1.4198 + JS_ASSERT(!arg.isString()); 1.4199 + 1.4200 + Value v = arg; 1.4201 + if (!v.isPrimitive()) { 1.4202 + if (!cx->shouldBeJSContext() || !allowGC) 1.4203 + return nullptr; 1.4204 + RootedValue v2(cx, v); 1.4205 + if (!ToPrimitive(cx->asJSContext(), JSTYPE_STRING, &v2)) 1.4206 + return nullptr; 1.4207 + v = v2; 1.4208 + } 1.4209 + 1.4210 + JSString *str; 1.4211 + if (v.isString()) { 1.4212 + str = v.toString(); 1.4213 + } else if (v.isInt32()) { 1.4214 + str = Int32ToString<allowGC>(cx, v.toInt32()); 1.4215 + } else if (v.isDouble()) { 1.4216 + str = NumberToString<allowGC>(cx, v.toDouble()); 1.4217 + } else if (v.isBoolean()) { 1.4218 + str = js_BooleanToString(cx, v.toBoolean()); 1.4219 + } else if (v.isNull()) { 1.4220 + str = cx->names().null; 1.4221 + } else { 1.4222 + str = cx->names().undefined; 1.4223 + } 1.4224 + return str; 1.4225 +} 1.4226 + 1.4227 +template JSString * 1.4228 +js::ToStringSlow<CanGC>(ExclusiveContext *cx, HandleValue arg); 1.4229 + 1.4230 +template JSString * 1.4231 +js::ToStringSlow<NoGC>(ExclusiveContext *cx, Value arg); 1.4232 + 1.4233 +JS_PUBLIC_API(JSString *) 1.4234 +js::ToStringSlow(JSContext *cx, HandleValue v) 1.4235 +{ 1.4236 + return ToStringSlow<CanGC>(cx, v); 1.4237 +} 1.4238 + 1.4239 +JSString * 1.4240 +js::ValueToSource(JSContext *cx, HandleValue v) 1.4241 +{ 1.4242 + JS_CHECK_RECURSION(cx, return nullptr); 1.4243 + assertSameCompartment(cx, v); 1.4244 + 1.4245 + if (v.isUndefined()) 1.4246 + return cx->names().void0; 1.4247 + if (v.isString()) 1.4248 + return StringToSource(cx, v.toString()); 1.4249 + if (v.isPrimitive()) { 1.4250 + /* Special case to preserve negative zero, _contra_ toString. */ 1.4251 + if (v.isDouble() && IsNegativeZero(v.toDouble())) { 1.4252 + /* NB: _ucNstr rather than _ucstr to indicate non-terminated. */ 1.4253 + static const jschar js_negzero_ucNstr[] = {'-', '0'}; 1.4254 + 1.4255 + return js_NewStringCopyN<CanGC>(cx, js_negzero_ucNstr, 2); 1.4256 + } 1.4257 + return ToString<CanGC>(cx, v); 1.4258 + } 1.4259 + 1.4260 + RootedValue fval(cx); 1.4261 + RootedObject obj(cx, &v.toObject()); 1.4262 + if (!JSObject::getProperty(cx, obj, obj, cx->names().toSource, &fval)) 1.4263 + return nullptr; 1.4264 + if (js_IsCallable(fval)) { 1.4265 + RootedValue rval(cx); 1.4266 + if (!Invoke(cx, ObjectValue(*obj), fval, 0, nullptr, &rval)) 1.4267 + return nullptr; 1.4268 + return ToString<CanGC>(cx, rval); 1.4269 + } 1.4270 + 1.4271 + return ObjectToSource(cx, obj); 1.4272 +} 1.4273 + 1.4274 +JSString * 1.4275 +js::StringToSource(JSContext *cx, JSString *str) 1.4276 +{ 1.4277 + return js_QuoteString(cx, str, '"'); 1.4278 +} 1.4279 + 1.4280 +bool 1.4281 +js::EqualStrings(JSContext *cx, JSString *str1, JSString *str2, bool *result) 1.4282 +{ 1.4283 + if (str1 == str2) { 1.4284 + *result = true; 1.4285 + return true; 1.4286 + } 1.4287 + 1.4288 + size_t length1 = str1->length(); 1.4289 + if (length1 != str2->length()) { 1.4290 + *result = false; 1.4291 + return true; 1.4292 + } 1.4293 + 1.4294 + JSLinearString *linear1 = str1->ensureLinear(cx); 1.4295 + if (!linear1) 1.4296 + return false; 1.4297 + JSLinearString *linear2 = str2->ensureLinear(cx); 1.4298 + if (!linear2) 1.4299 + return false; 1.4300 + 1.4301 + *result = PodEqual(linear1->chars(), linear2->chars(), length1); 1.4302 + return true; 1.4303 +} 1.4304 + 1.4305 +bool 1.4306 +js::EqualStrings(JSLinearString *str1, JSLinearString *str2) 1.4307 +{ 1.4308 + if (str1 == str2) 1.4309 + return true; 1.4310 + 1.4311 + size_t length1 = str1->length(); 1.4312 + if (length1 != str2->length()) 1.4313 + return false; 1.4314 + 1.4315 + return PodEqual(str1->chars(), str2->chars(), length1); 1.4316 +} 1.4317 + 1.4318 +static bool 1.4319 +CompareStringsImpl(JSContext *cx, JSString *str1, JSString *str2, int32_t *result) 1.4320 +{ 1.4321 + JS_ASSERT(str1); 1.4322 + JS_ASSERT(str2); 1.4323 + 1.4324 + if (str1 == str2) { 1.4325 + *result = 0; 1.4326 + return true; 1.4327 + } 1.4328 + 1.4329 + const jschar *s1 = str1->getChars(cx); 1.4330 + if (!s1) 1.4331 + return false; 1.4332 + 1.4333 + const jschar *s2 = str2->getChars(cx); 1.4334 + if (!s2) 1.4335 + return false; 1.4336 + 1.4337 + *result = CompareChars(s1, str1->length(), s2, str2->length()); 1.4338 + return true; 1.4339 +} 1.4340 + 1.4341 +bool 1.4342 +js::CompareStrings(JSContext *cx, JSString *str1, JSString *str2, int32_t *result) 1.4343 +{ 1.4344 + return CompareStringsImpl(cx, str1, str2, result); 1.4345 +} 1.4346 + 1.4347 +int32_t 1.4348 +js::CompareAtoms(JSAtom *atom1, JSAtom *atom2) 1.4349 +{ 1.4350 + return CompareChars(atom1->chars(), atom1->length(), atom2->chars(), atom2->length()); 1.4351 +} 1.4352 + 1.4353 +bool 1.4354 +js::StringEqualsAscii(JSLinearString *str, const char *asciiBytes) 1.4355 +{ 1.4356 + size_t length = strlen(asciiBytes); 1.4357 +#ifdef DEBUG 1.4358 + for (size_t i = 0; i != length; ++i) 1.4359 + JS_ASSERT(unsigned(asciiBytes[i]) <= 127); 1.4360 +#endif 1.4361 + if (length != str->length()) 1.4362 + return false; 1.4363 + const jschar *chars = str->chars(); 1.4364 + for (size_t i = 0; i != length; ++i) { 1.4365 + if (unsigned(asciiBytes[i]) != unsigned(chars[i])) 1.4366 + return false; 1.4367 + } 1.4368 + return true; 1.4369 +} 1.4370 + 1.4371 +size_t 1.4372 +js_strlen(const jschar *s) 1.4373 +{ 1.4374 + const jschar *t; 1.4375 + 1.4376 + for (t = s; *t != 0; t++) 1.4377 + continue; 1.4378 + return (size_t)(t - s); 1.4379 +} 1.4380 + 1.4381 +int32_t 1.4382 +js_strcmp(const jschar *lhs, const jschar *rhs) 1.4383 +{ 1.4384 + while (true) { 1.4385 + if (*lhs != *rhs) 1.4386 + return int32_t(*lhs) - int32_t(*rhs); 1.4387 + if (*lhs == 0) 1.4388 + return 0; 1.4389 + ++lhs, ++rhs; 1.4390 + } 1.4391 +} 1.4392 + 1.4393 +jschar * 1.4394 +js_strdup(js::ThreadSafeContext *cx, const jschar *s) 1.4395 +{ 1.4396 + size_t n = js_strlen(s); 1.4397 + jschar *ret = cx->pod_malloc<jschar>(n + 1); 1.4398 + if (!ret) 1.4399 + return nullptr; 1.4400 + js_strncpy(ret, s, n); 1.4401 + ret[n] = '\0'; 1.4402 + return ret; 1.4403 +} 1.4404 + 1.4405 +jschar * 1.4406 +js_strchr_limit(const jschar *s, jschar c, const jschar *limit) 1.4407 +{ 1.4408 + while (s < limit) { 1.4409 + if (*s == c) 1.4410 + return (jschar *)s; 1.4411 + s++; 1.4412 + } 1.4413 + return nullptr; 1.4414 +} 1.4415 + 1.4416 +jschar * 1.4417 +js::InflateString(ThreadSafeContext *cx, const char *bytes, size_t *lengthp) 1.4418 +{ 1.4419 + size_t nchars; 1.4420 + jschar *chars; 1.4421 + size_t nbytes = *lengthp; 1.4422 + 1.4423 + nchars = nbytes; 1.4424 + chars = cx->pod_malloc<jschar>(nchars + 1); 1.4425 + if (!chars) 1.4426 + goto bad; 1.4427 + for (size_t i = 0; i < nchars; i++) 1.4428 + chars[i] = (unsigned char) bytes[i]; 1.4429 + *lengthp = nchars; 1.4430 + chars[nchars] = 0; 1.4431 + return chars; 1.4432 + 1.4433 + bad: 1.4434 + // For compatibility with callers of JS_DecodeBytes we must zero lengthp 1.4435 + // on errors. 1.4436 + *lengthp = 0; 1.4437 + return nullptr; 1.4438 +} 1.4439 + 1.4440 +bool 1.4441 +js::DeflateStringToBuffer(JSContext *maybecx, const jschar *src, size_t srclen, 1.4442 + char *dst, size_t *dstlenp) 1.4443 +{ 1.4444 + size_t dstlen = *dstlenp; 1.4445 + if (srclen > dstlen) { 1.4446 + for (size_t i = 0; i < dstlen; i++) 1.4447 + dst[i] = (char) src[i]; 1.4448 + if (maybecx) { 1.4449 + AutoSuppressGC suppress(maybecx); 1.4450 + JS_ReportErrorNumber(maybecx, js_GetErrorMessage, nullptr, 1.4451 + JSMSG_BUFFER_TOO_SMALL); 1.4452 + } 1.4453 + return false; 1.4454 + } 1.4455 + for (size_t i = 0; i < srclen; i++) 1.4456 + dst[i] = (char) src[i]; 1.4457 + *dstlenp = srclen; 1.4458 + return true; 1.4459 +} 1.4460 + 1.4461 +#define ____ false 1.4462 + 1.4463 +/* 1.4464 + * Identifier start chars: 1.4465 + * - 36: $ 1.4466 + * - 65..90: A..Z 1.4467 + * - 95: _ 1.4468 + * - 97..122: a..z 1.4469 + */ 1.4470 +const bool js_isidstart[] = { 1.4471 +/* 0 1 2 3 4 5 6 7 8 9 */ 1.4472 +/* 0 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, 1.4473 +/* 1 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, 1.4474 +/* 2 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, 1.4475 +/* 3 */ ____, ____, ____, ____, ____, ____, true, ____, ____, ____, 1.4476 +/* 4 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, 1.4477 +/* 5 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, 1.4478 +/* 6 */ ____, ____, ____, ____, ____, true, true, true, true, true, 1.4479 +/* 7 */ true, true, true, true, true, true, true, true, true, true, 1.4480 +/* 8 */ true, true, true, true, true, true, true, true, true, true, 1.4481 +/* 9 */ true, ____, ____, ____, ____, true, ____, true, true, true, 1.4482 +/* 10 */ true, true, true, true, true, true, true, true, true, true, 1.4483 +/* 11 */ true, true, true, true, true, true, true, true, true, true, 1.4484 +/* 12 */ true, true, true, ____, ____, ____, ____, ____ 1.4485 +}; 1.4486 + 1.4487 +/* 1.4488 + * Identifier chars: 1.4489 + * - 36: $ 1.4490 + * - 48..57: 0..9 1.4491 + * - 65..90: A..Z 1.4492 + * - 95: _ 1.4493 + * - 97..122: a..z 1.4494 + */ 1.4495 +const bool js_isident[] = { 1.4496 +/* 0 1 2 3 4 5 6 7 8 9 */ 1.4497 +/* 0 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, 1.4498 +/* 1 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, 1.4499 +/* 2 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, 1.4500 +/* 3 */ ____, ____, ____, ____, ____, ____, true, ____, ____, ____, 1.4501 +/* 4 */ ____, ____, ____, ____, ____, ____, ____, ____, true, true, 1.4502 +/* 5 */ true, true, true, true, true, true, true, true, ____, ____, 1.4503 +/* 6 */ ____, ____, ____, ____, ____, true, true, true, true, true, 1.4504 +/* 7 */ true, true, true, true, true, true, true, true, true, true, 1.4505 +/* 8 */ true, true, true, true, true, true, true, true, true, true, 1.4506 +/* 9 */ true, ____, ____, ____, ____, true, ____, true, true, true, 1.4507 +/* 10 */ true, true, true, true, true, true, true, true, true, true, 1.4508 +/* 11 */ true, true, true, true, true, true, true, true, true, true, 1.4509 +/* 12 */ true, true, true, ____, ____, ____, ____, ____ 1.4510 +}; 1.4511 + 1.4512 +/* Whitespace chars: '\t', '\n', '\v', '\f', '\r', ' '. */ 1.4513 +const bool js_isspace[] = { 1.4514 +/* 0 1 2 3 4 5 6 7 8 9 */ 1.4515 +/* 0 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, true, 1.4516 +/* 1 */ true, true, true, true, ____, ____, ____, ____, ____, ____, 1.4517 +/* 2 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, 1.4518 +/* 3 */ ____, ____, true, ____, ____, ____, ____, ____, ____, ____, 1.4519 +/* 4 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, 1.4520 +/* 5 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, 1.4521 +/* 6 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, 1.4522 +/* 7 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, 1.4523 +/* 8 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, 1.4524 +/* 9 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, 1.4525 +/* 10 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, 1.4526 +/* 11 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, 1.4527 +/* 12 */ ____, ____, ____, ____, ____, ____, ____, ____ 1.4528 +}; 1.4529 + 1.4530 +/* 1.4531 + * Uri reserved chars + #: 1.4532 + * - 35: # 1.4533 + * - 36: $ 1.4534 + * - 38: & 1.4535 + * - 43: + 1.4536 + * - 44: , 1.4537 + * - 47: / 1.4538 + * - 58: : 1.4539 + * - 59: ; 1.4540 + * - 61: = 1.4541 + * - 63: ? 1.4542 + * - 64: @ 1.4543 + */ 1.4544 +static const bool js_isUriReservedPlusPound[] = { 1.4545 +/* 0 1 2 3 4 5 6 7 8 9 */ 1.4546 +/* 0 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, 1.4547 +/* 1 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, 1.4548 +/* 2 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, 1.4549 +/* 3 */ ____, ____, ____, ____, ____, true, true, ____, true, ____, 1.4550 +/* 4 */ ____, ____, ____, true, true, ____, ____, true, ____, ____, 1.4551 +/* 5 */ ____, ____, ____, ____, ____, ____, ____, ____, true, true, 1.4552 +/* 6 */ ____, true, ____, true, true, ____, ____, ____, ____, ____, 1.4553 +/* 7 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, 1.4554 +/* 8 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, 1.4555 +/* 9 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, 1.4556 +/* 10 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, 1.4557 +/* 11 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, 1.4558 +/* 12 */ ____, ____, ____, ____, ____, ____, ____, ____ 1.4559 +}; 1.4560 + 1.4561 +/* 1.4562 + * Uri unescaped chars: 1.4563 + * - 33: ! 1.4564 + * - 39: ' 1.4565 + * - 40: ( 1.4566 + * - 41: ) 1.4567 + * - 42: * 1.4568 + * - 45: - 1.4569 + * - 46: . 1.4570 + * - 48..57: 0-9 1.4571 + * - 65..90: A-Z 1.4572 + * - 95: _ 1.4573 + * - 97..122: a-z 1.4574 + * - 126: ~ 1.4575 + */ 1.4576 +static const bool js_isUriUnescaped[] = { 1.4577 +/* 0 1 2 3 4 5 6 7 8 9 */ 1.4578 +/* 0 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, 1.4579 +/* 1 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, 1.4580 +/* 2 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, 1.4581 +/* 3 */ ____, ____, ____, true, ____, ____, ____, ____, ____, true, 1.4582 +/* 4 */ true, true, true, ____, ____, true, true, ____, true, true, 1.4583 +/* 5 */ true, true, true, true, true, true, true, true, ____, ____, 1.4584 +/* 6 */ ____, ____, ____, ____, ____, true, true, true, true, true, 1.4585 +/* 7 */ true, true, true, true, true, true, true, true, true, true, 1.4586 +/* 8 */ true, true, true, true, true, true, true, true, true, true, 1.4587 +/* 9 */ true, ____, ____, ____, ____, true, ____, true, true, true, 1.4588 +/* 10 */ true, true, true, true, true, true, true, true, true, true, 1.4589 +/* 11 */ true, true, true, true, true, true, true, true, true, true, 1.4590 +/* 12 */ true, true, true, ____, ____, ____, true, ____ 1.4591 +}; 1.4592 + 1.4593 +#undef ____ 1.4594 + 1.4595 +#define URI_CHUNK 64U 1.4596 + 1.4597 +static inline bool 1.4598 +TransferBufferToString(StringBuffer &sb, MutableHandleValue rval) 1.4599 +{ 1.4600 + JSString *str = sb.finishString(); 1.4601 + if (!str) 1.4602 + return false; 1.4603 + rval.setString(str); 1.4604 + return true; 1.4605 +} 1.4606 + 1.4607 +/* 1.4608 + * ECMA 3, 15.1.3 URI Handling Function Properties 1.4609 + * 1.4610 + * The following are implementations of the algorithms 1.4611 + * given in the ECMA specification for the hidden functions 1.4612 + * 'Encode' and 'Decode'. 1.4613 + */ 1.4614 +static bool 1.4615 +Encode(JSContext *cx, Handle<JSLinearString*> str, const bool *unescapedSet, 1.4616 + const bool *unescapedSet2, MutableHandleValue rval) 1.4617 +{ 1.4618 + static const char HexDigits[] = "0123456789ABCDEF"; /* NB: uppercase */ 1.4619 + 1.4620 + size_t length = str->length(); 1.4621 + if (length == 0) { 1.4622 + rval.setString(cx->runtime()->emptyString); 1.4623 + return true; 1.4624 + } 1.4625 + 1.4626 + const jschar *chars = str->chars(); 1.4627 + StringBuffer sb(cx); 1.4628 + if (!sb.reserve(length)) 1.4629 + return false; 1.4630 + jschar hexBuf[4]; 1.4631 + hexBuf[0] = '%'; 1.4632 + hexBuf[3] = 0; 1.4633 + for (size_t k = 0; k < length; k++) { 1.4634 + jschar c = chars[k]; 1.4635 + if (c < 128 && (unescapedSet[c] || (unescapedSet2 && unescapedSet2[c]))) { 1.4636 + if (!sb.append(c)) 1.4637 + return false; 1.4638 + } else { 1.4639 + if ((c >= 0xDC00) && (c <= 0xDFFF)) { 1.4640 + JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_BAD_URI, nullptr); 1.4641 + return false; 1.4642 + } 1.4643 + uint32_t v; 1.4644 + if (c < 0xD800 || c > 0xDBFF) { 1.4645 + v = c; 1.4646 + } else { 1.4647 + k++; 1.4648 + if (k == length) { 1.4649 + JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, 1.4650 + JSMSG_BAD_URI, nullptr); 1.4651 + return false; 1.4652 + } 1.4653 + jschar c2 = chars[k]; 1.4654 + if ((c2 < 0xDC00) || (c2 > 0xDFFF)) { 1.4655 + JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, 1.4656 + JSMSG_BAD_URI, nullptr); 1.4657 + return false; 1.4658 + } 1.4659 + v = ((c - 0xD800) << 10) + (c2 - 0xDC00) + 0x10000; 1.4660 + } 1.4661 + uint8_t utf8buf[4]; 1.4662 + size_t L = js_OneUcs4ToUtf8Char(utf8buf, v); 1.4663 + for (size_t j = 0; j < L; j++) { 1.4664 + hexBuf[1] = HexDigits[utf8buf[j] >> 4]; 1.4665 + hexBuf[2] = HexDigits[utf8buf[j] & 0xf]; 1.4666 + if (!sb.append(hexBuf, 3)) 1.4667 + return false; 1.4668 + } 1.4669 + } 1.4670 + } 1.4671 + 1.4672 + return TransferBufferToString(sb, rval); 1.4673 +} 1.4674 + 1.4675 +static bool 1.4676 +Decode(JSContext *cx, Handle<JSLinearString*> str, const bool *reservedSet, MutableHandleValue rval) 1.4677 +{ 1.4678 + size_t length = str->length(); 1.4679 + if (length == 0) { 1.4680 + rval.setString(cx->runtime()->emptyString); 1.4681 + return true; 1.4682 + } 1.4683 + 1.4684 + const jschar *chars = str->chars(); 1.4685 + StringBuffer sb(cx); 1.4686 + for (size_t k = 0; k < length; k++) { 1.4687 + jschar c = chars[k]; 1.4688 + if (c == '%') { 1.4689 + size_t start = k; 1.4690 + if ((k + 2) >= length) 1.4691 + goto report_bad_uri; 1.4692 + if (!JS7_ISHEX(chars[k+1]) || !JS7_ISHEX(chars[k+2])) 1.4693 + goto report_bad_uri; 1.4694 + uint32_t B = JS7_UNHEX(chars[k+1]) * 16 + JS7_UNHEX(chars[k+2]); 1.4695 + k += 2; 1.4696 + if (!(B & 0x80)) { 1.4697 + c = (jschar)B; 1.4698 + } else { 1.4699 + int n = 1; 1.4700 + while (B & (0x80 >> n)) 1.4701 + n++; 1.4702 + if (n == 1 || n > 4) 1.4703 + goto report_bad_uri; 1.4704 + uint8_t octets[4]; 1.4705 + octets[0] = (uint8_t)B; 1.4706 + if (k + 3 * (n - 1) >= length) 1.4707 + goto report_bad_uri; 1.4708 + for (int j = 1; j < n; j++) { 1.4709 + k++; 1.4710 + if (chars[k] != '%') 1.4711 + goto report_bad_uri; 1.4712 + if (!JS7_ISHEX(chars[k+1]) || !JS7_ISHEX(chars[k+2])) 1.4713 + goto report_bad_uri; 1.4714 + B = JS7_UNHEX(chars[k+1]) * 16 + JS7_UNHEX(chars[k+2]); 1.4715 + if ((B & 0xC0) != 0x80) 1.4716 + goto report_bad_uri; 1.4717 + k += 2; 1.4718 + octets[j] = (char)B; 1.4719 + } 1.4720 + uint32_t v = JS::Utf8ToOneUcs4Char(octets, n); 1.4721 + if (v >= 0x10000) { 1.4722 + v -= 0x10000; 1.4723 + if (v > 0xFFFFF) 1.4724 + goto report_bad_uri; 1.4725 + c = (jschar)((v & 0x3FF) + 0xDC00); 1.4726 + jschar H = (jschar)((v >> 10) + 0xD800); 1.4727 + if (!sb.append(H)) 1.4728 + return false; 1.4729 + } else { 1.4730 + c = (jschar)v; 1.4731 + } 1.4732 + } 1.4733 + if (c < 128 && reservedSet && reservedSet[c]) { 1.4734 + if (!sb.append(chars + start, k - start + 1)) 1.4735 + return false; 1.4736 + } else { 1.4737 + if (!sb.append(c)) 1.4738 + return false; 1.4739 + } 1.4740 + } else { 1.4741 + if (!sb.append(c)) 1.4742 + return false; 1.4743 + } 1.4744 + } 1.4745 + 1.4746 + return TransferBufferToString(sb, rval); 1.4747 + 1.4748 + report_bad_uri: 1.4749 + JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_BAD_URI); 1.4750 + /* FALL THROUGH */ 1.4751 + 1.4752 + return false; 1.4753 +} 1.4754 + 1.4755 +static bool 1.4756 +str_decodeURI(JSContext *cx, unsigned argc, Value *vp) 1.4757 +{ 1.4758 + CallArgs args = CallArgsFromVp(argc, vp); 1.4759 + Rooted<JSLinearString*> str(cx, ArgToRootedString(cx, args, 0)); 1.4760 + if (!str) 1.4761 + return false; 1.4762 + 1.4763 + return Decode(cx, str, js_isUriReservedPlusPound, args.rval()); 1.4764 +} 1.4765 + 1.4766 +static bool 1.4767 +str_decodeURI_Component(JSContext *cx, unsigned argc, Value *vp) 1.4768 +{ 1.4769 + CallArgs args = CallArgsFromVp(argc, vp); 1.4770 + Rooted<JSLinearString*> str(cx, ArgToRootedString(cx, args, 0)); 1.4771 + if (!str) 1.4772 + return false; 1.4773 + 1.4774 + return Decode(cx, str, nullptr, args.rval()); 1.4775 +} 1.4776 + 1.4777 +static bool 1.4778 +str_encodeURI(JSContext *cx, unsigned argc, Value *vp) 1.4779 +{ 1.4780 + CallArgs args = CallArgsFromVp(argc, vp); 1.4781 + Rooted<JSLinearString*> str(cx, ArgToRootedString(cx, args, 0)); 1.4782 + if (!str) 1.4783 + return false; 1.4784 + 1.4785 + return Encode(cx, str, js_isUriUnescaped, js_isUriReservedPlusPound, args.rval()); 1.4786 +} 1.4787 + 1.4788 +static bool 1.4789 +str_encodeURI_Component(JSContext *cx, unsigned argc, Value *vp) 1.4790 +{ 1.4791 + CallArgs args = CallArgsFromVp(argc, vp); 1.4792 + Rooted<JSLinearString*> str(cx, ArgToRootedString(cx, args, 0)); 1.4793 + if (!str) 1.4794 + return false; 1.4795 + 1.4796 + return Encode(cx, str, js_isUriUnescaped, nullptr, args.rval()); 1.4797 +} 1.4798 + 1.4799 +/* 1.4800 + * Convert one UCS-4 char and write it into a UTF-8 buffer, which must be at 1.4801 + * least 4 bytes long. Return the number of UTF-8 bytes of data written. 1.4802 + */ 1.4803 +int 1.4804 +js_OneUcs4ToUtf8Char(uint8_t *utf8Buffer, uint32_t ucs4Char) 1.4805 +{ 1.4806 + int utf8Length = 1; 1.4807 + 1.4808 + JS_ASSERT(ucs4Char <= 0x10FFFF); 1.4809 + if (ucs4Char < 0x80) { 1.4810 + *utf8Buffer = (uint8_t)ucs4Char; 1.4811 + } else { 1.4812 + int i; 1.4813 + uint32_t a = ucs4Char >> 11; 1.4814 + utf8Length = 2; 1.4815 + while (a) { 1.4816 + a >>= 5; 1.4817 + utf8Length++; 1.4818 + } 1.4819 + i = utf8Length; 1.4820 + while (--i) { 1.4821 + utf8Buffer[i] = (uint8_t)((ucs4Char & 0x3F) | 0x80); 1.4822 + ucs4Char >>= 6; 1.4823 + } 1.4824 + *utf8Buffer = (uint8_t)(0x100 - (1 << (8-utf8Length)) + ucs4Char); 1.4825 + } 1.4826 + return utf8Length; 1.4827 +} 1.4828 + 1.4829 +size_t 1.4830 +js::PutEscapedStringImpl(char *buffer, size_t bufferSize, FILE *fp, JSLinearString *str, 1.4831 + uint32_t quote) 1.4832 +{ 1.4833 + return PutEscapedStringImpl(buffer, bufferSize, fp, str->chars(), 1.4834 + str->length(), quote); 1.4835 +} 1.4836 + 1.4837 +size_t 1.4838 +js::PutEscapedStringImpl(char *buffer, size_t bufferSize, FILE *fp, const jschar *chars, 1.4839 + size_t length, uint32_t quote) 1.4840 +{ 1.4841 + enum { 1.4842 + STOP, FIRST_QUOTE, LAST_QUOTE, CHARS, ESCAPE_START, ESCAPE_MORE 1.4843 + } state; 1.4844 + 1.4845 + JS_ASSERT(quote == 0 || quote == '\'' || quote == '"'); 1.4846 + JS_ASSERT_IF(!buffer, bufferSize == 0); 1.4847 + JS_ASSERT_IF(fp, !buffer); 1.4848 + 1.4849 + if (bufferSize == 0) 1.4850 + buffer = nullptr; 1.4851 + else 1.4852 + bufferSize--; 1.4853 + 1.4854 + const jschar *charsEnd = chars + length; 1.4855 + size_t n = 0; 1.4856 + state = FIRST_QUOTE; 1.4857 + unsigned shift = 0; 1.4858 + unsigned hex = 0; 1.4859 + unsigned u = 0; 1.4860 + char c = 0; /* to quell GCC warnings */ 1.4861 + 1.4862 + for (;;) { 1.4863 + switch (state) { 1.4864 + case STOP: 1.4865 + goto stop; 1.4866 + case FIRST_QUOTE: 1.4867 + state = CHARS; 1.4868 + goto do_quote; 1.4869 + case LAST_QUOTE: 1.4870 + state = STOP; 1.4871 + do_quote: 1.4872 + if (quote == 0) 1.4873 + continue; 1.4874 + c = (char)quote; 1.4875 + break; 1.4876 + case CHARS: 1.4877 + if (chars == charsEnd) { 1.4878 + state = LAST_QUOTE; 1.4879 + continue; 1.4880 + } 1.4881 + u = *chars++; 1.4882 + if (u < ' ') { 1.4883 + if (u != 0) { 1.4884 + const char *escape = strchr(js_EscapeMap, (int)u); 1.4885 + if (escape) { 1.4886 + u = escape[1]; 1.4887 + goto do_escape; 1.4888 + } 1.4889 + } 1.4890 + goto do_hex_escape; 1.4891 + } 1.4892 + if (u < 127) { 1.4893 + if (u == quote || u == '\\') 1.4894 + goto do_escape; 1.4895 + c = (char)u; 1.4896 + } else if (u < 0x100) { 1.4897 + goto do_hex_escape; 1.4898 + } else { 1.4899 + shift = 16; 1.4900 + hex = u; 1.4901 + u = 'u'; 1.4902 + goto do_escape; 1.4903 + } 1.4904 + break; 1.4905 + do_hex_escape: 1.4906 + shift = 8; 1.4907 + hex = u; 1.4908 + u = 'x'; 1.4909 + do_escape: 1.4910 + c = '\\'; 1.4911 + state = ESCAPE_START; 1.4912 + break; 1.4913 + case ESCAPE_START: 1.4914 + JS_ASSERT(' ' <= u && u < 127); 1.4915 + c = (char)u; 1.4916 + state = ESCAPE_MORE; 1.4917 + break; 1.4918 + case ESCAPE_MORE: 1.4919 + if (shift == 0) { 1.4920 + state = CHARS; 1.4921 + continue; 1.4922 + } 1.4923 + shift -= 4; 1.4924 + u = 0xF & (hex >> shift); 1.4925 + c = (char)(u + (u < 10 ? '0' : 'A' - 10)); 1.4926 + break; 1.4927 + } 1.4928 + if (buffer) { 1.4929 + JS_ASSERT(n <= bufferSize); 1.4930 + if (n != bufferSize) { 1.4931 + buffer[n] = c; 1.4932 + } else { 1.4933 + buffer[n] = '\0'; 1.4934 + buffer = nullptr; 1.4935 + } 1.4936 + } else if (fp) { 1.4937 + if (fputc(c, fp) < 0) 1.4938 + return size_t(-1); 1.4939 + } 1.4940 + n++; 1.4941 + } 1.4942 + stop: 1.4943 + if (buffer) 1.4944 + buffer[n] = '\0'; 1.4945 + return n; 1.4946 +}