Repository navigation
Conversation
…earch
Buffer.prototype.indexOf and includes with a string needle and a UTF-16
encoding rounded an odd byteOffset down to the code unit before it, so
the match could begin before byteOffset:
Buffer.from("abcabc", "utf16le").indexOf("abc", 1, "ucs2") // 0, now 6
Node.js rounds the offset up since v26.11.0 (nodejs/node#65960). The
rule lives in IndexOfString only. A Buffer or Uint8Array needle and
lastIndexOf still round down, in Node.js and here.
Sync test/js/node/test/parallel/test-buffer-indexof.js to Node.js
v26.12.1. The local change for `new Buffer.prototype.lastIndexOf` stays.
|
Warning Review limit reachedYou've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Next included review available in 34 seconds. View limit details
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
Updated 9:06 AM PT - Oct 29th, 2026
❌ @robobun, your commit 721299d has 2 failures in
CapacityLimitExceededThis account has reached its capacity limit. Reach out to your account team for assistance.490ded1c-efcc-4fc0-83de-74cca7b16750
CapacityLimitExceededThis account has reached its capacity limit. Reach out to your account team for assistance.45541005-22b4-4ef4-ba82-0794aac05a90
CapacityLimitExceededThis account has reached its capacity limit. Reach out to your account team for assistance.185cb306-e21d-42c6-9c72-bfaf23ef3d2a
CapacityLimitExceededThis account has reached its capacity limit. Reach out to your account team for assistance.e571aa31-ed44-41d2-8c8a-81b43ab9a783
CapacityLimitExceededThis account has reached its capacity limit. Reach out to your account team for assistance.e1c42fa7-be74-485f-989c-389eafaa4c8f
CapacityLimitExceededThis account has reached its capacity limit. Reach out to your account team for assistance.5273fd12-a64e-4b5c-9c31-8f8e8e290570
CapacityLimitExceededThis account has reached its capacity limit. Reach out to your account team for assistance.5db84dcb-398a-4fd8-89ce-acc61276b7fd
CapacityLimitExceededThis account has reached its capacity limit. Reach out to your account team for assistance.220c9e25-eba5-46cb-ba7a-fc096102f296
CapacityLimitExceededThis account has reached its capacity limit. Reach out to your account team for assistance.3dc5031b-d7d7-4bdb-9153-10278d0141a7
CapacityLimitExceededThis account has reached its capacity limit. Reach out to your account team for assistance.8dc40277-265a-481a-acdb-95428af7f35d
CapacityLimitExceededThis account has reached its capacity limit. Reach out to your account team for assistance.d6997ea4-3d91-4f83-91d0-635aecce8951
CapacityLimitExceededThis account has reached its capacity limit. Reach out to your account team for assistance.30230ff4-eae9-495a-9add-3b229836cd75
CapacityLimitExceededThis account has reached its capacity limit. Reach out to your account team for assistance.100f5067-3d60-4930-97e3-683b96e6012e
CapacityLimitExceededThis account has reached its capacity limit. Reach out to your account team for assistance.7d213214-67ce-4de3-b37b-a297f94e5d01
CapacityLimitExceededThis account has reached its capacity limit. Reach out to your account team for assistance.4e552ec4-7fbe-4098-915c-14321ac896a1
CapacityLimitExceededThis account has reached its capacity limit. Reach out to your account team for assistance.5c69a9c8-0c35-4b5d-b0b7-9d83493b1233
CapacityLimitExceededThis account has reached its capacity limit. Reach out to your account team for assistance.40e8be95-8c5d-44a5-a959-44bd04667e3b
CapacityLimitExceededThis account has reached its capacity limit. Reach out to your account team for assistance.f3ecc315-4ad7-4afa-830a-bb02b69b05c4
CapacityLimitExceededThis account has reached its capacity limit. Reach out to your account team for assistance.e81964a4-1bf4-4772-80ea-a0557ee4b09d
CapacityLimitExceededThis account has reached its capacity limit. Reach out to your account team for assistance.44de36cf-741f-42d3-be6b-61b76cd55df4
CapacityLimitExceededThis account has reached its capacity limit. Reach out to your account team for assistance.21b656a5-411c-44c7-836a-475932a0de09
CapacityLimitExceededThis account has reached its capacity limit. Reach out to your account team for assistance.b4d70a8e-c4a7-4221-8f87-25659c2df4c0
CapacityLimitExceededThis account has reached its capacity limit. Reach out to your account team for assistance.05aa143b-0f4c-405b-ac35-c8dcabe2627e
CapacityLimitExceededThis account has reached its capacity limit. Reach out to your account team for assistance.1c3772e4-8bb2-4fe1-bdc5-3f3e0be75235
CapacityLimitExceededThis account has reached its capacity limit. Reach out to your account team for assistance.6df945e4-5735-4c50-9efd-fbcbf3aa96ac
CapacityLimitExceededThis account has reached its capacity limit. Reach out to your account team for assistance.fabd24b7-3de1-4b38-a5bc-6c59a0846142
CapacityLimitExceededThis account has reached its capacity limit. Reach out to your account team for assistance.b1a2dad9-d201-4ee7-923f-7a49a91a7962
CapacityLimitExceededThis account has reached its capacity limit. Reach out to your account team for assistance.7fef7c71-8074-4dbc-bda0-fc32eb6a9090
CapacityLimitExceededThis account has reached its capacity limit. Reach out to your account team for assistance.e62e075a-ee49-445b-9dac-41853fc941d5
CapacityLimitExceededThis account has reached its capacity limit. Reach out to your account team for assistance.7ab93ea1-04a5-413e-87ac-3d4be8ddf573
CapacityLimitExceededThis account has reached its capacity limit. Reach out to your account team for assistance.c84adaa1-cd1f-4964-b295-683c495c8a63
CapacityLimitExceededThis account has reached its capacity limit. Reach out to your account team for assistance.3d3317ec-a926-4c4e-933e-b61863c5018a
CapacityLimitExceededThis account has reached its capacity limit. Reach out to your account team for assistance.b198369f-5a7b-423c-9c46-77766a6e90ac
CapacityLimitExceededThis account has reached its capacity limit. Reach out to your account team for assistance.afadfb56-56cd-4023-909c-a50cfc215582
CapacityLimitExceededThis account has reached its capacity limit. Reach out to your account team for assistance.d5c75d7f-1c49-4ec4-9a46-e5884e15a49d
CapacityLimitExceededThis account has reached its capacity limit. Reach out to your account team for assistance.ee512df6-2073-41b6-8db6-c0bc29efe787
CapacityLimitExceededThis account has reached its capacity limit. Reach out to your account team for assistance.d9092ed3-0a72-40dd-abcf-3c79a86f96e7
CapacityLimitExceededThis account has reached its capacity limit. Reach out to your account team for assistance.c59d3ff2-1bb1-4538-9c9c-e7eeb3596e1b
CapacityLimitExceededThis account has reached its capacity limit. Reach out to your account team for assistance.240a98cb-86c1-42a7-86f4-18394b02246f
CapacityLimitExceededThis account has reached its capacity limit. Reach out to your account team for assistance.19a035cf-4789-472f-a3d3-c687cd6cbe2f
CapacityLimitExceededThis account has reached its capacity limit. Reach out to your account team for assistance.881dc93d-53b6-47e7-b89d-faef69569893
CapacityLimitExceededThis account has reached its capacity limit. Reach out to your account team for assistance.7e2bd6d9-0712-47b4-b434-a6fc68f35546
CapacityLimitExceededThis account has reached its capacity limit. Reach out to your account team for assistance.a9984356-7ba6-40cf-97ca-3ecce3aeeee8
CapacityLimitExceededThis account has reached its capacity limit. Reach out to your account team for assistance.e2669abc-7df7-43f7-a187-b26afd857015
CapacityLimitExceededThis account has reached its capacity limit. Reach out to your account team for assistance.be1369f0-cd22-4c54-aa68-ebba517e201f
CapacityLimitExceededThis account has reached its capacity limit. Reach out to your account team for assistance.b961d8a9-004f-44fb-aae7-a8000a12e4f3
CapacityLimitExceededThis account has reached its capacity limit. Reach out to your account team for assistance.66f69dbb-5687-44be-b9d1-9a888fd24f51
CapacityLimitExceededThis account has reached its capacity limit. Reach out to your account team for assistance.907e84ff-ddab-4b4b-96db-8b24e1c932b7
CapacityLimitExceededThis account has reached its capacity limit. Reach out to your account team for assistance.c8409b1e-2887-41c1-a0ed-e931e8df299a
CapacityLimitExceededThis account has reached its capacity limit. Reach out to your account team for assistance.51a23aa2-1c52-4b86-8d77-8fbcd5f72794
CapacityLimitExceededThis account has reached its capacity limit. Reach out to your account team for assistance.7083b216-1910-4dea-b00e-74eac55fd273
CapacityLimitExceededThis account has reached its capacity limit. Reach out to your account team for assistance.14b3580d-a716-4918-9806-390881e8b899
CapacityLimitExceededThis account has reached its capacity limit. Reach out to your account team for assistance.ad189bae-4d5c-446b-865f-d9b61c4220d2
CapacityLimitExceededThis account has reached its capacity limit. Reach out to your account team for assistance.ce7d7d57-9082-4278-9147-04f6d4afde03
CapacityLimitExceededThis account has reached its capacity limit. Reach out to your account team for assistance.25dfbd6f-0583-4b35-842e-35b2dc5c27c7
CapacityLimitExceededThis account has reached its capacity limit. Reach out to your account team for assistance.68d774cc-9023-46c2-9916-30f2c0ee3fae
CapacityLimitExceededThis account has reached its capacity limit. Reach out to your account team for assistance.836ce9f5-1891-4082-8f9c-294758064739
CapacityLimitExceededThis account has reached its capacity limit. Reach out to your account team for assistance.3306f169-bb1b-431a-877c-625c146fcf25
CapacityLimitExceededThis account has reached its capacity limit. Reach out to your account team for assistance.1677e5f1-2767-49af-b1cb-bb3889fc7870
CapacityLimitExceededThis account has reached its capacity limit. Reach out to your account team for assistance.21e94b44-b0b8-4d5a-93cf-96077ec13ec0
CapacityLimitExceededThis account has reached its capacity limit. Reach out to your account team for assistance.6289b9b6-ecc0-471d-bd56-eb3631300a4f
CapacityLimitExceededThis account has reached its capacity limit. Reach out to your account team for assistance.fb2ee75c-ba9b-4aaf-ac3f-c7be39392c08
CapacityLimitExceededThis account has reached its capacity limit. Reach out to your account team for assistance.72b9994d-05a7-4c05-b550-26c71509fe53
CapacityLimitExceededThis account has reached its capacity limit. Reach out to your account team for assistance.e1153ab4-2568-4be3-9a24-8cec527abbe4
CapacityLimitExceededThis account has reached its capacity limit. Reach out to your account team for assistance.cc2bdce3-cd4c-4820-b231-dc9dfd6e2c79
CapacityLimitExceededThis account has reached its capacity limit. Reach out to your account team for assistance.871855e3-2785-47c2-a1ec-e7177c3ba871
CapacityLimitExceededThis account has reached its capacity limit. Reach out to your account team for assistance.e0da3f5d-48b6-4b8a-91d1-312bb26da5ae🧪 To try this PR locally: bunx bun-pr 47260That installs a local version of the PR into your bun-47260 --bun |
There was a problem hiding this comment.
LGTM — a one-line port of Node's forward UCS2 odd-offset round-up, scoped to the string-needle path as upstream is.
What was reviewed:
- Bounds after the round-up: computeIndexOfRange already enforces offset + needleLength <= searchEnd, and with searchEnd masked even and a UTF-16 string needle always an even length >= 2, an odd offset that passes still fits after +1, so indexOf16's haystack length cannot underflow.
- Sibling sites: lastIndexOf16 and the Buffer/Uint8Array-needle path in indexOfBuffer keep the round-down, matching Node's lastIndexOf and IndexOfBuffer; the test block asserts both negatively.
- Traced the new assertions (negative offsets, odd
end, odd-length haystack, empty needle) by hand through computeIndexOfRange/indexOf16 — values match the expected results. - The vendored test-buffer-indexof.js hunks are additive upstream syncs, no existing assertions weakened.
Extended reasoning...
The change adds one line in indexOfString in src/jsc/bindings/JSBuffer.cpp that rounds an odd byteOffset up to even before a forward UTF-16 string search, plus a Bun test block in test/js/node/buffer.test.js and two additive hunks in the vendored test/js/node/test/parallel/test-buffer-indexof.js. It touches Buffer search index arithmetic but no auth, crypto, or injection surface. The round-up runs after computeIndexOfRange's range validation, and the even-needle/even-searchEnd invariant means the rounded offset cannot push the search past the buffer. The bug hunt ran dry with no findings, the three ruled-out candidates all checked out on reading, no CODEOWNER covers the changed files, and the PR has no outstanding reviewer objections.
|
Status: ready for review. This PR has the fix, and its tests pass in CI. How I reproduced it: console.log(Buffer.from("abcabc", "utf16le").indexOf("abc", 1, "ucs2"));
CI on
CI on
PR: #47260 |
|
Merged main (da867c6) into this branch so that CI runs on a green main. There was no conflict.
|
Problem
BufferindexOfandincludeswith a string needle, a UTF-16 encoding, and an oddbyteOffsetreturn a match beforebyteOffset.Buffer.from("abcabc", "utf16le").indexOf("abc", 1, "ucs2")is0. Node.js v26.12.1, the versionmainreports, returns6.byteOffset /= 2inindexOf16(src/jsc/bindings/JSBuffer.cpp:1626). Node.js rounds up since v26.11.0 (buffer: fix odd UTF-16LE indexOf start nodejs/node#65960).Fix
indexOfStringrounds the offset up to the next even byte before a forward UTF-16 search. Node.js has the rule only inIndexOfString(node_buffer.cc#L1089-L1092).BufferorUint8Arrayneedle andlastIndexOfstill round down, as in Node.js.test/js/node/buffer.test.js(new block, 73 of 160 assertions fail on bun 1.4.3) andtest-buffer-indexof.js, synced to v26.12.1.Background
byteOffsetis inside a unit.indexOf16orcomputeIndexOfRange. Both also serve buffer needles, where Node.js keeps the round down, so each needs a flag.Downsides
-1. 2,064 of 1,527,876 results of a differential run change.JSBuffer.cppwith release flags, no LTO.RangeErrorfor this input. node-fallbacks: fix lastIndexOf(value, encoding) and UTF-16 search offsets in the Buffer polyfill #43654 and node-fallbacks: match Node for empty/Uint8Array/end in browser Buffer indexOf #43876 rewrite its search with the old round down.Notes
Repro
indexOf("abc", 1, "ucs2")includes("ca", 5, "ucs2")main, Node.js v26.3.00true[0, 0, 0, 0]6false[0, 6]Where Node.js has the change
IndexOfStringand added two assertions totest/parallel/test-buffer-indexof.js.src/node_buffer.cchas the round up at tags v26.11.0, v26.12.1 and v26.13.0, and on thev24.x-stagingbranch. It does not have it at v26.10.0, v24.22.2 or v22.23.4.IndexOfBufferand the backward search still useoffset / 2(node_buffer.cc#L1236).Why the second line of the Node.js change is not here
-1when the rounded offset reachessearch_end. Here the round up runs aftercomputeIndexOfRange, which already requires that the needle fits beforesearchEnd. A UTF-16 string needle has an even length of 2 bytes or more, andsearchEndis even. So an odd offset that passes still fits after+1, and that return cannot fire.Measurements
indexOforincludeswith a string needle, a UTF-16 encoding and an odd start. The run coversindexOf,lastIndexOfandincludes, string,Buffer,Uint8Arrayand number needles, six UTF-16 spellings and four one-byte encodings, offsets from -15 to 15 plus fractions,NaN, infinities and booleans, fourteenendvalues, and even, odd and empty haystacks.incq) on forward UTF-16 string searches, +0 on every other search path, +0 branches. Oneshrqbecomessarq. This is the whole disassembly diff ofJSBuffer.cppbuilt with the release flags, no LTO, merge base against this PR.JSBuffer.cpp.text: +3 bytes (166,672 -> 166,675).WebCore::indexOf: +3 bytes (4,362 -> 4,365). Measured withllvm-size-23andllvm-nm-23on the same two objects.JSBuffer.cpp: 2,089 before, 2,089 after. So the change adds 0 allocations, syscalls or queue entries per call.indexOf16, the sharedcomputeIndexOfRange, thelastIndexOfarm, and a position before the empty needle return.Bufferview that starts at an odd address: 0 of 15,664 cells differ from a brute-force oracle, and ASAN reports nothing. Node.js hangs on this input (buffer: UTF-16LE indexOf can hang on unaligned Buffer views nodejs/node#65959), so it is not in the differential run.Tests
bun bd test test/js/node/buffer.test.js: 720 pass, with the new test and every otherindexOf,lastIndexOfandincludestest among them. 2 tests time out at 5 s on the local debug build with ASAN: thefill()comparison that starts Node.js, and theSharedArrayBufferdecoder test. Neither callsindexOf. A local build ofmaintimes out on 4 tests of this kind.test/js/node/test/parallel/test-buffer-indexof.jsis the upstream file at v26.12.1 plus the existing local change fornew Buffer.prototype.lastIndexOf. It fails on bun 1.4.3 at line 313 (0 !== 4) and passes on this PR.test-buffer-*.jsfiles that changed upstream since v26.3.0 which is now at the v26.12.1 text. The other 7 are still at the v26.3.0 text, so this PR does not bringnode:bufferto v26.12.1. At the v26.12.1 text, 5 of them pass on this build.test-buffer-isascii.jsandtest-buffer-isutf8.jsdo not:isAsciiandisUtf8throwERR_INVALID_STATEfor a detachedArrayBuffer, and Node.js returnstruesince v26.8.0 (buffer: handle detached ArrayBuffer views nodejs/node#64504).test-buffer-includes.js,buffer-indexof-worstcase.test.ts,buffer-indexOf-detach.test.ts.Related open PRs
odd byteOffset 0 4 true. Node.js v26.12.1 printsodd byteOffset 6 4 truefor the same code.endlast in indexOf, lastIndexOf and includes #43522 adds a test after the same test inbuffer.test.js. The one that merges second needs a rebase that keeps both tests.no test proof · iteration 0 · platform-specific test(s) that do not run on this machine, deferring to CI, which covers all platforms: test/js/node/buffer.test.js