Commit fb14e6c
committed
[SPARK-57520][SQL] Fix UTF8String.codePointFrom and copyUTF8String reading past the end of a truncated trailing UTF-8 sequence
### What changes were proposed in this pull request?
`UTF8String.codePointFrom` decodes a code point by reading `numBytesForFirstByte(leader)` continuation bytes, and `copyUTF8String` copies `end - start + 1` bytes. Neither bounds the read by the bytes that actually remain, so when a string ends in a truncated multi-byte sequence (a leader byte whose declared width exceeds the remaining bytes), both read past the end of the backing memory. `trimLeft`/`trimRight` build their search character through `copyUTF8String`, so they over-read too.
This PR:
- `codePointFrom` reads continuation bytes through a small `continuationByte` helper that returns 0 once the index passes the end of the string.
- `copyUTF8String` clamps the copy length to `numBytes - start`.
- Once `copyUTF8String` stops over-reading, `trimRight` needs a matching accounting fix: it advanced `trimEnd` by the leader's declared width, which overshoots a truncated trailing character, so it now uses the actual (clamped) byte count, as `trimLeft` already does.
### Why are the changes needed?
`UTF8String` can hold malformed UTF-8 (for example, bytes from binary coercion or truncated input). For a string ending in an incomplete multi-byte sequence, these methods read out of bounds and produced wrong results: `codePointFrom` assembled a code point from adjacent memory, and `trimRight` could drop valid leading characters. Well-formed UTF-8 is unaffected, since a complete sequence never exceeds the remaining bytes.
This is a follow-up to SPARK-57507, which fixed the same kind of over-read in `reverse()`.
### Does this PR introduce _any_ user-facing change?
Yes, it fixes incorrect results on malformed input. String operations that reach these methods (such as trimming or code-point access) no longer read past the end of a value that ends in a truncated multi-byte sequence; only previously-incorrect results change. Well-formed strings behave exactly as before.
### How was this patch tested?
Added cases to `UTF8StringSuite`:
- `testCodePointFrom`: truncated trailing 2-, 3-, and 4-byte leaders, including a 4-byte leader with only the last continuation byte missing.
- `copyUTF8StringClampsToRemainingBytes`: an `end` one past the last byte, with a non-zero start so the clamp must use `numBytes - start`.
- `trimTruncatedTrailingSequence`: trimming a truncated trailing leader keeps the valid preceding character.
Each uses a sliced backing array with a trailing sentinel byte, so the previous over-read produces a deterministically wrong value; the cases fail on the old code and pass with the fix. `build/sbt 'unsafe/testOnly *UTF8StringSuite'` passes (51 tests).
### Was this patch authored or co-authored using generative AI tooling?
Generated-by: Claude Code (Claude Opus 4.8)
Closes #56585 from LuciferYang/SPARK-57520-utf8-overread.
Authored-by: YangJie <yangjie01@baidu.com>
Signed-off-by: yangjie01 <yangjie01@baidu.com>
(cherry picked from commit 33ae7f6)
Signed-off-by: yangjie01 <yangjie01@baidu.com>1 parent f5981e8 commit fb14e6c
2 files changed
Lines changed: 79 additions & 8 deletions
File tree
- common/unsafe/src
- main/java/org/apache/spark/unsafe/types
- test/java/org/apache/spark/unsafe/types
Lines changed: 24 additions & 8 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
724 | 724 | | |
725 | 725 | | |
726 | 726 | | |
727 | | - | |
| 727 | + | |
| 728 | + | |
728 | 729 | | |
729 | 730 | | |
730 | 731 | | |
| |||
736 | 737 | | |
737 | 738 | | |
738 | 739 | | |
739 | | - | |
| 740 | + | |
740 | 741 | | |
741 | | - | |
742 | | - | |
| 742 | + | |
| 743 | + | |
743 | 744 | | |
744 | | - | |
745 | | - | |
| 745 | + | |
| 746 | + | |
746 | 747 | | |
747 | 748 | | |
748 | 749 | | |
749 | 750 | | |
750 | 751 | | |
| 752 | + | |
| 753 | + | |
| 754 | + | |
| 755 | + | |
| 756 | + | |
| 757 | + | |
| 758 | + | |
| 759 | + | |
| 760 | + | |
| 761 | + | |
751 | 762 | | |
752 | 763 | | |
753 | 764 | | |
| |||
944 | 955 | | |
945 | 956 | | |
946 | 957 | | |
947 | | - | |
| 958 | + | |
| 959 | + | |
| 960 | + | |
| 961 | + | |
948 | 962 | | |
949 | 963 | | |
950 | 964 | | |
| |||
1137 | 1151 | | |
1138 | 1152 | | |
1139 | 1153 | | |
1140 | | - | |
| 1154 | + | |
| 1155 | + | |
| 1156 | + | |
1141 | 1157 | | |
1142 | 1158 | | |
1143 | 1159 | | |
| |||
Lines changed: 55 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1228 | 1228 | | |
1229 | 1229 | | |
1230 | 1230 | | |
| 1231 | + | |
| 1232 | + | |
| 1233 | + | |
| 1234 | + | |
| 1235 | + | |
| 1236 | + | |
| 1237 | + | |
| 1238 | + | |
| 1239 | + | |
| 1240 | + | |
| 1241 | + | |
| 1242 | + | |
| 1243 | + | |
| 1244 | + | |
| 1245 | + | |
| 1246 | + | |
| 1247 | + | |
| 1248 | + | |
| 1249 | + | |
| 1250 | + | |
| 1251 | + | |
| 1252 | + | |
| 1253 | + | |
| 1254 | + | |
| 1255 | + | |
| 1256 | + | |
| 1257 | + | |
| 1258 | + | |
| 1259 | + | |
| 1260 | + | |
| 1261 | + | |
| 1262 | + | |
| 1263 | + | |
| 1264 | + | |
| 1265 | + | |
| 1266 | + | |
| 1267 | + | |
| 1268 | + | |
| 1269 | + | |
| 1270 | + | |
| 1271 | + | |
| 1272 | + | |
| 1273 | + | |
| 1274 | + | |
| 1275 | + | |
| 1276 | + | |
| 1277 | + | |
| 1278 | + | |
| 1279 | + | |
| 1280 | + | |
| 1281 | + | |
| 1282 | + | |
| 1283 | + | |
| 1284 | + | |
| 1285 | + | |
1231 | 1286 | | |
1232 | 1287 | | |
1233 | 1288 | | |
| |||
0 commit comments