Skip to content
This repository was archived by the owner on Jul 9, 2025. It is now read-only.

Commit b05c79c

Browse files
committed
Bug 1965844 - Part 2: Add ToClampedStringIndex for String built-ins. r=jandem
Add `ToClampedStringIndex` to avoid duplicate code to clamp string indices. Drive-by change: - Update spec references. Differential Revision: https://phabricator.services.mozilla.com/D248927
1 parent 47a87ae commit b05c79c

File tree

1 file changed

+64
-85
lines changed

1 file changed

+64
-85
lines changed

js/src/builtin/String.cpp

Lines changed: 64 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -1731,6 +1731,28 @@ static bool str_toWellFormed(JSContext* cx, unsigned argc, Value* vp) {
17311731
return true;
17321732
}
17331733

1734+
// Clamp |value| to a string index between 0 and |length|.
1735+
static MOZ_ALWAYS_INLINE bool ToClampedStringIndex(JSContext* cx,
1736+
Handle<Value> value,
1737+
uint32_t length,
1738+
uint32_t* result) {
1739+
// Handle the common case of int32 indices first.
1740+
if (value.isInt32()) {
1741+
int32_t i = value.toInt32();
1742+
*result = std::min(uint32_t(std::max(i, 0)), length);
1743+
return true;
1744+
}
1745+
1746+
double d;
1747+
if (!ToInteger(cx, value, &d)) {
1748+
return false;
1749+
}
1750+
*result = uint32_t(std::clamp(d, 0.0, double(length)));
1751+
return true;
1752+
}
1753+
1754+
// Return |Some(index)| if |value| is a string index between 0 and |length|.
1755+
// Otherwise return |Nothing|.
17341756
static MOZ_ALWAYS_INLINE bool ToStringIndex(JSContext* cx, Handle<Value> value,
17351757
size_t length,
17361758
mozilla::Maybe<size_t>* result) {
@@ -1753,6 +1775,8 @@ static MOZ_ALWAYS_INLINE bool ToStringIndex(JSContext* cx, Handle<Value> value,
17531775
return true;
17541776
}
17551777

1778+
// Return |Some(index)| if |value| is a relative string index between 0 and
1779+
// |length|. Otherwise return |Nothing|.
17561780
static MOZ_ALWAYS_INLINE bool ToRelativeStringIndex(
17571781
JSContext* cx, Handle<Value> value, size_t length,
17581782
mozilla::Maybe<size_t>* result) {
@@ -2326,8 +2350,8 @@ static MOZ_ALWAYS_INLINE bool ReportErrorIfFirstArgIsRegExp(
23262350
return true;
23272351
}
23282352

2329-
// ES2018 draft rev de77aaeffce115deaf948ed30c7dbe4c60983c0c
2330-
// 21.1.3.7 String.prototype.includes ( searchString [ , position ] )
2353+
// ES2026 draft rev a562082b031d89d00ee667181ce8a6158656bd4b
2354+
// 22.1.3.8 String.prototype.includes ( searchString [ , position ] )
23312355
bool js::str_includes(JSContext* cx, unsigned argc, Value* vp) {
23322356
AutoJSMethodProfilerEntry pseudoFrame(cx, "String.prototype", "includes");
23332357
CallArgs args = CallArgsFromVp(argc, vp);
@@ -2349,28 +2373,15 @@ bool js::str_includes(JSContext* cx, unsigned argc, Value* vp) {
23492373
return false;
23502374
}
23512375

2352-
// Step 6.
2353-
uint32_t pos = 0;
2376+
// Steps 6-9.
2377+
uint32_t start = 0;
23542378
if (args.hasDefined(1)) {
2355-
if (args[1].isInt32()) {
2356-
int i = args[1].toInt32();
2357-
pos = (i < 0) ? 0U : uint32_t(i);
2358-
} else {
2359-
double d;
2360-
if (!ToInteger(cx, args[1], &d)) {
2361-
return false;
2362-
}
2363-
pos = uint32_t(std::clamp(d, 0.0, double(UINT32_MAX)));
2379+
if (!ToClampedStringIndex(cx, args[1], str->length(), &start)) {
2380+
return false;
23642381
}
23652382
}
23662383

2367-
// Step 7.
2368-
uint32_t textLen = str->length();
2369-
2370-
// Step 8.
2371-
uint32_t start = std::min(pos, textLen);
2372-
2373-
// Steps 9-10.
2384+
// Steps 10-12.
23742385
JSLinearString* text = str->ensureLinear(cx);
23752386
if (!text) {
23762387
return false;
@@ -2396,52 +2407,40 @@ bool js::StringIncludes(JSContext* cx, HandleString string,
23962407
return true;
23972408
}
23982409

2399-
/* ES6 20120927 draft 15.5.4.7. */
2410+
// ES2026 draft rev a562082b031d89d00ee667181ce8a6158656bd4b
2411+
// 22.1.3.9 String.prototype.indexOf ( searchString [ , position ] )
24002412
bool js::str_indexOf(JSContext* cx, unsigned argc, Value* vp) {
24012413
AutoJSMethodProfilerEntry pseudoFrame(cx, "String.prototype", "indexOf");
24022414
CallArgs args = CallArgsFromVp(argc, vp);
24032415

2404-
// Steps 1, 2, and 3
2416+
// Steps 1-2.
24052417
RootedString str(cx, ToStringForStringFunction(cx, "indexOf", args.thisv()));
24062418
if (!str) {
24072419
return false;
24082420
}
24092421

2410-
// Steps 4 and 5
2422+
// Step 3.
24112423
Rooted<JSLinearString*> searchStr(cx, ArgToLinearString(cx, args, 0));
24122424
if (!searchStr) {
24132425
return false;
24142426
}
24152427

2416-
// Steps 6 and 7
2417-
uint32_t pos = 0;
2428+
// Steps 4-7.
2429+
uint32_t start = 0;
24182430
if (args.hasDefined(1)) {
2419-
if (args[1].isInt32()) {
2420-
int i = args[1].toInt32();
2421-
pos = (i < 0) ? 0U : uint32_t(i);
2422-
} else {
2423-
double d;
2424-
if (!ToInteger(cx, args[1], &d)) {
2425-
return false;
2426-
}
2427-
pos = uint32_t(std::clamp(d, 0.0, double(UINT32_MAX)));
2431+
if (!ToClampedStringIndex(cx, args[1], str->length(), &start)) {
2432+
return false;
24282433
}
24292434
}
24302435

2431-
// Step 8
2432-
uint32_t textLen = str->length();
2433-
2434-
// Step 9
2435-
uint32_t start = std::min(pos, textLen);
2436-
24372436
if (str == searchStr) {
24382437
// AngularJS often invokes "false".indexOf("false"). This check should
24392438
// be cheap enough to not hurt anything else.
24402439
args.rval().setInt32(start == 0 ? 0 : -1);
24412440
return true;
24422441
}
24432442

2444-
// Steps 10 and 11
2443+
// Steps 8-10.
24452444
JSLinearString* text = str->ensureLinear(cx);
24462445
if (!text) {
24472446
return false;
@@ -2527,8 +2526,8 @@ static int32_t LastIndexOf(const JSLinearString* text,
25272526
searchLen, start);
25282527
}
25292528

2530-
// ES2017 draft rev 6859bb9ccaea9c6ede81d71e5320e3833b92cb3e
2531-
// 21.1.3.9 String.prototype.lastIndexOf ( searchString [ , position ] )
2529+
// ES2026 draft rev a562082b031d89d00ee667181ce8a6158656bd4b
2530+
// 22.1.3.11 String.prototype.lastIndexOf ( searchString [ , position ] )
25322531
static bool str_lastIndexOf(JSContext* cx, unsigned argc, Value* vp) {
25332532
AutoJSMethodProfilerEntry pseudoFrame(cx, "String.prototype", "lastIndexOf");
25342533
CallArgs args = CallArgsFromVp(argc, vp);
@@ -2546,13 +2545,13 @@ static bool str_lastIndexOf(JSContext* cx, unsigned argc, Value* vp) {
25462545
return false;
25472546
}
25482547

2549-
// Step 6.
2548+
// Step 7.
25502549
size_t len = str->length();
25512550

25522551
// Step 8.
25532552
size_t searchLen = searchStr->length();
25542553

2555-
// Steps 4-5, 7.
2554+
// Steps 4-6 and 9.
25562555
int start = len - searchLen; // Start searching here
25572556
if (args.hasDefined(1)) {
25582557
if (args[1].isInt32()) {
@@ -2599,7 +2598,7 @@ static bool str_lastIndexOf(JSContext* cx, unsigned argc, Value* vp) {
25992598
return false;
26002599
}
26012600

2602-
// Step 9.
2601+
// Step 10-12.
26032602
args.rval().setInt32(LastIndexOf(text, searchStr, start));
26042603
return true;
26052604
}
@@ -2642,8 +2641,8 @@ bool js::StringLastIndexOf(JSContext* cx, HandleString string,
26422641
return true;
26432642
}
26442643

2645-
// ES2018 draft rev de77aaeffce115deaf948ed30c7dbe4c60983c0c
2646-
// 21.1.3.20 String.prototype.startsWith ( searchString [ , position ] )
2644+
// ES2026 draft rev a562082b031d89d00ee667181ce8a6158656bd4b
2645+
// 22.1.3.24 String.prototype.startsWith ( searchString [ , position ] )
26472646
bool js::str_startsWith(JSContext* cx, unsigned argc, Value* vp) {
26482647
AutoJSMethodProfilerEntry pseudoFrame(cx, "String.prototype", "startsWith");
26492648
CallArgs args = CallArgsFromVp(argc, vp);
@@ -2667,36 +2666,26 @@ bool js::str_startsWith(JSContext* cx, unsigned argc, Value* vp) {
26672666
}
26682667

26692668
// Step 6.
2670-
uint32_t pos = 0;
2669+
uint32_t textLen = str->length();
2670+
2671+
// Steps 7-8.
2672+
uint32_t start = 0;
26712673
if (args.hasDefined(1)) {
2672-
if (args[1].isInt32()) {
2673-
int i = args[1].toInt32();
2674-
pos = (i < 0) ? 0U : uint32_t(i);
2675-
} else {
2676-
double d;
2677-
if (!ToInteger(cx, args[1], &d)) {
2678-
return false;
2679-
}
2680-
pos = uint32_t(std::clamp(d, 0.0, double(UINT32_MAX)));
2674+
if (!ToClampedStringIndex(cx, args[1], textLen, &start)) {
2675+
return false;
26812676
}
26822677
}
26832678

2684-
// Step 7.
2685-
uint32_t textLen = str->length();
2686-
2687-
// Step 8.
2688-
uint32_t start = std::min(pos, textLen);
2689-
26902679
// Step 9.
26912680
uint32_t searchLen = searchStr->length();
26922681

2693-
// Step 10.
2682+
// Step 12.
26942683
if (searchLen + start < searchLen || searchLen + start > textLen) {
26952684
args.rval().setBoolean(false);
26962685
return true;
26972686
}
26982687

2699-
// Steps 11-12.
2688+
// Steps 10-11 and 13-15.
27002689
JSLinearString* text = str->ensureLinear(cx);
27012690
if (!text) {
27022691
return false;
@@ -2727,8 +2716,8 @@ bool js::StringStartsWith(JSContext* cx, HandleString string,
27272716
return true;
27282717
}
27292718

2730-
// ES2018 draft rev de77aaeffce115deaf948ed30c7dbe4c60983c0c
2731-
// 21.1.3.6 String.prototype.endsWith ( searchString [ , endPosition ] )
2719+
// ES2026 draft rev a562082b031d89d00ee667181ce8a6158656bd4b
2720+
// 22.1.3.7 String.prototype.endsWith ( searchString [ , endPosition ] )
27322721
bool js::str_endsWith(JSContext* cx, unsigned argc, Value* vp) {
27332722
AutoJSMethodProfilerEntry pseudoFrame(cx, "String.prototype", "endsWith");
27342723
CallArgs args = CallArgsFromVp(argc, vp);
@@ -2753,37 +2742,27 @@ bool js::str_endsWith(JSContext* cx, unsigned argc, Value* vp) {
27532742
// Step 6.
27542743
uint32_t textLen = str->length();
27552744

2756-
// Step 7.
2757-
uint32_t pos = textLen;
2745+
// Steps 7-8.
2746+
uint32_t end = textLen;
27582747
if (args.hasDefined(1)) {
2759-
if (args[1].isInt32()) {
2760-
int i = args[1].toInt32();
2761-
pos = (i < 0) ? 0U : uint32_t(i);
2762-
} else {
2763-
double d;
2764-
if (!ToInteger(cx, args[1], &d)) {
2765-
return false;
2766-
}
2767-
pos = uint32_t(std::clamp(d, 0.0, double(UINT32_MAX)));
2748+
if (!ToClampedStringIndex(cx, args[1], textLen, &end)) {
2749+
return false;
27682750
}
27692751
}
27702752

2771-
// Step 8.
2772-
uint32_t end = std::min(pos, textLen);
2773-
27742753
// Step 9.
27752754
uint32_t searchLen = searchStr->length();
27762755

2777-
// Step 11 (reordered).
2756+
// Step 12 (reordered).
27782757
if (searchLen > end) {
27792758
args.rval().setBoolean(false);
27802759
return true;
27812760
}
27822761

2783-
// Step 10.
2762+
// Step 11.
27842763
uint32_t start = end - searchLen;
27852764

2786-
// Steps 12-13.
2765+
// Steps 10 and 13-15.
27872766
JSLinearString* text = str->ensureLinear(cx);
27882767
if (!text) {
27892768
return false;

0 commit comments

Comments
 (0)