js/src/jsstr.cpp

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

michael@0 1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
michael@0 2 * vim: set ts=8 sts=4 et sw=4 tw=99:
michael@0 3 * This Source Code Form is subject to the terms of the Mozilla Public
michael@0 4 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 6
michael@0 7 /*
michael@0 8 * JS string type implementation.
michael@0 9 *
michael@0 10 * In order to avoid unnecessary js_LockGCThing/js_UnlockGCThing calls, these
michael@0 11 * native methods store strings (possibly newborn) converted from their 'this'
michael@0 12 * parameter and arguments on the stack: 'this' conversions at argv[-1], arg
michael@0 13 * conversions at their index (argv[0], argv[1]). This is a legitimate method
michael@0 14 * of rooting things that might lose their newborn root due to subsequent GC
michael@0 15 * allocations in the same native method.
michael@0 16 */
michael@0 17
michael@0 18 #include "jsstr.h"
michael@0 19
michael@0 20 #include "mozilla/Attributes.h"
michael@0 21 #include "mozilla/Casting.h"
michael@0 22 #include "mozilla/CheckedInt.h"
michael@0 23 #include "mozilla/FloatingPoint.h"
michael@0 24 #include "mozilla/PodOperations.h"
michael@0 25
michael@0 26 #include <ctype.h>
michael@0 27 #include <string.h>
michael@0 28
michael@0 29 #include "jsapi.h"
michael@0 30 #include "jsarray.h"
michael@0 31 #include "jsatom.h"
michael@0 32 #include "jsbool.h"
michael@0 33 #include "jscntxt.h"
michael@0 34 #include "jsgc.h"
michael@0 35 #include "jsnum.h"
michael@0 36 #include "jsobj.h"
michael@0 37 #include "jsopcode.h"
michael@0 38 #include "jstypes.h"
michael@0 39 #include "jsutil.h"
michael@0 40
michael@0 41 #include "builtin/Intl.h"
michael@0 42 #include "builtin/RegExp.h"
michael@0 43 #if ENABLE_INTL_API
michael@0 44 #include "unicode/unorm.h"
michael@0 45 #endif
michael@0 46 #include "vm/GlobalObject.h"
michael@0 47 #include "vm/Interpreter.h"
michael@0 48 #include "vm/NumericConversions.h"
michael@0 49 #include "vm/Opcodes.h"
michael@0 50 #include "vm/RegExpObject.h"
michael@0 51 #include "vm/RegExpStatics.h"
michael@0 52 #include "vm/ScopeObject.h"
michael@0 53 #include "vm/StringBuffer.h"
michael@0 54
michael@0 55 #include "jsinferinlines.h"
michael@0 56
michael@0 57 #include "vm/Interpreter-inl.h"
michael@0 58 #include "vm/String-inl.h"
michael@0 59 #include "vm/StringObject-inl.h"
michael@0 60
michael@0 61 using namespace js;
michael@0 62 using namespace js::gc;
michael@0 63 using namespace js::types;
michael@0 64 using namespace js::unicode;
michael@0 65
michael@0 66 using mozilla::CheckedInt;
michael@0 67 using mozilla::IsNaN;
michael@0 68 using mozilla::IsNegativeZero;
michael@0 69 using mozilla::PodCopy;
michael@0 70 using mozilla::PodEqual;
michael@0 71 using mozilla::SafeCast;
michael@0 72
michael@0 73 typedef Handle<JSLinearString*> HandleLinearString;
michael@0 74
michael@0 75 static JSLinearString *
michael@0 76 ArgToRootedString(JSContext *cx, CallArgs &args, unsigned argno)
michael@0 77 {
michael@0 78 if (argno >= args.length())
michael@0 79 return cx->names().undefined;
michael@0 80
michael@0 81 JSString *str = ToString<CanGC>(cx, args[argno]);
michael@0 82 if (!str)
michael@0 83 return nullptr;
michael@0 84
michael@0 85 args[argno].setString(str);
michael@0 86 return str->ensureLinear(cx);
michael@0 87 }
michael@0 88
michael@0 89 /*
michael@0 90 * Forward declarations for URI encode/decode and helper routines
michael@0 91 */
michael@0 92 static bool
michael@0 93 str_decodeURI(JSContext *cx, unsigned argc, Value *vp);
michael@0 94
michael@0 95 static bool
michael@0 96 str_decodeURI_Component(JSContext *cx, unsigned argc, Value *vp);
michael@0 97
michael@0 98 static bool
michael@0 99 str_encodeURI(JSContext *cx, unsigned argc, Value *vp);
michael@0 100
michael@0 101 static bool
michael@0 102 str_encodeURI_Component(JSContext *cx, unsigned argc, Value *vp);
michael@0 103
michael@0 104 /*
michael@0 105 * Global string methods
michael@0 106 */
michael@0 107
michael@0 108
michael@0 109 /* ES5 B.2.1 */
michael@0 110 static bool
michael@0 111 str_escape(JSContext *cx, unsigned argc, Value *vp)
michael@0 112 {
michael@0 113 CallArgs args = CallArgsFromVp(argc, vp);
michael@0 114
michael@0 115 static const char digits[] = {'0', '1', '2', '3', '4', '5', '6', '7',
michael@0 116 '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
michael@0 117
michael@0 118 JSLinearString *str = ArgToRootedString(cx, args, 0);
michael@0 119 if (!str)
michael@0 120 return false;
michael@0 121
michael@0 122 size_t length = str->length();
michael@0 123 const jschar *chars = str->chars();
michael@0 124
michael@0 125 static const uint8_t shouldPassThrough[256] = {
michael@0 126 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
michael@0 127 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
michael@0 128 0,0,0,0,0,0,0,0,0,0,1,1,0,1,1,1, /* !"#$%&'()*+,-./ */
michael@0 129 1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0, /* 0123456789:;<=>? */
michael@0 130 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, /* @ABCDEFGHIJKLMNO */
michael@0 131 1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,1, /* PQRSTUVWXYZ[\]^_ */
michael@0 132 0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, /* `abcdefghijklmno */
michael@0 133 1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0, /* pqrstuvwxyz{\}~ DEL */
michael@0 134 };
michael@0 135
michael@0 136 /* In step 7, exactly 69 characters should pass through unencoded. */
michael@0 137 #ifdef DEBUG
michael@0 138 size_t count = 0;
michael@0 139 for (size_t i = 0; i < sizeof(shouldPassThrough); i++) {
michael@0 140 if (shouldPassThrough[i]) {
michael@0 141 count++;
michael@0 142 }
michael@0 143 }
michael@0 144 JS_ASSERT(count == 69);
michael@0 145 #endif
michael@0 146
michael@0 147
michael@0 148 /* Take a first pass and see how big the result string will need to be. */
michael@0 149 size_t newlength = length;
michael@0 150 for (size_t i = 0; i < length; i++) {
michael@0 151 jschar ch = chars[i];
michael@0 152 if (ch < 128 && shouldPassThrough[ch])
michael@0 153 continue;
michael@0 154
michael@0 155 /* The character will be encoded as %XX or %uXXXX. */
michael@0 156 newlength += (ch < 256) ? 2 : 5;
michael@0 157
michael@0 158 /*
michael@0 159 * This overflow test works because newlength is incremented by at
michael@0 160 * most 5 on each iteration.
michael@0 161 */
michael@0 162 if (newlength < length) {
michael@0 163 js_ReportAllocationOverflow(cx);
michael@0 164 return false;
michael@0 165 }
michael@0 166 }
michael@0 167
michael@0 168 if (newlength >= ~(size_t)0 / sizeof(jschar)) {
michael@0 169 js_ReportAllocationOverflow(cx);
michael@0 170 return false;
michael@0 171 }
michael@0 172
michael@0 173 jschar *newchars = cx->pod_malloc<jschar>(newlength + 1);
michael@0 174 if (!newchars)
michael@0 175 return false;
michael@0 176 size_t i, ni;
michael@0 177 for (i = 0, ni = 0; i < length; i++) {
michael@0 178 jschar ch = chars[i];
michael@0 179 if (ch < 128 && shouldPassThrough[ch]) {
michael@0 180 newchars[ni++] = ch;
michael@0 181 } else if (ch < 256) {
michael@0 182 newchars[ni++] = '%';
michael@0 183 newchars[ni++] = digits[ch >> 4];
michael@0 184 newchars[ni++] = digits[ch & 0xF];
michael@0 185 } else {
michael@0 186 newchars[ni++] = '%';
michael@0 187 newchars[ni++] = 'u';
michael@0 188 newchars[ni++] = digits[ch >> 12];
michael@0 189 newchars[ni++] = digits[(ch & 0xF00) >> 8];
michael@0 190 newchars[ni++] = digits[(ch & 0xF0) >> 4];
michael@0 191 newchars[ni++] = digits[ch & 0xF];
michael@0 192 }
michael@0 193 }
michael@0 194 JS_ASSERT(ni == newlength);
michael@0 195 newchars[newlength] = 0;
michael@0 196
michael@0 197 JSString *retstr = js_NewString<CanGC>(cx, newchars, newlength);
michael@0 198 if (!retstr) {
michael@0 199 js_free(newchars);
michael@0 200 return false;
michael@0 201 }
michael@0 202
michael@0 203 args.rval().setString(retstr);
michael@0 204 return true;
michael@0 205 }
michael@0 206
michael@0 207 static inline bool
michael@0 208 Unhex4(const jschar *chars, jschar *result)
michael@0 209 {
michael@0 210 jschar a = chars[0],
michael@0 211 b = chars[1],
michael@0 212 c = chars[2],
michael@0 213 d = chars[3];
michael@0 214
michael@0 215 if (!(JS7_ISHEX(a) && JS7_ISHEX(b) && JS7_ISHEX(c) && JS7_ISHEX(d)))
michael@0 216 return false;
michael@0 217
michael@0 218 *result = (((((JS7_UNHEX(a) << 4) + JS7_UNHEX(b)) << 4) + JS7_UNHEX(c)) << 4) + JS7_UNHEX(d);
michael@0 219 return true;
michael@0 220 }
michael@0 221
michael@0 222 static inline bool
michael@0 223 Unhex2(const jschar *chars, jschar *result)
michael@0 224 {
michael@0 225 jschar a = chars[0],
michael@0 226 b = chars[1];
michael@0 227
michael@0 228 if (!(JS7_ISHEX(a) && JS7_ISHEX(b)))
michael@0 229 return false;
michael@0 230
michael@0 231 *result = (JS7_UNHEX(a) << 4) + JS7_UNHEX(b);
michael@0 232 return true;
michael@0 233 }
michael@0 234
michael@0 235 /* ES5 B.2.2 */
michael@0 236 static bool
michael@0 237 str_unescape(JSContext *cx, unsigned argc, Value *vp)
michael@0 238 {
michael@0 239 CallArgs args = CallArgsFromVp(argc, vp);
michael@0 240
michael@0 241 /* Step 1. */
michael@0 242 JSLinearString *str = ArgToRootedString(cx, args, 0);
michael@0 243 if (!str)
michael@0 244 return false;
michael@0 245
michael@0 246 /*
michael@0 247 * NB: use signed integers for length/index to allow simple length
michael@0 248 * comparisons without unsigned-underflow hazards.
michael@0 249 */
michael@0 250 JS_STATIC_ASSERT(JSString::MAX_LENGTH <= INT_MAX);
michael@0 251
michael@0 252 /* Step 2. */
michael@0 253 int length = str->length();
michael@0 254 const jschar *chars = str->chars();
michael@0 255
michael@0 256 /* Step 3. */
michael@0 257 StringBuffer sb(cx);
michael@0 258
michael@0 259 /*
michael@0 260 * Note that the spec algorithm has been optimized to avoid building
michael@0 261 * a string in the case where no escapes are present.
michael@0 262 */
michael@0 263
michael@0 264 /* Step 4. */
michael@0 265 int k = 0;
michael@0 266 bool building = false;
michael@0 267
michael@0 268 while (true) {
michael@0 269 /* Step 5. */
michael@0 270 if (k == length) {
michael@0 271 JSLinearString *result;
michael@0 272 if (building) {
michael@0 273 result = sb.finishString();
michael@0 274 if (!result)
michael@0 275 return false;
michael@0 276 } else {
michael@0 277 result = str;
michael@0 278 }
michael@0 279
michael@0 280 args.rval().setString(result);
michael@0 281 return true;
michael@0 282 }
michael@0 283
michael@0 284 /* Step 6. */
michael@0 285 jschar c = chars[k];
michael@0 286
michael@0 287 /* Step 7. */
michael@0 288 if (c != '%')
michael@0 289 goto step_18;
michael@0 290
michael@0 291 /* Step 8. */
michael@0 292 if (k > length - 6)
michael@0 293 goto step_14;
michael@0 294
michael@0 295 /* Step 9. */
michael@0 296 if (chars[k + 1] != 'u')
michael@0 297 goto step_14;
michael@0 298
michael@0 299 #define ENSURE_BUILDING \
michael@0 300 JS_BEGIN_MACRO \
michael@0 301 if (!building) { \
michael@0 302 building = true; \
michael@0 303 if (!sb.reserve(length)) \
michael@0 304 return false; \
michael@0 305 sb.infallibleAppend(chars, chars + k); \
michael@0 306 } \
michael@0 307 JS_END_MACRO
michael@0 308
michael@0 309 /* Step 10-13. */
michael@0 310 if (Unhex4(&chars[k + 2], &c)) {
michael@0 311 ENSURE_BUILDING;
michael@0 312 k += 5;
michael@0 313 goto step_18;
michael@0 314 }
michael@0 315
michael@0 316 step_14:
michael@0 317 /* Step 14. */
michael@0 318 if (k > length - 3)
michael@0 319 goto step_18;
michael@0 320
michael@0 321 /* Step 15-17. */
michael@0 322 if (Unhex2(&chars[k + 1], &c)) {
michael@0 323 ENSURE_BUILDING;
michael@0 324 k += 2;
michael@0 325 }
michael@0 326
michael@0 327 step_18:
michael@0 328 if (building)
michael@0 329 sb.infallibleAppend(c);
michael@0 330
michael@0 331 /* Step 19. */
michael@0 332 k += 1;
michael@0 333 }
michael@0 334 #undef ENSURE_BUILDING
michael@0 335 }
michael@0 336
michael@0 337 #if JS_HAS_UNEVAL
michael@0 338 static bool
michael@0 339 str_uneval(JSContext *cx, unsigned argc, Value *vp)
michael@0 340 {
michael@0 341 CallArgs args = CallArgsFromVp(argc, vp);
michael@0 342 JSString *str = ValueToSource(cx, args.get(0));
michael@0 343 if (!str)
michael@0 344 return false;
michael@0 345
michael@0 346 args.rval().setString(str);
michael@0 347 return true;
michael@0 348 }
michael@0 349 #endif
michael@0 350
michael@0 351 static const JSFunctionSpec string_functions[] = {
michael@0 352 JS_FN(js_escape_str, str_escape, 1,0),
michael@0 353 JS_FN(js_unescape_str, str_unescape, 1,0),
michael@0 354 #if JS_HAS_UNEVAL
michael@0 355 JS_FN(js_uneval_str, str_uneval, 1,0),
michael@0 356 #endif
michael@0 357 JS_FN(js_decodeURI_str, str_decodeURI, 1,0),
michael@0 358 JS_FN(js_encodeURI_str, str_encodeURI, 1,0),
michael@0 359 JS_FN(js_decodeURIComponent_str, str_decodeURI_Component, 1,0),
michael@0 360 JS_FN(js_encodeURIComponent_str, str_encodeURI_Component, 1,0),
michael@0 361
michael@0 362 JS_FS_END
michael@0 363 };
michael@0 364
michael@0 365 const jschar js_empty_ucstr[] = {0};
michael@0 366 const JSSubString js_EmptySubString = {0, js_empty_ucstr};
michael@0 367
michael@0 368 static const unsigned STRING_ELEMENT_ATTRS = JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT;
michael@0 369
michael@0 370 static bool
michael@0 371 str_enumerate(JSContext *cx, HandleObject obj)
michael@0 372 {
michael@0 373 RootedString str(cx, obj->as<StringObject>().unbox());
michael@0 374 RootedValue value(cx);
michael@0 375 for (size_t i = 0, length = str->length(); i < length; i++) {
michael@0 376 JSString *str1 = js_NewDependentString(cx, str, i, 1);
michael@0 377 if (!str1)
michael@0 378 return false;
michael@0 379 value.setString(str1);
michael@0 380 if (!JSObject::defineElement(cx, obj, i, value,
michael@0 381 JS_PropertyStub, JS_StrictPropertyStub,
michael@0 382 STRING_ELEMENT_ATTRS))
michael@0 383 {
michael@0 384 return false;
michael@0 385 }
michael@0 386 }
michael@0 387
michael@0 388 return true;
michael@0 389 }
michael@0 390
michael@0 391 bool
michael@0 392 js::str_resolve(JSContext *cx, HandleObject obj, HandleId id, MutableHandleObject objp)
michael@0 393 {
michael@0 394 if (!JSID_IS_INT(id))
michael@0 395 return true;
michael@0 396
michael@0 397 RootedString str(cx, obj->as<StringObject>().unbox());
michael@0 398
michael@0 399 int32_t slot = JSID_TO_INT(id);
michael@0 400 if ((size_t)slot < str->length()) {
michael@0 401 JSString *str1 = cx->staticStrings().getUnitStringForElement(cx, str, size_t(slot));
michael@0 402 if (!str1)
michael@0 403 return false;
michael@0 404 RootedValue value(cx, StringValue(str1));
michael@0 405 if (!JSObject::defineElement(cx, obj, uint32_t(slot), value, nullptr, nullptr,
michael@0 406 STRING_ELEMENT_ATTRS))
michael@0 407 {
michael@0 408 return false;
michael@0 409 }
michael@0 410 objp.set(obj);
michael@0 411 }
michael@0 412 return true;
michael@0 413 }
michael@0 414
michael@0 415 const Class StringObject::class_ = {
michael@0 416 js_String_str,
michael@0 417 JSCLASS_HAS_RESERVED_SLOTS(StringObject::RESERVED_SLOTS) |
michael@0 418 JSCLASS_NEW_RESOLVE | JSCLASS_HAS_CACHED_PROTO(JSProto_String),
michael@0 419 JS_PropertyStub, /* addProperty */
michael@0 420 JS_DeletePropertyStub, /* delProperty */
michael@0 421 JS_PropertyStub, /* getProperty */
michael@0 422 JS_StrictPropertyStub, /* setProperty */
michael@0 423 str_enumerate,
michael@0 424 (JSResolveOp)str_resolve,
michael@0 425 JS_ConvertStub
michael@0 426 };
michael@0 427
michael@0 428 /*
michael@0 429 * Returns a JSString * for the |this| value associated with 'call', or throws
michael@0 430 * a TypeError if |this| is null or undefined. This algorithm is the same as
michael@0 431 * calling CheckObjectCoercible(this), then returning ToString(this), as all
michael@0 432 * String.prototype.* methods do (other than toString and valueOf).
michael@0 433 */
michael@0 434 static MOZ_ALWAYS_INLINE JSString *
michael@0 435 ThisToStringForStringProto(JSContext *cx, CallReceiver call)
michael@0 436 {
michael@0 437 JS_CHECK_RECURSION(cx, return nullptr);
michael@0 438
michael@0 439 if (call.thisv().isString())
michael@0 440 return call.thisv().toString();
michael@0 441
michael@0 442 if (call.thisv().isObject()) {
michael@0 443 RootedObject obj(cx, &call.thisv().toObject());
michael@0 444 if (obj->is<StringObject>()) {
michael@0 445 Rooted<jsid> id(cx, NameToId(cx->names().toString));
michael@0 446 if (ClassMethodIsNative(cx, obj, &StringObject::class_, id, js_str_toString)) {
michael@0 447 JSString *str = obj->as<StringObject>().unbox();
michael@0 448 call.setThis(StringValue(str));
michael@0 449 return str;
michael@0 450 }
michael@0 451 }
michael@0 452 } else if (call.thisv().isNullOrUndefined()) {
michael@0 453 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_CANT_CONVERT_TO,
michael@0 454 call.thisv().isNull() ? "null" : "undefined", "object");
michael@0 455 return nullptr;
michael@0 456 }
michael@0 457
michael@0 458 JSString *str = ToStringSlow<CanGC>(cx, call.thisv());
michael@0 459 if (!str)
michael@0 460 return nullptr;
michael@0 461
michael@0 462 call.setThis(StringValue(str));
michael@0 463 return str;
michael@0 464 }
michael@0 465
michael@0 466 MOZ_ALWAYS_INLINE bool
michael@0 467 IsString(HandleValue v)
michael@0 468 {
michael@0 469 return v.isString() || (v.isObject() && v.toObject().is<StringObject>());
michael@0 470 }
michael@0 471
michael@0 472 #if JS_HAS_TOSOURCE
michael@0 473
michael@0 474 /*
michael@0 475 * String.prototype.quote is generic (as are most string methods), unlike
michael@0 476 * toSource, toString, and valueOf.
michael@0 477 */
michael@0 478 static bool
michael@0 479 str_quote(JSContext *cx, unsigned argc, Value *vp)
michael@0 480 {
michael@0 481 CallArgs args = CallArgsFromVp(argc, vp);
michael@0 482 RootedString str(cx, ThisToStringForStringProto(cx, args));
michael@0 483 if (!str)
michael@0 484 return false;
michael@0 485 str = js_QuoteString(cx, str, '"');
michael@0 486 if (!str)
michael@0 487 return false;
michael@0 488 args.rval().setString(str);
michael@0 489 return true;
michael@0 490 }
michael@0 491
michael@0 492 MOZ_ALWAYS_INLINE bool
michael@0 493 str_toSource_impl(JSContext *cx, CallArgs args)
michael@0 494 {
michael@0 495 JS_ASSERT(IsString(args.thisv()));
michael@0 496
michael@0 497 Rooted<JSString*> str(cx, ToString<CanGC>(cx, args.thisv()));
michael@0 498 if (!str)
michael@0 499 return false;
michael@0 500
michael@0 501 str = js_QuoteString(cx, str, '"');
michael@0 502 if (!str)
michael@0 503 return false;
michael@0 504
michael@0 505 StringBuffer sb(cx);
michael@0 506 if (!sb.append("(new String(") || !sb.append(str) || !sb.append("))"))
michael@0 507 return false;
michael@0 508
michael@0 509 str = sb.finishString();
michael@0 510 if (!str)
michael@0 511 return false;
michael@0 512 args.rval().setString(str);
michael@0 513 return true;
michael@0 514 }
michael@0 515
michael@0 516 static bool
michael@0 517 str_toSource(JSContext *cx, unsigned argc, Value *vp)
michael@0 518 {
michael@0 519 CallArgs args = CallArgsFromVp(argc, vp);
michael@0 520 return CallNonGenericMethod<IsString, str_toSource_impl>(cx, args);
michael@0 521 }
michael@0 522
michael@0 523 #endif /* JS_HAS_TOSOURCE */
michael@0 524
michael@0 525 MOZ_ALWAYS_INLINE bool
michael@0 526 str_toString_impl(JSContext *cx, CallArgs args)
michael@0 527 {
michael@0 528 JS_ASSERT(IsString(args.thisv()));
michael@0 529
michael@0 530 args.rval().setString(args.thisv().isString()
michael@0 531 ? args.thisv().toString()
michael@0 532 : args.thisv().toObject().as<StringObject>().unbox());
michael@0 533 return true;
michael@0 534 }
michael@0 535
michael@0 536 bool
michael@0 537 js_str_toString(JSContext *cx, unsigned argc, Value *vp)
michael@0 538 {
michael@0 539 CallArgs args = CallArgsFromVp(argc, vp);
michael@0 540 return CallNonGenericMethod<IsString, str_toString_impl>(cx, args);
michael@0 541 }
michael@0 542
michael@0 543 /*
michael@0 544 * Java-like string native methods.
michael@0 545 */
michael@0 546
michael@0 547 static MOZ_ALWAYS_INLINE bool
michael@0 548 ValueToIntegerRange(JSContext *cx, HandleValue v, int32_t *out)
michael@0 549 {
michael@0 550 if (v.isInt32()) {
michael@0 551 *out = v.toInt32();
michael@0 552 } else {
michael@0 553 double d;
michael@0 554 if (!ToInteger(cx, v, &d))
michael@0 555 return false;
michael@0 556 if (d > INT32_MAX)
michael@0 557 *out = INT32_MAX;
michael@0 558 else if (d < INT32_MIN)
michael@0 559 *out = INT32_MIN;
michael@0 560 else
michael@0 561 *out = int32_t(d);
michael@0 562 }
michael@0 563
michael@0 564 return true;
michael@0 565 }
michael@0 566
michael@0 567 static JSString *
michael@0 568 DoSubstr(JSContext *cx, JSString *str, size_t begin, size_t len)
michael@0 569 {
michael@0 570 /*
michael@0 571 * Optimization for one level deep ropes.
michael@0 572 * This is common for the following pattern:
michael@0 573 *
michael@0 574 * while() {
michael@0 575 * text = text.substr(0, x) + "bla" + text.substr(x)
michael@0 576 * test.charCodeAt(x + 1)
michael@0 577 * }
michael@0 578 */
michael@0 579 if (str->isRope()) {
michael@0 580 JSRope *rope = &str->asRope();
michael@0 581
michael@0 582 /* Substring is totally in leftChild of rope. */
michael@0 583 if (begin + len <= rope->leftChild()->length()) {
michael@0 584 str = rope->leftChild();
michael@0 585 return js_NewDependentString(cx, str, begin, len);
michael@0 586 }
michael@0 587
michael@0 588 /* Substring is totally in rightChild of rope. */
michael@0 589 if (begin >= rope->leftChild()->length()) {
michael@0 590 str = rope->rightChild();
michael@0 591 begin -= rope->leftChild()->length();
michael@0 592 return js_NewDependentString(cx, str, begin, len);
michael@0 593 }
michael@0 594
michael@0 595 /*
michael@0 596 * Requested substring is partly in the left and partly in right child.
michael@0 597 * Create a rope of substrings for both childs.
michael@0 598 */
michael@0 599 JS_ASSERT (begin < rope->leftChild()->length() &&
michael@0 600 begin + len > rope->leftChild()->length());
michael@0 601
michael@0 602 size_t lhsLength = rope->leftChild()->length() - begin;
michael@0 603 size_t rhsLength = begin + len - rope->leftChild()->length();
michael@0 604
michael@0 605 Rooted<JSRope *> ropeRoot(cx, rope);
michael@0 606 RootedString lhs(cx, js_NewDependentString(cx, ropeRoot->leftChild(),
michael@0 607 begin, lhsLength));
michael@0 608 if (!lhs)
michael@0 609 return nullptr;
michael@0 610
michael@0 611 RootedString rhs(cx, js_NewDependentString(cx, ropeRoot->rightChild(), 0, rhsLength));
michael@0 612 if (!rhs)
michael@0 613 return nullptr;
michael@0 614
michael@0 615 return JSRope::new_<CanGC>(cx, lhs, rhs, len);
michael@0 616 }
michael@0 617
michael@0 618 return js_NewDependentString(cx, str, begin, len);
michael@0 619 }
michael@0 620
michael@0 621 static bool
michael@0 622 str_substring(JSContext *cx, unsigned argc, Value *vp)
michael@0 623 {
michael@0 624 CallArgs args = CallArgsFromVp(argc, vp);
michael@0 625
michael@0 626 JSString *str = ThisToStringForStringProto(cx, args);
michael@0 627 if (!str)
michael@0 628 return false;
michael@0 629
michael@0 630 int32_t length, begin, end;
michael@0 631 if (args.length() > 0) {
michael@0 632 end = length = int32_t(str->length());
michael@0 633
michael@0 634 if (args[0].isInt32()) {
michael@0 635 begin = args[0].toInt32();
michael@0 636 } else {
michael@0 637 RootedString strRoot(cx, str);
michael@0 638 if (!ValueToIntegerRange(cx, args[0], &begin))
michael@0 639 return false;
michael@0 640 str = strRoot;
michael@0 641 }
michael@0 642
michael@0 643 if (begin < 0)
michael@0 644 begin = 0;
michael@0 645 else if (begin > length)
michael@0 646 begin = length;
michael@0 647
michael@0 648 if (args.hasDefined(1)) {
michael@0 649 if (args[1].isInt32()) {
michael@0 650 end = args[1].toInt32();
michael@0 651 } else {
michael@0 652 RootedString strRoot(cx, str);
michael@0 653 if (!ValueToIntegerRange(cx, args[1], &end))
michael@0 654 return false;
michael@0 655 str = strRoot;
michael@0 656 }
michael@0 657
michael@0 658 if (end > length) {
michael@0 659 end = length;
michael@0 660 } else {
michael@0 661 if (end < 0)
michael@0 662 end = 0;
michael@0 663 if (end < begin) {
michael@0 664 int32_t tmp = begin;
michael@0 665 begin = end;
michael@0 666 end = tmp;
michael@0 667 }
michael@0 668 }
michael@0 669 }
michael@0 670
michael@0 671 str = DoSubstr(cx, str, size_t(begin), size_t(end - begin));
michael@0 672 if (!str)
michael@0 673 return false;
michael@0 674 }
michael@0 675
michael@0 676 args.rval().setString(str);
michael@0 677 return true;
michael@0 678 }
michael@0 679
michael@0 680 JSString* JS_FASTCALL
michael@0 681 js_toLowerCase(JSContext *cx, JSString *str)
michael@0 682 {
michael@0 683 size_t n = str->length();
michael@0 684 const jschar *s = str->getChars(cx);
michael@0 685 if (!s)
michael@0 686 return nullptr;
michael@0 687
michael@0 688 jschar *news = cx->pod_malloc<jschar>(n + 1);
michael@0 689 if (!news)
michael@0 690 return nullptr;
michael@0 691 for (size_t i = 0; i < n; i++)
michael@0 692 news[i] = unicode::ToLowerCase(s[i]);
michael@0 693 news[n] = 0;
michael@0 694 str = js_NewString<CanGC>(cx, news, n);
michael@0 695 if (!str) {
michael@0 696 js_free(news);
michael@0 697 return nullptr;
michael@0 698 }
michael@0 699 return str;
michael@0 700 }
michael@0 701
michael@0 702 static inline bool
michael@0 703 ToLowerCaseHelper(JSContext *cx, CallReceiver call)
michael@0 704 {
michael@0 705 RootedString str(cx, ThisToStringForStringProto(cx, call));
michael@0 706 if (!str)
michael@0 707 return false;
michael@0 708
michael@0 709 str = js_toLowerCase(cx, str);
michael@0 710 if (!str)
michael@0 711 return false;
michael@0 712
michael@0 713 call.rval().setString(str);
michael@0 714 return true;
michael@0 715 }
michael@0 716
michael@0 717 static bool
michael@0 718 str_toLowerCase(JSContext *cx, unsigned argc, Value *vp)
michael@0 719 {
michael@0 720 return ToLowerCaseHelper(cx, CallArgsFromVp(argc, vp));
michael@0 721 }
michael@0 722
michael@0 723 static bool
michael@0 724 str_toLocaleLowerCase(JSContext *cx, unsigned argc, Value *vp)
michael@0 725 {
michael@0 726 CallArgs args = CallArgsFromVp(argc, vp);
michael@0 727
michael@0 728 /*
michael@0 729 * Forcefully ignore the first (or any) argument and return toLowerCase(),
michael@0 730 * ECMA has reserved that argument, presumably for defining the locale.
michael@0 731 */
michael@0 732 if (cx->runtime()->localeCallbacks && cx->runtime()->localeCallbacks->localeToLowerCase) {
michael@0 733 RootedString str(cx, ThisToStringForStringProto(cx, args));
michael@0 734 if (!str)
michael@0 735 return false;
michael@0 736
michael@0 737 RootedValue result(cx);
michael@0 738 if (!cx->runtime()->localeCallbacks->localeToLowerCase(cx, str, &result))
michael@0 739 return false;
michael@0 740
michael@0 741 args.rval().set(result);
michael@0 742 return true;
michael@0 743 }
michael@0 744
michael@0 745 return ToLowerCaseHelper(cx, args);
michael@0 746 }
michael@0 747
michael@0 748 JSString* JS_FASTCALL
michael@0 749 js_toUpperCase(JSContext *cx, JSString *str)
michael@0 750 {
michael@0 751 size_t n = str->length();
michael@0 752 const jschar *s = str->getChars(cx);
michael@0 753 if (!s)
michael@0 754 return nullptr;
michael@0 755 jschar *news = cx->pod_malloc<jschar>(n + 1);
michael@0 756 if (!news)
michael@0 757 return nullptr;
michael@0 758 for (size_t i = 0; i < n; i++)
michael@0 759 news[i] = unicode::ToUpperCase(s[i]);
michael@0 760 news[n] = 0;
michael@0 761 str = js_NewString<CanGC>(cx, news, n);
michael@0 762 if (!str) {
michael@0 763 js_free(news);
michael@0 764 return nullptr;
michael@0 765 }
michael@0 766 return str;
michael@0 767 }
michael@0 768
michael@0 769 static bool
michael@0 770 ToUpperCaseHelper(JSContext *cx, CallReceiver call)
michael@0 771 {
michael@0 772 RootedString str(cx, ThisToStringForStringProto(cx, call));
michael@0 773 if (!str)
michael@0 774 return false;
michael@0 775
michael@0 776 str = js_toUpperCase(cx, str);
michael@0 777 if (!str)
michael@0 778 return false;
michael@0 779
michael@0 780 call.rval().setString(str);
michael@0 781 return true;
michael@0 782 }
michael@0 783
michael@0 784 static bool
michael@0 785 str_toUpperCase(JSContext *cx, unsigned argc, Value *vp)
michael@0 786 {
michael@0 787 return ToUpperCaseHelper(cx, CallArgsFromVp(argc, vp));
michael@0 788 }
michael@0 789
michael@0 790 static bool
michael@0 791 str_toLocaleUpperCase(JSContext *cx, unsigned argc, Value *vp)
michael@0 792 {
michael@0 793 CallArgs args = CallArgsFromVp(argc, vp);
michael@0 794
michael@0 795 /*
michael@0 796 * Forcefully ignore the first (or any) argument and return toUpperCase(),
michael@0 797 * ECMA has reserved that argument, presumably for defining the locale.
michael@0 798 */
michael@0 799 if (cx->runtime()->localeCallbacks && cx->runtime()->localeCallbacks->localeToUpperCase) {
michael@0 800 RootedString str(cx, ThisToStringForStringProto(cx, args));
michael@0 801 if (!str)
michael@0 802 return false;
michael@0 803
michael@0 804 RootedValue result(cx);
michael@0 805 if (!cx->runtime()->localeCallbacks->localeToUpperCase(cx, str, &result))
michael@0 806 return false;
michael@0 807
michael@0 808 args.rval().set(result);
michael@0 809 return true;
michael@0 810 }
michael@0 811
michael@0 812 return ToUpperCaseHelper(cx, args);
michael@0 813 }
michael@0 814
michael@0 815 #if !EXPOSE_INTL_API
michael@0 816 static bool
michael@0 817 str_localeCompare(JSContext *cx, unsigned argc, Value *vp)
michael@0 818 {
michael@0 819 CallArgs args = CallArgsFromVp(argc, vp);
michael@0 820 RootedString str(cx, ThisToStringForStringProto(cx, args));
michael@0 821 if (!str)
michael@0 822 return false;
michael@0 823
michael@0 824 RootedString thatStr(cx, ToString<CanGC>(cx, args.get(0)));
michael@0 825 if (!thatStr)
michael@0 826 return false;
michael@0 827
michael@0 828 if (cx->runtime()->localeCallbacks && cx->runtime()->localeCallbacks->localeCompare) {
michael@0 829 RootedValue result(cx);
michael@0 830 if (!cx->runtime()->localeCallbacks->localeCompare(cx, str, thatStr, &result))
michael@0 831 return false;
michael@0 832
michael@0 833 args.rval().set(result);
michael@0 834 return true;
michael@0 835 }
michael@0 836
michael@0 837 int32_t result;
michael@0 838 if (!CompareStrings(cx, str, thatStr, &result))
michael@0 839 return false;
michael@0 840
michael@0 841 args.rval().setInt32(result);
michael@0 842 return true;
michael@0 843 }
michael@0 844 #endif
michael@0 845
michael@0 846 #if EXPOSE_INTL_API
michael@0 847 static const size_t SB_LENGTH = 32;
michael@0 848
michael@0 849 /* ES6 20140210 draft 21.1.3.12. */
michael@0 850 static bool
michael@0 851 str_normalize(JSContext *cx, unsigned argc, Value *vp)
michael@0 852 {
michael@0 853 CallArgs args = CallArgsFromVp(argc, vp);
michael@0 854
michael@0 855 // Steps 1-3.
michael@0 856 RootedString str(cx, ThisToStringForStringProto(cx, args));
michael@0 857 if (!str)
michael@0 858 return false;
michael@0 859
michael@0 860 // Step 4.
michael@0 861 UNormalizationMode form;
michael@0 862 if (!args.hasDefined(0)) {
michael@0 863 form = UNORM_NFC;
michael@0 864 } else {
michael@0 865 // Steps 5-6.
michael@0 866 Rooted<JSLinearString*> formStr(cx, ArgToRootedString(cx, args, 0));
michael@0 867 if (!formStr)
michael@0 868 return false;
michael@0 869
michael@0 870 // Step 7.
michael@0 871 if (formStr == cx->names().NFC) {
michael@0 872 form = UNORM_NFC;
michael@0 873 } else if (formStr == cx->names().NFD) {
michael@0 874 form = UNORM_NFD;
michael@0 875 } else if (formStr == cx->names().NFKC) {
michael@0 876 form = UNORM_NFKC;
michael@0 877 } else if (formStr == cx->names().NFKD) {
michael@0 878 form = UNORM_NFKD;
michael@0 879 } else {
michael@0 880 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr,
michael@0 881 JSMSG_INVALID_NORMALIZE_FORM);
michael@0 882 return false;
michael@0 883 }
michael@0 884 }
michael@0 885
michael@0 886 // Step 8.
michael@0 887 Rooted<JSFlatString*> flatStr(cx, str->ensureFlat(cx));
michael@0 888 if (!flatStr)
michael@0 889 return false;
michael@0 890 const UChar *srcChars = JSCharToUChar(flatStr->chars());
michael@0 891 int32_t srcLen = SafeCast<int32_t>(flatStr->length());
michael@0 892 StringBuffer chars(cx);
michael@0 893 if (!chars.resize(SB_LENGTH))
michael@0 894 return false;
michael@0 895 UErrorCode status = U_ZERO_ERROR;
michael@0 896 int32_t size = unorm_normalize(srcChars, srcLen, form, 0,
michael@0 897 JSCharToUChar(chars.begin()), SB_LENGTH,
michael@0 898 &status);
michael@0 899 if (status == U_BUFFER_OVERFLOW_ERROR) {
michael@0 900 if (!chars.resize(size))
michael@0 901 return false;
michael@0 902 status = U_ZERO_ERROR;
michael@0 903 #ifdef DEBUG
michael@0 904 int32_t finalSize =
michael@0 905 #endif
michael@0 906 unorm_normalize(srcChars, srcLen, form, 0,
michael@0 907 JSCharToUChar(chars.begin()), size,
michael@0 908 &status);
michael@0 909 MOZ_ASSERT(size == finalSize || U_FAILURE(status), "unorm_normalize behaved inconsistently");
michael@0 910 }
michael@0 911 if (U_FAILURE(status))
michael@0 912 return false;
michael@0 913 // Trim any unused characters.
michael@0 914 if (!chars.resize(size))
michael@0 915 return false;
michael@0 916 RootedString ns(cx, chars.finishString());
michael@0 917 if (!ns)
michael@0 918 return false;
michael@0 919
michael@0 920 // Step 9.
michael@0 921 args.rval().setString(ns);
michael@0 922 return true;
michael@0 923 }
michael@0 924 #endif
michael@0 925
michael@0 926 bool
michael@0 927 js_str_charAt(JSContext *cx, unsigned argc, Value *vp)
michael@0 928 {
michael@0 929 CallArgs args = CallArgsFromVp(argc, vp);
michael@0 930
michael@0 931 RootedString str(cx);
michael@0 932 size_t i;
michael@0 933 if (args.thisv().isString() && args.length() != 0 && args[0].isInt32()) {
michael@0 934 str = args.thisv().toString();
michael@0 935 i = size_t(args[0].toInt32());
michael@0 936 if (i >= str->length())
michael@0 937 goto out_of_range;
michael@0 938 } else {
michael@0 939 str = ThisToStringForStringProto(cx, args);
michael@0 940 if (!str)
michael@0 941 return false;
michael@0 942
michael@0 943 double d = 0.0;
michael@0 944 if (args.length() > 0 && !ToInteger(cx, args[0], &d))
michael@0 945 return false;
michael@0 946
michael@0 947 if (d < 0 || str->length() <= d)
michael@0 948 goto out_of_range;
michael@0 949 i = size_t(d);
michael@0 950 }
michael@0 951
michael@0 952 str = cx->staticStrings().getUnitStringForElement(cx, str, i);
michael@0 953 if (!str)
michael@0 954 return false;
michael@0 955 args.rval().setString(str);
michael@0 956 return true;
michael@0 957
michael@0 958 out_of_range:
michael@0 959 args.rval().setString(cx->runtime()->emptyString);
michael@0 960 return true;
michael@0 961 }
michael@0 962
michael@0 963 bool
michael@0 964 js_str_charCodeAt(JSContext *cx, unsigned argc, Value *vp)
michael@0 965 {
michael@0 966 CallArgs args = CallArgsFromVp(argc, vp);
michael@0 967
michael@0 968 RootedString str(cx);
michael@0 969 size_t i;
michael@0 970 if (args.thisv().isString() && args.length() != 0 && args[0].isInt32()) {
michael@0 971 str = args.thisv().toString();
michael@0 972 i = size_t(args[0].toInt32());
michael@0 973 if (i >= str->length())
michael@0 974 goto out_of_range;
michael@0 975 } else {
michael@0 976 str = ThisToStringForStringProto(cx, args);
michael@0 977 if (!str)
michael@0 978 return false;
michael@0 979
michael@0 980 double d = 0.0;
michael@0 981 if (args.length() > 0 && !ToInteger(cx, args[0], &d))
michael@0 982 return false;
michael@0 983
michael@0 984 if (d < 0 || str->length() <= d)
michael@0 985 goto out_of_range;
michael@0 986 i = size_t(d);
michael@0 987 }
michael@0 988
michael@0 989 jschar c;
michael@0 990 if (!str->getChar(cx, i, &c))
michael@0 991 return false;
michael@0 992 args.rval().setInt32(c);
michael@0 993 return true;
michael@0 994
michael@0 995 out_of_range:
michael@0 996 args.rval().setNaN();
michael@0 997 return true;
michael@0 998 }
michael@0 999
michael@0 1000 /*
michael@0 1001 * Boyer-Moore-Horspool superlinear search for pat:patlen in text:textlen.
michael@0 1002 * The patlen argument must be positive and no greater than sBMHPatLenMax.
michael@0 1003 *
michael@0 1004 * Return the index of pat in text, or -1 if not found.
michael@0 1005 */
michael@0 1006 static const uint32_t sBMHCharSetSize = 256; /* ISO-Latin-1 */
michael@0 1007 static const uint32_t sBMHPatLenMax = 255; /* skip table element is uint8_t */
michael@0 1008 static const int sBMHBadPattern = -2; /* return value if pat is not ISO-Latin-1 */
michael@0 1009
michael@0 1010 int
michael@0 1011 js_BoyerMooreHorspool(const jschar *text, uint32_t textlen,
michael@0 1012 const jschar *pat, uint32_t patlen)
michael@0 1013 {
michael@0 1014 uint8_t skip[sBMHCharSetSize];
michael@0 1015
michael@0 1016 JS_ASSERT(0 < patlen && patlen <= sBMHPatLenMax);
michael@0 1017 for (uint32_t i = 0; i < sBMHCharSetSize; i++)
michael@0 1018 skip[i] = (uint8_t)patlen;
michael@0 1019 uint32_t m = patlen - 1;
michael@0 1020 for (uint32_t i = 0; i < m; i++) {
michael@0 1021 jschar c = pat[i];
michael@0 1022 if (c >= sBMHCharSetSize)
michael@0 1023 return sBMHBadPattern;
michael@0 1024 skip[c] = (uint8_t)(m - i);
michael@0 1025 }
michael@0 1026 jschar c;
michael@0 1027 for (uint32_t k = m;
michael@0 1028 k < textlen;
michael@0 1029 k += ((c = text[k]) >= sBMHCharSetSize) ? patlen : skip[c]) {
michael@0 1030 for (uint32_t i = k, j = m; ; i--, j--) {
michael@0 1031 if (text[i] != pat[j])
michael@0 1032 break;
michael@0 1033 if (j == 0)
michael@0 1034 return static_cast<int>(i); /* safe: max string size */
michael@0 1035 }
michael@0 1036 }
michael@0 1037 return -1;
michael@0 1038 }
michael@0 1039
michael@0 1040 struct MemCmp {
michael@0 1041 typedef uint32_t Extent;
michael@0 1042 static MOZ_ALWAYS_INLINE Extent computeExtent(const jschar *, uint32_t patlen) {
michael@0 1043 return (patlen - 1) * sizeof(jschar);
michael@0 1044 }
michael@0 1045 static MOZ_ALWAYS_INLINE bool match(const jschar *p, const jschar *t, Extent extent) {
michael@0 1046 return memcmp(p, t, extent) == 0;
michael@0 1047 }
michael@0 1048 };
michael@0 1049
michael@0 1050 struct ManualCmp {
michael@0 1051 typedef const jschar *Extent;
michael@0 1052 static MOZ_ALWAYS_INLINE Extent computeExtent(const jschar *pat, uint32_t patlen) {
michael@0 1053 return pat + patlen;
michael@0 1054 }
michael@0 1055 static MOZ_ALWAYS_INLINE bool match(const jschar *p, const jschar *t, Extent extent) {
michael@0 1056 for (; p != extent; ++p, ++t) {
michael@0 1057 if (*p != *t)
michael@0 1058 return false;
michael@0 1059 }
michael@0 1060 return true;
michael@0 1061 }
michael@0 1062 };
michael@0 1063
michael@0 1064 template <class InnerMatch>
michael@0 1065 static int
michael@0 1066 UnrolledMatch(const jschar *text, uint32_t textlen, const jschar *pat, uint32_t patlen)
michael@0 1067 {
michael@0 1068 JS_ASSERT(patlen > 0 && textlen > 0);
michael@0 1069 const jschar *textend = text + textlen - (patlen - 1);
michael@0 1070 const jschar p0 = *pat;
michael@0 1071 const jschar *const patNext = pat + 1;
michael@0 1072 const typename InnerMatch::Extent extent = InnerMatch::computeExtent(pat, patlen);
michael@0 1073 uint8_t fixup;
michael@0 1074
michael@0 1075 const jschar *t = text;
michael@0 1076 switch ((textend - t) & 7) {
michael@0 1077 case 0: if (*t++ == p0) { fixup = 8; goto match; }
michael@0 1078 case 7: if (*t++ == p0) { fixup = 7; goto match; }
michael@0 1079 case 6: if (*t++ == p0) { fixup = 6; goto match; }
michael@0 1080 case 5: if (*t++ == p0) { fixup = 5; goto match; }
michael@0 1081 case 4: if (*t++ == p0) { fixup = 4; goto match; }
michael@0 1082 case 3: if (*t++ == p0) { fixup = 3; goto match; }
michael@0 1083 case 2: if (*t++ == p0) { fixup = 2; goto match; }
michael@0 1084 case 1: if (*t++ == p0) { fixup = 1; goto match; }
michael@0 1085 }
michael@0 1086 while (t != textend) {
michael@0 1087 if (t[0] == p0) { t += 1; fixup = 8; goto match; }
michael@0 1088 if (t[1] == p0) { t += 2; fixup = 7; goto match; }
michael@0 1089 if (t[2] == p0) { t += 3; fixup = 6; goto match; }
michael@0 1090 if (t[3] == p0) { t += 4; fixup = 5; goto match; }
michael@0 1091 if (t[4] == p0) { t += 5; fixup = 4; goto match; }
michael@0 1092 if (t[5] == p0) { t += 6; fixup = 3; goto match; }
michael@0 1093 if (t[6] == p0) { t += 7; fixup = 2; goto match; }
michael@0 1094 if (t[7] == p0) { t += 8; fixup = 1; goto match; }
michael@0 1095 t += 8;
michael@0 1096 continue;
michael@0 1097 do {
michael@0 1098 if (*t++ == p0) {
michael@0 1099 match:
michael@0 1100 if (!InnerMatch::match(patNext, t, extent))
michael@0 1101 goto failed_match;
michael@0 1102 return t - text - 1;
michael@0 1103 }
michael@0 1104 failed_match:;
michael@0 1105 } while (--fixup > 0);
michael@0 1106 }
michael@0 1107 return -1;
michael@0 1108 }
michael@0 1109
michael@0 1110 static MOZ_ALWAYS_INLINE int
michael@0 1111 StringMatch(const jschar *text, uint32_t textlen,
michael@0 1112 const jschar *pat, uint32_t patlen)
michael@0 1113 {
michael@0 1114 if (patlen == 0)
michael@0 1115 return 0;
michael@0 1116 if (textlen < patlen)
michael@0 1117 return -1;
michael@0 1118
michael@0 1119 #if defined(__i386__) || defined(_M_IX86) || defined(__i386)
michael@0 1120 /*
michael@0 1121 * Given enough registers, the unrolled loop below is faster than the
michael@0 1122 * following loop. 32-bit x86 does not have enough registers.
michael@0 1123 */
michael@0 1124 if (patlen == 1) {
michael@0 1125 const jschar p0 = *pat;
michael@0 1126 for (const jschar *c = text, *end = text + textlen; c != end; ++c) {
michael@0 1127 if (*c == p0)
michael@0 1128 return c - text;
michael@0 1129 }
michael@0 1130 return -1;
michael@0 1131 }
michael@0 1132 #endif
michael@0 1133
michael@0 1134 /*
michael@0 1135 * If the text or pattern string is short, BMH will be more expensive than
michael@0 1136 * the basic linear scan due to initialization cost and a more complex loop
michael@0 1137 * body. While the correct threshold is input-dependent, we can make a few
michael@0 1138 * conservative observations:
michael@0 1139 * - When |textlen| is "big enough", the initialization time will be
michael@0 1140 * proportionally small, so the worst-case slowdown is minimized.
michael@0 1141 * - When |patlen| is "too small", even the best case for BMH will be
michael@0 1142 * slower than a simple scan for large |textlen| due to the more complex
michael@0 1143 * loop body of BMH.
michael@0 1144 * From this, the values for "big enough" and "too small" are determined
michael@0 1145 * empirically. See bug 526348.
michael@0 1146 */
michael@0 1147 if (textlen >= 512 && patlen >= 11 && patlen <= sBMHPatLenMax) {
michael@0 1148 int index = js_BoyerMooreHorspool(text, textlen, pat, patlen);
michael@0 1149 if (index != sBMHBadPattern)
michael@0 1150 return index;
michael@0 1151 }
michael@0 1152
michael@0 1153 /*
michael@0 1154 * For big patterns with large potential overlap we want the SIMD-optimized
michael@0 1155 * speed of memcmp. For small patterns, a simple loop is faster.
michael@0 1156 *
michael@0 1157 * FIXME: Linux memcmp performance is sad and the manual loop is faster.
michael@0 1158 */
michael@0 1159 return
michael@0 1160 #if !defined(__linux__)
michael@0 1161 patlen > 128 ? UnrolledMatch<MemCmp>(text, textlen, pat, patlen)
michael@0 1162 :
michael@0 1163 #endif
michael@0 1164 UnrolledMatch<ManualCmp>(text, textlen, pat, patlen);
michael@0 1165 }
michael@0 1166
michael@0 1167 static const size_t sRopeMatchThresholdRatioLog2 = 5;
michael@0 1168
michael@0 1169 bool
michael@0 1170 js::StringHasPattern(const jschar *text, uint32_t textlen,
michael@0 1171 const jschar *pat, uint32_t patlen)
michael@0 1172 {
michael@0 1173 return StringMatch(text, textlen, pat, patlen) != -1;
michael@0 1174 }
michael@0 1175
michael@0 1176 // When an algorithm does not need a string represented as a single linear
michael@0 1177 // array of characters, this range utility may be used to traverse the string a
michael@0 1178 // sequence of linear arrays of characters. This avoids flattening ropes.
michael@0 1179 class StringSegmentRange
michael@0 1180 {
michael@0 1181 // If malloc() shows up in any profiles from this vector, we can add a new
michael@0 1182 // StackAllocPolicy which stashes a reusable freed-at-gc buffer in the cx.
michael@0 1183 AutoStringVector stack;
michael@0 1184 Rooted<JSLinearString*> cur;
michael@0 1185
michael@0 1186 bool settle(JSString *str) {
michael@0 1187 while (str->isRope()) {
michael@0 1188 JSRope &rope = str->asRope();
michael@0 1189 if (!stack.append(rope.rightChild()))
michael@0 1190 return false;
michael@0 1191 str = rope.leftChild();
michael@0 1192 }
michael@0 1193 cur = &str->asLinear();
michael@0 1194 return true;
michael@0 1195 }
michael@0 1196
michael@0 1197 public:
michael@0 1198 StringSegmentRange(JSContext *cx)
michael@0 1199 : stack(cx), cur(cx)
michael@0 1200 {}
michael@0 1201
michael@0 1202 MOZ_WARN_UNUSED_RESULT bool init(JSString *str) {
michael@0 1203 JS_ASSERT(stack.empty());
michael@0 1204 return settle(str);
michael@0 1205 }
michael@0 1206
michael@0 1207 bool empty() const {
michael@0 1208 return cur == nullptr;
michael@0 1209 }
michael@0 1210
michael@0 1211 JSLinearString *front() const {
michael@0 1212 JS_ASSERT(!cur->isRope());
michael@0 1213 return cur;
michael@0 1214 }
michael@0 1215
michael@0 1216 MOZ_WARN_UNUSED_RESULT bool popFront() {
michael@0 1217 JS_ASSERT(!empty());
michael@0 1218 if (stack.empty()) {
michael@0 1219 cur = nullptr;
michael@0 1220 return true;
michael@0 1221 }
michael@0 1222 return settle(stack.popCopy());
michael@0 1223 }
michael@0 1224 };
michael@0 1225
michael@0 1226 /*
michael@0 1227 * RopeMatch takes the text to search and the pattern to search for in the text.
michael@0 1228 * RopeMatch returns false on OOM and otherwise returns the match index through
michael@0 1229 * the 'match' outparam (-1 for not found).
michael@0 1230 */
michael@0 1231 static bool
michael@0 1232 RopeMatch(JSContext *cx, JSString *textstr, const jschar *pat, uint32_t patlen, int *match)
michael@0 1233 {
michael@0 1234 JS_ASSERT(textstr->isRope());
michael@0 1235
michael@0 1236 if (patlen == 0) {
michael@0 1237 *match = 0;
michael@0 1238 return true;
michael@0 1239 }
michael@0 1240 if (textstr->length() < patlen) {
michael@0 1241 *match = -1;
michael@0 1242 return true;
michael@0 1243 }
michael@0 1244
michael@0 1245 /*
michael@0 1246 * List of leaf nodes in the rope. If we run out of memory when trying to
michael@0 1247 * append to this list, we can still fall back to StringMatch, so use the
michael@0 1248 * system allocator so we don't report OOM in that case.
michael@0 1249 */
michael@0 1250 Vector<JSLinearString *, 16, SystemAllocPolicy> strs;
michael@0 1251
michael@0 1252 /*
michael@0 1253 * We don't want to do rope matching if there is a poor node-to-char ratio,
michael@0 1254 * since this means spending a lot of time in the match loop below. We also
michael@0 1255 * need to build the list of leaf nodes. Do both here: iterate over the
michael@0 1256 * nodes so long as there are not too many.
michael@0 1257 */
michael@0 1258 {
michael@0 1259 size_t textstrlen = textstr->length();
michael@0 1260 size_t threshold = textstrlen >> sRopeMatchThresholdRatioLog2;
michael@0 1261 StringSegmentRange r(cx);
michael@0 1262 if (!r.init(textstr))
michael@0 1263 return false;
michael@0 1264 while (!r.empty()) {
michael@0 1265 if (threshold-- == 0 || !strs.append(r.front())) {
michael@0 1266 const jschar *chars = textstr->getChars(cx);
michael@0 1267 if (!chars)
michael@0 1268 return false;
michael@0 1269 *match = StringMatch(chars, textstrlen, pat, patlen);
michael@0 1270 return true;
michael@0 1271 }
michael@0 1272 if (!r.popFront())
michael@0 1273 return false;
michael@0 1274 }
michael@0 1275 }
michael@0 1276
michael@0 1277 /* Absolute offset from the beginning of the logical string textstr. */
michael@0 1278 int pos = 0;
michael@0 1279
michael@0 1280 for (JSLinearString **outerp = strs.begin(); outerp != strs.end(); ++outerp) {
michael@0 1281 /* Try to find a match within 'outer'. */
michael@0 1282 JSLinearString *outer = *outerp;
michael@0 1283 const jschar *chars = outer->chars();
michael@0 1284 size_t len = outer->length();
michael@0 1285 int matchResult = StringMatch(chars, len, pat, patlen);
michael@0 1286 if (matchResult != -1) {
michael@0 1287 /* Matched! */
michael@0 1288 *match = pos + matchResult;
michael@0 1289 return true;
michael@0 1290 }
michael@0 1291
michael@0 1292 /* Try to find a match starting in 'outer' and running into other nodes. */
michael@0 1293 const jschar *const text = chars + (patlen > len ? 0 : len - patlen + 1);
michael@0 1294 const jschar *const textend = chars + len;
michael@0 1295 const jschar p0 = *pat;
michael@0 1296 const jschar *const p1 = pat + 1;
michael@0 1297 const jschar *const patend = pat + patlen;
michael@0 1298 for (const jschar *t = text; t != textend; ) {
michael@0 1299 if (*t++ != p0)
michael@0 1300 continue;
michael@0 1301 JSLinearString **innerp = outerp;
michael@0 1302 const jschar *ttend = textend;
michael@0 1303 for (const jschar *pp = p1, *tt = t; pp != patend; ++pp, ++tt) {
michael@0 1304 while (tt == ttend) {
michael@0 1305 if (++innerp == strs.end()) {
michael@0 1306 *match = -1;
michael@0 1307 return true;
michael@0 1308 }
michael@0 1309 JSLinearString *inner = *innerp;
michael@0 1310 tt = inner->chars();
michael@0 1311 ttend = tt + inner->length();
michael@0 1312 }
michael@0 1313 if (*pp != *tt)
michael@0 1314 goto break_continue;
michael@0 1315 }
michael@0 1316
michael@0 1317 /* Matched! */
michael@0 1318 *match = pos + (t - chars) - 1; /* -1 because of *t++ above */
michael@0 1319 return true;
michael@0 1320
michael@0 1321 break_continue:;
michael@0 1322 }
michael@0 1323
michael@0 1324 pos += len;
michael@0 1325 }
michael@0 1326
michael@0 1327 *match = -1;
michael@0 1328 return true;
michael@0 1329 }
michael@0 1330
michael@0 1331 /* ES6 20121026 draft 15.5.4.24. */
michael@0 1332 static bool
michael@0 1333 str_contains(JSContext *cx, unsigned argc, Value *vp)
michael@0 1334 {
michael@0 1335 CallArgs args = CallArgsFromVp(argc, vp);
michael@0 1336
michael@0 1337 // Steps 1, 2, and 3
michael@0 1338 RootedString str(cx, ThisToStringForStringProto(cx, args));
michael@0 1339 if (!str)
michael@0 1340 return false;
michael@0 1341
michael@0 1342 // Steps 4 and 5
michael@0 1343 Rooted<JSLinearString*> searchStr(cx, ArgToRootedString(cx, args, 0));
michael@0 1344 if (!searchStr)
michael@0 1345 return false;
michael@0 1346
michael@0 1347 // Steps 6 and 7
michael@0 1348 uint32_t pos = 0;
michael@0 1349 if (args.hasDefined(1)) {
michael@0 1350 if (args[1].isInt32()) {
michael@0 1351 int i = args[1].toInt32();
michael@0 1352 pos = (i < 0) ? 0U : uint32_t(i);
michael@0 1353 } else {
michael@0 1354 double d;
michael@0 1355 if (!ToInteger(cx, args[1], &d))
michael@0 1356 return false;
michael@0 1357 pos = uint32_t(Min(Max(d, 0.0), double(UINT32_MAX)));
michael@0 1358 }
michael@0 1359 }
michael@0 1360
michael@0 1361 // Step 8
michael@0 1362 uint32_t textLen = str->length();
michael@0 1363 const jschar *textChars = str->getChars(cx);
michael@0 1364 if (!textChars)
michael@0 1365 return false;
michael@0 1366
michael@0 1367 // Step 9
michael@0 1368 uint32_t start = Min(Max(pos, 0U), textLen);
michael@0 1369
michael@0 1370 // Step 10
michael@0 1371 uint32_t searchLen = searchStr->length();
michael@0 1372 const jschar *searchChars = searchStr->chars();
michael@0 1373
michael@0 1374 // Step 11
michael@0 1375 textChars += start;
michael@0 1376 textLen -= start;
michael@0 1377 int match = StringMatch(textChars, textLen, searchChars, searchLen);
michael@0 1378 args.rval().setBoolean(match != -1);
michael@0 1379 return true;
michael@0 1380 }
michael@0 1381
michael@0 1382 /* ES6 20120927 draft 15.5.4.7. */
michael@0 1383 static bool
michael@0 1384 str_indexOf(JSContext *cx, unsigned argc, Value *vp)
michael@0 1385 {
michael@0 1386 CallArgs args = CallArgsFromVp(argc, vp);
michael@0 1387
michael@0 1388 // Steps 1, 2, and 3
michael@0 1389 RootedString str(cx, ThisToStringForStringProto(cx, args));
michael@0 1390 if (!str)
michael@0 1391 return false;
michael@0 1392
michael@0 1393 // Steps 4 and 5
michael@0 1394 Rooted<JSLinearString*> searchStr(cx, ArgToRootedString(cx, args, 0));
michael@0 1395 if (!searchStr)
michael@0 1396 return false;
michael@0 1397
michael@0 1398 // Steps 6 and 7
michael@0 1399 uint32_t pos = 0;
michael@0 1400 if (args.hasDefined(1)) {
michael@0 1401 if (args[1].isInt32()) {
michael@0 1402 int i = args[1].toInt32();
michael@0 1403 pos = (i < 0) ? 0U : uint32_t(i);
michael@0 1404 } else {
michael@0 1405 double d;
michael@0 1406 if (!ToInteger(cx, args[1], &d))
michael@0 1407 return false;
michael@0 1408 pos = uint32_t(Min(Max(d, 0.0), double(UINT32_MAX)));
michael@0 1409 }
michael@0 1410 }
michael@0 1411
michael@0 1412 // Step 8
michael@0 1413 uint32_t textLen = str->length();
michael@0 1414 const jschar *textChars = str->getChars(cx);
michael@0 1415 if (!textChars)
michael@0 1416 return false;
michael@0 1417
michael@0 1418 // Step 9
michael@0 1419 uint32_t start = Min(Max(pos, 0U), textLen);
michael@0 1420
michael@0 1421 // Step 10
michael@0 1422 uint32_t searchLen = searchStr->length();
michael@0 1423 const jschar *searchChars = searchStr->chars();
michael@0 1424
michael@0 1425 // Step 11
michael@0 1426 textChars += start;
michael@0 1427 textLen -= start;
michael@0 1428 int match = StringMatch(textChars, textLen, searchChars, searchLen);
michael@0 1429 args.rval().setInt32((match == -1) ? -1 : start + match);
michael@0 1430 return true;
michael@0 1431 }
michael@0 1432
michael@0 1433 static bool
michael@0 1434 str_lastIndexOf(JSContext *cx, unsigned argc, Value *vp)
michael@0 1435 {
michael@0 1436 CallArgs args = CallArgsFromVp(argc, vp);
michael@0 1437 RootedString textstr(cx, ThisToStringForStringProto(cx, args));
michael@0 1438 if (!textstr)
michael@0 1439 return false;
michael@0 1440
michael@0 1441 size_t textlen = textstr->length();
michael@0 1442
michael@0 1443 Rooted<JSLinearString*> patstr(cx, ArgToRootedString(cx, args, 0));
michael@0 1444 if (!patstr)
michael@0 1445 return false;
michael@0 1446
michael@0 1447 size_t patlen = patstr->length();
michael@0 1448
michael@0 1449 int i = textlen - patlen; // Start searching here
michael@0 1450 if (i < 0) {
michael@0 1451 args.rval().setInt32(-1);
michael@0 1452 return true;
michael@0 1453 }
michael@0 1454
michael@0 1455 if (args.length() > 1) {
michael@0 1456 if (args[1].isInt32()) {
michael@0 1457 int j = args[1].toInt32();
michael@0 1458 if (j <= 0)
michael@0 1459 i = 0;
michael@0 1460 else if (j < i)
michael@0 1461 i = j;
michael@0 1462 } else {
michael@0 1463 double d;
michael@0 1464 if (!ToNumber(cx, args[1], &d))
michael@0 1465 return false;
michael@0 1466 if (!IsNaN(d)) {
michael@0 1467 d = ToInteger(d);
michael@0 1468 if (d <= 0)
michael@0 1469 i = 0;
michael@0 1470 else if (d < i)
michael@0 1471 i = (int)d;
michael@0 1472 }
michael@0 1473 }
michael@0 1474 }
michael@0 1475
michael@0 1476 if (patlen == 0) {
michael@0 1477 args.rval().setInt32(i);
michael@0 1478 return true;
michael@0 1479 }
michael@0 1480
michael@0 1481 const jschar *text = textstr->getChars(cx);
michael@0 1482 if (!text)
michael@0 1483 return false;
michael@0 1484
michael@0 1485 const jschar *pat = patstr->chars();
michael@0 1486
michael@0 1487 const jschar *t = text + i;
michael@0 1488 const jschar *textend = text - 1;
michael@0 1489 const jschar p0 = *pat;
michael@0 1490 const jschar *patNext = pat + 1;
michael@0 1491 const jschar *patEnd = pat + patlen;
michael@0 1492
michael@0 1493 for (; t != textend; --t) {
michael@0 1494 if (*t == p0) {
michael@0 1495 const jschar *t1 = t + 1;
michael@0 1496 for (const jschar *p1 = patNext; p1 != patEnd; ++p1, ++t1) {
michael@0 1497 if (*t1 != *p1)
michael@0 1498 goto break_continue;
michael@0 1499 }
michael@0 1500 args.rval().setInt32(t - text);
michael@0 1501 return true;
michael@0 1502 }
michael@0 1503 break_continue:;
michael@0 1504 }
michael@0 1505
michael@0 1506 args.rval().setInt32(-1);
michael@0 1507 return true;
michael@0 1508 }
michael@0 1509
michael@0 1510 /* ES6 20131108 draft 21.1.3.18. */
michael@0 1511 static bool
michael@0 1512 str_startsWith(JSContext *cx, unsigned argc, Value *vp)
michael@0 1513 {
michael@0 1514 CallArgs args = CallArgsFromVp(argc, vp);
michael@0 1515
michael@0 1516 // Steps 1, 2, and 3
michael@0 1517 RootedString str(cx, ThisToStringForStringProto(cx, args));
michael@0 1518 if (!str)
michael@0 1519 return false;
michael@0 1520
michael@0 1521 // Step 4
michael@0 1522 if (args.get(0).isObject() && IsObjectWithClass(args[0], ESClass_RegExp, cx)) {
michael@0 1523 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INVALID_ARG_TYPE,
michael@0 1524 "first", "", "Regular Expression");
michael@0 1525 return false;
michael@0 1526 }
michael@0 1527
michael@0 1528 // Steps 5 and 6
michael@0 1529 Rooted<JSLinearString*> searchStr(cx, ArgToRootedString(cx, args, 0));
michael@0 1530 if (!searchStr)
michael@0 1531 return false;
michael@0 1532
michael@0 1533 // Steps 7 and 8
michael@0 1534 uint32_t pos = 0;
michael@0 1535 if (args.hasDefined(1)) {
michael@0 1536 if (args[1].isInt32()) {
michael@0 1537 int i = args[1].toInt32();
michael@0 1538 pos = (i < 0) ? 0U : uint32_t(i);
michael@0 1539 } else {
michael@0 1540 double d;
michael@0 1541 if (!ToInteger(cx, args[1], &d))
michael@0 1542 return false;
michael@0 1543 pos = uint32_t(Min(Max(d, 0.0), double(UINT32_MAX)));
michael@0 1544 }
michael@0 1545 }
michael@0 1546
michael@0 1547 // Step 9
michael@0 1548 uint32_t textLen = str->length();
michael@0 1549 const jschar *textChars = str->getChars(cx);
michael@0 1550 if (!textChars)
michael@0 1551 return false;
michael@0 1552
michael@0 1553 // Step 10
michael@0 1554 uint32_t start = Min(Max(pos, 0U), textLen);
michael@0 1555
michael@0 1556 // Step 11
michael@0 1557 uint32_t searchLen = searchStr->length();
michael@0 1558 const jschar *searchChars = searchStr->chars();
michael@0 1559
michael@0 1560 // Step 12
michael@0 1561 if (searchLen + start < searchLen || searchLen + start > textLen) {
michael@0 1562 args.rval().setBoolean(false);
michael@0 1563 return true;
michael@0 1564 }
michael@0 1565
michael@0 1566 // Steps 13 and 14
michael@0 1567 args.rval().setBoolean(PodEqual(textChars + start, searchChars, searchLen));
michael@0 1568 return true;
michael@0 1569 }
michael@0 1570
michael@0 1571 /* ES6 20131108 draft 21.1.3.7. */
michael@0 1572 static bool
michael@0 1573 str_endsWith(JSContext *cx, unsigned argc, Value *vp)
michael@0 1574 {
michael@0 1575 CallArgs args = CallArgsFromVp(argc, vp);
michael@0 1576
michael@0 1577 // Steps 1, 2, and 3
michael@0 1578 RootedString str(cx, ThisToStringForStringProto(cx, args));
michael@0 1579 if (!str)
michael@0 1580 return false;
michael@0 1581
michael@0 1582 // Step 4
michael@0 1583 if (args.get(0).isObject() && IsObjectWithClass(args[0], ESClass_RegExp, cx)) {
michael@0 1584 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INVALID_ARG_TYPE,
michael@0 1585 "first", "", "Regular Expression");
michael@0 1586 return false;
michael@0 1587 }
michael@0 1588
michael@0 1589 // Steps 5 and 6
michael@0 1590 Rooted<JSLinearString *> searchStr(cx, ArgToRootedString(cx, args, 0));
michael@0 1591 if (!searchStr)
michael@0 1592 return false;
michael@0 1593
michael@0 1594 // Step 7
michael@0 1595 uint32_t textLen = str->length();
michael@0 1596 const jschar *textChars = str->getChars(cx);
michael@0 1597 if (!textChars)
michael@0 1598 return false;
michael@0 1599
michael@0 1600 // Steps 8 and 9
michael@0 1601 uint32_t pos = textLen;
michael@0 1602 if (args.hasDefined(1)) {
michael@0 1603 if (args[1].isInt32()) {
michael@0 1604 int i = args[1].toInt32();
michael@0 1605 pos = (i < 0) ? 0U : uint32_t(i);
michael@0 1606 } else {
michael@0 1607 double d;
michael@0 1608 if (!ToInteger(cx, args[1], &d))
michael@0 1609 return false;
michael@0 1610 pos = uint32_t(Min(Max(d, 0.0), double(UINT32_MAX)));
michael@0 1611 }
michael@0 1612 }
michael@0 1613
michael@0 1614 // Step 10
michael@0 1615 uint32_t end = Min(Max(pos, 0U), textLen);
michael@0 1616
michael@0 1617 // Step 11
michael@0 1618 uint32_t searchLen = searchStr->length();
michael@0 1619 const jschar *searchChars = searchStr->chars();
michael@0 1620
michael@0 1621 // Step 13 (reordered)
michael@0 1622 if (searchLen > end) {
michael@0 1623 args.rval().setBoolean(false);
michael@0 1624 return true;
michael@0 1625 }
michael@0 1626
michael@0 1627 // Step 12
michael@0 1628 uint32_t start = end - searchLen;
michael@0 1629
michael@0 1630 // Steps 14 and 15
michael@0 1631 args.rval().setBoolean(PodEqual(textChars + start, searchChars, searchLen));
michael@0 1632 return true;
michael@0 1633 }
michael@0 1634
michael@0 1635 static bool
michael@0 1636 js_TrimString(JSContext *cx, Value *vp, bool trimLeft, bool trimRight)
michael@0 1637 {
michael@0 1638 CallReceiver call = CallReceiverFromVp(vp);
michael@0 1639 RootedString str(cx, ThisToStringForStringProto(cx, call));
michael@0 1640 if (!str)
michael@0 1641 return false;
michael@0 1642 size_t length = str->length();
michael@0 1643 const jschar *chars = str->getChars(cx);
michael@0 1644 if (!chars)
michael@0 1645 return false;
michael@0 1646
michael@0 1647 size_t begin = 0;
michael@0 1648 size_t end = length;
michael@0 1649
michael@0 1650 if (trimLeft) {
michael@0 1651 while (begin < length && unicode::IsSpace(chars[begin]))
michael@0 1652 ++begin;
michael@0 1653 }
michael@0 1654
michael@0 1655 if (trimRight) {
michael@0 1656 while (end > begin && unicode::IsSpace(chars[end - 1]))
michael@0 1657 --end;
michael@0 1658 }
michael@0 1659
michael@0 1660 str = js_NewDependentString(cx, str, begin, end - begin);
michael@0 1661 if (!str)
michael@0 1662 return false;
michael@0 1663
michael@0 1664 call.rval().setString(str);
michael@0 1665 return true;
michael@0 1666 }
michael@0 1667
michael@0 1668 static bool
michael@0 1669 str_trim(JSContext *cx, unsigned argc, Value *vp)
michael@0 1670 {
michael@0 1671 return js_TrimString(cx, vp, true, true);
michael@0 1672 }
michael@0 1673
michael@0 1674 static bool
michael@0 1675 str_trimLeft(JSContext *cx, unsigned argc, Value *vp)
michael@0 1676 {
michael@0 1677 return js_TrimString(cx, vp, true, false);
michael@0 1678 }
michael@0 1679
michael@0 1680 static bool
michael@0 1681 str_trimRight(JSContext *cx, unsigned argc, Value *vp)
michael@0 1682 {
michael@0 1683 return js_TrimString(cx, vp, false, true);
michael@0 1684 }
michael@0 1685
michael@0 1686 /*
michael@0 1687 * Perl-inspired string functions.
michael@0 1688 */
michael@0 1689
michael@0 1690 namespace {
michael@0 1691
michael@0 1692 /* Result of a successfully performed flat match. */
michael@0 1693 class FlatMatch
michael@0 1694 {
michael@0 1695 RootedAtom patstr;
michael@0 1696 const jschar *pat;
michael@0 1697 size_t patlen;
michael@0 1698 int32_t match_;
michael@0 1699
michael@0 1700 friend class StringRegExpGuard;
michael@0 1701
michael@0 1702 public:
michael@0 1703 FlatMatch(JSContext *cx) : patstr(cx) {}
michael@0 1704 JSLinearString *pattern() const { return patstr; }
michael@0 1705 size_t patternLength() const { return patlen; }
michael@0 1706
michael@0 1707 /*
michael@0 1708 * Note: The match is -1 when the match is performed successfully,
michael@0 1709 * but no match is found.
michael@0 1710 */
michael@0 1711 int32_t match() const { return match_; }
michael@0 1712 };
michael@0 1713
michael@0 1714 } /* anonymous namespace */
michael@0 1715
michael@0 1716 static inline bool
michael@0 1717 IsRegExpMetaChar(jschar c)
michael@0 1718 {
michael@0 1719 switch (c) {
michael@0 1720 /* Taken from the PatternCharacter production in 15.10.1. */
michael@0 1721 case '^': case '$': case '\\': case '.': case '*': case '+':
michael@0 1722 case '?': case '(': case ')': case '[': case ']': case '{':
michael@0 1723 case '}': case '|':
michael@0 1724 return true;
michael@0 1725 default:
michael@0 1726 return false;
michael@0 1727 }
michael@0 1728 }
michael@0 1729
michael@0 1730 static inline bool
michael@0 1731 HasRegExpMetaChars(const jschar *chars, size_t length)
michael@0 1732 {
michael@0 1733 for (size_t i = 0; i < length; ++i) {
michael@0 1734 if (IsRegExpMetaChar(chars[i]))
michael@0 1735 return true;
michael@0 1736 }
michael@0 1737 return false;
michael@0 1738 }
michael@0 1739
michael@0 1740 namespace {
michael@0 1741
michael@0 1742 /*
michael@0 1743 * StringRegExpGuard factors logic out of String regexp operations.
michael@0 1744 *
michael@0 1745 * |optarg| indicates in which argument position RegExp flags will be found, if
michael@0 1746 * present. This is a Mozilla extension and not part of any ECMA spec.
michael@0 1747 */
michael@0 1748 class MOZ_STACK_CLASS StringRegExpGuard
michael@0 1749 {
michael@0 1750 RegExpGuard re_;
michael@0 1751 FlatMatch fm;
michael@0 1752 RootedObject obj_;
michael@0 1753
michael@0 1754 /*
michael@0 1755 * Upper bound on the number of characters we are willing to potentially
michael@0 1756 * waste on searching for RegExp meta-characters.
michael@0 1757 */
michael@0 1758 static const size_t MAX_FLAT_PAT_LEN = 256;
michael@0 1759
michael@0 1760 static JSAtom *
michael@0 1761 flattenPattern(JSContext *cx, JSAtom *patstr)
michael@0 1762 {
michael@0 1763 StringBuffer sb(cx);
michael@0 1764 if (!sb.reserve(patstr->length()))
michael@0 1765 return nullptr;
michael@0 1766
michael@0 1767 static const jschar ESCAPE_CHAR = '\\';
michael@0 1768 const jschar *chars = patstr->chars();
michael@0 1769 size_t len = patstr->length();
michael@0 1770 for (const jschar *it = chars; it != chars + len; ++it) {
michael@0 1771 if (IsRegExpMetaChar(*it)) {
michael@0 1772 if (!sb.append(ESCAPE_CHAR) || !sb.append(*it))
michael@0 1773 return nullptr;
michael@0 1774 } else {
michael@0 1775 if (!sb.append(*it))
michael@0 1776 return nullptr;
michael@0 1777 }
michael@0 1778 }
michael@0 1779 return sb.finishAtom();
michael@0 1780 }
michael@0 1781
michael@0 1782 public:
michael@0 1783 StringRegExpGuard(JSContext *cx)
michael@0 1784 : re_(cx), fm(cx), obj_(cx)
michael@0 1785 { }
michael@0 1786
michael@0 1787 /* init must succeed in order to call tryFlatMatch or normalizeRegExp. */
michael@0 1788 bool init(JSContext *cx, CallArgs args, bool convertVoid = false)
michael@0 1789 {
michael@0 1790 if (args.length() != 0 && IsObjectWithClass(args[0], ESClass_RegExp, cx))
michael@0 1791 return init(cx, &args[0].toObject());
michael@0 1792
michael@0 1793 if (convertVoid && !args.hasDefined(0)) {
michael@0 1794 fm.patstr = cx->runtime()->emptyString;
michael@0 1795 return true;
michael@0 1796 }
michael@0 1797
michael@0 1798 JSString *arg = ArgToRootedString(cx, args, 0);
michael@0 1799 if (!arg)
michael@0 1800 return false;
michael@0 1801
michael@0 1802 fm.patstr = AtomizeString(cx, arg);
michael@0 1803 if (!fm.patstr)
michael@0 1804 return false;
michael@0 1805
michael@0 1806 return true;
michael@0 1807 }
michael@0 1808
michael@0 1809 bool init(JSContext *cx, JSObject *regexp) {
michael@0 1810 obj_ = regexp;
michael@0 1811
michael@0 1812 JS_ASSERT(ObjectClassIs(obj_, ESClass_RegExp, cx));
michael@0 1813
michael@0 1814 if (!RegExpToShared(cx, obj_, &re_))
michael@0 1815 return false;
michael@0 1816 return true;
michael@0 1817 }
michael@0 1818
michael@0 1819 bool init(JSContext *cx, HandleString pattern) {
michael@0 1820 fm.patstr = AtomizeString(cx, pattern);
michael@0 1821 if (!fm.patstr)
michael@0 1822 return false;
michael@0 1823 return true;
michael@0 1824 }
michael@0 1825
michael@0 1826 /*
michael@0 1827 * Attempt to match |patstr| to |textstr|. A flags argument, metachars in
michael@0 1828 * the pattern string, or a lengthy pattern string can thwart this process.
michael@0 1829 *
michael@0 1830 * |checkMetaChars| looks for regexp metachars in the pattern string.
michael@0 1831 *
michael@0 1832 * Return whether flat matching could be used.
michael@0 1833 *
michael@0 1834 * N.B. tryFlatMatch returns nullptr on OOM, so the caller must check
michael@0 1835 * cx->isExceptionPending().
michael@0 1836 */
michael@0 1837 const FlatMatch *
michael@0 1838 tryFlatMatch(JSContext *cx, JSString *textstr, unsigned optarg, unsigned argc,
michael@0 1839 bool checkMetaChars = true)
michael@0 1840 {
michael@0 1841 if (re_.initialized())
michael@0 1842 return nullptr;
michael@0 1843
michael@0 1844 fm.pat = fm.patstr->chars();
michael@0 1845 fm.patlen = fm.patstr->length();
michael@0 1846
michael@0 1847 if (optarg < argc)
michael@0 1848 return nullptr;
michael@0 1849
michael@0 1850 if (checkMetaChars &&
michael@0 1851 (fm.patlen > MAX_FLAT_PAT_LEN || HasRegExpMetaChars(fm.pat, fm.patlen))) {
michael@0 1852 return nullptr;
michael@0 1853 }
michael@0 1854
michael@0 1855 /*
michael@0 1856 * textstr could be a rope, so we want to avoid flattening it for as
michael@0 1857 * long as possible.
michael@0 1858 */
michael@0 1859 if (textstr->isRope()) {
michael@0 1860 if (!RopeMatch(cx, textstr, fm.pat, fm.patlen, &fm.match_))
michael@0 1861 return nullptr;
michael@0 1862 } else {
michael@0 1863 const jschar *text = textstr->asLinear().chars();
michael@0 1864 size_t textlen = textstr->length();
michael@0 1865 fm.match_ = StringMatch(text, textlen, fm.pat, fm.patlen);
michael@0 1866 }
michael@0 1867 return &fm;
michael@0 1868 }
michael@0 1869
michael@0 1870 /* If the pattern is not already a regular expression, make it so. */
michael@0 1871 bool normalizeRegExp(JSContext *cx, bool flat, unsigned optarg, CallArgs args)
michael@0 1872 {
michael@0 1873 if (re_.initialized())
michael@0 1874 return true;
michael@0 1875
michael@0 1876 /* Build RegExp from pattern string. */
michael@0 1877 RootedString opt(cx);
michael@0 1878 if (optarg < args.length()) {
michael@0 1879 opt = ToString<CanGC>(cx, args[optarg]);
michael@0 1880 if (!opt)
michael@0 1881 return false;
michael@0 1882 } else {
michael@0 1883 opt = nullptr;
michael@0 1884 }
michael@0 1885
michael@0 1886 Rooted<JSAtom *> patstr(cx);
michael@0 1887 if (flat) {
michael@0 1888 patstr = flattenPattern(cx, fm.patstr);
michael@0 1889 if (!patstr)
michael@0 1890 return false;
michael@0 1891 } else {
michael@0 1892 patstr = fm.patstr;
michael@0 1893 }
michael@0 1894 JS_ASSERT(patstr);
michael@0 1895
michael@0 1896 return cx->compartment()->regExps.get(cx, patstr, opt, &re_);
michael@0 1897 }
michael@0 1898
michael@0 1899 bool zeroLastIndex(JSContext *cx) {
michael@0 1900 if (!regExpIsObject())
michael@0 1901 return true;
michael@0 1902
michael@0 1903 // Use a fast path for same-global RegExp objects with writable
michael@0 1904 // lastIndex.
michael@0 1905 if (obj_->is<RegExpObject>() && obj_->nativeLookup(cx, cx->names().lastIndex)->writable()) {
michael@0 1906 obj_->as<RegExpObject>().zeroLastIndex();
michael@0 1907 return true;
michael@0 1908 }
michael@0 1909
michael@0 1910 // Handle everything else generically (including throwing if .lastIndex is non-writable).
michael@0 1911 RootedValue zero(cx, Int32Value(0));
michael@0 1912 return JSObject::setProperty(cx, obj_, obj_, cx->names().lastIndex, &zero, true);
michael@0 1913 }
michael@0 1914
michael@0 1915 RegExpShared &regExp() { return *re_; }
michael@0 1916
michael@0 1917 bool regExpIsObject() { return obj_ != nullptr; }
michael@0 1918 HandleObject regExpObject() {
michael@0 1919 JS_ASSERT(regExpIsObject());
michael@0 1920 return obj_;
michael@0 1921 }
michael@0 1922
michael@0 1923 private:
michael@0 1924 StringRegExpGuard(const StringRegExpGuard &) MOZ_DELETE;
michael@0 1925 void operator=(const StringRegExpGuard &) MOZ_DELETE;
michael@0 1926 };
michael@0 1927
michael@0 1928 } /* anonymous namespace */
michael@0 1929
michael@0 1930 static bool
michael@0 1931 DoMatchLocal(JSContext *cx, CallArgs args, RegExpStatics *res, Handle<JSLinearString*> input,
michael@0 1932 RegExpShared &re)
michael@0 1933 {
michael@0 1934 size_t charsLen = input->length();
michael@0 1935 const jschar *chars = input->chars();
michael@0 1936
michael@0 1937 size_t i = 0;
michael@0 1938 ScopedMatchPairs matches(&cx->tempLifoAlloc());
michael@0 1939 RegExpRunStatus status = re.execute(cx, chars, charsLen, &i, matches);
michael@0 1940 if (status == RegExpRunStatus_Error)
michael@0 1941 return false;
michael@0 1942
michael@0 1943 if (status == RegExpRunStatus_Success_NotFound) {
michael@0 1944 args.rval().setNull();
michael@0 1945 return true;
michael@0 1946 }
michael@0 1947
michael@0 1948 if (!res->updateFromMatchPairs(cx, input, matches))
michael@0 1949 return false;
michael@0 1950
michael@0 1951 RootedValue rval(cx);
michael@0 1952 if (!CreateRegExpMatchResult(cx, input, matches, &rval))
michael@0 1953 return false;
michael@0 1954
michael@0 1955 args.rval().set(rval);
michael@0 1956 return true;
michael@0 1957 }
michael@0 1958
michael@0 1959 /* ES5 15.5.4.10 step 8. */
michael@0 1960 static bool
michael@0 1961 DoMatchGlobal(JSContext *cx, CallArgs args, RegExpStatics *res, Handle<JSLinearString*> input,
michael@0 1962 StringRegExpGuard &g)
michael@0 1963 {
michael@0 1964 // Step 8a.
michael@0 1965 //
michael@0 1966 // This single zeroing of "lastIndex" covers all "lastIndex" changes in the
michael@0 1967 // rest of String.prototype.match, particularly in steps 8f(i) and
michael@0 1968 // 8f(iii)(2)(a). Here's why.
michael@0 1969 //
michael@0 1970 // The inputs to the calls to RegExp.prototype.exec are a RegExp object
michael@0 1971 // whose .global is true and a string. The only side effect of a call in
michael@0 1972 // these circumstances is that the RegExp's .lastIndex will be modified to
michael@0 1973 // the next starting index after the discovered match (or to 0 if there's
michael@0 1974 // no remaining match). Because .lastIndex is a non-configurable data
michael@0 1975 // property and no script-controllable code executes after step 8a, passing
michael@0 1976 // step 8a implies *every* .lastIndex set succeeds. String.prototype.match
michael@0 1977 // calls RegExp.prototype.exec repeatedly, and the last call doesn't match,
michael@0 1978 // so the final value of .lastIndex is 0: exactly the state after step 8a
michael@0 1979 // succeeds. No spec step lets script observe intermediate .lastIndex
michael@0 1980 // values.
michael@0 1981 //
michael@0 1982 // The arrays returned by RegExp.prototype.exec always have a string at
michael@0 1983 // index 0, for which [[Get]]s have no side effects.
michael@0 1984 //
michael@0 1985 // Filling in a new array using [[DefineOwnProperty]] is unobservable.
michael@0 1986 //
michael@0 1987 // This is a tricky point, because after this set, our implementation *can*
michael@0 1988 // fail. The key is that script can't distinguish these failure modes from
michael@0 1989 // one where, in spec terms, we fail immediately after step 8a. That *in
michael@0 1990 // reality* we might have done extra matching work, or created a partial
michael@0 1991 // results array to return, or hit an interrupt, is irrelevant. The
michael@0 1992 // script can't tell we did any of those things but didn't update
michael@0 1993 // .lastIndex. Thus we can optimize steps 8b onward however we want,
michael@0 1994 // including eliminating intermediate .lastIndex sets, as long as we don't
michael@0 1995 // add ways for script to observe the intermediate states.
michael@0 1996 //
michael@0 1997 // In short: it's okay to cheat (by setting .lastIndex to 0, once) because
michael@0 1998 // we can't get caught.
michael@0 1999 if (!g.zeroLastIndex(cx))
michael@0 2000 return false;
michael@0 2001
michael@0 2002 // Step 8b.
michael@0 2003 AutoValueVector elements(cx);
michael@0 2004
michael@0 2005 size_t lastSuccessfulStart = 0;
michael@0 2006
michael@0 2007 // The loop variables from steps 8c-e aren't needed, as we use different
michael@0 2008 // techniques from the spec to implement step 8f's loop.
michael@0 2009
michael@0 2010 // Step 8f.
michael@0 2011 MatchPair match;
michael@0 2012 size_t charsLen = input->length();
michael@0 2013 const jschar *chars = input->chars();
michael@0 2014 RegExpShared &re = g.regExp();
michael@0 2015 for (size_t searchIndex = 0; searchIndex <= charsLen; ) {
michael@0 2016 if (!CheckForInterrupt(cx))
michael@0 2017 return false;
michael@0 2018
michael@0 2019 // Steps 8f(i-ii), minus "lastIndex" updates (see above).
michael@0 2020 size_t nextSearchIndex = searchIndex;
michael@0 2021 RegExpRunStatus status = re.executeMatchOnly(cx, chars, charsLen, &nextSearchIndex, match);
michael@0 2022 if (status == RegExpRunStatus_Error)
michael@0 2023 return false;
michael@0 2024
michael@0 2025 // Step 8f(ii).
michael@0 2026 if (status == RegExpRunStatus_Success_NotFound)
michael@0 2027 break;
michael@0 2028
michael@0 2029 lastSuccessfulStart = searchIndex;
michael@0 2030
michael@0 2031 // Steps 8f(iii)(1-3).
michael@0 2032 searchIndex = match.isEmpty() ? nextSearchIndex + 1 : nextSearchIndex;
michael@0 2033
michael@0 2034 // Step 8f(iii)(4-5).
michael@0 2035 JSLinearString *str = js_NewDependentString(cx, input, match.start, match.length());
michael@0 2036 if (!str)
michael@0 2037 return false;
michael@0 2038 if (!elements.append(StringValue(str)))
michael@0 2039 return false;
michael@0 2040 }
michael@0 2041
michael@0 2042 // Step 8g.
michael@0 2043 if (elements.empty()) {
michael@0 2044 args.rval().setNull();
michael@0 2045 return true;
michael@0 2046 }
michael@0 2047
michael@0 2048 // The last *successful* match updates the RegExpStatics. (Interestingly,
michael@0 2049 // this implies that String.prototype.match's semantics aren't those
michael@0 2050 // implied by the RegExp.prototype.exec calls in the ES5 algorithm.)
michael@0 2051 res->updateLazily(cx, input, &re, lastSuccessfulStart);
michael@0 2052
michael@0 2053 // Steps 8b, 8f(iii)(5-6), 8h.
michael@0 2054 JSObject *array = NewDenseCopiedArray(cx, elements.length(), elements.begin());
michael@0 2055 if (!array)
michael@0 2056 return false;
michael@0 2057
michael@0 2058 args.rval().setObject(*array);
michael@0 2059 return true;
michael@0 2060 }
michael@0 2061
michael@0 2062 static bool
michael@0 2063 BuildFlatMatchArray(JSContext *cx, HandleString textstr, const FlatMatch &fm, CallArgs *args)
michael@0 2064 {
michael@0 2065 if (fm.match() < 0) {
michael@0 2066 args->rval().setNull();
michael@0 2067 return true;
michael@0 2068 }
michael@0 2069
michael@0 2070 /* For this non-global match, produce a RegExp.exec-style array. */
michael@0 2071 RootedObject obj(cx, NewDenseEmptyArray(cx));
michael@0 2072 if (!obj)
michael@0 2073 return false;
michael@0 2074
michael@0 2075 RootedValue patternVal(cx, StringValue(fm.pattern()));
michael@0 2076 RootedValue matchVal(cx, Int32Value(fm.match()));
michael@0 2077 RootedValue textVal(cx, StringValue(textstr));
michael@0 2078
michael@0 2079 if (!JSObject::defineElement(cx, obj, 0, patternVal) ||
michael@0 2080 !JSObject::defineProperty(cx, obj, cx->names().index, matchVal) ||
michael@0 2081 !JSObject::defineProperty(cx, obj, cx->names().input, textVal))
michael@0 2082 {
michael@0 2083 return false;
michael@0 2084 }
michael@0 2085
michael@0 2086 args->rval().setObject(*obj);
michael@0 2087 return true;
michael@0 2088 }
michael@0 2089
michael@0 2090 /* ES5 15.5.4.10. */
michael@0 2091 bool
michael@0 2092 js::str_match(JSContext *cx, unsigned argc, Value *vp)
michael@0 2093 {
michael@0 2094 CallArgs args = CallArgsFromVp(argc, vp);
michael@0 2095
michael@0 2096 /* Steps 1-2. */
michael@0 2097 RootedString str(cx, ThisToStringForStringProto(cx, args));
michael@0 2098 if (!str)
michael@0 2099 return false;
michael@0 2100
michael@0 2101 /* Steps 3-4, plus the trailing-argument "flags" extension. */
michael@0 2102 StringRegExpGuard g(cx);
michael@0 2103 if (!g.init(cx, args, true))
michael@0 2104 return false;
michael@0 2105
michael@0 2106 /* Fast path when the search pattern can be searched for as a string. */
michael@0 2107 if (const FlatMatch *fm = g.tryFlatMatch(cx, str, 1, args.length()))
michael@0 2108 return BuildFlatMatchArray(cx, str, *fm, &args);
michael@0 2109
michael@0 2110 /* Return if there was an error in tryFlatMatch. */
michael@0 2111 if (cx->isExceptionPending())
michael@0 2112 return false;
michael@0 2113
michael@0 2114 /* Create regular-expression internals as needed to perform the match. */
michael@0 2115 if (!g.normalizeRegExp(cx, false, 1, args))
michael@0 2116 return false;
michael@0 2117
michael@0 2118 RegExpStatics *res = cx->global()->getRegExpStatics();
michael@0 2119 Rooted<JSLinearString*> linearStr(cx, str->ensureLinear(cx));
michael@0 2120 if (!linearStr)
michael@0 2121 return false;
michael@0 2122
michael@0 2123 /* Steps 5-6, 7. */
michael@0 2124 if (!g.regExp().global())
michael@0 2125 return DoMatchLocal(cx, args, res, linearStr, g.regExp());
michael@0 2126
michael@0 2127 /* Steps 6, 8. */
michael@0 2128 return DoMatchGlobal(cx, args, res, linearStr, g);
michael@0 2129 }
michael@0 2130
michael@0 2131 bool
michael@0 2132 js::str_search(JSContext *cx, unsigned argc, Value *vp)
michael@0 2133 {
michael@0 2134 CallArgs args = CallArgsFromVp(argc, vp);
michael@0 2135 RootedString str(cx, ThisToStringForStringProto(cx, args));
michael@0 2136 if (!str)
michael@0 2137 return false;
michael@0 2138
michael@0 2139 StringRegExpGuard g(cx);
michael@0 2140 if (!g.init(cx, args, true))
michael@0 2141 return false;
michael@0 2142 if (const FlatMatch *fm = g.tryFlatMatch(cx, str, 1, args.length())) {
michael@0 2143 args.rval().setInt32(fm->match());
michael@0 2144 return true;
michael@0 2145 }
michael@0 2146
michael@0 2147 if (cx->isExceptionPending()) /* from tryFlatMatch */
michael@0 2148 return false;
michael@0 2149
michael@0 2150 if (!g.normalizeRegExp(cx, false, 1, args))
michael@0 2151 return false;
michael@0 2152
michael@0 2153 Rooted<JSLinearString*> linearStr(cx, str->ensureLinear(cx));
michael@0 2154 if (!linearStr)
michael@0 2155 return false;
michael@0 2156
michael@0 2157 const jschar *chars = linearStr->chars();
michael@0 2158 size_t length = linearStr->length();
michael@0 2159 RegExpStatics *res = cx->global()->getRegExpStatics();
michael@0 2160
michael@0 2161 /* Per ECMAv5 15.5.4.12 (5) The last index property is ignored and left unchanged. */
michael@0 2162 size_t i = 0;
michael@0 2163 MatchPair match;
michael@0 2164
michael@0 2165 RegExpRunStatus status = g.regExp().executeMatchOnly(cx, chars, length, &i, match);
michael@0 2166 if (status == RegExpRunStatus_Error)
michael@0 2167 return false;
michael@0 2168
michael@0 2169 if (status == RegExpRunStatus_Success)
michael@0 2170 res->updateLazily(cx, linearStr, &g.regExp(), 0);
michael@0 2171
michael@0 2172 JS_ASSERT_IF(status == RegExpRunStatus_Success_NotFound, match.start == -1);
michael@0 2173 args.rval().setInt32(match.start);
michael@0 2174 return true;
michael@0 2175 }
michael@0 2176
michael@0 2177 // Utility for building a rope (lazy concatenation) of strings.
michael@0 2178 class RopeBuilder {
michael@0 2179 JSContext *cx;
michael@0 2180 RootedString res;
michael@0 2181
michael@0 2182 RopeBuilder(const RopeBuilder &other) MOZ_DELETE;
michael@0 2183 void operator=(const RopeBuilder &other) MOZ_DELETE;
michael@0 2184
michael@0 2185 public:
michael@0 2186 RopeBuilder(JSContext *cx)
michael@0 2187 : cx(cx), res(cx, cx->runtime()->emptyString)
michael@0 2188 {}
michael@0 2189
michael@0 2190 inline bool append(HandleString str) {
michael@0 2191 res = ConcatStrings<CanGC>(cx, res, str);
michael@0 2192 return !!res;
michael@0 2193 }
michael@0 2194
michael@0 2195 inline JSString *result() {
michael@0 2196 return res;
michael@0 2197 }
michael@0 2198 };
michael@0 2199
michael@0 2200 namespace {
michael@0 2201
michael@0 2202 struct ReplaceData
michael@0 2203 {
michael@0 2204 ReplaceData(JSContext *cx)
michael@0 2205 : str(cx), g(cx), lambda(cx), elembase(cx), repstr(cx),
michael@0 2206 fig(cx, NullValue()), sb(cx)
michael@0 2207 {}
michael@0 2208
michael@0 2209 inline void setReplacementString(JSLinearString *string) {
michael@0 2210 JS_ASSERT(string);
michael@0 2211 lambda = nullptr;
michael@0 2212 elembase = nullptr;
michael@0 2213 repstr = string;
michael@0 2214
michael@0 2215 /* We're about to store pointers into the middle of our string. */
michael@0 2216 dollarEnd = repstr->chars() + repstr->length();
michael@0 2217 dollar = js_strchr_limit(repstr->chars(), '$', dollarEnd);
michael@0 2218 }
michael@0 2219
michael@0 2220 inline void setReplacementFunction(JSObject *func) {
michael@0 2221 JS_ASSERT(func);
michael@0 2222 lambda = func;
michael@0 2223 elembase = nullptr;
michael@0 2224 repstr = nullptr;
michael@0 2225 dollar = dollarEnd = nullptr;
michael@0 2226 }
michael@0 2227
michael@0 2228 RootedString str; /* 'this' parameter object as a string */
michael@0 2229 StringRegExpGuard g; /* regexp parameter object and private data */
michael@0 2230 RootedObject lambda; /* replacement function object or null */
michael@0 2231 RootedObject elembase; /* object for function(a){return b[a]} replace */
michael@0 2232 Rooted<JSLinearString*> repstr; /* replacement string */
michael@0 2233 const jschar *dollar; /* null or pointer to first $ in repstr */
michael@0 2234 const jschar *dollarEnd; /* limit pointer for js_strchr_limit */
michael@0 2235 int leftIndex; /* left context index in str->chars */
michael@0 2236 JSSubString dollarStr; /* for "$$" InterpretDollar result */
michael@0 2237 bool calledBack; /* record whether callback has been called */
michael@0 2238 FastInvokeGuard fig; /* used for lambda calls, also holds arguments */
michael@0 2239 StringBuffer sb; /* buffer built during DoMatch */
michael@0 2240 };
michael@0 2241
michael@0 2242 } /* anonymous namespace */
michael@0 2243
michael@0 2244 static bool
michael@0 2245 ReplaceRegExp(JSContext *cx, RegExpStatics *res, ReplaceData &rdata);
michael@0 2246
michael@0 2247 static bool
michael@0 2248 DoMatchForReplaceLocal(JSContext *cx, RegExpStatics *res, Handle<JSLinearString*> linearStr,
michael@0 2249 RegExpShared &re, ReplaceData &rdata)
michael@0 2250 {
michael@0 2251 size_t charsLen = linearStr->length();
michael@0 2252 size_t i = 0;
michael@0 2253 ScopedMatchPairs matches(&cx->tempLifoAlloc());
michael@0 2254 RegExpRunStatus status = re.execute(cx, linearStr->chars(), charsLen, &i, matches);
michael@0 2255 if (status == RegExpRunStatus_Error)
michael@0 2256 return false;
michael@0 2257
michael@0 2258 if (status == RegExpRunStatus_Success_NotFound)
michael@0 2259 return true;
michael@0 2260
michael@0 2261 if (!res->updateFromMatchPairs(cx, linearStr, matches))
michael@0 2262 return false;
michael@0 2263
michael@0 2264 return ReplaceRegExp(cx, res, rdata);
michael@0 2265 }
michael@0 2266
michael@0 2267 static bool
michael@0 2268 DoMatchForReplaceGlobal(JSContext *cx, RegExpStatics *res, Handle<JSLinearString*> linearStr,
michael@0 2269 RegExpShared &re, ReplaceData &rdata)
michael@0 2270 {
michael@0 2271 size_t charsLen = linearStr->length();
michael@0 2272 ScopedMatchPairs matches(&cx->tempLifoAlloc());
michael@0 2273 for (size_t count = 0, i = 0; i <= charsLen; ++count) {
michael@0 2274 if (!CheckForInterrupt(cx))
michael@0 2275 return false;
michael@0 2276
michael@0 2277 RegExpRunStatus status = re.execute(cx, linearStr->chars(), charsLen, &i, matches);
michael@0 2278 if (status == RegExpRunStatus_Error)
michael@0 2279 return false;
michael@0 2280
michael@0 2281 if (status == RegExpRunStatus_Success_NotFound)
michael@0 2282 break;
michael@0 2283
michael@0 2284 if (!res->updateFromMatchPairs(cx, linearStr, matches))
michael@0 2285 return false;
michael@0 2286
michael@0 2287 if (!ReplaceRegExp(cx, res, rdata))
michael@0 2288 return false;
michael@0 2289 if (!res->matched())
michael@0 2290 ++i;
michael@0 2291 }
michael@0 2292
michael@0 2293 return true;
michael@0 2294 }
michael@0 2295
michael@0 2296 static bool
michael@0 2297 InterpretDollar(RegExpStatics *res, const jschar *dp, const jschar *ep,
michael@0 2298 ReplaceData &rdata, JSSubString *out, size_t *skip)
michael@0 2299 {
michael@0 2300 JS_ASSERT(*dp == '$');
michael@0 2301
michael@0 2302 /* If there is only a dollar, bail now */
michael@0 2303 if (dp + 1 >= ep)
michael@0 2304 return false;
michael@0 2305
michael@0 2306 /* Interpret all Perl match-induced dollar variables. */
michael@0 2307 jschar dc = dp[1];
michael@0 2308 if (JS7_ISDEC(dc)) {
michael@0 2309 /* ECMA-262 Edition 3: 1-9 or 01-99 */
michael@0 2310 unsigned num = JS7_UNDEC(dc);
michael@0 2311 if (num > res->getMatches().parenCount())
michael@0 2312 return false;
michael@0 2313
michael@0 2314 const jschar *cp = dp + 2;
michael@0 2315 if (cp < ep && (dc = *cp, JS7_ISDEC(dc))) {
michael@0 2316 unsigned tmp = 10 * num + JS7_UNDEC(dc);
michael@0 2317 if (tmp <= res->getMatches().parenCount()) {
michael@0 2318 cp++;
michael@0 2319 num = tmp;
michael@0 2320 }
michael@0 2321 }
michael@0 2322 if (num == 0)
michael@0 2323 return false;
michael@0 2324
michael@0 2325 *skip = cp - dp;
michael@0 2326
michael@0 2327 JS_ASSERT(num <= res->getMatches().parenCount());
michael@0 2328
michael@0 2329 /*
michael@0 2330 * Note: we index to get the paren with the (1-indexed) pair
michael@0 2331 * number, as opposed to a (0-indexed) paren number.
michael@0 2332 */
michael@0 2333 res->getParen(num, out);
michael@0 2334 return true;
michael@0 2335 }
michael@0 2336
michael@0 2337 *skip = 2;
michael@0 2338 switch (dc) {
michael@0 2339 case '$':
michael@0 2340 rdata.dollarStr.chars = dp;
michael@0 2341 rdata.dollarStr.length = 1;
michael@0 2342 *out = rdata.dollarStr;
michael@0 2343 return true;
michael@0 2344 case '&':
michael@0 2345 res->getLastMatch(out);
michael@0 2346 return true;
michael@0 2347 case '+':
michael@0 2348 res->getLastParen(out);
michael@0 2349 return true;
michael@0 2350 case '`':
michael@0 2351 res->getLeftContext(out);
michael@0 2352 return true;
michael@0 2353 case '\'':
michael@0 2354 res->getRightContext(out);
michael@0 2355 return true;
michael@0 2356 }
michael@0 2357 return false;
michael@0 2358 }
michael@0 2359
michael@0 2360 static bool
michael@0 2361 FindReplaceLength(JSContext *cx, RegExpStatics *res, ReplaceData &rdata, size_t *sizep)
michael@0 2362 {
michael@0 2363 if (rdata.elembase) {
michael@0 2364 /*
michael@0 2365 * The base object is used when replace was passed a lambda which looks like
michael@0 2366 * 'function(a) { return b[a]; }' for the base object b. b will not change
michael@0 2367 * in the course of the replace unless we end up making a scripted call due
michael@0 2368 * to accessing a scripted getter or a value with a scripted toString.
michael@0 2369 */
michael@0 2370 JS_ASSERT(rdata.lambda);
michael@0 2371 JS_ASSERT(!rdata.elembase->getOps()->lookupProperty);
michael@0 2372 JS_ASSERT(!rdata.elembase->getOps()->getProperty);
michael@0 2373
michael@0 2374 RootedValue match(cx);
michael@0 2375 if (!res->createLastMatch(cx, &match))
michael@0 2376 return false;
michael@0 2377 JSAtom *atom = ToAtom<CanGC>(cx, match);
michael@0 2378 if (!atom)
michael@0 2379 return false;
michael@0 2380
michael@0 2381 RootedValue v(cx);
michael@0 2382 if (HasDataProperty(cx, rdata.elembase, AtomToId(atom), v.address()) && v.isString()) {
michael@0 2383 rdata.repstr = v.toString()->ensureLinear(cx);
michael@0 2384 if (!rdata.repstr)
michael@0 2385 return false;
michael@0 2386 *sizep = rdata.repstr->length();
michael@0 2387 return true;
michael@0 2388 }
michael@0 2389
michael@0 2390 /*
michael@0 2391 * Couldn't handle this property, fall through and despecialize to the
michael@0 2392 * general lambda case.
michael@0 2393 */
michael@0 2394 rdata.elembase = nullptr;
michael@0 2395 }
michael@0 2396
michael@0 2397 if (rdata.lambda) {
michael@0 2398 RootedObject lambda(cx, rdata.lambda);
michael@0 2399 PreserveRegExpStatics staticsGuard(cx, res);
michael@0 2400 if (!staticsGuard.init(cx))
michael@0 2401 return false;
michael@0 2402
michael@0 2403 /*
michael@0 2404 * In the lambda case, not only do we find the replacement string's
michael@0 2405 * length, we compute repstr and return it via rdata for use within
michael@0 2406 * DoReplace. The lambda is called with arguments ($&, $1, $2, ...,
michael@0 2407 * index, input), i.e., all the properties of a regexp match array.
michael@0 2408 * For $&, etc., we must create string jsvals from cx->regExpStatics.
michael@0 2409 * We grab up stack space to keep the newborn strings GC-rooted.
michael@0 2410 */
michael@0 2411 unsigned p = res->getMatches().parenCount();
michael@0 2412 unsigned argc = 1 + p + 2;
michael@0 2413
michael@0 2414 InvokeArgs &args = rdata.fig.args();
michael@0 2415 if (!args.init(argc))
michael@0 2416 return false;
michael@0 2417
michael@0 2418 args.setCallee(ObjectValue(*lambda));
michael@0 2419 args.setThis(UndefinedValue());
michael@0 2420
michael@0 2421 /* Push $&, $1, $2, ... */
michael@0 2422 unsigned argi = 0;
michael@0 2423 if (!res->createLastMatch(cx, args[argi++]))
michael@0 2424 return false;
michael@0 2425
michael@0 2426 for (size_t i = 0; i < res->getMatches().parenCount(); ++i) {
michael@0 2427 if (!res->createParen(cx, i + 1, args[argi++]))
michael@0 2428 return false;
michael@0 2429 }
michael@0 2430
michael@0 2431 /* Push match index and input string. */
michael@0 2432 args[argi++].setInt32(res->getMatches()[0].start);
michael@0 2433 args[argi].setString(rdata.str);
michael@0 2434
michael@0 2435 if (!rdata.fig.invoke(cx))
michael@0 2436 return false;
michael@0 2437
michael@0 2438 /* root repstr: rdata is on the stack, so scanned by conservative gc. */
michael@0 2439 JSString *repstr = ToString<CanGC>(cx, args.rval());
michael@0 2440 if (!repstr)
michael@0 2441 return false;
michael@0 2442 rdata.repstr = repstr->ensureLinear(cx);
michael@0 2443 if (!rdata.repstr)
michael@0 2444 return false;
michael@0 2445 *sizep = rdata.repstr->length();
michael@0 2446 return true;
michael@0 2447 }
michael@0 2448
michael@0 2449 JSString *repstr = rdata.repstr;
michael@0 2450 CheckedInt<uint32_t> replen = repstr->length();
michael@0 2451 for (const jschar *dp = rdata.dollar, *ep = rdata.dollarEnd; dp;
michael@0 2452 dp = js_strchr_limit(dp, '$', ep)) {
michael@0 2453 JSSubString sub;
michael@0 2454 size_t skip;
michael@0 2455 if (InterpretDollar(res, dp, ep, rdata, &sub, &skip)) {
michael@0 2456 if (sub.length > skip)
michael@0 2457 replen += sub.length - skip;
michael@0 2458 else
michael@0 2459 replen -= skip - sub.length;
michael@0 2460 dp += skip;
michael@0 2461 } else {
michael@0 2462 dp++;
michael@0 2463 }
michael@0 2464 }
michael@0 2465
michael@0 2466 if (!replen.isValid()) {
michael@0 2467 js_ReportAllocationOverflow(cx);
michael@0 2468 return false;
michael@0 2469 }
michael@0 2470
michael@0 2471 *sizep = replen.value();
michael@0 2472 return true;
michael@0 2473 }
michael@0 2474
michael@0 2475 /*
michael@0 2476 * Precondition: |rdata.sb| already has necessary growth space reserved (as
michael@0 2477 * derived from FindReplaceLength).
michael@0 2478 */
michael@0 2479 static void
michael@0 2480 DoReplace(RegExpStatics *res, ReplaceData &rdata)
michael@0 2481 {
michael@0 2482 JSLinearString *repstr = rdata.repstr;
michael@0 2483 const jschar *cp;
michael@0 2484 const jschar *bp = cp = repstr->chars();
michael@0 2485
michael@0 2486 const jschar *dp = rdata.dollar;
michael@0 2487 const jschar *ep = rdata.dollarEnd;
michael@0 2488 for (; dp; dp = js_strchr_limit(dp, '$', ep)) {
michael@0 2489 /* Move one of the constant portions of the replacement value. */
michael@0 2490 size_t len = dp - cp;
michael@0 2491 rdata.sb.infallibleAppend(cp, len);
michael@0 2492 cp = dp;
michael@0 2493
michael@0 2494 JSSubString sub;
michael@0 2495 size_t skip;
michael@0 2496 if (InterpretDollar(res, dp, ep, rdata, &sub, &skip)) {
michael@0 2497 len = sub.length;
michael@0 2498 rdata.sb.infallibleAppend(sub.chars, len);
michael@0 2499 cp += skip;
michael@0 2500 dp += skip;
michael@0 2501 } else {
michael@0 2502 dp++;
michael@0 2503 }
michael@0 2504 }
michael@0 2505 rdata.sb.infallibleAppend(cp, repstr->length() - (cp - bp));
michael@0 2506 }
michael@0 2507
michael@0 2508 static bool
michael@0 2509 ReplaceRegExp(JSContext *cx, RegExpStatics *res, ReplaceData &rdata)
michael@0 2510 {
michael@0 2511
michael@0 2512 const MatchPair &match = res->getMatches()[0];
michael@0 2513 JS_ASSERT(!match.isUndefined());
michael@0 2514 JS_ASSERT(match.limit >= match.start && match.limit >= 0);
michael@0 2515
michael@0 2516 rdata.calledBack = true;
michael@0 2517 size_t leftoff = rdata.leftIndex;
michael@0 2518 size_t leftlen = match.start - leftoff;
michael@0 2519 rdata.leftIndex = match.limit;
michael@0 2520
michael@0 2521 size_t replen = 0; /* silence 'unused' warning */
michael@0 2522 if (!FindReplaceLength(cx, res, rdata, &replen))
michael@0 2523 return false;
michael@0 2524
michael@0 2525 CheckedInt<uint32_t> newlen(rdata.sb.length());
michael@0 2526 newlen += leftlen;
michael@0 2527 newlen += replen;
michael@0 2528 if (!newlen.isValid()) {
michael@0 2529 js_ReportAllocationOverflow(cx);
michael@0 2530 return false;
michael@0 2531 }
michael@0 2532 if (!rdata.sb.reserve(newlen.value()))
michael@0 2533 return false;
michael@0 2534
michael@0 2535 JSLinearString &str = rdata.str->asLinear(); /* flattened for regexp */
michael@0 2536 const jschar *left = str.chars() + leftoff;
michael@0 2537
michael@0 2538 rdata.sb.infallibleAppend(left, leftlen); /* skipped-over portion of the search value */
michael@0 2539 DoReplace(res, rdata);
michael@0 2540 return true;
michael@0 2541 }
michael@0 2542
michael@0 2543 static bool
michael@0 2544 BuildFlatReplacement(JSContext *cx, HandleString textstr, HandleString repstr,
michael@0 2545 const FlatMatch &fm, MutableHandleValue rval)
michael@0 2546 {
michael@0 2547 RopeBuilder builder(cx);
michael@0 2548 size_t match = fm.match();
michael@0 2549 size_t matchEnd = match + fm.patternLength();
michael@0 2550
michael@0 2551 if (textstr->isRope()) {
michael@0 2552 /*
michael@0 2553 * If we are replacing over a rope, avoid flattening it by iterating
michael@0 2554 * through it, building a new rope.
michael@0 2555 */
michael@0 2556 StringSegmentRange r(cx);
michael@0 2557 if (!r.init(textstr))
michael@0 2558 return false;
michael@0 2559 size_t pos = 0;
michael@0 2560 while (!r.empty()) {
michael@0 2561 RootedString str(cx, r.front());
michael@0 2562 size_t len = str->length();
michael@0 2563 size_t strEnd = pos + len;
michael@0 2564 if (pos < matchEnd && strEnd > match) {
michael@0 2565 /*
michael@0 2566 * We need to special-case any part of the rope that overlaps
michael@0 2567 * with the replacement string.
michael@0 2568 */
michael@0 2569 if (match >= pos) {
michael@0 2570 /*
michael@0 2571 * If this part of the rope overlaps with the left side of
michael@0 2572 * the pattern, then it must be the only one to overlap with
michael@0 2573 * the first character in the pattern, so we include the
michael@0 2574 * replacement string here.
michael@0 2575 */
michael@0 2576 RootedString leftSide(cx, js_NewDependentString(cx, str, 0, match - pos));
michael@0 2577 if (!leftSide ||
michael@0 2578 !builder.append(leftSide) ||
michael@0 2579 !builder.append(repstr)) {
michael@0 2580 return false;
michael@0 2581 }
michael@0 2582 }
michael@0 2583
michael@0 2584 /*
michael@0 2585 * If str runs off the end of the matched string, append the
michael@0 2586 * last part of str.
michael@0 2587 */
michael@0 2588 if (strEnd > matchEnd) {
michael@0 2589 RootedString rightSide(cx, js_NewDependentString(cx, str, matchEnd - pos,
michael@0 2590 strEnd - matchEnd));
michael@0 2591 if (!rightSide || !builder.append(rightSide))
michael@0 2592 return false;
michael@0 2593 }
michael@0 2594 } else {
michael@0 2595 if (!builder.append(str))
michael@0 2596 return false;
michael@0 2597 }
michael@0 2598 pos += str->length();
michael@0 2599 if (!r.popFront())
michael@0 2600 return false;
michael@0 2601 }
michael@0 2602 } else {
michael@0 2603 RootedString leftSide(cx, js_NewDependentString(cx, textstr, 0, match));
michael@0 2604 if (!leftSide)
michael@0 2605 return false;
michael@0 2606 RootedString rightSide(cx);
michael@0 2607 rightSide = js_NewDependentString(cx, textstr, match + fm.patternLength(),
michael@0 2608 textstr->length() - match - fm.patternLength());
michael@0 2609 if (!rightSide ||
michael@0 2610 !builder.append(leftSide) ||
michael@0 2611 !builder.append(repstr) ||
michael@0 2612 !builder.append(rightSide)) {
michael@0 2613 return false;
michael@0 2614 }
michael@0 2615 }
michael@0 2616
michael@0 2617 rval.setString(builder.result());
michael@0 2618 return true;
michael@0 2619 }
michael@0 2620
michael@0 2621 /*
michael@0 2622 * Perform a linear-scan dollar substitution on the replacement text,
michael@0 2623 * constructing a result string that looks like:
michael@0 2624 *
michael@0 2625 * newstring = string[:matchStart] + dollarSub(replaceValue) + string[matchLimit:]
michael@0 2626 */
michael@0 2627 static inline bool
michael@0 2628 BuildDollarReplacement(JSContext *cx, JSString *textstrArg, JSLinearString *repstr,
michael@0 2629 const jschar *firstDollar, const FlatMatch &fm, MutableHandleValue rval)
michael@0 2630 {
michael@0 2631 Rooted<JSLinearString*> textstr(cx, textstrArg->ensureLinear(cx));
michael@0 2632 if (!textstr)
michael@0 2633 return false;
michael@0 2634
michael@0 2635 JS_ASSERT(repstr->chars() <= firstDollar && firstDollar < repstr->chars() + repstr->length());
michael@0 2636 size_t matchStart = fm.match();
michael@0 2637 size_t matchLimit = matchStart + fm.patternLength();
michael@0 2638
michael@0 2639 /*
michael@0 2640 * Most probably:
michael@0 2641 *
michael@0 2642 * len(newstr) >= len(orig) - len(match) + len(replacement)
michael@0 2643 *
michael@0 2644 * Note that dollar vars _could_ make the resulting text smaller than this.
michael@0 2645 */
michael@0 2646 StringBuffer newReplaceChars(cx);
michael@0 2647 if (!newReplaceChars.reserve(textstr->length() - fm.patternLength() + repstr->length()))
michael@0 2648 return false;
michael@0 2649
michael@0 2650 /* Move the pre-dollar chunk in bulk. */
michael@0 2651 newReplaceChars.infallibleAppend(repstr->chars(), firstDollar);
michael@0 2652
michael@0 2653 /* Move the rest char-by-char, interpreting dollars as we encounter them. */
michael@0 2654 #define ENSURE(__cond) if (!(__cond)) return false;
michael@0 2655 const jschar *repstrLimit = repstr->chars() + repstr->length();
michael@0 2656 for (const jschar *it = firstDollar; it < repstrLimit; ++it) {
michael@0 2657 if (*it != '$' || it == repstrLimit - 1) {
michael@0 2658 ENSURE(newReplaceChars.append(*it));
michael@0 2659 continue;
michael@0 2660 }
michael@0 2661
michael@0 2662 switch (*(it + 1)) {
michael@0 2663 case '$': /* Eat one of the dollars. */
michael@0 2664 ENSURE(newReplaceChars.append(*it));
michael@0 2665 break;
michael@0 2666 case '&':
michael@0 2667 ENSURE(newReplaceChars.append(textstr->chars() + matchStart,
michael@0 2668 textstr->chars() + matchLimit));
michael@0 2669 break;
michael@0 2670 case '`':
michael@0 2671 ENSURE(newReplaceChars.append(textstr->chars(), textstr->chars() + matchStart));
michael@0 2672 break;
michael@0 2673 case '\'':
michael@0 2674 ENSURE(newReplaceChars.append(textstr->chars() + matchLimit,
michael@0 2675 textstr->chars() + textstr->length()));
michael@0 2676 break;
michael@0 2677 default: /* The dollar we saw was not special (no matter what its mother told it). */
michael@0 2678 ENSURE(newReplaceChars.append(*it));
michael@0 2679 continue;
michael@0 2680 }
michael@0 2681 ++it; /* We always eat an extra char in the above switch. */
michael@0 2682 }
michael@0 2683
michael@0 2684 RootedString leftSide(cx, js_NewDependentString(cx, textstr, 0, matchStart));
michael@0 2685 ENSURE(leftSide);
michael@0 2686
michael@0 2687 RootedString newReplace(cx, newReplaceChars.finishString());
michael@0 2688 ENSURE(newReplace);
michael@0 2689
michael@0 2690 JS_ASSERT(textstr->length() >= matchLimit);
michael@0 2691 RootedString rightSide(cx, js_NewDependentString(cx, textstr, matchLimit,
michael@0 2692 textstr->length() - matchLimit));
michael@0 2693 ENSURE(rightSide);
michael@0 2694
michael@0 2695 RopeBuilder builder(cx);
michael@0 2696 ENSURE(builder.append(leftSide) &&
michael@0 2697 builder.append(newReplace) &&
michael@0 2698 builder.append(rightSide));
michael@0 2699 #undef ENSURE
michael@0 2700
michael@0 2701 rval.setString(builder.result());
michael@0 2702 return true;
michael@0 2703 }
michael@0 2704
michael@0 2705 struct StringRange
michael@0 2706 {
michael@0 2707 size_t start;
michael@0 2708 size_t length;
michael@0 2709
michael@0 2710 StringRange(size_t s, size_t l)
michael@0 2711 : start(s), length(l)
michael@0 2712 { }
michael@0 2713 };
michael@0 2714
michael@0 2715 static inline JSFatInlineString *
michael@0 2716 FlattenSubstrings(JSContext *cx, const jschar *chars,
michael@0 2717 const StringRange *ranges, size_t rangesLen, size_t outputLen)
michael@0 2718 {
michael@0 2719 JS_ASSERT(JSFatInlineString::lengthFits(outputLen));
michael@0 2720
michael@0 2721 JSFatInlineString *str = js_NewGCFatInlineString<CanGC>(cx);
michael@0 2722 if (!str)
michael@0 2723 return nullptr;
michael@0 2724 jschar *buf = str->init(outputLen);
michael@0 2725
michael@0 2726 size_t pos = 0;
michael@0 2727 for (size_t i = 0; i < rangesLen; i++) {
michael@0 2728 PodCopy(buf + pos, chars + ranges[i].start, ranges[i].length);
michael@0 2729 pos += ranges[i].length;
michael@0 2730 }
michael@0 2731 JS_ASSERT(pos == outputLen);
michael@0 2732
michael@0 2733 buf[outputLen] = 0;
michael@0 2734 return str;
michael@0 2735 }
michael@0 2736
michael@0 2737 static JSString *
michael@0 2738 AppendSubstrings(JSContext *cx, Handle<JSFlatString*> flatStr,
michael@0 2739 const StringRange *ranges, size_t rangesLen)
michael@0 2740 {
michael@0 2741 JS_ASSERT(rangesLen);
michael@0 2742
michael@0 2743 /* For single substrings, construct a dependent string. */
michael@0 2744 if (rangesLen == 1)
michael@0 2745 return js_NewDependentString(cx, flatStr, ranges[0].start, ranges[0].length);
michael@0 2746
michael@0 2747 const jschar *chars = flatStr->getChars(cx);
michael@0 2748 if (!chars)
michael@0 2749 return nullptr;
michael@0 2750
michael@0 2751 /* Collect substrings into a rope */
michael@0 2752 size_t i = 0;
michael@0 2753 RopeBuilder rope(cx);
michael@0 2754 RootedString part(cx, nullptr);
michael@0 2755 while (i < rangesLen) {
michael@0 2756
michael@0 2757 /* Find maximum range that fits in JSFatInlineString */
michael@0 2758 size_t substrLen = 0;
michael@0 2759 size_t end = i;
michael@0 2760 for (; end < rangesLen; end++) {
michael@0 2761 if (substrLen + ranges[end].length > JSFatInlineString::MAX_FAT_INLINE_LENGTH)
michael@0 2762 break;
michael@0 2763 substrLen += ranges[end].length;
michael@0 2764 }
michael@0 2765
michael@0 2766 if (i == end) {
michael@0 2767 /* Not even one range fits JSFatInlineString, use DependentString */
michael@0 2768 const StringRange &sr = ranges[i++];
michael@0 2769 part = js_NewDependentString(cx, flatStr, sr.start, sr.length);
michael@0 2770 } else {
michael@0 2771 /* Copy the ranges (linearly) into a JSFatInlineString */
michael@0 2772 part = FlattenSubstrings(cx, chars, ranges + i, end - i, substrLen);
michael@0 2773 i = end;
michael@0 2774 }
michael@0 2775
michael@0 2776 if (!part)
michael@0 2777 return nullptr;
michael@0 2778
michael@0 2779 /* Appending to the rope permanently roots the substring. */
michael@0 2780 if (!rope.append(part))
michael@0 2781 return nullptr;
michael@0 2782 }
michael@0 2783
michael@0 2784 return rope.result();
michael@0 2785 }
michael@0 2786
michael@0 2787 static bool
michael@0 2788 StrReplaceRegexpRemove(JSContext *cx, HandleString str, RegExpShared &re, MutableHandleValue rval)
michael@0 2789 {
michael@0 2790 Rooted<JSFlatString*> flatStr(cx, str->ensureFlat(cx));
michael@0 2791 if (!flatStr)
michael@0 2792 return false;
michael@0 2793
michael@0 2794 Vector<StringRange, 16, SystemAllocPolicy> ranges;
michael@0 2795
michael@0 2796 size_t charsLen = flatStr->length();
michael@0 2797
michael@0 2798 MatchPair match;
michael@0 2799 size_t startIndex = 0; /* Index used for iterating through the string. */
michael@0 2800 size_t lastIndex = 0; /* Index after last successful match. */
michael@0 2801 size_t lazyIndex = 0; /* Index before last successful match. */
michael@0 2802
michael@0 2803 /* Accumulate StringRanges for unmatched substrings. */
michael@0 2804 while (startIndex <= charsLen) {
michael@0 2805 if (!CheckForInterrupt(cx))
michael@0 2806 return false;
michael@0 2807
michael@0 2808 RegExpRunStatus status =
michael@0 2809 re.executeMatchOnly(cx, flatStr->chars(), charsLen, &startIndex, match);
michael@0 2810 if (status == RegExpRunStatus_Error)
michael@0 2811 return false;
michael@0 2812 if (status == RegExpRunStatus_Success_NotFound)
michael@0 2813 break;
michael@0 2814
michael@0 2815 /* Include the latest unmatched substring. */
michael@0 2816 if (size_t(match.start) > lastIndex) {
michael@0 2817 if (!ranges.append(StringRange(lastIndex, match.start - lastIndex)))
michael@0 2818 return false;
michael@0 2819 }
michael@0 2820
michael@0 2821 lazyIndex = lastIndex;
michael@0 2822 lastIndex = startIndex;
michael@0 2823
michael@0 2824 if (match.isEmpty())
michael@0 2825 startIndex++;
michael@0 2826
michael@0 2827 /* Non-global removal executes at most once. */
michael@0 2828 if (!re.global())
michael@0 2829 break;
michael@0 2830 }
michael@0 2831
michael@0 2832 /* If unmatched, return the input string. */
michael@0 2833 if (!lastIndex) {
michael@0 2834 if (startIndex > 0)
michael@0 2835 cx->global()->getRegExpStatics()->updateLazily(cx, flatStr, &re, lazyIndex);
michael@0 2836 rval.setString(str);
michael@0 2837 return true;
michael@0 2838 }
michael@0 2839
michael@0 2840 /* The last successful match updates the RegExpStatics. */
michael@0 2841 cx->global()->getRegExpStatics()->updateLazily(cx, flatStr, &re, lazyIndex);
michael@0 2842
michael@0 2843 /* Include any remaining part of the string. */
michael@0 2844 if (lastIndex < charsLen) {
michael@0 2845 if (!ranges.append(StringRange(lastIndex, charsLen - lastIndex)))
michael@0 2846 return false;
michael@0 2847 }
michael@0 2848
michael@0 2849 /* Handle the empty string before calling .begin(). */
michael@0 2850 if (ranges.empty()) {
michael@0 2851 rval.setString(cx->runtime()->emptyString);
michael@0 2852 return true;
michael@0 2853 }
michael@0 2854
michael@0 2855 JSString *result = AppendSubstrings(cx, flatStr, ranges.begin(), ranges.length());
michael@0 2856 if (!result)
michael@0 2857 return false;
michael@0 2858
michael@0 2859 rval.setString(result);
michael@0 2860 return true;
michael@0 2861 }
michael@0 2862
michael@0 2863 static inline bool
michael@0 2864 StrReplaceRegExp(JSContext *cx, ReplaceData &rdata, MutableHandleValue rval)
michael@0 2865 {
michael@0 2866 rdata.leftIndex = 0;
michael@0 2867 rdata.calledBack = false;
michael@0 2868
michael@0 2869 RegExpStatics *res = cx->global()->getRegExpStatics();
michael@0 2870 RegExpShared &re = rdata.g.regExp();
michael@0 2871
michael@0 2872 // The spec doesn't describe this function very clearly, so we go ahead and
michael@0 2873 // assume that when the input to String.prototype.replace is a global
michael@0 2874 // RegExp, calling the replacer function (assuming one was provided) takes
michael@0 2875 // place only after the matching is done. See the comment at the beginning
michael@0 2876 // of DoMatchGlobal explaining why we can zero the the RegExp object's
michael@0 2877 // lastIndex property here.
michael@0 2878 if (re.global() && !rdata.g.zeroLastIndex(cx))
michael@0 2879 return false;
michael@0 2880
michael@0 2881 /* Optimize removal. */
michael@0 2882 if (rdata.repstr && rdata.repstr->length() == 0) {
michael@0 2883 JS_ASSERT(!rdata.lambda && !rdata.elembase && !rdata.dollar);
michael@0 2884 return StrReplaceRegexpRemove(cx, rdata.str, re, rval);
michael@0 2885 }
michael@0 2886
michael@0 2887 Rooted<JSLinearString*> linearStr(cx, rdata.str->ensureLinear(cx));
michael@0 2888 if (!linearStr)
michael@0 2889 return false;
michael@0 2890
michael@0 2891 if (re.global()) {
michael@0 2892 if (!DoMatchForReplaceGlobal(cx, res, linearStr, re, rdata))
michael@0 2893 return false;
michael@0 2894 } else {
michael@0 2895 if (!DoMatchForReplaceLocal(cx, res, linearStr, re, rdata))
michael@0 2896 return false;
michael@0 2897 }
michael@0 2898
michael@0 2899 if (!rdata.calledBack) {
michael@0 2900 /* Didn't match, so the string is unmodified. */
michael@0 2901 rval.setString(rdata.str);
michael@0 2902 return true;
michael@0 2903 }
michael@0 2904
michael@0 2905 JSSubString sub;
michael@0 2906 res->getRightContext(&sub);
michael@0 2907 if (!rdata.sb.append(sub.chars, sub.length))
michael@0 2908 return false;
michael@0 2909
michael@0 2910 JSString *retstr = rdata.sb.finishString();
michael@0 2911 if (!retstr)
michael@0 2912 return false;
michael@0 2913
michael@0 2914 rval.setString(retstr);
michael@0 2915 return true;
michael@0 2916 }
michael@0 2917
michael@0 2918 static inline bool
michael@0 2919 str_replace_regexp(JSContext *cx, CallArgs args, ReplaceData &rdata)
michael@0 2920 {
michael@0 2921 if (!rdata.g.normalizeRegExp(cx, true, 2, args))
michael@0 2922 return false;
michael@0 2923
michael@0 2924 return StrReplaceRegExp(cx, rdata, args.rval());
michael@0 2925 }
michael@0 2926
michael@0 2927 bool
michael@0 2928 js::str_replace_regexp_raw(JSContext *cx, HandleString string, HandleObject regexp,
michael@0 2929 HandleString replacement, MutableHandleValue rval)
michael@0 2930 {
michael@0 2931 /* Optimize removal, so we don't have to create ReplaceData */
michael@0 2932 if (replacement->length() == 0) {
michael@0 2933 StringRegExpGuard guard(cx);
michael@0 2934 if (!guard.init(cx, regexp))
michael@0 2935 return false;
michael@0 2936
michael@0 2937 RegExpShared &re = guard.regExp();
michael@0 2938 return StrReplaceRegexpRemove(cx, string, re, rval);
michael@0 2939 }
michael@0 2940
michael@0 2941 ReplaceData rdata(cx);
michael@0 2942 rdata.str = string;
michael@0 2943
michael@0 2944 JSLinearString *repl = replacement->ensureLinear(cx);
michael@0 2945 if (!repl)
michael@0 2946 return false;
michael@0 2947
michael@0 2948 rdata.setReplacementString(repl);
michael@0 2949
michael@0 2950 if (!rdata.g.init(cx, regexp))
michael@0 2951 return false;
michael@0 2952
michael@0 2953 return StrReplaceRegExp(cx, rdata, rval);
michael@0 2954 }
michael@0 2955
michael@0 2956 static inline bool
michael@0 2957 StrReplaceString(JSContext *cx, ReplaceData &rdata, const FlatMatch &fm, MutableHandleValue rval)
michael@0 2958 {
michael@0 2959 /*
michael@0 2960 * Note: we could optimize the text.length == pattern.length case if we wanted,
michael@0 2961 * even in the presence of dollar metachars.
michael@0 2962 */
michael@0 2963 if (rdata.dollar)
michael@0 2964 return BuildDollarReplacement(cx, rdata.str, rdata.repstr, rdata.dollar, fm, rval);
michael@0 2965 return BuildFlatReplacement(cx, rdata.str, rdata.repstr, fm, rval);
michael@0 2966 }
michael@0 2967
michael@0 2968 static const uint32_t ReplaceOptArg = 2;
michael@0 2969
michael@0 2970 bool
michael@0 2971 js::str_replace_string_raw(JSContext *cx, HandleString string, HandleString pattern,
michael@0 2972 HandleString replacement, MutableHandleValue rval)
michael@0 2973 {
michael@0 2974 ReplaceData rdata(cx);
michael@0 2975
michael@0 2976 rdata.str = string;
michael@0 2977 JSLinearString *repl = replacement->ensureLinear(cx);
michael@0 2978 if (!repl)
michael@0 2979 return false;
michael@0 2980 rdata.setReplacementString(repl);
michael@0 2981
michael@0 2982 if (!rdata.g.init(cx, pattern))
michael@0 2983 return false;
michael@0 2984 const FlatMatch *fm = rdata.g.tryFlatMatch(cx, rdata.str, ReplaceOptArg, ReplaceOptArg, false);
michael@0 2985
michael@0 2986 if (fm->match() < 0) {
michael@0 2987 rval.setString(string);
michael@0 2988 return true;
michael@0 2989 }
michael@0 2990
michael@0 2991 return StrReplaceString(cx, rdata, *fm, rval);
michael@0 2992 }
michael@0 2993
michael@0 2994 static inline bool
michael@0 2995 str_replace_flat_lambda(JSContext *cx, CallArgs outerArgs, ReplaceData &rdata, const FlatMatch &fm)
michael@0 2996 {
michael@0 2997 RootedString matchStr(cx, js_NewDependentString(cx, rdata.str, fm.match(), fm.patternLength()));
michael@0 2998 if (!matchStr)
michael@0 2999 return false;
michael@0 3000
michael@0 3001 /* lambda(matchStr, matchStart, textstr) */
michael@0 3002 static const uint32_t lambdaArgc = 3;
michael@0 3003 if (!rdata.fig.args().init(lambdaArgc))
michael@0 3004 return false;
michael@0 3005
michael@0 3006 CallArgs &args = rdata.fig.args();
michael@0 3007 args.setCallee(ObjectValue(*rdata.lambda));
michael@0 3008 args.setThis(UndefinedValue());
michael@0 3009
michael@0 3010 Value *sp = args.array();
michael@0 3011 sp[0].setString(matchStr);
michael@0 3012 sp[1].setInt32(fm.match());
michael@0 3013 sp[2].setString(rdata.str);
michael@0 3014
michael@0 3015 if (!rdata.fig.invoke(cx))
michael@0 3016 return false;
michael@0 3017
michael@0 3018 RootedString repstr(cx, ToString<CanGC>(cx, args.rval()));
michael@0 3019 if (!repstr)
michael@0 3020 return false;
michael@0 3021
michael@0 3022 RootedString leftSide(cx, js_NewDependentString(cx, rdata.str, 0, fm.match()));
michael@0 3023 if (!leftSide)
michael@0 3024 return false;
michael@0 3025
michael@0 3026 size_t matchLimit = fm.match() + fm.patternLength();
michael@0 3027 RootedString rightSide(cx, js_NewDependentString(cx, rdata.str, matchLimit,
michael@0 3028 rdata.str->length() - matchLimit));
michael@0 3029 if (!rightSide)
michael@0 3030 return false;
michael@0 3031
michael@0 3032 RopeBuilder builder(cx);
michael@0 3033 if (!(builder.append(leftSide) &&
michael@0 3034 builder.append(repstr) &&
michael@0 3035 builder.append(rightSide))) {
michael@0 3036 return false;
michael@0 3037 }
michael@0 3038
michael@0 3039 outerArgs.rval().setString(builder.result());
michael@0 3040 return true;
michael@0 3041 }
michael@0 3042
michael@0 3043 /*
michael@0 3044 * Pattern match the script to check if it is is indexing into a particular
michael@0 3045 * object, e.g. 'function(a) { return b[a]; }'. Avoid calling the script in
michael@0 3046 * such cases, which are used by javascript packers (particularly the popular
michael@0 3047 * Dean Edwards packer) to efficiently encode large scripts. We only handle the
michael@0 3048 * code patterns generated by such packers here.
michael@0 3049 */
michael@0 3050 static bool
michael@0 3051 LambdaIsGetElem(JSContext *cx, JSObject &lambda, MutableHandleObject pobj)
michael@0 3052 {
michael@0 3053 if (!lambda.is<JSFunction>())
michael@0 3054 return true;
michael@0 3055
michael@0 3056 RootedFunction fun(cx, &lambda.as<JSFunction>());
michael@0 3057 if (!fun->isInterpreted())
michael@0 3058 return true;
michael@0 3059
michael@0 3060 JSScript *script = fun->getOrCreateScript(cx);
michael@0 3061 if (!script)
michael@0 3062 return false;
michael@0 3063
michael@0 3064 jsbytecode *pc = script->code();
michael@0 3065
michael@0 3066 /*
michael@0 3067 * JSOP_GETALIASEDVAR tells us exactly where to find the base object 'b'.
michael@0 3068 * Rule out the (unlikely) possibility of a heavyweight function since it
michael@0 3069 * would make our scope walk off by 1.
michael@0 3070 */
michael@0 3071 if (JSOp(*pc) != JSOP_GETALIASEDVAR || fun->isHeavyweight())
michael@0 3072 return true;
michael@0 3073 ScopeCoordinate sc(pc);
michael@0 3074 ScopeObject *scope = &fun->environment()->as<ScopeObject>();
michael@0 3075 for (unsigned i = 0; i < sc.hops(); ++i)
michael@0 3076 scope = &scope->enclosingScope().as<ScopeObject>();
michael@0 3077 Value b = scope->aliasedVar(sc);
michael@0 3078 pc += JSOP_GETALIASEDVAR_LENGTH;
michael@0 3079
michael@0 3080 /* Look for 'a' to be the lambda's first argument. */
michael@0 3081 if (JSOp(*pc) != JSOP_GETARG || GET_ARGNO(pc) != 0)
michael@0 3082 return true;
michael@0 3083 pc += JSOP_GETARG_LENGTH;
michael@0 3084
michael@0 3085 /* 'b[a]' */
michael@0 3086 if (JSOp(*pc) != JSOP_GETELEM)
michael@0 3087 return true;
michael@0 3088 pc += JSOP_GETELEM_LENGTH;
michael@0 3089
michael@0 3090 /* 'return b[a]' */
michael@0 3091 if (JSOp(*pc) != JSOP_RETURN)
michael@0 3092 return true;
michael@0 3093
michael@0 3094 /* 'b' must behave like a normal object. */
michael@0 3095 if (!b.isObject())
michael@0 3096 return true;
michael@0 3097
michael@0 3098 JSObject &bobj = b.toObject();
michael@0 3099 const Class *clasp = bobj.getClass();
michael@0 3100 if (!clasp->isNative() || clasp->ops.lookupProperty || clasp->ops.getProperty)
michael@0 3101 return true;
michael@0 3102
michael@0 3103 pobj.set(&bobj);
michael@0 3104 return true;
michael@0 3105 }
michael@0 3106
michael@0 3107 bool
michael@0 3108 js::str_replace(JSContext *cx, unsigned argc, Value *vp)
michael@0 3109 {
michael@0 3110 CallArgs args = CallArgsFromVp(argc, vp);
michael@0 3111
michael@0 3112 ReplaceData rdata(cx);
michael@0 3113 rdata.str = ThisToStringForStringProto(cx, args);
michael@0 3114 if (!rdata.str)
michael@0 3115 return false;
michael@0 3116
michael@0 3117 if (!rdata.g.init(cx, args))
michael@0 3118 return false;
michael@0 3119
michael@0 3120 /* Extract replacement string/function. */
michael@0 3121 if (args.length() >= ReplaceOptArg && js_IsCallable(args[1])) {
michael@0 3122 rdata.setReplacementFunction(&args[1].toObject());
michael@0 3123
michael@0 3124 if (!LambdaIsGetElem(cx, *rdata.lambda, &rdata.elembase))
michael@0 3125 return false;
michael@0 3126 } else {
michael@0 3127 JSLinearString *string = ArgToRootedString(cx, args, 1);
michael@0 3128 if (!string)
michael@0 3129 return false;
michael@0 3130
michael@0 3131 rdata.setReplacementString(string);
michael@0 3132 }
michael@0 3133
michael@0 3134 rdata.fig.initFunction(ObjectOrNullValue(rdata.lambda));
michael@0 3135
michael@0 3136 /*
michael@0 3137 * Unlike its |String.prototype| brethren, |replace| doesn't convert
michael@0 3138 * its input to a regular expression. (Even if it contains metachars.)
michael@0 3139 *
michael@0 3140 * However, if the user invokes our (non-standard) |flags| argument
michael@0 3141 * extension then we revert to creating a regular expression. Note that
michael@0 3142 * this is observable behavior through the side-effect mutation of the
michael@0 3143 * |RegExp| statics.
michael@0 3144 */
michael@0 3145
michael@0 3146 const FlatMatch *fm = rdata.g.tryFlatMatch(cx, rdata.str, ReplaceOptArg, args.length(), false);
michael@0 3147
michael@0 3148 if (!fm) {
michael@0 3149 if (cx->isExceptionPending()) /* oom in RopeMatch in tryFlatMatch */
michael@0 3150 return false;
michael@0 3151 return str_replace_regexp(cx, args, rdata);
michael@0 3152 }
michael@0 3153
michael@0 3154 if (fm->match() < 0) {
michael@0 3155 args.rval().setString(rdata.str);
michael@0 3156 return true;
michael@0 3157 }
michael@0 3158
michael@0 3159 if (rdata.lambda)
michael@0 3160 return str_replace_flat_lambda(cx, args, rdata, *fm);
michael@0 3161 return StrReplaceString(cx, rdata, *fm, args.rval());
michael@0 3162 }
michael@0 3163
michael@0 3164 namespace {
michael@0 3165
michael@0 3166 class SplitMatchResult {
michael@0 3167 size_t endIndex_;
michael@0 3168 size_t length_;
michael@0 3169
michael@0 3170 public:
michael@0 3171 void setFailure() {
michael@0 3172 JS_STATIC_ASSERT(SIZE_MAX > JSString::MAX_LENGTH);
michael@0 3173 endIndex_ = SIZE_MAX;
michael@0 3174 }
michael@0 3175 bool isFailure() const {
michael@0 3176 return endIndex_ == SIZE_MAX;
michael@0 3177 }
michael@0 3178 size_t endIndex() const {
michael@0 3179 JS_ASSERT(!isFailure());
michael@0 3180 return endIndex_;
michael@0 3181 }
michael@0 3182 size_t length() const {
michael@0 3183 JS_ASSERT(!isFailure());
michael@0 3184 return length_;
michael@0 3185 }
michael@0 3186 void setResult(size_t length, size_t endIndex) {
michael@0 3187 length_ = length;
michael@0 3188 endIndex_ = endIndex;
michael@0 3189 }
michael@0 3190 };
michael@0 3191
michael@0 3192 } /* anonymous namespace */
michael@0 3193
michael@0 3194 template<class Matcher>
michael@0 3195 static ArrayObject *
michael@0 3196 SplitHelper(JSContext *cx, Handle<JSLinearString*> str, uint32_t limit, const Matcher &splitMatch,
michael@0 3197 Handle<TypeObject*> type)
michael@0 3198 {
michael@0 3199 size_t strLength = str->length();
michael@0 3200 SplitMatchResult result;
michael@0 3201
michael@0 3202 /* Step 11. */
michael@0 3203 if (strLength == 0) {
michael@0 3204 if (!splitMatch(cx, str, 0, &result))
michael@0 3205 return nullptr;
michael@0 3206
michael@0 3207 /*
michael@0 3208 * NB: Unlike in the non-empty string case, it's perfectly fine
michael@0 3209 * (indeed the spec requires it) if we match at the end of the
michael@0 3210 * string. Thus these cases should hold:
michael@0 3211 *
michael@0 3212 * var a = "".split("");
michael@0 3213 * assertEq(a.length, 0);
michael@0 3214 * var b = "".split(/.?/);
michael@0 3215 * assertEq(b.length, 0);
michael@0 3216 */
michael@0 3217 if (!result.isFailure())
michael@0 3218 return NewDenseEmptyArray(cx);
michael@0 3219
michael@0 3220 RootedValue v(cx, StringValue(str));
michael@0 3221 return NewDenseCopiedArray(cx, 1, v.address());
michael@0 3222 }
michael@0 3223
michael@0 3224 /* Step 12. */
michael@0 3225 size_t lastEndIndex = 0;
michael@0 3226 size_t index = 0;
michael@0 3227
michael@0 3228 /* Step 13. */
michael@0 3229 AutoValueVector splits(cx);
michael@0 3230
michael@0 3231 while (index < strLength) {
michael@0 3232 /* Step 13(a). */
michael@0 3233 if (!splitMatch(cx, str, index, &result))
michael@0 3234 return nullptr;
michael@0 3235
michael@0 3236 /*
michael@0 3237 * Step 13(b).
michael@0 3238 *
michael@0 3239 * Our match algorithm differs from the spec in that it returns the
michael@0 3240 * next index at which a match happens. If no match happens we're
michael@0 3241 * done.
michael@0 3242 *
michael@0 3243 * But what if the match is at the end of the string (and the string is
michael@0 3244 * not empty)? Per 13(c)(ii) this shouldn't be a match, so we have to
michael@0 3245 * specially exclude it. Thus this case should hold:
michael@0 3246 *
michael@0 3247 * var a = "abc".split(/\b/);
michael@0 3248 * assertEq(a.length, 1);
michael@0 3249 * assertEq(a[0], "abc");
michael@0 3250 */
michael@0 3251 if (result.isFailure())
michael@0 3252 break;
michael@0 3253
michael@0 3254 /* Step 13(c)(i). */
michael@0 3255 size_t sepLength = result.length();
michael@0 3256 size_t endIndex = result.endIndex();
michael@0 3257 if (sepLength == 0 && endIndex == strLength)
michael@0 3258 break;
michael@0 3259
michael@0 3260 /* Step 13(c)(ii). */
michael@0 3261 if (endIndex == lastEndIndex) {
michael@0 3262 index++;
michael@0 3263 continue;
michael@0 3264 }
michael@0 3265
michael@0 3266 /* Step 13(c)(iii). */
michael@0 3267 JS_ASSERT(lastEndIndex < endIndex);
michael@0 3268 JS_ASSERT(sepLength <= strLength);
michael@0 3269 JS_ASSERT(lastEndIndex + sepLength <= endIndex);
michael@0 3270
michael@0 3271 /* Steps 13(c)(iii)(1-3). */
michael@0 3272 size_t subLength = size_t(endIndex - sepLength - lastEndIndex);
michael@0 3273 JSString *sub = js_NewDependentString(cx, str, lastEndIndex, subLength);
michael@0 3274 if (!sub || !splits.append(StringValue(sub)))
michael@0 3275 return nullptr;
michael@0 3276
michael@0 3277 /* Step 13(c)(iii)(4). */
michael@0 3278 if (splits.length() == limit)
michael@0 3279 return NewDenseCopiedArray(cx, splits.length(), splits.begin());
michael@0 3280
michael@0 3281 /* Step 13(c)(iii)(5). */
michael@0 3282 lastEndIndex = endIndex;
michael@0 3283
michael@0 3284 /* Step 13(c)(iii)(6-7). */
michael@0 3285 if (Matcher::returnsCaptures) {
michael@0 3286 RegExpStatics *res = cx->global()->getRegExpStatics();
michael@0 3287 const MatchPairs &matches = res->getMatches();
michael@0 3288 for (size_t i = 0; i < matches.parenCount(); i++) {
michael@0 3289 /* Steps 13(c)(iii)(7)(a-c). */
michael@0 3290 if (!matches[i + 1].isUndefined()) {
michael@0 3291 JSSubString parsub;
michael@0 3292 res->getParen(i + 1, &parsub);
michael@0 3293 sub = js_NewStringCopyN<CanGC>(cx, parsub.chars, parsub.length);
michael@0 3294 if (!sub || !splits.append(StringValue(sub)))
michael@0 3295 return nullptr;
michael@0 3296 } else {
michael@0 3297 /* Only string entries have been accounted for so far. */
michael@0 3298 AddTypePropertyId(cx, type, JSID_VOID, UndefinedValue());
michael@0 3299 if (!splits.append(UndefinedValue()))
michael@0 3300 return nullptr;
michael@0 3301 }
michael@0 3302
michael@0 3303 /* Step 13(c)(iii)(7)(d). */
michael@0 3304 if (splits.length() == limit)
michael@0 3305 return NewDenseCopiedArray(cx, splits.length(), splits.begin());
michael@0 3306 }
michael@0 3307 }
michael@0 3308
michael@0 3309 /* Step 13(c)(iii)(8). */
michael@0 3310 index = lastEndIndex;
michael@0 3311 }
michael@0 3312
michael@0 3313 /* Steps 14-15. */
michael@0 3314 JSString *sub = js_NewDependentString(cx, str, lastEndIndex, strLength - lastEndIndex);
michael@0 3315 if (!sub || !splits.append(StringValue(sub)))
michael@0 3316 return nullptr;
michael@0 3317
michael@0 3318 /* Step 16. */
michael@0 3319 return NewDenseCopiedArray(cx, splits.length(), splits.begin());
michael@0 3320 }
michael@0 3321
michael@0 3322 // Fast-path for splitting a string into a character array via split("").
michael@0 3323 static ArrayObject *
michael@0 3324 CharSplitHelper(JSContext *cx, Handle<JSLinearString*> str, uint32_t limit)
michael@0 3325 {
michael@0 3326 size_t strLength = str->length();
michael@0 3327 if (strLength == 0)
michael@0 3328 return NewDenseEmptyArray(cx);
michael@0 3329
michael@0 3330 js::StaticStrings &staticStrings = cx->staticStrings();
michael@0 3331 uint32_t resultlen = (limit < strLength ? limit : strLength);
michael@0 3332
michael@0 3333 AutoValueVector splits(cx);
michael@0 3334 if (!splits.reserve(resultlen))
michael@0 3335 return nullptr;
michael@0 3336
michael@0 3337 for (size_t i = 0; i < resultlen; ++i) {
michael@0 3338 JSString *sub = staticStrings.getUnitStringForElement(cx, str, i);
michael@0 3339 if (!sub)
michael@0 3340 return nullptr;
michael@0 3341 splits.infallibleAppend(StringValue(sub));
michael@0 3342 }
michael@0 3343
michael@0 3344 return NewDenseCopiedArray(cx, splits.length(), splits.begin());
michael@0 3345 }
michael@0 3346
michael@0 3347 namespace {
michael@0 3348
michael@0 3349 /*
michael@0 3350 * The SplitMatch operation from ES5 15.5.4.14 is implemented using different
michael@0 3351 * paths for regular expression and string separators.
michael@0 3352 *
michael@0 3353 * The algorithm differs from the spec in that the we return the next index at
michael@0 3354 * which a match happens.
michael@0 3355 */
michael@0 3356 class SplitRegExpMatcher
michael@0 3357 {
michael@0 3358 RegExpShared &re;
michael@0 3359 RegExpStatics *res;
michael@0 3360
michael@0 3361 public:
michael@0 3362 SplitRegExpMatcher(RegExpShared &re, RegExpStatics *res) : re(re), res(res) {}
michael@0 3363
michael@0 3364 static const bool returnsCaptures = true;
michael@0 3365
michael@0 3366 bool operator()(JSContext *cx, Handle<JSLinearString*> str, size_t index,
michael@0 3367 SplitMatchResult *result) const
michael@0 3368 {
michael@0 3369 const jschar *chars = str->chars();
michael@0 3370 size_t length = str->length();
michael@0 3371
michael@0 3372 ScopedMatchPairs matches(&cx->tempLifoAlloc());
michael@0 3373 RegExpRunStatus status = re.execute(cx, chars, length, &index, matches);
michael@0 3374 if (status == RegExpRunStatus_Error)
michael@0 3375 return false;
michael@0 3376
michael@0 3377 if (status == RegExpRunStatus_Success_NotFound) {
michael@0 3378 result->setFailure();
michael@0 3379 return true;
michael@0 3380 }
michael@0 3381
michael@0 3382 if (!res->updateFromMatchPairs(cx, str, matches))
michael@0 3383 return false;
michael@0 3384
michael@0 3385 JSSubString sep;
michael@0 3386 res->getLastMatch(&sep);
michael@0 3387
michael@0 3388 result->setResult(sep.length, index);
michael@0 3389 return true;
michael@0 3390 }
michael@0 3391 };
michael@0 3392
michael@0 3393 class SplitStringMatcher
michael@0 3394 {
michael@0 3395 Rooted<JSLinearString*> sep;
michael@0 3396
michael@0 3397 public:
michael@0 3398 SplitStringMatcher(JSContext *cx, HandleLinearString sep)
michael@0 3399 : sep(cx, sep)
michael@0 3400 {}
michael@0 3401
michael@0 3402 static const bool returnsCaptures = false;
michael@0 3403
michael@0 3404 bool operator()(JSContext *cx, JSLinearString *str, size_t index, SplitMatchResult *res) const
michael@0 3405 {
michael@0 3406 JS_ASSERT(index == 0 || index < str->length());
michael@0 3407 const jschar *chars = str->chars();
michael@0 3408 int match = StringMatch(chars + index, str->length() - index,
michael@0 3409 sep->chars(), sep->length());
michael@0 3410 if (match == -1)
michael@0 3411 res->setFailure();
michael@0 3412 else
michael@0 3413 res->setResult(sep->length(), index + match + sep->length());
michael@0 3414 return true;
michael@0 3415 }
michael@0 3416 };
michael@0 3417
michael@0 3418 } /* anonymous namespace */
michael@0 3419
michael@0 3420 /* ES5 15.5.4.14 */
michael@0 3421 bool
michael@0 3422 js::str_split(JSContext *cx, unsigned argc, Value *vp)
michael@0 3423 {
michael@0 3424 CallArgs args = CallArgsFromVp(argc, vp);
michael@0 3425
michael@0 3426 /* Steps 1-2. */
michael@0 3427 RootedString str(cx, ThisToStringForStringProto(cx, args));
michael@0 3428 if (!str)
michael@0 3429 return false;
michael@0 3430
michael@0 3431 RootedTypeObject type(cx, GetTypeCallerInitObject(cx, JSProto_Array));
michael@0 3432 if (!type)
michael@0 3433 return false;
michael@0 3434 AddTypePropertyId(cx, type, JSID_VOID, Type::StringType());
michael@0 3435
michael@0 3436 /* Step 5: Use the second argument as the split limit, if given. */
michael@0 3437 uint32_t limit;
michael@0 3438 if (args.hasDefined(1)) {
michael@0 3439 double d;
michael@0 3440 if (!ToNumber(cx, args[1], &d))
michael@0 3441 return false;
michael@0 3442 limit = ToUint32(d);
michael@0 3443 } else {
michael@0 3444 limit = UINT32_MAX;
michael@0 3445 }
michael@0 3446
michael@0 3447 /* Step 8. */
michael@0 3448 RegExpGuard re(cx);
michael@0 3449 RootedLinearString sepstr(cx);
michael@0 3450 bool sepDefined = args.hasDefined(0);
michael@0 3451 if (sepDefined) {
michael@0 3452 if (IsObjectWithClass(args[0], ESClass_RegExp, cx)) {
michael@0 3453 RootedObject obj(cx, &args[0].toObject());
michael@0 3454 if (!RegExpToShared(cx, obj, &re))
michael@0 3455 return false;
michael@0 3456 } else {
michael@0 3457 sepstr = ArgToRootedString(cx, args, 0);
michael@0 3458 if (!sepstr)
michael@0 3459 return false;
michael@0 3460 }
michael@0 3461 }
michael@0 3462
michael@0 3463 /* Step 9. */
michael@0 3464 if (limit == 0) {
michael@0 3465 JSObject *aobj = NewDenseEmptyArray(cx);
michael@0 3466 if (!aobj)
michael@0 3467 return false;
michael@0 3468 aobj->setType(type);
michael@0 3469 args.rval().setObject(*aobj);
michael@0 3470 return true;
michael@0 3471 }
michael@0 3472
michael@0 3473 /* Step 10. */
michael@0 3474 if (!sepDefined) {
michael@0 3475 RootedValue v(cx, StringValue(str));
michael@0 3476 JSObject *aobj = NewDenseCopiedArray(cx, 1, v.address());
michael@0 3477 if (!aobj)
michael@0 3478 return false;
michael@0 3479 aobj->setType(type);
michael@0 3480 args.rval().setObject(*aobj);
michael@0 3481 return true;
michael@0 3482 }
michael@0 3483 Rooted<JSLinearString*> linearStr(cx, str->ensureLinear(cx));
michael@0 3484 if (!linearStr)
michael@0 3485 return false;
michael@0 3486
michael@0 3487 /* Steps 11-15. */
michael@0 3488 RootedObject aobj(cx);
michael@0 3489 if (!re.initialized()) {
michael@0 3490 if (sepstr->length() == 0) {
michael@0 3491 aobj = CharSplitHelper(cx, linearStr, limit);
michael@0 3492 } else {
michael@0 3493 SplitStringMatcher matcher(cx, sepstr);
michael@0 3494 aobj = SplitHelper(cx, linearStr, limit, matcher, type);
michael@0 3495 }
michael@0 3496 } else {
michael@0 3497 SplitRegExpMatcher matcher(*re, cx->global()->getRegExpStatics());
michael@0 3498 aobj = SplitHelper(cx, linearStr, limit, matcher, type);
michael@0 3499 }
michael@0 3500 if (!aobj)
michael@0 3501 return false;
michael@0 3502
michael@0 3503 /* Step 16. */
michael@0 3504 aobj->setType(type);
michael@0 3505 args.rval().setObject(*aobj);
michael@0 3506 return true;
michael@0 3507 }
michael@0 3508
michael@0 3509 JSObject *
michael@0 3510 js::str_split_string(JSContext *cx, HandleTypeObject type, HandleString str, HandleString sep)
michael@0 3511 {
michael@0 3512 Rooted<JSLinearString*> linearStr(cx, str->ensureLinear(cx));
michael@0 3513 if (!linearStr)
michael@0 3514 return nullptr;
michael@0 3515
michael@0 3516 Rooted<JSLinearString*> linearSep(cx, sep->ensureLinear(cx));
michael@0 3517 if (!linearSep)
michael@0 3518 return nullptr;
michael@0 3519
michael@0 3520 uint32_t limit = UINT32_MAX;
michael@0 3521
michael@0 3522 RootedObject aobj(cx);
michael@0 3523 if (linearSep->length() == 0) {
michael@0 3524 aobj = CharSplitHelper(cx, linearStr, limit);
michael@0 3525 } else {
michael@0 3526 SplitStringMatcher matcher(cx, linearSep);
michael@0 3527 aobj = SplitHelper(cx, linearStr, limit, matcher, type);
michael@0 3528 }
michael@0 3529
michael@0 3530 if (!aobj)
michael@0 3531 return nullptr;
michael@0 3532
michael@0 3533 aobj->setType(type);
michael@0 3534 return aobj;
michael@0 3535 }
michael@0 3536
michael@0 3537 static bool
michael@0 3538 str_substr(JSContext *cx, unsigned argc, Value *vp)
michael@0 3539 {
michael@0 3540 CallArgs args = CallArgsFromVp(argc, vp);
michael@0 3541 RootedString str(cx, ThisToStringForStringProto(cx, args));
michael@0 3542 if (!str)
michael@0 3543 return false;
michael@0 3544
michael@0 3545 int32_t length, len, begin;
michael@0 3546 if (args.length() > 0) {
michael@0 3547 length = int32_t(str->length());
michael@0 3548 if (!ValueToIntegerRange(cx, args[0], &begin))
michael@0 3549 return false;
michael@0 3550
michael@0 3551 if (begin >= length) {
michael@0 3552 args.rval().setString(cx->runtime()->emptyString);
michael@0 3553 return true;
michael@0 3554 }
michael@0 3555 if (begin < 0) {
michael@0 3556 begin += length; /* length + INT_MIN will always be less than 0 */
michael@0 3557 if (begin < 0)
michael@0 3558 begin = 0;
michael@0 3559 }
michael@0 3560
michael@0 3561 if (args.hasDefined(1)) {
michael@0 3562 if (!ValueToIntegerRange(cx, args[1], &len))
michael@0 3563 return false;
michael@0 3564
michael@0 3565 if (len <= 0) {
michael@0 3566 args.rval().setString(cx->runtime()->emptyString);
michael@0 3567 return true;
michael@0 3568 }
michael@0 3569
michael@0 3570 if (uint32_t(length) < uint32_t(begin + len))
michael@0 3571 len = length - begin;
michael@0 3572 } else {
michael@0 3573 len = length - begin;
michael@0 3574 }
michael@0 3575
michael@0 3576 str = DoSubstr(cx, str, size_t(begin), size_t(len));
michael@0 3577 if (!str)
michael@0 3578 return false;
michael@0 3579 }
michael@0 3580
michael@0 3581 args.rval().setString(str);
michael@0 3582 return true;
michael@0 3583 }
michael@0 3584
michael@0 3585 /*
michael@0 3586 * Python-esque sequence operations.
michael@0 3587 */
michael@0 3588 static bool
michael@0 3589 str_concat(JSContext *cx, unsigned argc, Value *vp)
michael@0 3590 {
michael@0 3591 CallArgs args = CallArgsFromVp(argc, vp);
michael@0 3592 JSString *str = ThisToStringForStringProto(cx, args);
michael@0 3593 if (!str)
michael@0 3594 return false;
michael@0 3595
michael@0 3596 for (unsigned i = 0; i < args.length(); i++) {
michael@0 3597 JSString *argStr = ToString<NoGC>(cx, args[i]);
michael@0 3598 if (!argStr) {
michael@0 3599 RootedString strRoot(cx, str);
michael@0 3600 argStr = ToString<CanGC>(cx, args[i]);
michael@0 3601 if (!argStr)
michael@0 3602 return false;
michael@0 3603 str = strRoot;
michael@0 3604 }
michael@0 3605
michael@0 3606 JSString *next = ConcatStrings<NoGC>(cx, str, argStr);
michael@0 3607 if (next) {
michael@0 3608 str = next;
michael@0 3609 } else {
michael@0 3610 RootedString strRoot(cx, str), argStrRoot(cx, argStr);
michael@0 3611 str = ConcatStrings<CanGC>(cx, strRoot, argStrRoot);
michael@0 3612 if (!str)
michael@0 3613 return false;
michael@0 3614 }
michael@0 3615 }
michael@0 3616
michael@0 3617 args.rval().setString(str);
michael@0 3618 return true;
michael@0 3619 }
michael@0 3620
michael@0 3621 static bool
michael@0 3622 str_slice(JSContext *cx, unsigned argc, Value *vp)
michael@0 3623 {
michael@0 3624 CallArgs args = CallArgsFromVp(argc, vp);
michael@0 3625
michael@0 3626 if (args.length() == 1 && args.thisv().isString() && args[0].isInt32()) {
michael@0 3627 JSString *str = args.thisv().toString();
michael@0 3628 size_t begin = args[0].toInt32();
michael@0 3629 size_t end = str->length();
michael@0 3630 if (begin <= end) {
michael@0 3631 size_t length = end - begin;
michael@0 3632 if (length == 0) {
michael@0 3633 str = cx->runtime()->emptyString;
michael@0 3634 } else {
michael@0 3635 str = (length == 1)
michael@0 3636 ? cx->staticStrings().getUnitStringForElement(cx, str, begin)
michael@0 3637 : js_NewDependentString(cx, str, begin, length);
michael@0 3638 if (!str)
michael@0 3639 return false;
michael@0 3640 }
michael@0 3641 args.rval().setString(str);
michael@0 3642 return true;
michael@0 3643 }
michael@0 3644 }
michael@0 3645
michael@0 3646 RootedString str(cx, ThisToStringForStringProto(cx, args));
michael@0 3647 if (!str)
michael@0 3648 return false;
michael@0 3649
michael@0 3650 if (args.length() != 0) {
michael@0 3651 double begin, end, length;
michael@0 3652
michael@0 3653 if (!ToInteger(cx, args[0], &begin))
michael@0 3654 return false;
michael@0 3655 length = str->length();
michael@0 3656 if (begin < 0) {
michael@0 3657 begin += length;
michael@0 3658 if (begin < 0)
michael@0 3659 begin = 0;
michael@0 3660 } else if (begin > length) {
michael@0 3661 begin = length;
michael@0 3662 }
michael@0 3663
michael@0 3664 if (args.hasDefined(1)) {
michael@0 3665 if (!ToInteger(cx, args[1], &end))
michael@0 3666 return false;
michael@0 3667 if (end < 0) {
michael@0 3668 end += length;
michael@0 3669 if (end < 0)
michael@0 3670 end = 0;
michael@0 3671 } else if (end > length) {
michael@0 3672 end = length;
michael@0 3673 }
michael@0 3674 if (end < begin)
michael@0 3675 end = begin;
michael@0 3676 } else {
michael@0 3677 end = length;
michael@0 3678 }
michael@0 3679
michael@0 3680 str = js_NewDependentString(cx, str,
michael@0 3681 (size_t)begin,
michael@0 3682 (size_t)(end - begin));
michael@0 3683 if (!str)
michael@0 3684 return false;
michael@0 3685 }
michael@0 3686 args.rval().setString(str);
michael@0 3687 return true;
michael@0 3688 }
michael@0 3689
michael@0 3690 #if JS_HAS_STR_HTML_HELPERS
michael@0 3691 /*
michael@0 3692 * HTML composition aids.
michael@0 3693 */
michael@0 3694 static bool
michael@0 3695 tagify(JSContext *cx, const char *begin, HandleLinearString param, const char *end,
michael@0 3696 CallReceiver call)
michael@0 3697 {
michael@0 3698 JSString *thisstr = ThisToStringForStringProto(cx, call);
michael@0 3699 if (!thisstr)
michael@0 3700 return false;
michael@0 3701
michael@0 3702 JSLinearString *str = thisstr->ensureLinear(cx);
michael@0 3703 if (!str)
michael@0 3704 return false;
michael@0 3705
michael@0 3706 if (!end)
michael@0 3707 end = begin;
michael@0 3708
michael@0 3709 size_t beglen = strlen(begin);
michael@0 3710 size_t taglen = 1 + beglen + 1; /* '<begin' + '>' */
michael@0 3711 if (param) {
michael@0 3712 size_t numChars = param->length();
michael@0 3713 const jschar *parchars = param->chars();
michael@0 3714 for (size_t i = 0, parlen = numChars; i < parlen; ++i) {
michael@0 3715 if (parchars[i] == '"')
michael@0 3716 numChars += 5; /* len(&quot;) - len(") */
michael@0 3717 }
michael@0 3718 taglen += 2 + numChars + 1; /* '="param"' */
michael@0 3719 }
michael@0 3720 size_t endlen = strlen(end);
michael@0 3721 taglen += str->length() + 2 + endlen + 1; /* 'str</end>' */
michael@0 3722
michael@0 3723
michael@0 3724 StringBuffer sb(cx);
michael@0 3725 if (!sb.reserve(taglen))
michael@0 3726 return false;
michael@0 3727
michael@0 3728 sb.infallibleAppend('<');
michael@0 3729
michael@0 3730 MOZ_ALWAYS_TRUE(sb.appendInflated(begin, beglen));
michael@0 3731
michael@0 3732 if (param) {
michael@0 3733 sb.infallibleAppend('=');
michael@0 3734 sb.infallibleAppend('"');
michael@0 3735 const jschar *parchars = param->chars();
michael@0 3736 for (size_t i = 0, parlen = param->length(); i < parlen; ++i) {
michael@0 3737 if (parchars[i] != '"') {
michael@0 3738 sb.infallibleAppend(parchars[i]);
michael@0 3739 } else {
michael@0 3740 MOZ_ALWAYS_TRUE(sb.append("&quot;"));
michael@0 3741 }
michael@0 3742 }
michael@0 3743 sb.infallibleAppend('"');
michael@0 3744 }
michael@0 3745
michael@0 3746 sb.infallibleAppend('>');
michael@0 3747
michael@0 3748 MOZ_ALWAYS_TRUE(sb.append(str));
michael@0 3749
michael@0 3750 sb.infallibleAppend('<');
michael@0 3751 sb.infallibleAppend('/');
michael@0 3752
michael@0 3753 MOZ_ALWAYS_TRUE(sb.appendInflated(end, endlen));
michael@0 3754
michael@0 3755 sb.infallibleAppend('>');
michael@0 3756
michael@0 3757 JSFlatString *retstr = sb.finishString();
michael@0 3758 if (!retstr)
michael@0 3759 return false;
michael@0 3760
michael@0 3761 call.rval().setString(retstr);
michael@0 3762 return true;
michael@0 3763 }
michael@0 3764
michael@0 3765 static bool
michael@0 3766 tagify_value(JSContext *cx, CallArgs args, const char *begin, const char *end)
michael@0 3767 {
michael@0 3768 RootedLinearString param(cx, ArgToRootedString(cx, args, 0));
michael@0 3769 if (!param)
michael@0 3770 return false;
michael@0 3771
michael@0 3772 return tagify(cx, begin, param, end, args);
michael@0 3773 }
michael@0 3774
michael@0 3775 static bool
michael@0 3776 str_bold(JSContext *cx, unsigned argc, Value *vp)
michael@0 3777 {
michael@0 3778 return tagify(cx, "b", NullPtr(), nullptr, CallReceiverFromVp(vp));
michael@0 3779 }
michael@0 3780
michael@0 3781 static bool
michael@0 3782 str_italics(JSContext *cx, unsigned argc, Value *vp)
michael@0 3783 {
michael@0 3784 return tagify(cx, "i", NullPtr(), nullptr, CallReceiverFromVp(vp));
michael@0 3785 }
michael@0 3786
michael@0 3787 static bool
michael@0 3788 str_fixed(JSContext *cx, unsigned argc, Value *vp)
michael@0 3789 {
michael@0 3790 return tagify(cx, "tt", NullPtr(), nullptr, CallReceiverFromVp(vp));
michael@0 3791 }
michael@0 3792
michael@0 3793 static bool
michael@0 3794 str_fontsize(JSContext *cx, unsigned argc, Value *vp)
michael@0 3795 {
michael@0 3796 return tagify_value(cx, CallArgsFromVp(argc, vp), "font size", "font");
michael@0 3797 }
michael@0 3798
michael@0 3799 static bool
michael@0 3800 str_fontcolor(JSContext *cx, unsigned argc, Value *vp)
michael@0 3801 {
michael@0 3802 return tagify_value(cx, CallArgsFromVp(argc, vp), "font color", "font");
michael@0 3803 }
michael@0 3804
michael@0 3805 static bool
michael@0 3806 str_link(JSContext *cx, unsigned argc, Value *vp)
michael@0 3807 {
michael@0 3808 return tagify_value(cx, CallArgsFromVp(argc, vp), "a href", "a");
michael@0 3809 }
michael@0 3810
michael@0 3811 static bool
michael@0 3812 str_anchor(JSContext *cx, unsigned argc, Value *vp)
michael@0 3813 {
michael@0 3814 return tagify_value(cx, CallArgsFromVp(argc, vp), "a name", "a");
michael@0 3815 }
michael@0 3816
michael@0 3817 static bool
michael@0 3818 str_strike(JSContext *cx, unsigned argc, Value *vp)
michael@0 3819 {
michael@0 3820 return tagify(cx, "strike", NullPtr(), nullptr, CallReceiverFromVp(vp));
michael@0 3821 }
michael@0 3822
michael@0 3823 static bool
michael@0 3824 str_small(JSContext *cx, unsigned argc, Value *vp)
michael@0 3825 {
michael@0 3826 return tagify(cx, "small", NullPtr(), nullptr, CallReceiverFromVp(vp));
michael@0 3827 }
michael@0 3828
michael@0 3829 static bool
michael@0 3830 str_big(JSContext *cx, unsigned argc, Value *vp)
michael@0 3831 {
michael@0 3832 return tagify(cx, "big", NullPtr(), nullptr, CallReceiverFromVp(vp));
michael@0 3833 }
michael@0 3834
michael@0 3835 static bool
michael@0 3836 str_blink(JSContext *cx, unsigned argc, Value *vp)
michael@0 3837 {
michael@0 3838 return tagify(cx, "blink", NullPtr(), nullptr, CallReceiverFromVp(vp));
michael@0 3839 }
michael@0 3840
michael@0 3841 static bool
michael@0 3842 str_sup(JSContext *cx, unsigned argc, Value *vp)
michael@0 3843 {
michael@0 3844 return tagify(cx, "sup", NullPtr(), nullptr, CallReceiverFromVp(vp));
michael@0 3845 }
michael@0 3846
michael@0 3847 static bool
michael@0 3848 str_sub(JSContext *cx, unsigned argc, Value *vp)
michael@0 3849 {
michael@0 3850 return tagify(cx, "sub", NullPtr(), nullptr, CallReceiverFromVp(vp));
michael@0 3851 }
michael@0 3852 #endif /* JS_HAS_STR_HTML_HELPERS */
michael@0 3853
michael@0 3854 static const JSFunctionSpec string_methods[] = {
michael@0 3855 #if JS_HAS_TOSOURCE
michael@0 3856 JS_FN("quote", str_quote, 0,JSFUN_GENERIC_NATIVE),
michael@0 3857 JS_FN(js_toSource_str, str_toSource, 0,0),
michael@0 3858 #endif
michael@0 3859
michael@0 3860 /* Java-like methods. */
michael@0 3861 JS_FN(js_toString_str, js_str_toString, 0,0),
michael@0 3862 JS_FN(js_valueOf_str, js_str_toString, 0,0),
michael@0 3863 JS_FN("substring", str_substring, 2,JSFUN_GENERIC_NATIVE),
michael@0 3864 JS_FN("toLowerCase", str_toLowerCase, 0,JSFUN_GENERIC_NATIVE),
michael@0 3865 JS_FN("toUpperCase", str_toUpperCase, 0,JSFUN_GENERIC_NATIVE),
michael@0 3866 JS_FN("charAt", js_str_charAt, 1,JSFUN_GENERIC_NATIVE),
michael@0 3867 JS_FN("charCodeAt", js_str_charCodeAt, 1,JSFUN_GENERIC_NATIVE),
michael@0 3868 JS_SELF_HOSTED_FN("codePointAt", "String_codePointAt", 1,0),
michael@0 3869 JS_FN("contains", str_contains, 1,JSFUN_GENERIC_NATIVE),
michael@0 3870 JS_FN("indexOf", str_indexOf, 1,JSFUN_GENERIC_NATIVE),
michael@0 3871 JS_FN("lastIndexOf", str_lastIndexOf, 1,JSFUN_GENERIC_NATIVE),
michael@0 3872 JS_FN("startsWith", str_startsWith, 1,JSFUN_GENERIC_NATIVE),
michael@0 3873 JS_FN("endsWith", str_endsWith, 1,JSFUN_GENERIC_NATIVE),
michael@0 3874 JS_FN("trim", str_trim, 0,JSFUN_GENERIC_NATIVE),
michael@0 3875 JS_FN("trimLeft", str_trimLeft, 0,JSFUN_GENERIC_NATIVE),
michael@0 3876 JS_FN("trimRight", str_trimRight, 0,JSFUN_GENERIC_NATIVE),
michael@0 3877 JS_FN("toLocaleLowerCase", str_toLocaleLowerCase, 0,JSFUN_GENERIC_NATIVE),
michael@0 3878 JS_FN("toLocaleUpperCase", str_toLocaleUpperCase, 0,JSFUN_GENERIC_NATIVE),
michael@0 3879 #if EXPOSE_INTL_API
michael@0 3880 JS_SELF_HOSTED_FN("localeCompare", "String_localeCompare", 1,0),
michael@0 3881 #else
michael@0 3882 JS_FN("localeCompare", str_localeCompare, 1,JSFUN_GENERIC_NATIVE),
michael@0 3883 #endif
michael@0 3884 JS_SELF_HOSTED_FN("repeat", "String_repeat", 1,0),
michael@0 3885 #if EXPOSE_INTL_API
michael@0 3886 JS_FN("normalize", str_normalize, 0,JSFUN_GENERIC_NATIVE),
michael@0 3887 #endif
michael@0 3888
michael@0 3889 /* Perl-ish methods (search is actually Python-esque). */
michael@0 3890 JS_FN("match", str_match, 1,JSFUN_GENERIC_NATIVE),
michael@0 3891 JS_FN("search", str_search, 1,JSFUN_GENERIC_NATIVE),
michael@0 3892 JS_FN("replace", str_replace, 2,JSFUN_GENERIC_NATIVE),
michael@0 3893 JS_FN("split", str_split, 2,JSFUN_GENERIC_NATIVE),
michael@0 3894 JS_FN("substr", str_substr, 2,JSFUN_GENERIC_NATIVE),
michael@0 3895
michael@0 3896 /* Python-esque sequence methods. */
michael@0 3897 JS_FN("concat", str_concat, 1,JSFUN_GENERIC_NATIVE),
michael@0 3898 JS_FN("slice", str_slice, 2,JSFUN_GENERIC_NATIVE),
michael@0 3899
michael@0 3900 /* HTML string methods. */
michael@0 3901 #if JS_HAS_STR_HTML_HELPERS
michael@0 3902 JS_FN("bold", str_bold, 0,0),
michael@0 3903 JS_FN("italics", str_italics, 0,0),
michael@0 3904 JS_FN("fixed", str_fixed, 0,0),
michael@0 3905 JS_FN("fontsize", str_fontsize, 1,0),
michael@0 3906 JS_FN("fontcolor", str_fontcolor, 1,0),
michael@0 3907 JS_FN("link", str_link, 1,0),
michael@0 3908 JS_FN("anchor", str_anchor, 1,0),
michael@0 3909 JS_FN("strike", str_strike, 0,0),
michael@0 3910 JS_FN("small", str_small, 0,0),
michael@0 3911 JS_FN("big", str_big, 0,0),
michael@0 3912 JS_FN("blink", str_blink, 0,0),
michael@0 3913 JS_FN("sup", str_sup, 0,0),
michael@0 3914 JS_FN("sub", str_sub, 0,0),
michael@0 3915 #endif
michael@0 3916 JS_SELF_HOSTED_FN("@@iterator", "String_iterator", 0,0),
michael@0 3917 JS_FS_END
michael@0 3918 };
michael@0 3919
michael@0 3920 bool
michael@0 3921 js_String(JSContext *cx, unsigned argc, Value *vp)
michael@0 3922 {
michael@0 3923 CallArgs args = CallArgsFromVp(argc, vp);
michael@0 3924
michael@0 3925 RootedString str(cx);
michael@0 3926 if (args.length() > 0) {
michael@0 3927 str = ToString<CanGC>(cx, args[0]);
michael@0 3928 if (!str)
michael@0 3929 return false;
michael@0 3930 } else {
michael@0 3931 str = cx->runtime()->emptyString;
michael@0 3932 }
michael@0 3933
michael@0 3934 if (args.isConstructing()) {
michael@0 3935 StringObject *strobj = StringObject::create(cx, str);
michael@0 3936 if (!strobj)
michael@0 3937 return false;
michael@0 3938 args.rval().setObject(*strobj);
michael@0 3939 return true;
michael@0 3940 }
michael@0 3941
michael@0 3942 args.rval().setString(str);
michael@0 3943 return true;
michael@0 3944 }
michael@0 3945
michael@0 3946 bool
michael@0 3947 js::str_fromCharCode(JSContext *cx, unsigned argc, Value *vp)
michael@0 3948 {
michael@0 3949 CallArgs args = CallArgsFromVp(argc, vp);
michael@0 3950
michael@0 3951 JS_ASSERT(args.length() <= ARGS_LENGTH_MAX);
michael@0 3952 if (args.length() == 1) {
michael@0 3953 uint16_t code;
michael@0 3954 if (!ToUint16(cx, args[0], &code))
michael@0 3955 return false;
michael@0 3956 if (StaticStrings::hasUnit(code)) {
michael@0 3957 args.rval().setString(cx->staticStrings().getUnit(code));
michael@0 3958 return true;
michael@0 3959 }
michael@0 3960 args[0].setInt32(code);
michael@0 3961 }
michael@0 3962 jschar *chars = cx->pod_malloc<jschar>(args.length() + 1);
michael@0 3963 if (!chars)
michael@0 3964 return false;
michael@0 3965 for (unsigned i = 0; i < args.length(); i++) {
michael@0 3966 uint16_t code;
michael@0 3967 if (!ToUint16(cx, args[i], &code)) {
michael@0 3968 js_free(chars);
michael@0 3969 return false;
michael@0 3970 }
michael@0 3971 chars[i] = (jschar)code;
michael@0 3972 }
michael@0 3973 chars[args.length()] = 0;
michael@0 3974 JSString *str = js_NewString<CanGC>(cx, chars, args.length());
michael@0 3975 if (!str) {
michael@0 3976 js_free(chars);
michael@0 3977 return false;
michael@0 3978 }
michael@0 3979
michael@0 3980 args.rval().setString(str);
michael@0 3981 return true;
michael@0 3982 }
michael@0 3983
michael@0 3984 static const JSFunctionSpec string_static_methods[] = {
michael@0 3985 JS_FN("fromCharCode", js::str_fromCharCode, 1, 0),
michael@0 3986 JS_SELF_HOSTED_FN("fromCodePoint", "String_static_fromCodePoint", 0,0),
michael@0 3987
michael@0 3988 // This must be at the end because of bug 853075: functions listed after
michael@0 3989 // self-hosted methods aren't available in self-hosted code.
michael@0 3990 #if EXPOSE_INTL_API
michael@0 3991 JS_SELF_HOSTED_FN("localeCompare", "String_static_localeCompare", 2,0),
michael@0 3992 #endif
michael@0 3993 JS_FS_END
michael@0 3994 };
michael@0 3995
michael@0 3996 /* static */ Shape *
michael@0 3997 StringObject::assignInitialShape(ExclusiveContext *cx, Handle<StringObject*> obj)
michael@0 3998 {
michael@0 3999 JS_ASSERT(obj->nativeEmpty());
michael@0 4000
michael@0 4001 return obj->addDataProperty(cx, cx->names().length, LENGTH_SLOT,
michael@0 4002 JSPROP_PERMANENT | JSPROP_READONLY);
michael@0 4003 }
michael@0 4004
michael@0 4005 JSObject *
michael@0 4006 js_InitStringClass(JSContext *cx, HandleObject obj)
michael@0 4007 {
michael@0 4008 JS_ASSERT(obj->isNative());
michael@0 4009
michael@0 4010 Rooted<GlobalObject*> global(cx, &obj->as<GlobalObject>());
michael@0 4011
michael@0 4012 Rooted<JSString*> empty(cx, cx->runtime()->emptyString);
michael@0 4013 RootedObject proto(cx, global->createBlankPrototype(cx, &StringObject::class_));
michael@0 4014 if (!proto || !proto->as<StringObject>().init(cx, empty))
michael@0 4015 return nullptr;
michael@0 4016
michael@0 4017 /* Now create the String function. */
michael@0 4018 RootedFunction ctor(cx);
michael@0 4019 ctor = global->createConstructor(cx, js_String, cx->names().String, 1);
michael@0 4020 if (!ctor)
michael@0 4021 return nullptr;
michael@0 4022
michael@0 4023 if (!LinkConstructorAndPrototype(cx, ctor, proto))
michael@0 4024 return nullptr;
michael@0 4025
michael@0 4026 if (!DefinePropertiesAndBrand(cx, proto, nullptr, string_methods) ||
michael@0 4027 !DefinePropertiesAndBrand(cx, ctor, nullptr, string_static_methods))
michael@0 4028 {
michael@0 4029 return nullptr;
michael@0 4030 }
michael@0 4031
michael@0 4032 if (!GlobalObject::initBuiltinConstructor(cx, global, JSProto_String, ctor, proto))
michael@0 4033 return nullptr;
michael@0 4034
michael@0 4035 /*
michael@0 4036 * Define escape/unescape, the URI encode/decode functions, and maybe
michael@0 4037 * uneval on the global object.
michael@0 4038 */
michael@0 4039 if (!JS_DefineFunctions(cx, global, string_functions))
michael@0 4040 return nullptr;
michael@0 4041
michael@0 4042 return proto;
michael@0 4043 }
michael@0 4044
michael@0 4045 template <AllowGC allowGC>
michael@0 4046 JSFlatString *
michael@0 4047 js_NewString(ThreadSafeContext *cx, jschar *chars, size_t length)
michael@0 4048 {
michael@0 4049 if (length == 1) {
michael@0 4050 jschar c = chars[0];
michael@0 4051 if (StaticStrings::hasUnit(c)) {
michael@0 4052 // Free |chars| because we're taking possession of it, but it's no
michael@0 4053 // longer needed because we use the static string instead.
michael@0 4054 js_free(chars);
michael@0 4055 return cx->staticStrings().getUnit(c);
michael@0 4056 }
michael@0 4057 }
michael@0 4058
michael@0 4059 return JSFlatString::new_<allowGC>(cx, chars, length);
michael@0 4060 }
michael@0 4061
michael@0 4062 template JSFlatString *
michael@0 4063 js_NewString<CanGC>(ThreadSafeContext *cx, jschar *chars, size_t length);
michael@0 4064
michael@0 4065 template JSFlatString *
michael@0 4066 js_NewString<NoGC>(ThreadSafeContext *cx, jschar *chars, size_t length);
michael@0 4067
michael@0 4068 JSLinearString *
michael@0 4069 js_NewDependentString(JSContext *cx, JSString *baseArg, size_t start, size_t length)
michael@0 4070 {
michael@0 4071 if (length == 0)
michael@0 4072 return cx->emptyString();
michael@0 4073
michael@0 4074 JSLinearString *base = baseArg->ensureLinear(cx);
michael@0 4075 if (!base)
michael@0 4076 return nullptr;
michael@0 4077
michael@0 4078 if (start == 0 && length == base->length())
michael@0 4079 return base;
michael@0 4080
michael@0 4081 const jschar *chars = base->chars() + start;
michael@0 4082
michael@0 4083 if (JSLinearString *staticStr = cx->staticStrings().lookup(chars, length))
michael@0 4084 return staticStr;
michael@0 4085
michael@0 4086 return JSDependentString::new_(cx, base, chars, length);
michael@0 4087 }
michael@0 4088
michael@0 4089 template <AllowGC allowGC>
michael@0 4090 JSFlatString *
michael@0 4091 js_NewStringCopyN(ExclusiveContext *cx, const jschar *s, size_t n)
michael@0 4092 {
michael@0 4093 if (JSFatInlineString::lengthFits(n))
michael@0 4094 return NewFatInlineString<allowGC>(cx, TwoByteChars(s, n));
michael@0 4095
michael@0 4096 jschar *news = cx->pod_malloc<jschar>(n + 1);
michael@0 4097 if (!news)
michael@0 4098 return nullptr;
michael@0 4099 js_strncpy(news, s, n);
michael@0 4100 news[n] = 0;
michael@0 4101 JSFlatString *str = js_NewString<allowGC>(cx, news, n);
michael@0 4102 if (!str)
michael@0 4103 js_free(news);
michael@0 4104 return str;
michael@0 4105 }
michael@0 4106
michael@0 4107 template JSFlatString *
michael@0 4108 js_NewStringCopyN<CanGC>(ExclusiveContext *cx, const jschar *s, size_t n);
michael@0 4109
michael@0 4110 template JSFlatString *
michael@0 4111 js_NewStringCopyN<NoGC>(ExclusiveContext *cx, const jschar *s, size_t n);
michael@0 4112
michael@0 4113 template <AllowGC allowGC>
michael@0 4114 JSFlatString *
michael@0 4115 js_NewStringCopyN(ThreadSafeContext *cx, const char *s, size_t n)
michael@0 4116 {
michael@0 4117 if (JSFatInlineString::lengthFits(n))
michael@0 4118 return NewFatInlineString<allowGC>(cx, JS::Latin1Chars(s, n));
michael@0 4119
michael@0 4120 jschar *chars = InflateString(cx, s, &n);
michael@0 4121 if (!chars)
michael@0 4122 return nullptr;
michael@0 4123 JSFlatString *str = js_NewString<allowGC>(cx, chars, n);
michael@0 4124 if (!str)
michael@0 4125 js_free(chars);
michael@0 4126 return str;
michael@0 4127 }
michael@0 4128
michael@0 4129 template JSFlatString *
michael@0 4130 js_NewStringCopyN<CanGC>(ThreadSafeContext *cx, const char *s, size_t n);
michael@0 4131
michael@0 4132 template JSFlatString *
michael@0 4133 js_NewStringCopyN<NoGC>(ThreadSafeContext *cx, const char *s, size_t n);
michael@0 4134
michael@0 4135 template <AllowGC allowGC>
michael@0 4136 JSFlatString *
michael@0 4137 js_NewStringCopyZ(ExclusiveContext *cx, const jschar *s)
michael@0 4138 {
michael@0 4139 size_t n = js_strlen(s);
michael@0 4140 if (JSFatInlineString::lengthFits(n))
michael@0 4141 return NewFatInlineString<allowGC>(cx, TwoByteChars(s, n));
michael@0 4142
michael@0 4143 size_t m = (n + 1) * sizeof(jschar);
michael@0 4144 jschar *news = (jschar *) cx->malloc_(m);
michael@0 4145 if (!news)
michael@0 4146 return nullptr;
michael@0 4147 js_memcpy(news, s, m);
michael@0 4148 JSFlatString *str = js_NewString<allowGC>(cx, news, n);
michael@0 4149 if (!str)
michael@0 4150 js_free(news);
michael@0 4151 return str;
michael@0 4152 }
michael@0 4153
michael@0 4154 template JSFlatString *
michael@0 4155 js_NewStringCopyZ<CanGC>(ExclusiveContext *cx, const jschar *s);
michael@0 4156
michael@0 4157 template JSFlatString *
michael@0 4158 js_NewStringCopyZ<NoGC>(ExclusiveContext *cx, const jschar *s);
michael@0 4159
michael@0 4160 template <AllowGC allowGC>
michael@0 4161 JSFlatString *
michael@0 4162 js_NewStringCopyZ(ThreadSafeContext *cx, const char *s)
michael@0 4163 {
michael@0 4164 return js_NewStringCopyN<allowGC>(cx, s, strlen(s));
michael@0 4165 }
michael@0 4166
michael@0 4167 template JSFlatString *
michael@0 4168 js_NewStringCopyZ<CanGC>(ThreadSafeContext *cx, const char *s);
michael@0 4169
michael@0 4170 template JSFlatString *
michael@0 4171 js_NewStringCopyZ<NoGC>(ThreadSafeContext *cx, const char *s);
michael@0 4172
michael@0 4173 const char *
michael@0 4174 js_ValueToPrintable(JSContext *cx, const Value &vArg, JSAutoByteString *bytes, bool asSource)
michael@0 4175 {
michael@0 4176 RootedValue v(cx, vArg);
michael@0 4177 JSString *str;
michael@0 4178 if (asSource)
michael@0 4179 str = ValueToSource(cx, v);
michael@0 4180 else
michael@0 4181 str = ToString<CanGC>(cx, v);
michael@0 4182 if (!str)
michael@0 4183 return nullptr;
michael@0 4184 str = js_QuoteString(cx, str, 0);
michael@0 4185 if (!str)
michael@0 4186 return nullptr;
michael@0 4187 return bytes->encodeLatin1(cx, str);
michael@0 4188 }
michael@0 4189
michael@0 4190 template <AllowGC allowGC>
michael@0 4191 JSString *
michael@0 4192 js::ToStringSlow(ExclusiveContext *cx, typename MaybeRooted<Value, allowGC>::HandleType arg)
michael@0 4193 {
michael@0 4194 /* As with ToObjectSlow, callers must verify that |arg| isn't a string. */
michael@0 4195 JS_ASSERT(!arg.isString());
michael@0 4196
michael@0 4197 Value v = arg;
michael@0 4198 if (!v.isPrimitive()) {
michael@0 4199 if (!cx->shouldBeJSContext() || !allowGC)
michael@0 4200 return nullptr;
michael@0 4201 RootedValue v2(cx, v);
michael@0 4202 if (!ToPrimitive(cx->asJSContext(), JSTYPE_STRING, &v2))
michael@0 4203 return nullptr;
michael@0 4204 v = v2;
michael@0 4205 }
michael@0 4206
michael@0 4207 JSString *str;
michael@0 4208 if (v.isString()) {
michael@0 4209 str = v.toString();
michael@0 4210 } else if (v.isInt32()) {
michael@0 4211 str = Int32ToString<allowGC>(cx, v.toInt32());
michael@0 4212 } else if (v.isDouble()) {
michael@0 4213 str = NumberToString<allowGC>(cx, v.toDouble());
michael@0 4214 } else if (v.isBoolean()) {
michael@0 4215 str = js_BooleanToString(cx, v.toBoolean());
michael@0 4216 } else if (v.isNull()) {
michael@0 4217 str = cx->names().null;
michael@0 4218 } else {
michael@0 4219 str = cx->names().undefined;
michael@0 4220 }
michael@0 4221 return str;
michael@0 4222 }
michael@0 4223
michael@0 4224 template JSString *
michael@0 4225 js::ToStringSlow<CanGC>(ExclusiveContext *cx, HandleValue arg);
michael@0 4226
michael@0 4227 template JSString *
michael@0 4228 js::ToStringSlow<NoGC>(ExclusiveContext *cx, Value arg);
michael@0 4229
michael@0 4230 JS_PUBLIC_API(JSString *)
michael@0 4231 js::ToStringSlow(JSContext *cx, HandleValue v)
michael@0 4232 {
michael@0 4233 return ToStringSlow<CanGC>(cx, v);
michael@0 4234 }
michael@0 4235
michael@0 4236 JSString *
michael@0 4237 js::ValueToSource(JSContext *cx, HandleValue v)
michael@0 4238 {
michael@0 4239 JS_CHECK_RECURSION(cx, return nullptr);
michael@0 4240 assertSameCompartment(cx, v);
michael@0 4241
michael@0 4242 if (v.isUndefined())
michael@0 4243 return cx->names().void0;
michael@0 4244 if (v.isString())
michael@0 4245 return StringToSource(cx, v.toString());
michael@0 4246 if (v.isPrimitive()) {
michael@0 4247 /* Special case to preserve negative zero, _contra_ toString. */
michael@0 4248 if (v.isDouble() && IsNegativeZero(v.toDouble())) {
michael@0 4249 /* NB: _ucNstr rather than _ucstr to indicate non-terminated. */
michael@0 4250 static const jschar js_negzero_ucNstr[] = {'-', '0'};
michael@0 4251
michael@0 4252 return js_NewStringCopyN<CanGC>(cx, js_negzero_ucNstr, 2);
michael@0 4253 }
michael@0 4254 return ToString<CanGC>(cx, v);
michael@0 4255 }
michael@0 4256
michael@0 4257 RootedValue fval(cx);
michael@0 4258 RootedObject obj(cx, &v.toObject());
michael@0 4259 if (!JSObject::getProperty(cx, obj, obj, cx->names().toSource, &fval))
michael@0 4260 return nullptr;
michael@0 4261 if (js_IsCallable(fval)) {
michael@0 4262 RootedValue rval(cx);
michael@0 4263 if (!Invoke(cx, ObjectValue(*obj), fval, 0, nullptr, &rval))
michael@0 4264 return nullptr;
michael@0 4265 return ToString<CanGC>(cx, rval);
michael@0 4266 }
michael@0 4267
michael@0 4268 return ObjectToSource(cx, obj);
michael@0 4269 }
michael@0 4270
michael@0 4271 JSString *
michael@0 4272 js::StringToSource(JSContext *cx, JSString *str)
michael@0 4273 {
michael@0 4274 return js_QuoteString(cx, str, '"');
michael@0 4275 }
michael@0 4276
michael@0 4277 bool
michael@0 4278 js::EqualStrings(JSContext *cx, JSString *str1, JSString *str2, bool *result)
michael@0 4279 {
michael@0 4280 if (str1 == str2) {
michael@0 4281 *result = true;
michael@0 4282 return true;
michael@0 4283 }
michael@0 4284
michael@0 4285 size_t length1 = str1->length();
michael@0 4286 if (length1 != str2->length()) {
michael@0 4287 *result = false;
michael@0 4288 return true;
michael@0 4289 }
michael@0 4290
michael@0 4291 JSLinearString *linear1 = str1->ensureLinear(cx);
michael@0 4292 if (!linear1)
michael@0 4293 return false;
michael@0 4294 JSLinearString *linear2 = str2->ensureLinear(cx);
michael@0 4295 if (!linear2)
michael@0 4296 return false;
michael@0 4297
michael@0 4298 *result = PodEqual(linear1->chars(), linear2->chars(), length1);
michael@0 4299 return true;
michael@0 4300 }
michael@0 4301
michael@0 4302 bool
michael@0 4303 js::EqualStrings(JSLinearString *str1, JSLinearString *str2)
michael@0 4304 {
michael@0 4305 if (str1 == str2)
michael@0 4306 return true;
michael@0 4307
michael@0 4308 size_t length1 = str1->length();
michael@0 4309 if (length1 != str2->length())
michael@0 4310 return false;
michael@0 4311
michael@0 4312 return PodEqual(str1->chars(), str2->chars(), length1);
michael@0 4313 }
michael@0 4314
michael@0 4315 static bool
michael@0 4316 CompareStringsImpl(JSContext *cx, JSString *str1, JSString *str2, int32_t *result)
michael@0 4317 {
michael@0 4318 JS_ASSERT(str1);
michael@0 4319 JS_ASSERT(str2);
michael@0 4320
michael@0 4321 if (str1 == str2) {
michael@0 4322 *result = 0;
michael@0 4323 return true;
michael@0 4324 }
michael@0 4325
michael@0 4326 const jschar *s1 = str1->getChars(cx);
michael@0 4327 if (!s1)
michael@0 4328 return false;
michael@0 4329
michael@0 4330 const jschar *s2 = str2->getChars(cx);
michael@0 4331 if (!s2)
michael@0 4332 return false;
michael@0 4333
michael@0 4334 *result = CompareChars(s1, str1->length(), s2, str2->length());
michael@0 4335 return true;
michael@0 4336 }
michael@0 4337
michael@0 4338 bool
michael@0 4339 js::CompareStrings(JSContext *cx, JSString *str1, JSString *str2, int32_t *result)
michael@0 4340 {
michael@0 4341 return CompareStringsImpl(cx, str1, str2, result);
michael@0 4342 }
michael@0 4343
michael@0 4344 int32_t
michael@0 4345 js::CompareAtoms(JSAtom *atom1, JSAtom *atom2)
michael@0 4346 {
michael@0 4347 return CompareChars(atom1->chars(), atom1->length(), atom2->chars(), atom2->length());
michael@0 4348 }
michael@0 4349
michael@0 4350 bool
michael@0 4351 js::StringEqualsAscii(JSLinearString *str, const char *asciiBytes)
michael@0 4352 {
michael@0 4353 size_t length = strlen(asciiBytes);
michael@0 4354 #ifdef DEBUG
michael@0 4355 for (size_t i = 0; i != length; ++i)
michael@0 4356 JS_ASSERT(unsigned(asciiBytes[i]) <= 127);
michael@0 4357 #endif
michael@0 4358 if (length != str->length())
michael@0 4359 return false;
michael@0 4360 const jschar *chars = str->chars();
michael@0 4361 for (size_t i = 0; i != length; ++i) {
michael@0 4362 if (unsigned(asciiBytes[i]) != unsigned(chars[i]))
michael@0 4363 return false;
michael@0 4364 }
michael@0 4365 return true;
michael@0 4366 }
michael@0 4367
michael@0 4368 size_t
michael@0 4369 js_strlen(const jschar *s)
michael@0 4370 {
michael@0 4371 const jschar *t;
michael@0 4372
michael@0 4373 for (t = s; *t != 0; t++)
michael@0 4374 continue;
michael@0 4375 return (size_t)(t - s);
michael@0 4376 }
michael@0 4377
michael@0 4378 int32_t
michael@0 4379 js_strcmp(const jschar *lhs, const jschar *rhs)
michael@0 4380 {
michael@0 4381 while (true) {
michael@0 4382 if (*lhs != *rhs)
michael@0 4383 return int32_t(*lhs) - int32_t(*rhs);
michael@0 4384 if (*lhs == 0)
michael@0 4385 return 0;
michael@0 4386 ++lhs, ++rhs;
michael@0 4387 }
michael@0 4388 }
michael@0 4389
michael@0 4390 jschar *
michael@0 4391 js_strdup(js::ThreadSafeContext *cx, const jschar *s)
michael@0 4392 {
michael@0 4393 size_t n = js_strlen(s);
michael@0 4394 jschar *ret = cx->pod_malloc<jschar>(n + 1);
michael@0 4395 if (!ret)
michael@0 4396 return nullptr;
michael@0 4397 js_strncpy(ret, s, n);
michael@0 4398 ret[n] = '\0';
michael@0 4399 return ret;
michael@0 4400 }
michael@0 4401
michael@0 4402 jschar *
michael@0 4403 js_strchr_limit(const jschar *s, jschar c, const jschar *limit)
michael@0 4404 {
michael@0 4405 while (s < limit) {
michael@0 4406 if (*s == c)
michael@0 4407 return (jschar *)s;
michael@0 4408 s++;
michael@0 4409 }
michael@0 4410 return nullptr;
michael@0 4411 }
michael@0 4412
michael@0 4413 jschar *
michael@0 4414 js::InflateString(ThreadSafeContext *cx, const char *bytes, size_t *lengthp)
michael@0 4415 {
michael@0 4416 size_t nchars;
michael@0 4417 jschar *chars;
michael@0 4418 size_t nbytes = *lengthp;
michael@0 4419
michael@0 4420 nchars = nbytes;
michael@0 4421 chars = cx->pod_malloc<jschar>(nchars + 1);
michael@0 4422 if (!chars)
michael@0 4423 goto bad;
michael@0 4424 for (size_t i = 0; i < nchars; i++)
michael@0 4425 chars[i] = (unsigned char) bytes[i];
michael@0 4426 *lengthp = nchars;
michael@0 4427 chars[nchars] = 0;
michael@0 4428 return chars;
michael@0 4429
michael@0 4430 bad:
michael@0 4431 // For compatibility with callers of JS_DecodeBytes we must zero lengthp
michael@0 4432 // on errors.
michael@0 4433 *lengthp = 0;
michael@0 4434 return nullptr;
michael@0 4435 }
michael@0 4436
michael@0 4437 bool
michael@0 4438 js::DeflateStringToBuffer(JSContext *maybecx, const jschar *src, size_t srclen,
michael@0 4439 char *dst, size_t *dstlenp)
michael@0 4440 {
michael@0 4441 size_t dstlen = *dstlenp;
michael@0 4442 if (srclen > dstlen) {
michael@0 4443 for (size_t i = 0; i < dstlen; i++)
michael@0 4444 dst[i] = (char) src[i];
michael@0 4445 if (maybecx) {
michael@0 4446 AutoSuppressGC suppress(maybecx);
michael@0 4447 JS_ReportErrorNumber(maybecx, js_GetErrorMessage, nullptr,
michael@0 4448 JSMSG_BUFFER_TOO_SMALL);
michael@0 4449 }
michael@0 4450 return false;
michael@0 4451 }
michael@0 4452 for (size_t i = 0; i < srclen; i++)
michael@0 4453 dst[i] = (char) src[i];
michael@0 4454 *dstlenp = srclen;
michael@0 4455 return true;
michael@0 4456 }
michael@0 4457
michael@0 4458 #define ____ false
michael@0 4459
michael@0 4460 /*
michael@0 4461 * Identifier start chars:
michael@0 4462 * - 36: $
michael@0 4463 * - 65..90: A..Z
michael@0 4464 * - 95: _
michael@0 4465 * - 97..122: a..z
michael@0 4466 */
michael@0 4467 const bool js_isidstart[] = {
michael@0 4468 /* 0 1 2 3 4 5 6 7 8 9 */
michael@0 4469 /* 0 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
michael@0 4470 /* 1 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
michael@0 4471 /* 2 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
michael@0 4472 /* 3 */ ____, ____, ____, ____, ____, ____, true, ____, ____, ____,
michael@0 4473 /* 4 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
michael@0 4474 /* 5 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
michael@0 4475 /* 6 */ ____, ____, ____, ____, ____, true, true, true, true, true,
michael@0 4476 /* 7 */ true, true, true, true, true, true, true, true, true, true,
michael@0 4477 /* 8 */ true, true, true, true, true, true, true, true, true, true,
michael@0 4478 /* 9 */ true, ____, ____, ____, ____, true, ____, true, true, true,
michael@0 4479 /* 10 */ true, true, true, true, true, true, true, true, true, true,
michael@0 4480 /* 11 */ true, true, true, true, true, true, true, true, true, true,
michael@0 4481 /* 12 */ true, true, true, ____, ____, ____, ____, ____
michael@0 4482 };
michael@0 4483
michael@0 4484 /*
michael@0 4485 * Identifier chars:
michael@0 4486 * - 36: $
michael@0 4487 * - 48..57: 0..9
michael@0 4488 * - 65..90: A..Z
michael@0 4489 * - 95: _
michael@0 4490 * - 97..122: a..z
michael@0 4491 */
michael@0 4492 const bool js_isident[] = {
michael@0 4493 /* 0 1 2 3 4 5 6 7 8 9 */
michael@0 4494 /* 0 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
michael@0 4495 /* 1 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
michael@0 4496 /* 2 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
michael@0 4497 /* 3 */ ____, ____, ____, ____, ____, ____, true, ____, ____, ____,
michael@0 4498 /* 4 */ ____, ____, ____, ____, ____, ____, ____, ____, true, true,
michael@0 4499 /* 5 */ true, true, true, true, true, true, true, true, ____, ____,
michael@0 4500 /* 6 */ ____, ____, ____, ____, ____, true, true, true, true, true,
michael@0 4501 /* 7 */ true, true, true, true, true, true, true, true, true, true,
michael@0 4502 /* 8 */ true, true, true, true, true, true, true, true, true, true,
michael@0 4503 /* 9 */ true, ____, ____, ____, ____, true, ____, true, true, true,
michael@0 4504 /* 10 */ true, true, true, true, true, true, true, true, true, true,
michael@0 4505 /* 11 */ true, true, true, true, true, true, true, true, true, true,
michael@0 4506 /* 12 */ true, true, true, ____, ____, ____, ____, ____
michael@0 4507 };
michael@0 4508
michael@0 4509 /* Whitespace chars: '\t', '\n', '\v', '\f', '\r', ' '. */
michael@0 4510 const bool js_isspace[] = {
michael@0 4511 /* 0 1 2 3 4 5 6 7 8 9 */
michael@0 4512 /* 0 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, true,
michael@0 4513 /* 1 */ true, true, true, true, ____, ____, ____, ____, ____, ____,
michael@0 4514 /* 2 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
michael@0 4515 /* 3 */ ____, ____, true, ____, ____, ____, ____, ____, ____, ____,
michael@0 4516 /* 4 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
michael@0 4517 /* 5 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
michael@0 4518 /* 6 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
michael@0 4519 /* 7 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
michael@0 4520 /* 8 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
michael@0 4521 /* 9 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
michael@0 4522 /* 10 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
michael@0 4523 /* 11 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
michael@0 4524 /* 12 */ ____, ____, ____, ____, ____, ____, ____, ____
michael@0 4525 };
michael@0 4526
michael@0 4527 /*
michael@0 4528 * Uri reserved chars + #:
michael@0 4529 * - 35: #
michael@0 4530 * - 36: $
michael@0 4531 * - 38: &
michael@0 4532 * - 43: +
michael@0 4533 * - 44: ,
michael@0 4534 * - 47: /
michael@0 4535 * - 58: :
michael@0 4536 * - 59: ;
michael@0 4537 * - 61: =
michael@0 4538 * - 63: ?
michael@0 4539 * - 64: @
michael@0 4540 */
michael@0 4541 static const bool js_isUriReservedPlusPound[] = {
michael@0 4542 /* 0 1 2 3 4 5 6 7 8 9 */
michael@0 4543 /* 0 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
michael@0 4544 /* 1 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
michael@0 4545 /* 2 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
michael@0 4546 /* 3 */ ____, ____, ____, ____, ____, true, true, ____, true, ____,
michael@0 4547 /* 4 */ ____, ____, ____, true, true, ____, ____, true, ____, ____,
michael@0 4548 /* 5 */ ____, ____, ____, ____, ____, ____, ____, ____, true, true,
michael@0 4549 /* 6 */ ____, true, ____, true, true, ____, ____, ____, ____, ____,
michael@0 4550 /* 7 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
michael@0 4551 /* 8 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
michael@0 4552 /* 9 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
michael@0 4553 /* 10 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
michael@0 4554 /* 11 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
michael@0 4555 /* 12 */ ____, ____, ____, ____, ____, ____, ____, ____
michael@0 4556 };
michael@0 4557
michael@0 4558 /*
michael@0 4559 * Uri unescaped chars:
michael@0 4560 * - 33: !
michael@0 4561 * - 39: '
michael@0 4562 * - 40: (
michael@0 4563 * - 41: )
michael@0 4564 * - 42: *
michael@0 4565 * - 45: -
michael@0 4566 * - 46: .
michael@0 4567 * - 48..57: 0-9
michael@0 4568 * - 65..90: A-Z
michael@0 4569 * - 95: _
michael@0 4570 * - 97..122: a-z
michael@0 4571 * - 126: ~
michael@0 4572 */
michael@0 4573 static const bool js_isUriUnescaped[] = {
michael@0 4574 /* 0 1 2 3 4 5 6 7 8 9 */
michael@0 4575 /* 0 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
michael@0 4576 /* 1 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
michael@0 4577 /* 2 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
michael@0 4578 /* 3 */ ____, ____, ____, true, ____, ____, ____, ____, ____, true,
michael@0 4579 /* 4 */ true, true, true, ____, ____, true, true, ____, true, true,
michael@0 4580 /* 5 */ true, true, true, true, true, true, true, true, ____, ____,
michael@0 4581 /* 6 */ ____, ____, ____, ____, ____, true, true, true, true, true,
michael@0 4582 /* 7 */ true, true, true, true, true, true, true, true, true, true,
michael@0 4583 /* 8 */ true, true, true, true, true, true, true, true, true, true,
michael@0 4584 /* 9 */ true, ____, ____, ____, ____, true, ____, true, true, true,
michael@0 4585 /* 10 */ true, true, true, true, true, true, true, true, true, true,
michael@0 4586 /* 11 */ true, true, true, true, true, true, true, true, true, true,
michael@0 4587 /* 12 */ true, true, true, ____, ____, ____, true, ____
michael@0 4588 };
michael@0 4589
michael@0 4590 #undef ____
michael@0 4591
michael@0 4592 #define URI_CHUNK 64U
michael@0 4593
michael@0 4594 static inline bool
michael@0 4595 TransferBufferToString(StringBuffer &sb, MutableHandleValue rval)
michael@0 4596 {
michael@0 4597 JSString *str = sb.finishString();
michael@0 4598 if (!str)
michael@0 4599 return false;
michael@0 4600 rval.setString(str);
michael@0 4601 return true;
michael@0 4602 }
michael@0 4603
michael@0 4604 /*
michael@0 4605 * ECMA 3, 15.1.3 URI Handling Function Properties
michael@0 4606 *
michael@0 4607 * The following are implementations of the algorithms
michael@0 4608 * given in the ECMA specification for the hidden functions
michael@0 4609 * 'Encode' and 'Decode'.
michael@0 4610 */
michael@0 4611 static bool
michael@0 4612 Encode(JSContext *cx, Handle<JSLinearString*> str, const bool *unescapedSet,
michael@0 4613 const bool *unescapedSet2, MutableHandleValue rval)
michael@0 4614 {
michael@0 4615 static const char HexDigits[] = "0123456789ABCDEF"; /* NB: uppercase */
michael@0 4616
michael@0 4617 size_t length = str->length();
michael@0 4618 if (length == 0) {
michael@0 4619 rval.setString(cx->runtime()->emptyString);
michael@0 4620 return true;
michael@0 4621 }
michael@0 4622
michael@0 4623 const jschar *chars = str->chars();
michael@0 4624 StringBuffer sb(cx);
michael@0 4625 if (!sb.reserve(length))
michael@0 4626 return false;
michael@0 4627 jschar hexBuf[4];
michael@0 4628 hexBuf[0] = '%';
michael@0 4629 hexBuf[3] = 0;
michael@0 4630 for (size_t k = 0; k < length; k++) {
michael@0 4631 jschar c = chars[k];
michael@0 4632 if (c < 128 && (unescapedSet[c] || (unescapedSet2 && unescapedSet2[c]))) {
michael@0 4633 if (!sb.append(c))
michael@0 4634 return false;
michael@0 4635 } else {
michael@0 4636 if ((c >= 0xDC00) && (c <= 0xDFFF)) {
michael@0 4637 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_BAD_URI, nullptr);
michael@0 4638 return false;
michael@0 4639 }
michael@0 4640 uint32_t v;
michael@0 4641 if (c < 0xD800 || c > 0xDBFF) {
michael@0 4642 v = c;
michael@0 4643 } else {
michael@0 4644 k++;
michael@0 4645 if (k == length) {
michael@0 4646 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr,
michael@0 4647 JSMSG_BAD_URI, nullptr);
michael@0 4648 return false;
michael@0 4649 }
michael@0 4650 jschar c2 = chars[k];
michael@0 4651 if ((c2 < 0xDC00) || (c2 > 0xDFFF)) {
michael@0 4652 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr,
michael@0 4653 JSMSG_BAD_URI, nullptr);
michael@0 4654 return false;
michael@0 4655 }
michael@0 4656 v = ((c - 0xD800) << 10) + (c2 - 0xDC00) + 0x10000;
michael@0 4657 }
michael@0 4658 uint8_t utf8buf[4];
michael@0 4659 size_t L = js_OneUcs4ToUtf8Char(utf8buf, v);
michael@0 4660 for (size_t j = 0; j < L; j++) {
michael@0 4661 hexBuf[1] = HexDigits[utf8buf[j] >> 4];
michael@0 4662 hexBuf[2] = HexDigits[utf8buf[j] & 0xf];
michael@0 4663 if (!sb.append(hexBuf, 3))
michael@0 4664 return false;
michael@0 4665 }
michael@0 4666 }
michael@0 4667 }
michael@0 4668
michael@0 4669 return TransferBufferToString(sb, rval);
michael@0 4670 }
michael@0 4671
michael@0 4672 static bool
michael@0 4673 Decode(JSContext *cx, Handle<JSLinearString*> str, const bool *reservedSet, MutableHandleValue rval)
michael@0 4674 {
michael@0 4675 size_t length = str->length();
michael@0 4676 if (length == 0) {
michael@0 4677 rval.setString(cx->runtime()->emptyString);
michael@0 4678 return true;
michael@0 4679 }
michael@0 4680
michael@0 4681 const jschar *chars = str->chars();
michael@0 4682 StringBuffer sb(cx);
michael@0 4683 for (size_t k = 0; k < length; k++) {
michael@0 4684 jschar c = chars[k];
michael@0 4685 if (c == '%') {
michael@0 4686 size_t start = k;
michael@0 4687 if ((k + 2) >= length)
michael@0 4688 goto report_bad_uri;
michael@0 4689 if (!JS7_ISHEX(chars[k+1]) || !JS7_ISHEX(chars[k+2]))
michael@0 4690 goto report_bad_uri;
michael@0 4691 uint32_t B = JS7_UNHEX(chars[k+1]) * 16 + JS7_UNHEX(chars[k+2]);
michael@0 4692 k += 2;
michael@0 4693 if (!(B & 0x80)) {
michael@0 4694 c = (jschar)B;
michael@0 4695 } else {
michael@0 4696 int n = 1;
michael@0 4697 while (B & (0x80 >> n))
michael@0 4698 n++;
michael@0 4699 if (n == 1 || n > 4)
michael@0 4700 goto report_bad_uri;
michael@0 4701 uint8_t octets[4];
michael@0 4702 octets[0] = (uint8_t)B;
michael@0 4703 if (k + 3 * (n - 1) >= length)
michael@0 4704 goto report_bad_uri;
michael@0 4705 for (int j = 1; j < n; j++) {
michael@0 4706 k++;
michael@0 4707 if (chars[k] != '%')
michael@0 4708 goto report_bad_uri;
michael@0 4709 if (!JS7_ISHEX(chars[k+1]) || !JS7_ISHEX(chars[k+2]))
michael@0 4710 goto report_bad_uri;
michael@0 4711 B = JS7_UNHEX(chars[k+1]) * 16 + JS7_UNHEX(chars[k+2]);
michael@0 4712 if ((B & 0xC0) != 0x80)
michael@0 4713 goto report_bad_uri;
michael@0 4714 k += 2;
michael@0 4715 octets[j] = (char)B;
michael@0 4716 }
michael@0 4717 uint32_t v = JS::Utf8ToOneUcs4Char(octets, n);
michael@0 4718 if (v >= 0x10000) {
michael@0 4719 v -= 0x10000;
michael@0 4720 if (v > 0xFFFFF)
michael@0 4721 goto report_bad_uri;
michael@0 4722 c = (jschar)((v & 0x3FF) + 0xDC00);
michael@0 4723 jschar H = (jschar)((v >> 10) + 0xD800);
michael@0 4724 if (!sb.append(H))
michael@0 4725 return false;
michael@0 4726 } else {
michael@0 4727 c = (jschar)v;
michael@0 4728 }
michael@0 4729 }
michael@0 4730 if (c < 128 && reservedSet && reservedSet[c]) {
michael@0 4731 if (!sb.append(chars + start, k - start + 1))
michael@0 4732 return false;
michael@0 4733 } else {
michael@0 4734 if (!sb.append(c))
michael@0 4735 return false;
michael@0 4736 }
michael@0 4737 } else {
michael@0 4738 if (!sb.append(c))
michael@0 4739 return false;
michael@0 4740 }
michael@0 4741 }
michael@0 4742
michael@0 4743 return TransferBufferToString(sb, rval);
michael@0 4744
michael@0 4745 report_bad_uri:
michael@0 4746 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_BAD_URI);
michael@0 4747 /* FALL THROUGH */
michael@0 4748
michael@0 4749 return false;
michael@0 4750 }
michael@0 4751
michael@0 4752 static bool
michael@0 4753 str_decodeURI(JSContext *cx, unsigned argc, Value *vp)
michael@0 4754 {
michael@0 4755 CallArgs args = CallArgsFromVp(argc, vp);
michael@0 4756 Rooted<JSLinearString*> str(cx, ArgToRootedString(cx, args, 0));
michael@0 4757 if (!str)
michael@0 4758 return false;
michael@0 4759
michael@0 4760 return Decode(cx, str, js_isUriReservedPlusPound, args.rval());
michael@0 4761 }
michael@0 4762
michael@0 4763 static bool
michael@0 4764 str_decodeURI_Component(JSContext *cx, unsigned argc, Value *vp)
michael@0 4765 {
michael@0 4766 CallArgs args = CallArgsFromVp(argc, vp);
michael@0 4767 Rooted<JSLinearString*> str(cx, ArgToRootedString(cx, args, 0));
michael@0 4768 if (!str)
michael@0 4769 return false;
michael@0 4770
michael@0 4771 return Decode(cx, str, nullptr, args.rval());
michael@0 4772 }
michael@0 4773
michael@0 4774 static bool
michael@0 4775 str_encodeURI(JSContext *cx, unsigned argc, Value *vp)
michael@0 4776 {
michael@0 4777 CallArgs args = CallArgsFromVp(argc, vp);
michael@0 4778 Rooted<JSLinearString*> str(cx, ArgToRootedString(cx, args, 0));
michael@0 4779 if (!str)
michael@0 4780 return false;
michael@0 4781
michael@0 4782 return Encode(cx, str, js_isUriUnescaped, js_isUriReservedPlusPound, args.rval());
michael@0 4783 }
michael@0 4784
michael@0 4785 static bool
michael@0 4786 str_encodeURI_Component(JSContext *cx, unsigned argc, Value *vp)
michael@0 4787 {
michael@0 4788 CallArgs args = CallArgsFromVp(argc, vp);
michael@0 4789 Rooted<JSLinearString*> str(cx, ArgToRootedString(cx, args, 0));
michael@0 4790 if (!str)
michael@0 4791 return false;
michael@0 4792
michael@0 4793 return Encode(cx, str, js_isUriUnescaped, nullptr, args.rval());
michael@0 4794 }
michael@0 4795
michael@0 4796 /*
michael@0 4797 * Convert one UCS-4 char and write it into a UTF-8 buffer, which must be at
michael@0 4798 * least 4 bytes long. Return the number of UTF-8 bytes of data written.
michael@0 4799 */
michael@0 4800 int
michael@0 4801 js_OneUcs4ToUtf8Char(uint8_t *utf8Buffer, uint32_t ucs4Char)
michael@0 4802 {
michael@0 4803 int utf8Length = 1;
michael@0 4804
michael@0 4805 JS_ASSERT(ucs4Char <= 0x10FFFF);
michael@0 4806 if (ucs4Char < 0x80) {
michael@0 4807 *utf8Buffer = (uint8_t)ucs4Char;
michael@0 4808 } else {
michael@0 4809 int i;
michael@0 4810 uint32_t a = ucs4Char >> 11;
michael@0 4811 utf8Length = 2;
michael@0 4812 while (a) {
michael@0 4813 a >>= 5;
michael@0 4814 utf8Length++;
michael@0 4815 }
michael@0 4816 i = utf8Length;
michael@0 4817 while (--i) {
michael@0 4818 utf8Buffer[i] = (uint8_t)((ucs4Char & 0x3F) | 0x80);
michael@0 4819 ucs4Char >>= 6;
michael@0 4820 }
michael@0 4821 *utf8Buffer = (uint8_t)(0x100 - (1 << (8-utf8Length)) + ucs4Char);
michael@0 4822 }
michael@0 4823 return utf8Length;
michael@0 4824 }
michael@0 4825
michael@0 4826 size_t
michael@0 4827 js::PutEscapedStringImpl(char *buffer, size_t bufferSize, FILE *fp, JSLinearString *str,
michael@0 4828 uint32_t quote)
michael@0 4829 {
michael@0 4830 return PutEscapedStringImpl(buffer, bufferSize, fp, str->chars(),
michael@0 4831 str->length(), quote);
michael@0 4832 }
michael@0 4833
michael@0 4834 size_t
michael@0 4835 js::PutEscapedStringImpl(char *buffer, size_t bufferSize, FILE *fp, const jschar *chars,
michael@0 4836 size_t length, uint32_t quote)
michael@0 4837 {
michael@0 4838 enum {
michael@0 4839 STOP, FIRST_QUOTE, LAST_QUOTE, CHARS, ESCAPE_START, ESCAPE_MORE
michael@0 4840 } state;
michael@0 4841
michael@0 4842 JS_ASSERT(quote == 0 || quote == '\'' || quote == '"');
michael@0 4843 JS_ASSERT_IF(!buffer, bufferSize == 0);
michael@0 4844 JS_ASSERT_IF(fp, !buffer);
michael@0 4845
michael@0 4846 if (bufferSize == 0)
michael@0 4847 buffer = nullptr;
michael@0 4848 else
michael@0 4849 bufferSize--;
michael@0 4850
michael@0 4851 const jschar *charsEnd = chars + length;
michael@0 4852 size_t n = 0;
michael@0 4853 state = FIRST_QUOTE;
michael@0 4854 unsigned shift = 0;
michael@0 4855 unsigned hex = 0;
michael@0 4856 unsigned u = 0;
michael@0 4857 char c = 0; /* to quell GCC warnings */
michael@0 4858
michael@0 4859 for (;;) {
michael@0 4860 switch (state) {
michael@0 4861 case STOP:
michael@0 4862 goto stop;
michael@0 4863 case FIRST_QUOTE:
michael@0 4864 state = CHARS;
michael@0 4865 goto do_quote;
michael@0 4866 case LAST_QUOTE:
michael@0 4867 state = STOP;
michael@0 4868 do_quote:
michael@0 4869 if (quote == 0)
michael@0 4870 continue;
michael@0 4871 c = (char)quote;
michael@0 4872 break;
michael@0 4873 case CHARS:
michael@0 4874 if (chars == charsEnd) {
michael@0 4875 state = LAST_QUOTE;
michael@0 4876 continue;
michael@0 4877 }
michael@0 4878 u = *chars++;
michael@0 4879 if (u < ' ') {
michael@0 4880 if (u != 0) {
michael@0 4881 const char *escape = strchr(js_EscapeMap, (int)u);
michael@0 4882 if (escape) {
michael@0 4883 u = escape[1];
michael@0 4884 goto do_escape;
michael@0 4885 }
michael@0 4886 }
michael@0 4887 goto do_hex_escape;
michael@0 4888 }
michael@0 4889 if (u < 127) {
michael@0 4890 if (u == quote || u == '\\')
michael@0 4891 goto do_escape;
michael@0 4892 c = (char)u;
michael@0 4893 } else if (u < 0x100) {
michael@0 4894 goto do_hex_escape;
michael@0 4895 } else {
michael@0 4896 shift = 16;
michael@0 4897 hex = u;
michael@0 4898 u = 'u';
michael@0 4899 goto do_escape;
michael@0 4900 }
michael@0 4901 break;
michael@0 4902 do_hex_escape:
michael@0 4903 shift = 8;
michael@0 4904 hex = u;
michael@0 4905 u = 'x';
michael@0 4906 do_escape:
michael@0 4907 c = '\\';
michael@0 4908 state = ESCAPE_START;
michael@0 4909 break;
michael@0 4910 case ESCAPE_START:
michael@0 4911 JS_ASSERT(' ' <= u && u < 127);
michael@0 4912 c = (char)u;
michael@0 4913 state = ESCAPE_MORE;
michael@0 4914 break;
michael@0 4915 case ESCAPE_MORE:
michael@0 4916 if (shift == 0) {
michael@0 4917 state = CHARS;
michael@0 4918 continue;
michael@0 4919 }
michael@0 4920 shift -= 4;
michael@0 4921 u = 0xF & (hex >> shift);
michael@0 4922 c = (char)(u + (u < 10 ? '0' : 'A' - 10));
michael@0 4923 break;
michael@0 4924 }
michael@0 4925 if (buffer) {
michael@0 4926 JS_ASSERT(n <= bufferSize);
michael@0 4927 if (n != bufferSize) {
michael@0 4928 buffer[n] = c;
michael@0 4929 } else {
michael@0 4930 buffer[n] = '\0';
michael@0 4931 buffer = nullptr;
michael@0 4932 }
michael@0 4933 } else if (fp) {
michael@0 4934 if (fputc(c, fp) < 0)
michael@0 4935 return size_t(-1);
michael@0 4936 }
michael@0 4937 n++;
michael@0 4938 }
michael@0 4939 stop:
michael@0 4940 if (buffer)
michael@0 4941 buffer[n] = '\0';
michael@0 4942 return n;
michael@0 4943 }

mercurial