|
| 1 | +--TEST-- |
| 2 | +json_encode() SSE2 fast-path boundary handling |
| 3 | +--FILE-- |
| 4 | +<?php |
| 5 | +/* Regression tests for the SSE2 chunked fast path in |
| 6 | + * php_json_escape_string() (ext/json/json_encoder.c). The fast path scans |
| 7 | + * 16-byte chunks and resumes scanning right after each escaped byte, so |
| 8 | + * exercise lengths and escape/codepoint positions around that boundary. */ |
| 9 | + |
| 10 | +function check($label, $actual, $expected) { |
| 11 | + if ($actual === $expected) { |
| 12 | + echo "$label: OK\n"; |
| 13 | + } else { |
| 14 | + echo "$label: FAIL\n"; |
| 15 | + var_dump($expected, $actual); |
| 16 | + } |
| 17 | +} |
| 18 | + |
| 19 | +// 1. Pure clean ASCII at/around the 16-byte chunk boundary. |
| 20 | +foreach ([15, 16, 17, 31, 32, 33] as $len) { |
| 21 | + $s = str_repeat('a', $len); |
| 22 | + check("clean len=$len", json_encode($s), '"' . $s . '"'); |
| 23 | +} |
| 24 | + |
| 25 | +// 2. A single escapable byte at each offset around the boundary. |
| 26 | +foreach ([0, 15, 16, 17] as $off) { |
| 27 | + $len = 24; |
| 28 | + $s = str_repeat('a', $off) . '"' . str_repeat('a', $len - $off - 1); |
| 29 | + $expected = '"' . str_repeat('a', $off) . '\\"' . str_repeat('a', $len - $off - 1) . '"'; |
| 30 | + check("quote at offset=$off", json_encode($s), $expected); |
| 31 | +} |
| 32 | + |
| 33 | +// 3. A 3-byte UTF-8 sequence (EUR SIGN, U+20AC) straddling the boundary. |
| 34 | +// Checked both with the default \uXXXX escaping and with |
| 35 | +// JSON_UNESCAPED_UNICODE, which appends the raw UTF-8 bytes instead. |
| 36 | +foreach ([13, 14, 15, 16] as $off) { |
| 37 | + $len = 20; |
| 38 | + $s = str_repeat('a', $off) . "\xe2\x82\xac" . str_repeat('a', $len - $off - 3); |
| 39 | + $tail = str_repeat('a', $len - $off - 3); |
| 40 | + check("3-byte utf8 at offset=$off", json_encode($s), |
| 41 | + '"' . str_repeat('a', $off) . "\\u20ac" . $tail . '"'); |
| 42 | + check("3-byte utf8 at offset=$off, UNESCAPED_UNICODE", json_encode($s, JSON_UNESCAPED_UNICODE), |
| 43 | + '"' . str_repeat('a', $off) . '€' . $tail . '"'); |
| 44 | +} |
| 45 | + |
| 46 | +// 4. A 4-byte UTF-8 sequence (surrogate pair, U+1F600) straddling the boundary. |
| 47 | +foreach ([12, 13, 14, 15, 16] as $off) { |
| 48 | + $len = 20; |
| 49 | + $s = str_repeat('a', $off) . "\xf0\x9f\x98\x80" . str_repeat('a', $len - $off - 4); |
| 50 | + $tail = str_repeat('a', $len - $off - 4); |
| 51 | + check("4-byte utf8 at offset=$off", json_encode($s), |
| 52 | + '"' . str_repeat('a', $off) . "\\ud83d\\ude00" . $tail . '"'); |
| 53 | + check("4-byte utf8 at offset=$off, UNESCAPED_UNICODE", json_encode($s, JSON_UNESCAPED_UNICODE), |
| 54 | + '"' . str_repeat('a', $off) . '😀' . $tail . '"'); |
| 55 | +} |
| 56 | + |
| 57 | +// 5. Invalid UTF-8 straddling the boundary: confirm the checkpoint/rollback |
| 58 | +// and each INVALID_UTF8_* option still land correctly after the fast path |
| 59 | +// has already appended bytes. |
| 60 | +foreach ([14, 15, 16, 17] as $off) { |
| 61 | + $len = 20; |
| 62 | + $s = str_repeat('a', $off) . "\xb0" . str_repeat('a', $len - $off - 1); |
| 63 | + $tail = str_repeat('a', $len - $off - 1); |
| 64 | + |
| 65 | + check("invalid utf8 at offset=$off, no flag", json_encode($s), false); |
| 66 | + |
| 67 | + check("invalid utf8 at offset=$off, IGNORE", |
| 68 | + json_encode($s, JSON_INVALID_UTF8_IGNORE), |
| 69 | + '"' . str_repeat('a', $off) . $tail . '"'); |
| 70 | + |
| 71 | + check("invalid utf8 at offset=$off, SUBSTITUTE", |
| 72 | + json_encode($s, JSON_INVALID_UTF8_SUBSTITUTE), |
| 73 | + '"' . str_repeat('a', $off) . "\\ufffd" . $tail . '"'); |
| 74 | +} |
| 75 | +?> |
| 76 | +--EXPECT-- |
| 77 | +clean len=15: OK |
| 78 | +clean len=16: OK |
| 79 | +clean len=17: OK |
| 80 | +clean len=31: OK |
| 81 | +clean len=32: OK |
| 82 | +clean len=33: OK |
| 83 | +quote at offset=0: OK |
| 84 | +quote at offset=15: OK |
| 85 | +quote at offset=16: OK |
| 86 | +quote at offset=17: OK |
| 87 | +3-byte utf8 at offset=13: OK |
| 88 | +3-byte utf8 at offset=13, UNESCAPED_UNICODE: OK |
| 89 | +3-byte utf8 at offset=14: OK |
| 90 | +3-byte utf8 at offset=14, UNESCAPED_UNICODE: OK |
| 91 | +3-byte utf8 at offset=15: OK |
| 92 | +3-byte utf8 at offset=15, UNESCAPED_UNICODE: OK |
| 93 | +3-byte utf8 at offset=16: OK |
| 94 | +3-byte utf8 at offset=16, UNESCAPED_UNICODE: OK |
| 95 | +4-byte utf8 at offset=12: OK |
| 96 | +4-byte utf8 at offset=12, UNESCAPED_UNICODE: OK |
| 97 | +4-byte utf8 at offset=13: OK |
| 98 | +4-byte utf8 at offset=13, UNESCAPED_UNICODE: OK |
| 99 | +4-byte utf8 at offset=14: OK |
| 100 | +4-byte utf8 at offset=14, UNESCAPED_UNICODE: OK |
| 101 | +4-byte utf8 at offset=15: OK |
| 102 | +4-byte utf8 at offset=15, UNESCAPED_UNICODE: OK |
| 103 | +4-byte utf8 at offset=16: OK |
| 104 | +4-byte utf8 at offset=16, UNESCAPED_UNICODE: OK |
| 105 | +invalid utf8 at offset=14, no flag: OK |
| 106 | +invalid utf8 at offset=14, IGNORE: OK |
| 107 | +invalid utf8 at offset=14, SUBSTITUTE: OK |
| 108 | +invalid utf8 at offset=15, no flag: OK |
| 109 | +invalid utf8 at offset=15, IGNORE: OK |
| 110 | +invalid utf8 at offset=15, SUBSTITUTE: OK |
| 111 | +invalid utf8 at offset=16, no flag: OK |
| 112 | +invalid utf8 at offset=16, IGNORE: OK |
| 113 | +invalid utf8 at offset=16, SUBSTITUTE: OK |
| 114 | +invalid utf8 at offset=17, no flag: OK |
| 115 | +invalid utf8 at offset=17, IGNORE: OK |
| 116 | +invalid utf8 at offset=17, SUBSTITUTE: OK |
0 commit comments