Skip to content

Commit 927c25a

Browse files
ronagclaude
andcommitted
buffer: fix end parameter bugs in indexOf/lastIndexOf
- Fix FastIndexOfNumber parameter order mismatch (end_i64 and is_forward were swapped vs the JS call site and slow path) - Clamp negative end values to 0 to prevent size_t overflow in IndexOfString, IndexOfBuffer, and IndexOfNumberImpl - Clamp empty needle result to search_end Signed-off-by: Robert Nagy <ronagy@icloud.com> Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 4563cb3 commit 927c25a

File tree

2 files changed

+52
-11
lines changed

2 files changed

+52
-11
lines changed

src/node_buffer.cc

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -983,8 +983,8 @@ void IndexOfString(const FunctionCallbackInfo<Value>& args) {
983983
if (!StringBytes::Size(isolate, needle, enc).To(&needle_length)) return;
984984

985985
// search_end is the exclusive upper bound of the search range.
986-
size_t search_end = static_cast<size_t>(
987-
std::min(end_i64, static_cast<int64_t>(haystack_length)));
986+
size_t search_end = static_cast<size_t>(std::min(
987+
std::max(end_i64, int64_t{0}), static_cast<int64_t>(haystack_length)));
988988
if (enc == UCS2) search_end &= ~static_cast<size_t>(1);
989989

990990
int64_t opt_offset = IndexOfOffset(haystack_length,
@@ -993,8 +993,11 @@ void IndexOfString(const FunctionCallbackInfo<Value>& args) {
993993
is_forward);
994994

995995
if (needle_length == 0) {
996-
// Match String#indexOf() and String#lastIndexOf() behavior.
997-
args.GetReturnValue().Set(static_cast<double>(opt_offset));
996+
// Match String#indexOf() and String#lastIndexOf() behavior,
997+
// but clamp to search_end.
998+
int64_t clamped =
999+
std::min(opt_offset, static_cast<int64_t>(search_end));
1000+
args.GetReturnValue().Set(static_cast<double>(clamped));
9981001
return;
9991002
}
10001003

@@ -1108,8 +1111,8 @@ void IndexOfBuffer(const FunctionCallbackInfo<Value>& args) {
11081111
const size_t needle_length = needle_contents.length();
11091112

11101113
// search_end is the exclusive upper bound of the search range.
1111-
size_t search_end = static_cast<size_t>(
1112-
std::min(end_i64, static_cast<int64_t>(haystack_length)));
1114+
size_t search_end = static_cast<size_t>(std::min(
1115+
std::max(end_i64, int64_t{0}), static_cast<int64_t>(haystack_length)));
11131116
if (enc == UCS2) search_end &= ~static_cast<size_t>(1);
11141117

11151118
int64_t opt_offset = IndexOfOffset(haystack_length,
@@ -1118,8 +1121,11 @@ void IndexOfBuffer(const FunctionCallbackInfo<Value>& args) {
11181121
is_forward);
11191122

11201123
if (needle_length == 0) {
1121-
// Match String#indexOf() and String#lastIndexOf() behavior.
1122-
args.GetReturnValue().Set(static_cast<double>(opt_offset));
1124+
// Match String#indexOf() and String#lastIndexOf() behavior,
1125+
// but clamp to search_end.
1126+
int64_t clamped =
1127+
std::min(opt_offset, static_cast<int64_t>(search_end));
1128+
args.GetReturnValue().Set(static_cast<double>(clamped));
11231129
return;
11241130
}
11251131

@@ -1184,8 +1190,8 @@ int32_t IndexOfNumberImpl(Local<Value> buffer_obj,
11841190
}
11851191
size_t offset = static_cast<size_t>(opt_offset);
11861192
// search_end is the exclusive upper bound of the search range.
1187-
size_t search_end = static_cast<size_t>(
1188-
std::min(end_i64, static_cast<int64_t>(buffer_length)));
1193+
size_t search_end = static_cast<size_t>(std::min(
1194+
std::max(end_i64, int64_t{0}), static_cast<int64_t>(buffer_length)));
11891195

11901196
const void* ptr;
11911197
if (is_forward) {
@@ -1222,8 +1228,8 @@ int32_t FastIndexOfNumber(Local<Value>,
12221228
Local<Value> buffer_obj,
12231229
uint32_t needle,
12241230
int64_t offset_i64,
1225-
int64_t end_i64,
12261231
bool is_forward,
1232+
int64_t end_i64,
12271233
// NOLINTNEXTLINE(runtime/references)
12281234
FastApiCallbackOptions& options) {
12291235
HandleScope scope(options.isolate);

test/parallel/test-buffer-indexof.js

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -691,3 +691,38 @@ assert.strictEqual(reallyLong.lastIndexOf(pattern), 0);
691691

692692
assert.strictEqual(buf.includes('c'), true);
693693
}
694+
695+
{
696+
const buf = Buffer.from('abcabc');
697+
698+
// negative end should be treated as 0 (no match possible).
699+
assert.strictEqual(buf.indexOf('a', 0, -1), -1);
700+
assert.strictEqual(buf.indexOf('a', 0, -100), -1);
701+
assert.strictEqual(buf.indexOf(0x61, 0, -1), -1);
702+
assert.strictEqual(buf.lastIndexOf('a', 5, -1), -1);
703+
assert.strictEqual(buf.lastIndexOf(0x61, 5, -1), -1);
704+
assert.strictEqual(buf.includes('a', 0, -1), false);
705+
assert.strictEqual(buf.indexOf(Buffer.from('a'), 0, -1), -1);
706+
assert.strictEqual(buf.lastIndexOf(Buffer.from('a'), 5, -1), -1);
707+
708+
// end = 0 means empty search range.
709+
assert.strictEqual(buf.indexOf('a', 0, 0), -1);
710+
assert.strictEqual(buf.indexOf(0x61, 0, 0), -1);
711+
assert.strictEqual(buf.lastIndexOf('a', 5, 0), -1);
712+
assert.strictEqual(buf.lastIndexOf(0x61, 5, 0), -1);
713+
714+
// end greater than buffer length should be clamped.
715+
assert.strictEqual(buf.indexOf('c', 0, 100), 2);
716+
assert.strictEqual(buf.indexOf(0x63, 0, 100), 2);
717+
assert.strictEqual(buf.lastIndexOf('c', 5, 100), 5);
718+
assert.strictEqual(buf.lastIndexOf(0x63, 5, 100), 5);
719+
assert.strictEqual(buf.indexOf(Buffer.from('c'), 0, 100), 2);
720+
721+
// empty needle with end parameter should clamp to search_end.
722+
assert.strictEqual(buf.indexOf('', 0, 3), 0);
723+
assert.strictEqual(buf.indexOf('', 5, 3), 3);
724+
assert.strictEqual(buf.indexOf(Buffer.from(''), 5, 3), 3);
725+
assert.strictEqual(buf.indexOf('', 0, 0), 0);
726+
assert.strictEqual(buf.lastIndexOf('', 5, 3), 3);
727+
assert.strictEqual(buf.lastIndexOf(Buffer.from(''), 5, 3), 3);
728+
}

0 commit comments

Comments
 (0)