File Coverage

UTF8.xs
Criterion Covered Total %
statement 271 321 84.4
branch 148 222 66.6
condition n/a
subroutine n/a
pod n/a
total 419 543 77.1


line stmt bran cond sub pod time code
1             #define PERL_NO_GET_CONTEXT
2             #include "EXTERN.h"
3             #include "perl.h"
4             #include "XSUB.h"
5             #define NEED_sv_2pv_flags
6             #include "ppport.h"
7              
8             #include "utf8_dfa32.h"
9             #include "utf8_valid.h"
10             #include "utf8_valid_stream.h"
11             #include "utf8_distance_unsafe.h"
12             #include "utf8_advance_forward_unsafe.h"
13              
14             #ifndef WARN_NON_UNICODE
15             # define WARN_NON_UNICODE WARN_UTF8
16             # define WARN_NONCHAR WARN_UTF8
17             # define WARN_SURROGATE WARN_UTF8
18             #endif
19              
20             #ifndef SvPVCLEAR
21             # define SvPVCLEAR(sv) sv_setpvs((sv), "")
22             #endif
23              
24             static inline STRLEN
25 2586           xs_utf8_check(const U8 *src, STRLEN len) {
26             STRLEN off;
27 2586           utf8_check_ascii((const char *)src, len, &off);
28 2586           return off;
29             };
30              
31             static void
32 3072           xs_report_unmappable(pTHX_ const UV cp, const STRLEN pos) {
33             const char *fmt;
34             U32 cat;
35              
36 3072 100         if (cp > 0x10FFFF) {
37 1024           fmt = "Can't represent super code point \\x{%"UVXf"} in position %"UVuf;
38 1024           cat = WARN_NON_UNICODE;
39             }
40 2048 50         else if ((cp & 0xF800) == 0xD800) {
41 2048           fmt = "Can't represent surrogate code point U+%"UVXf" in position %"UVuf;
42 2048           cat = WARN_SURROGATE;
43             }
44             else {
45 0           fmt = "Can't represent code point U+%04"UVXf" in position %"UVuf;
46 0           cat = WARN_UTF8;
47             }
48              
49             #if PERL_REVISION == 5 && PERL_VERSION >= 14
50 3072           Perl_ck_warner_d(aTHX_ packWARN(cat), fmt, cp, (UV)pos);
51             #else
52             Perl_warner(aTHX_ packWARN(cat), fmt, cp, (UV)pos);
53             #endif
54 0           }
55              
56             static void
57 2622           xs_report_illformed(pTHX_ const U8 *s, STRLEN len, const char *enc, STRLEN pos, const bool fatal) {
58             static const char *fmt = "Can't decode ill-formed %s octet sequence <%s> in position %"UVuf;
59             static const char *hex = "0123456789ABCDEF";
60             char seq[20 * 3 + 4];
61 2622           char *d = seq, *dstop = d + sizeof(seq) - 4;
62              
63 6350 100         while (len-- > 0) {
64 3728           const U8 c = *s++;
65 3728           *d++ = hex[c >> 4];
66 3728           *d++ = hex[c & 15];
67 3728 100         if (len) {
68 1106           *d++ = ' ';
69 1106 50         if (d == dstop) {
70 0           *d++ = '.', *d++ = '.', *d++ = '.';
71 0           break;
72             }
73             }
74             }
75 2622           *d = 0;
76              
77 2622 100         if (fatal)
78 287           Perl_croak(aTHX_ fmt, enc, seq, (UV)pos);
79             else
80 2335           Perl_warner(aTHX_ packWARN(WARN_UTF8), fmt, enc, seq, (UV)pos);
81 0           }
82              
83             static void
84 2           xs_report_illformed_read(pTHX_ const char* cur, STRLEN len, bool eof) {
85             static const char hex[] = "0123456789ABCDEF";
86             char seq[4 * 3];
87 2           char* d = seq;
88 2           STRLEN n = len;
89 2           const char* fmt =
90             eof ? "Can't decode ill-formed UTF-8 octet sequence <%s> at end of file"
91 2 100         : "Can't decode ill-formed UTF-8 octet sequence <%s>";
92              
93 4 100         while (n-- > 0) {
94 2           const U8 c = (U8)*cur++;
95 2           *d++ = hex[c >> 4];
96 2           *d++ = hex[c & 0xF];
97 2 50         if (n)
98 0           *d++ = ' ';
99             }
100 2           *d = '\0';
101 2           Perl_warner(aTHX_ packWARN(WARN_UTF8), fmt, seq);
102 2           }
103              
104             static void
105             xs_utf8_encode_native(pTHX_ SV *, const U8 *, STRLEN, const bool);
106              
107             static void
108 21           xs_handle_fallback(pTHX_ SV *dsv, CV *fallback, SV *val, UV usv, STRLEN pos) {
109 21           dSP;
110             SV *str;
111             const char *src;
112             STRLEN len;
113             int count;
114              
115 21           ENTER;
116 21           SAVETMPS;
117              
118 21 50         PUSHMARK(SP);
119 21 50         EXTEND(SP, 2);
120 21           mPUSHs(val);
121 21           mPUSHu(usv);
122 21           mPUSHu((UV)pos);
123 21           PUTBACK;
124              
125 21           count = call_sv((SV *)fallback, G_SCALAR);
126              
127 21           SPAGAIN;
128              
129 21 50         if (count != 1)
130 0           croak("expected 1 return value from fallback sub, got %d\n", count);
131              
132 21           str = POPs;
133 21           src = SvPV_const(str, len);
134 21 100         if (SvUTF8(str))
135 9           sv_catpvn_nomg(dsv, src, len); /* XXX validate? */
136             else
137 12           xs_utf8_encode_native(aTHX_ dsv, (const U8 *)src, len, TRUE);
138              
139 21           PUTBACK;
140 21 50         FREETMPS;
141 21           LEAVE;
142 21           }
143              
144             static void
145 3943           xs_utf8_decode_replace(pTHX_ SV *dsv, const U8 *src, STRLEN len, STRLEN off, CV *fallback) {
146 3943           const bool do_warn = ckWARN_d(WARN_UTF8);
147              
148 3943           STRLEN pos = 0;
149             STRLEN skip;
150              
151 3943 100         (void)SvUPGRADE(dsv, SVt_PV);
152 3943 100         (void)SvGROW(dsv, off + 1);
    100          
153 3943           SvCUR_set(dsv, 0);
154 3943           SvPOK_only(dsv);
155              
156             do {
157 4913           src += off;
158 4913           len -= off;
159 4913           pos += off;
160              
161 4913           skip = utf8_maximal_subpart((const char *)src, len);
162              
163 4913 100         if (do_warn) {
164 2335           xs_report_illformed(aTHX_ src, skip, "UTF-8", pos, FALSE);
165             }
166              
167 2578           sv_catpvn_nomg(dsv, (const char *)src - off, off);
168              
169 2578 100         if (fallback) {
170 16           SV *octets = newSVpvn((const char *)src, skip);
171 16           xs_handle_fallback(aTHX_ dsv, fallback, octets, 0, pos);
172             }
173             else
174 2562           sv_catpvn_nomg(dsv, "\xEF\xBF\xBD", 3);
175              
176 2578           src += skip;
177 2578           len -= skip;
178 2578           pos += skip;
179              
180 2578           off = xs_utf8_check(src, len);
181 2578 100         if (off == len) {
182 1608           sv_catpvn_nomg(dsv, (const char *)src, off);
183 1608           break;
184             }
185 970 50         } while (len);
186 1608           }
187              
188             static void
189 3363           xs_utf8_encode_replace(pTHX_ SV *dsv, const U8 *src, STRLEN len, STRLEN off, CV *fallback) {
190             #if PERL_REVISION == 5 && PERL_VERSION >= 14
191 3363           const bool do_warn = ckWARN4_d(WARN_UTF8, WARN_NONCHAR, WARN_SURROGATE, WARN_NON_UNICODE);
192             #else
193             const bool do_warn = ckWARN_d(WARN_UTF8);
194             #endif
195 3363           STRLEN pos = 0;
196             STRLEN skip;
197             UV v;
198              
199 3363 100         (void)SvUPGRADE(dsv, SVt_PV);
200 3363 50         (void)SvGROW(dsv, off + 1);
    100          
201 3363           SvCUR_set(dsv, 0);
202 3363           SvPOK_only(dsv);
203              
204             do {
205 3367           src += off;
206 3367           len -= off;
207 3367           pos += utf8_length(src - off, src);
208              
209 3367           v = utf8n_to_uvchr(src, len, &skip, (UTF8_ALLOW_ANYUV|UTF8_CHECK_ONLY) & ~UTF8_ALLOW_LONG);
210 3367 100         if (skip == (STRLEN) -1) {
211 287           skip = 1;
212 287 100         if (UTF8_IS_START(*src)) {
213 286           STRLEN n = UTF8SKIP(src);
214 286 100         if (n > len)
215 272           n = len;
216 865 100         while (skip < n && UTF8_IS_CONTINUATION(src[skip]))
    50          
217 579           skip++;
218             }
219 287           xs_report_illformed(aTHX_ src, skip, "UTF-X", pos, TRUE);
220             }
221 3080 100         if (do_warn)
222 3072           xs_report_unmappable(aTHX_ v, pos);
223              
224 8           sv_catpvn_nomg(dsv, (const char *)src - off, off);
225              
226 8 100         if (fallback) {
227 5           SV *codepoint = newSVuv(v);
228 5 50         UV usv = (v <= 0x10FFFF && (v & 0xF800) != 0xD800) ? v : 0;
    0          
229 5           xs_handle_fallback(aTHX_ dsv, fallback, codepoint, usv, pos);
230             }
231             else
232 3           sv_catpvn_nomg(dsv, "\xEF\xBF\xBD", 3);
233              
234 8           src += skip;
235 8           len -= skip;
236 8           pos += 1;
237              
238 8           off = xs_utf8_check(src, len);
239 8 100         if (off == len) {
240 4           sv_catpvn_nomg(dsv, (const char *)src, off);
241 4           break;
242             }
243 4 50         } while (len);
244 4           }
245              
246             static void
247 14           xs_utf8_encode_native(pTHX_ SV *dsv, const U8 *src, STRLEN len, const bool append) {
248 14           const U8 *end = src + len;
249             U8 *d;
250 14           STRLEN off = 0;
251              
252 14 100         if (append)
253 12           off = SvCUR(dsv);
254              
255 14 100         (void)SvUPGRADE(dsv, SVt_PV);
256 14 50         (void)SvGROW(dsv, off + len * 2 + 1);
    100          
257 14           d = (U8 *)SvPVX(dsv) + off;
258              
259 48 100         for (; src < end; src++) {
260 34           const U8 c = *src;
261 34 100         if (c < 0x80)
262 23           *d++ = c;
263             else {
264 11           *d++ = (U8)(0xC0 | ((c >> 6) & 0x1F));
265 11           *d++ = (U8)(0x80 | ((c ) & 0x3F));
266             }
267             }
268 14           *d = 0;
269 14           SvCUR_set(dsv, d - (U8 *)SvPVX(dsv));
270 14           SvPOK_only(dsv);
271 14           }
272              
273             static void
274 0           xs_utf8_encode_native_inplace(pTHX_ SV *sv, const U8 *s, STRLEN len) {
275 0           const U8 *p = s;
276 0           const U8 *e = s + len;
277              
278 0 0         while (p < e && *p < 0x80)
    0          
279 0           p++;
280              
281 0 0         if (p != e) {
282             STRLEN size, off;
283             U8 *d;
284              
285 0           off = p - s;
286 0           size = len;
287 0 0         while (p < e)
288 0           size += (*p++ > 0x7F);
289              
290 0 0         if (SvLEN(sv) < size + 1) {
291 0           (void)sv_grow(sv, size + 1);
292 0           s = (const U8 *)SvPVX(sv);
293 0           e = s + len;
294             }
295 0           d = (U8 *)SvPVX(sv) + size;
296 0           *d = 0;
297 0 0         for (s += off, e--; e >= s; e--) {
298 0           const U8 c = *e;
299 0 0         if (c < 0x80)
300 0           *--d = c;
301             else {
302 0           *--d = (U8)(0x80 | ((c ) & 0x3F));
303 0           *--d = (U8)(0xC0 | ((c >> 6) & 0x1F));
304             }
305             }
306 0           SvCUR_set(sv, size);
307             }
308 0           SvPOK_only(sv);
309 0           }
310              
311             static void
312 3           xs_utf8_downgrade(pTHX_ SV *dsv, const U8 *s, STRLEN len) {
313 3           const U8 *e = s + len - 1;
314             U8 *d, c, v;
315              
316 3 50         (void)SvUPGRADE(dsv, SVt_PV);
317 3 50         (void)SvGROW(dsv, len + 1);
    50          
318 3           d = (U8 *)SvPVX(dsv);
319              
320 28 100         while (s < e) {
321 26           c = *s++;
322 26 100         if (c < 0x80)
323 14           *d++ = c;
324             else {
325 12 100         if ((c & 0xFE) != 0xC2)
326 1           goto error;
327 11           v = (c & 0x1F) << 6;
328 11           c = *s++;
329 11 50         if ((c & 0xC0) != 0x80)
330 0           goto error;
331 11           *d++ = (U8)(v | (c & 0x3F));
332             }
333             }
334 2 100         if (s < e + 1) {
335 1 50         if (*s < 0x80)
336 1           *d++ = *s;
337             else {
338 0           error:
339 1           croak("Can't decode a wide character string");
340             }
341             }
342 2           *d = 0;
343 2           SvCUR_set(dsv, d - (U8 *)SvPVX(dsv));
344 2           SvPOK_only(dsv);
345 2           }
346              
347             /* SVt_PV, SVt_PVIV, SVt_PVNV, SVt_PVMG */
348             #define SvPV_stealable(sv) \
349             ((SvFLAGS(sv) & ~(SVTYPEMASK|SVf_UTF8)) == (SVs_TEMP|SVf_POK|SVp_POK) && \
350             (SvTYPE(sv) >= SVt_PV && SvTYPE(sv) <= SVt_PVMG) && SvREFCNT(sv) == 1)
351              
352             MODULE = Unicode::UTF8 PACKAGE = Unicode::UTF8
353              
354             PROTOTYPES: DISABLE
355              
356             void
357             decode_utf8(octets, fallback=NULL)
358             SV *octets
359             CV *fallback
360             PREINIT:
361             const U8 *src;
362             STRLEN len, off;
363             bool reuse_sv;
364             PPCODE:
365 4289           src = (const U8 *)SvPV_const(octets, len);
366 4289 50         reuse_sv = SvPV_stealable(octets);
    0          
    0          
    0          
