| 1481 | |
| 1482 | #if PY_VERSION_HEX >= 0x030E0000 |
| 1483 | static PyObject * |
| 1484 | scanstring_unicode(_speedups_state *state, PyObject *pystr, Py_ssize_t end, |
| 1485 | int strict, Py_ssize_t *next_end_ptr) |
| 1486 | { |
| 1487 | /* Python 3.14+: use PyUnicodeWriter instead of a chunks list. |
| 1488 | * The writer is lazily created on the first escape sequence so that |
| 1489 | * the common no-escape path returns a cheap PyUnicode_Substring. */ |
| 1490 | PyObject *rval; |
| 1491 | Py_ssize_t begin = end - 1; |
| 1492 | Py_ssize_t next = begin; |
| 1493 | int kind = PyUnicode_KIND(pystr); |
| 1494 | Py_ssize_t len = PyUnicode_GET_LENGTH(pystr); |
| 1495 | void *buf = PyUnicode_DATA(pystr); |
| 1496 | PyUnicodeWriter *writer = NULL; |
| 1497 | Py_ssize_t literal_start; |
| 1498 | |
| 1499 | if (len == end) { |
| 1500 | raise_errmsg(state, ERR_STRING_UNTERMINATED, pystr, begin); |
| 1501 | goto bail; |
| 1502 | } |
| 1503 | else if (end < 0 || len < end) { |
| 1504 | /* Out-of-range end: match py_scanstring, which raises |
| 1505 | * JSONDecodeError("Unterminated string starting at") so that |
| 1506 | * user code using `except JSONDecodeError` catches the C path |
| 1507 | * the same way it catches the pure-Python path. */ |
| 1508 | raise_errmsg(state, ERR_STRING_UNTERMINATED, pystr, begin); |
| 1509 | goto bail; |
| 1510 | } |
| 1511 | |
| 1512 | literal_start = end; |
| 1513 | while (1) { |
| 1514 | /* Find the end of the string or the next escape */ |
| 1515 | JSON_UNICHR c = 0; |
| 1516 | for (next = end; next < len; next++) { |
| 1517 | c = PyUnicode_READ(kind, buf, next); |
| 1518 | if (c == '"' || c == '\\') { |
| 1519 | break; |
| 1520 | } |
| 1521 | else if (strict && c <= 0x1f) { |
| 1522 | raise_errmsg(state, ERR_STRING_CONTROL, pystr, next); |
| 1523 | goto bail; |
| 1524 | } |
| 1525 | } |
| 1526 | if (!(c == '"' || c == '\\')) { |
| 1527 | raise_errmsg(state, ERR_STRING_UNTERMINATED, pystr, begin); |
| 1528 | goto bail; |
| 1529 | } |
| 1530 | next++; |
| 1531 | if (c == '"') { |
| 1532 | end = next; |
| 1533 | break; |
| 1534 | } |
| 1535 | /* Backslash escape — ensure writer exists and flush the |
| 1536 | * literal span [literal_start, next-1). */ |
| 1537 | if (writer == NULL) { |
| 1538 | writer = PyUnicodeWriter_Create(len - begin); |
| 1539 | if (writer == NULL) |
| 1540 | goto bail; |
no test coverage detected
searching dependent graphs…