367 4289 100         if (SvUTF8(octets)) {
368 3 50         if (!reuse_sv) {
369 3           octets = sv_newmortal();
370 3           reuse_sv = TRUE;
371             }
372 3           xs_utf8_downgrade(aTHX_ octets, src, len);
373 2 50         if (SvCUR(octets) == len) {
374 0           ST(0) = octets;
375 0           SvUTF8_on(octets);
376 0           XSRETURN(1);
377             }
378 2           src = (const U8 *)SvPV_const(octets, len);
379             }
380 4288 100         if (utf8_check_ascii((const char *)src, len, &off)) {
381 345 100         if (reuse_sv) {
382 2           ST(0) = octets;
383 2           SvUTF8_on(octets);
384 2           XSRETURN(1);
385             }
386             else {
387 343 50         dXSTARG;
388 343           sv_setpvn(TARG, (const char *)src, len);
389 343           SvUTF8_on(TARG);
390 343 50         PUSHTARG;
391             }
392             }
393             else {
394 3943 50         dXSTARG;
395 3943           xs_utf8_decode_replace(aTHX_ TARG, src, len, off, fallback);
396 1608           SvUTF8_on(TARG);
397 1608 50         PUSHTARG;
398             }
399              
400             void
401             encode_utf8(string, fallback=NULL)
402             SV *string
403             CV *fallback
404             PREINIT:
405             const U8 *src;
406             STRLEN len;
407             bool reuse_sv;
408             PPCODE:
409 3707           src = (const U8 *)SvPV_const(string, len);
410 3707 50         reuse_sv = SvPV_stealable(string);
    0          
    0          
    0          
411 3707 100         if (!SvUTF8(string)) {
412 2 50         if (reuse_sv) {
413 0           xs_utf8_encode_native_inplace(aTHX_ string, src, len);
414 0           ST(0) = string;
415 0           XSRETURN(1);
416             }
417             else {
418 2 50         dXSTARG;
419 2           xs_utf8_encode_native(aTHX_ TARG, src, len, FALSE);
420 2 50         SvTAINT(TARG);
    0          
421 2 50         PUSHTARG;
422             }
423             }
424             else {
425             STRLEN off;
426 3705 100         if (utf8_check_ascii((const char *)src, len, &off)) {
427 342 50         if (reuse_sv) {
428 0           ST(0) = string;
429 0           SvUTF8_off(string);
430 0           XSRETURN(1);
431             }
432             else {
433 342 50         dXSTARG;
434 342           sv_setpvn(TARG, (const char *)src, len);
435 342           SvUTF8_off(TARG);
436 342 50         PUSHTARG;
437             }
438             }
439             else {
440 3363 50         dXSTARG;
441 3363           xs_utf8_encode_replace(aTHX_ TARG, src, len, off, fallback);
442 4 50         PUSHTARG;
443             }
444             }
445              
446             IV
447             read_utf8(fh, bufsv, length, offset = 0)
448             PerlIO *fh
449             SV *bufsv
450             IV length
451             IV offset
452             PROTOTYPE:
453             *$$;$
454             CODE:
455             {
456             utf8_valid_stream_t s;
457 16           UV got = 0; // code points counted so far
458 16           STRLEN fed = 0; // bytes present in the output region
459 16           STRLEN complete = 0; // last counted code-point boundary
460 16           STRLEN base = 0; // byte offset this call writes at
461 16           STRLEN pad = 0; // zero-fill gap for offset past end
462             STRLEN blen; // existing content length
463             char* buf;
464              
465 16 100         if (length < 0)
466 1           croak("Negative length");
467              
468 15 100         if (!SvOK(bufsv))
469 13           SvPVCLEAR(bufsv);
470 15           (void)SvPVutf8_force(bufsv, blen);
471              
472             // Resolve the byte offset (base) where this call starts writing
473 15 100         if (offset != 0) {
474 2           char *pv = SvPVX(bufsv);
475 2           size_t chars = utf8_distance_unsafe(pv, blen);
476 2 50         if (offset < 0) {
477 0           size_t back = (size_t)-offset;
478 0 0         if (back > chars)
479 0           croak("Offset outside string");
480 0           base = utf8_advance_forward_unsafe(pv, blen, chars - back, NULL);
481             }
482 2 100         else if ((size_t)offset == chars) {
483 1           base = blen;
484             }
485 1 50         else if ((size_t)offset < chars) {
486 0           base = utf8_advance_forward_unsafe(pv, blen, (size_t)offset, NULL);
487             }
488             else {
489 1           pad = (STRLEN)((size_t)offset - chars);
490 1           base = blen + pad;
491             }
492             }
493              
494             // Single worst-case allocation: FFFD is 3 bytes, a valid code point up
495             // to 4, so base + length*4 + 1 covers the call; buffer never regrows.
496 15 50         buf = SvGROW(bufsv, base + (STRLEN)length * 4 + 1);
    100          
497              
498             // Zero-fill the offset-past-end gap.
499 15 100         if (pad) {
500 1           Zero(buf + blen, pad, char);
501 1           SvCUR_set(bufsv, base);
502             }
503              
504 15           buf += base;
505 15           utf8_valid_stream_init(&s);
506              
507 37 100         while (got < (size_t)length) {
508 29           STRLEN req = (STRLEN)(length - got);
509 29           SSize_t count = PerlIO_read(fh, buf + fed, req);
510              
511 29 50         if (count < 0 || (count == 0 && PerlIO_error(fh)))
    100          
    50          
512 0           XSRETURN_UNDEF;
513              
514 29           bool eof = (count == 0);
515 29           STRLEN scan = fed;
516 29           fed += (STRLEN)count;
517              
518             // Drain the newly read bytes; a single read may contain several
519             // ill-formed subparts, each replaced in place with U+FFFD.
520 4           for (;;) {
521             utf8_valid_stream_result_t r;
522 33           r = utf8_valid_stream_check(&s, buf + scan, fed - scan, eof);
523              
524 33 100         if (r.status == UTF8_VALID_STREAM_ILLFORMED ||
525 29 100         r.status == UTF8_VALID_STREAM_TRUNCATED) {
526 5           STRLEN sub_start = scan + r.consumed - r.carried; // region-rel
527 5           STRLEN sublen = r.carried + r.advance; // <= 3
528 5           STRLEN delta = 3 - sublen; // >= 0
529              
530 5 100         if (ckWARN_d(WARN_UTF8))
531 2           xs_report_illformed_read(aTHX_ buf + sub_start, sublen, eof);
532              
533 5 50         if (delta) { // make room, then write FFFD
534 5           Move(buf + sub_start + sublen,
535             buf + sub_start + sublen + delta,
536             fed - (sub_start + sublen), char);
537 5           fed += delta;
538             }
539              
540 5           memcpy(buf + sub_start, "\xEF\xBF\xBD", 3);
541 5           scan = sub_start + 3; // resume just past the FFFD
542              
543 5 100         if (r.status == UTF8_VALID_STREAM_TRUNCATED)
544 1           break; // truncated is the final tail
545 4           continue; // ill-formed: keep draining
546             }
547 28           break; // OK / PARTIAL: chunk drained
548             }
549              
550             // Count completed code points (FFFD included).
551             {
552 29           STRLEN boundary = fed - s.pending;
553 29           got += utf8_distance_unsafe(buf + complete, boundary - complete);
554 29           complete = boundary;
555             }
556              
557 29 100         if (eof)
558 7           break;
559             }
560              
561 15           SvCUR_set(bufsv, base + fed);
562 15           *SvEND(bufsv) = '\0';
563 15           SvUTF8_on(bufsv);
564 15 50         SvSETMAGIC(bufsv);
565 15 100         RETVAL = (IV)got;
566             }
567             OUTPUT:
568             RETVAL
569              
570             void
571             valid_utf8(octets)
572             SV *octets
573             PREINIT:
574             const char *src;
575             STRLEN len;
576             PPCODE:
577 2676           src = SvPV_const(octets, len);
578 2676 100         if (SvUTF8(octets)) {
579 2           octets = sv_mortalcopy(octets);
580 2 100         if (!sv_utf8_downgrade(octets, TRUE))
581 1           croak("Can't validate a wide character string");
582 1           src = SvPV_const(octets, len);
583             }
584 2675 100         ST(0) = boolSV(utf8_valid_ascii(src, len));
585 2675           XSRETURN(1);
586