Age Owner Branch data TLA Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * regc_pg_locale.c
4 : : * ctype functions adapted to work on pg_wchar (a/k/a chr),
5 : : * and functions to cache the results of wholesale ctype probing.
6 : : *
7 : : * This file is #included by regcomp.c; it's not meant to compile standalone.
8 : : *
9 : : * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
10 : : * Portions Copyright (c) 1994, Regents of the University of California
11 : : *
12 : : * IDENTIFICATION
13 : : * src/backend/regex/regc_pg_locale.c
14 : : *
15 : : *-------------------------------------------------------------------------
16 : : */
17 : :
18 : : #include "catalog/pg_collation.h"
19 : : #include "common/unicode_case.h"
20 : : #include "common/unicode_category.h"
21 : : #include "utils/pg_locale.h"
22 : : #include "utils/pg_locale_c.h"
23 : :
24 : : static pg_locale_t pg_regex_locale;
25 : :
26 : : static struct pg_locale_struct dummy_c_locale = {
27 : : .collate_is_c = true,
28 : : .ctype_is_c = true,
29 : : };
30 : :
31 : :
32 : : /*
33 : : * pg_set_regex_collation: set collation for these functions to obey
34 : : *
35 : : * This is called when beginning compilation or execution of a regexp.
36 : : * Since there's no need for reentrancy of regexp operations, it's okay
37 : : * to store the results in static variables.
38 : : */
39 : : void
5314 tgl@sss.pgh.pa.us 40 :CBC 4050644 : pg_set_regex_collation(Oid collation)
41 : : {
417 jdavis@postgresql.or 42 : 4050644 : pg_locale_t locale = 0;
43 : :
1376 peter@eisentraut.org 44 [ - + ]: 4050644 : if (!OidIsValid(collation))
45 : : {
46 : : /*
47 : : * This typically means that the parser could not resolve a conflict
48 : : * of implicit collations, so report it that way.
49 : : */
1376 peter@eisentraut.org 50 [ # # ]:UBC 0 : ereport(ERROR,
51 : : (errcode(ERRCODE_INDETERMINATE_COLLATION),
52 : : errmsg("could not determine which collation to use for regular expression"),
53 : : errhint("Use the COLLATE clause to set the collation explicitly.")));
54 : : }
55 : :
416 jdavis@postgresql.or 56 [ + + ]:CBC 4050644 : if (collation == C_COLLATION_OID)
57 : : {
58 : : /*
59 : : * Some callers expect regexes to work for C_COLLATION_OID before
60 : : * catalog access is available, so we can't call
61 : : * pg_newlocale_from_collation().
62 : : */
118 jdavis@postgresql.or 63 :GNC 82781 : locale = &dummy_c_locale;
64 : : }
65 : : else
66 : : {
417 jdavis@postgresql.or 67 :CBC 3967863 : locale = pg_newlocale_from_collation(collation);
68 : :
410 69 [ + + ]: 3967863 : if (!locale->deterministic)
2411 peter@eisentraut.org 70 [ + - ]: 12 : ereport(ERROR,
71 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
72 : : errmsg("nondeterministic collations are not supported for regular expressions")));
73 : :
416 jdavis@postgresql.or 74 [ + + ]: 3967851 : if (locale->ctype_is_c)
75 : : {
76 : : /*
77 : : * C/POSIX collations use this path regardless of database
78 : : * encoding
79 : : */
118 jdavis@postgresql.or 80 :GNC 142 : locale = &dummy_c_locale;
81 : : }
82 : : }
83 : :
417 jdavis@postgresql.or 84 :CBC 4050632 : pg_regex_locale = locale;
5314 tgl@sss.pgh.pa.us 85 : 4050632 : }
86 : :
87 : : /*
88 : : * The following functions overlap with those defined in pg_locale.c. XXX:
89 : : * consider refactor.
90 : : */
91 : :
92 : : static int
13 jdavis@postgresql.or 93 :GNC 93455 : regc_wc_isdigit(pg_wchar c)
94 : : {
118 95 [ + + ]: 93455 : if (pg_regex_locale->ctype_is_c)
96 [ + - ]: 2130 : return (c <= (pg_wchar) 127 &&
97 [ + + ]: 1065 : (pg_char_properties[c] & PG_ISDIGIT));
98 : : else
99 : 92390 : return pg_regex_locale->ctype->wc_isdigit(c, pg_regex_locale);
100 : : }
101 : :
102 : : static int
13 103 : 14859 : regc_wc_isalpha(pg_wchar c)
104 : : {
118 105 [ + + ]: 14859 : if (pg_regex_locale->ctype_is_c)
106 [ + - ]: 768 : return (c <= (pg_wchar) 127 &&
107 [ + + ]: 384 : (pg_char_properties[c] & PG_ISALPHA));
108 : : else
109 : 14475 : return pg_regex_locale->ctype->wc_isalpha(c, pg_regex_locale);
110 : : }
111 : :
112 : : static int
13 113 : 47493 : regc_wc_isalnum(pg_wchar c)
114 : : {
118 115 [ + + ]: 47493 : if (pg_regex_locale->ctype_is_c)
116 [ + - ]: 762 : return (c <= (pg_wchar) 127 &&
117 [ + + ]: 381 : (pg_char_properties[c] & PG_ISALNUM));
118 : : else
119 : 47112 : return pg_regex_locale->ctype->wc_isalnum(c, pg_regex_locale);
120 : : }
121 : :
122 : : static int
13 123 : 18817 : regc_wc_isword(pg_wchar c)
124 : : {
125 : : /* We define word characters as alnum class plus underscore */
1705 tgl@sss.pgh.pa.us 126 [ + + ]:CBC 18817 : if (c == CHR('_'))
127 : 12 : return 1;
13 jdavis@postgresql.or 128 :GNC 18805 : return regc_wc_isalnum(c);
129 : : }
130 : :
131 : : static int
132 : 20488 : regc_wc_isupper(pg_wchar c)
133 : : {
118 134 [ - + ]: 20488 : if (pg_regex_locale->ctype_is_c)
118 jdavis@postgresql.or 135 [ # # ]:UNC 0 : return (c <= (pg_wchar) 127 &&
136 [ # # ]: 0 : (pg_char_properties[c] & PG_ISUPPER));
137 : : else
118 jdavis@postgresql.or 138 :GNC 20488 : return pg_regex_locale->ctype->wc_isupper(c, pg_regex_locale);
139 : : }
140 : :
141 : : static int
13 142 : 8195 : regc_wc_islower(pg_wchar c)
143 : : {
118 144 [ - + ]: 8195 : if (pg_regex_locale->ctype_is_c)
118 jdavis@postgresql.or 145 [ # # ]:UNC 0 : return (c <= (pg_wchar) 127 &&
146 [ # # ]: 0 : (pg_char_properties[c] & PG_ISLOWER));
147 : : else
118 jdavis@postgresql.or 148 :GNC 8195 : return pg_regex_locale->ctype->wc_islower(c, pg_regex_locale);
149 : : }
150 : :
151 : : static int
13 152 : 8195 : regc_wc_isgraph(pg_wchar c)
153 : : {
118 154 [ - + ]: 8195 : if (pg_regex_locale->ctype_is_c)
118 jdavis@postgresql.or 155 [ # # ]:UNC 0 : return (c <= (pg_wchar) 127 &&
156 [ # # ]: 0 : (pg_char_properties[c] & PG_ISGRAPH));
157 : : else
118 jdavis@postgresql.or 158 :GNC 8195 : return pg_regex_locale->ctype->wc_isgraph(c, pg_regex_locale);
159 : : }
160 : :
161 : : static int
13 162 : 8195 : regc_wc_isprint(pg_wchar c)
163 : : {
118 164 [ - + ]: 8195 : if (pg_regex_locale->ctype_is_c)
118 jdavis@postgresql.or 165 [ # # ]:UNC 0 : return (c <= (pg_wchar) 127 &&
166 [ # # ]: 0 : (pg_char_properties[c] & PG_ISPRINT));
167 : : else
118 jdavis@postgresql.or 168 :GNC 8195 : return pg_regex_locale->ctype->wc_isprint(c, pg_regex_locale);
169 : : }
170 : :
171 : : static int
13 172 : 20483 : regc_wc_ispunct(pg_wchar c)
173 : : {
118 174 [ - + ]: 20483 : if (pg_regex_locale->ctype_is_c)
118 jdavis@postgresql.or 175 [ # # ]:UNC 0 : return (c <= (pg_wchar) 127 &&
176 [ # # ]: 0 : (pg_char_properties[c] & PG_ISPUNCT));
177 : : else
118 jdavis@postgresql.or 178 :GNC 20483 : return pg_regex_locale->ctype->wc_ispunct(c, pg_regex_locale);
179 : : }
180 : :
181 : : static int
13 182 : 38193 : regc_wc_isspace(pg_wchar c)
183 : : {
118 184 [ - + ]: 38193 : if (pg_regex_locale->ctype_is_c)
118 jdavis@postgresql.or 185 [ # # ]:UNC 0 : return (c <= (pg_wchar) 127 &&
186 [ # # ]: 0 : (pg_char_properties[c] & PG_ISSPACE));
187 : : else
118 jdavis@postgresql.or 188 :GNC 38193 : return pg_regex_locale->ctype->wc_isspace(c, pg_regex_locale);
189 : : }
190 : :
191 : : static pg_wchar
13 192 : 5351 : regc_wc_toupper(pg_wchar c)
193 : : {
118 194 [ + + ]: 5351 : if (pg_regex_locale->ctype_is_c)
195 : : {
196 [ + - ]: 489 : if (c <= (pg_wchar) 127)
197 : 489 : return pg_ascii_toupper((unsigned char) c);
118 jdavis@postgresql.or 198 :UNC 0 : return c;
199 : : }
200 : : else
118 jdavis@postgresql.or 201 :GNC 4862 : return pg_regex_locale->ctype->wc_toupper(c, pg_regex_locale);
202 : : }
203 : :
204 : : static pg_wchar
13 205 : 5353 : regc_wc_tolower(pg_wchar c)
206 : : {
118 207 [ + + ]: 5353 : if (pg_regex_locale->ctype_is_c)
208 : : {
209 [ + - ]: 489 : if (c <= (pg_wchar) 127)
210 : 489 : return pg_ascii_tolower((unsigned char) c);
118 jdavis@postgresql.or 211 :UNC 0 : return c;
212 : : }
213 : : else
118 jdavis@postgresql.or 214 :GNC 4864 : return pg_regex_locale->ctype->wc_tolower(c, pg_regex_locale);
215 : : }
216 : :
217 : :
218 : : /*
219 : : * These functions cache the results of probing libc's ctype behavior for
220 : : * all character codes of interest in a given encoding/collation. The
221 : : * result is provided as a "struct cvec", but notice that the representation
222 : : * is a touch different from a cvec created by regc_cvec.c: we allocate the
223 : : * chrs[] and ranges[] arrays separately from the struct so that we can
224 : : * realloc them larger at need. This is okay since the cvecs made here
225 : : * should never be freed by freecvec().
226 : : *
227 : : * We use malloc not palloc since we mustn't lose control on out-of-memory;
228 : : * the main regex code expects us to return a failure indication instead.
229 : : */
230 : :
231 : : typedef int (*regc_wc_probefunc) (pg_wchar c);
232 : :
233 : : typedef struct pg_ctype_cache
234 : : {
235 : : regc_wc_probefunc probefunc; /* regc_wc_isalpha or a sibling */
236 : : pg_locale_t locale; /* locale this entry is for */
237 : : struct cvec cv; /* cache entry contents */
238 : : struct pg_ctype_cache *next; /* chain link */
239 : : } pg_ctype_cache;
240 : :
241 : : static pg_ctype_cache *pg_ctype_cache_list = NULL;
242 : :
243 : : /*
244 : : * Add a chr or range to pcc->cv; return false if run out of memory
245 : : */
246 : : static bool
4999 tgl@sss.pgh.pa.us 247 :CBC 5897 : store_match(pg_ctype_cache *pcc, pg_wchar chr1, int nchrs)
248 : : {
249 : : chr *newchrs;
250 : :
251 [ + + ]: 5897 : if (nchrs > 1)
252 : : {
253 [ - + ]: 1860 : if (pcc->cv.nranges >= pcc->cv.rangespace)
254 : : {
4999 tgl@sss.pgh.pa.us 255 :UBC 0 : pcc->cv.rangespace *= 2;
256 : 0 : newchrs = (chr *) realloc(pcc->cv.ranges,
257 : 0 : pcc->cv.rangespace * sizeof(chr) * 2);
258 [ # # ]: 0 : if (newchrs == NULL)
259 : 0 : return false;
260 : 0 : pcc->cv.ranges = newchrs;
261 : : }
4999 tgl@sss.pgh.pa.us 262 :CBC 1860 : pcc->cv.ranges[pcc->cv.nranges * 2] = chr1;
263 : 1860 : pcc->cv.ranges[pcc->cv.nranges * 2 + 1] = chr1 + nchrs - 1;
264 : 1860 : pcc->cv.nranges++;
265 : : }
266 : : else
267 : : {
268 [ - + ]: 4037 : assert(nchrs == 1);
269 [ + + ]: 4037 : if (pcc->cv.nchrs >= pcc->cv.chrspace)
270 : : {
271 : 14 : pcc->cv.chrspace *= 2;
272 : 14 : newchrs = (chr *) realloc(pcc->cv.chrs,
273 : 14 : pcc->cv.chrspace * sizeof(chr));
274 [ - + ]: 14 : if (newchrs == NULL)
4999 tgl@sss.pgh.pa.us 275 :UBC 0 : return false;
4999 tgl@sss.pgh.pa.us 276 :CBC 14 : pcc->cv.chrs = newchrs;
277 : : }
278 : 4037 : pcc->cv.chrs[pcc->cv.nchrs++] = chr1;
279 : : }
280 : 5897 : return true;
281 : : }
282 : :
283 : : /*
284 : : * Given a probe function (e.g., regc_wc_isalpha) get a struct cvec for all
285 : : * chrs satisfying the probe function. The active collation is the one
286 : : * previously set by pg_set_regex_collation. Return NULL if out of memory.
287 : : *
288 : : * Note that the result must not be freed or modified by caller.
289 : : */
290 : : static struct cvec *
13 jdavis@postgresql.or 291 :GNC 439 : regc_ctype_get_cache(regc_wc_probefunc probefunc, int cclasscode)
292 : : {
293 : : pg_ctype_cache *pcc;
294 : : pg_wchar max_chr;
295 : : pg_wchar cur_chr;
296 : : int nmatches;
297 : : chr *newchrs;
298 : :
299 : : /*
300 : : * Do we already have the answer cached?
301 : : */
4999 tgl@sss.pgh.pa.us 302 [ + + ]:CBC 1020 : for (pcc = pg_ctype_cache_list; pcc != NULL; pcc = pcc->next)
303 : : {
304 [ + + ]: 881 : if (pcc->probefunc == probefunc &&
326 peter@eisentraut.org 305 [ + + ]: 336 : pcc->locale == pg_regex_locale)
4999 tgl@sss.pgh.pa.us 306 : 300 : return &pcc->cv;
307 : : }
308 : :
309 : : /*
310 : : * Nope, so initialize some workspace ...
311 : : */
312 : 139 : pcc = (pg_ctype_cache *) malloc(sizeof(pg_ctype_cache));
313 [ - + ]: 139 : if (pcc == NULL)
4999 tgl@sss.pgh.pa.us 314 :UBC 0 : return NULL;
4999 tgl@sss.pgh.pa.us 315 :CBC 139 : pcc->probefunc = probefunc;
326 peter@eisentraut.org 316 : 139 : pcc->locale = pg_regex_locale;
4999 tgl@sss.pgh.pa.us 317 : 139 : pcc->cv.nchrs = 0;
318 : 139 : pcc->cv.chrspace = 128;
319 : 139 : pcc->cv.chrs = (chr *) malloc(pcc->cv.chrspace * sizeof(chr));
320 : 139 : pcc->cv.nranges = 0;
321 : 139 : pcc->cv.rangespace = 64;
322 : 139 : pcc->cv.ranges = (chr *) malloc(pcc->cv.rangespace * sizeof(chr) * 2);
323 [ + - - + ]: 139 : if (pcc->cv.chrs == NULL || pcc->cv.ranges == NULL)
4999 tgl@sss.pgh.pa.us 324 :UBC 0 : goto out_of_memory;
3339 tgl@sss.pgh.pa.us 325 :CBC 139 : pcc->cv.cclasscode = cclasscode;
326 : :
327 : : /*
328 : : * Decide how many character codes we ought to look through. In general
329 : : * we don't go past MAX_SIMPLE_CHR; chr codes above that are handled at
330 : : * runtime using the "high colormap" mechanism. However, in C locale
331 : : * there's no need to go further than 127, and if we only have a 1-byte
332 : : * <ctype.h> API there's no need to go further than that can handle.
333 : : *
334 : : * If it's not MAX_SIMPLE_CHR that's constraining the search, mark the
335 : : * output cvec as not having any locale-dependent behavior, since there
336 : : * will be no need to do any run-time locale checks. (The #if's here
337 : : * would always be true for production values of MAX_SIMPLE_CHR, but it's
338 : : * useful to allow it to be small for testing purposes.)
339 : : */
118 jdavis@postgresql.or 340 [ + + ]:GNC 139 : if (pg_regex_locale->ctype_is_c)
341 : : {
342 : : #if MAX_SIMPLE_CHR >= 127
343 : 14 : max_chr = (pg_wchar) 127;
344 : 14 : pcc->cv.cclasscode = -1;
345 : : #else
346 : : max_chr = (pg_wchar) MAX_SIMPLE_CHR;
347 : : #endif
348 : : }
349 : : else
350 : : {
351 [ - + ]: 125 : if (pg_regex_locale->ctype->max_chr != 0 &&
118 jdavis@postgresql.or 352 [ # # ]:UNC 0 : pg_regex_locale->ctype->max_chr <= MAX_SIMPLE_CHR)
353 : : {
354 : 0 : max_chr = pg_regex_locale->ctype->max_chr;
3339 tgl@sss.pgh.pa.us 355 :UBC 0 : pcc->cv.cclasscode = -1;
356 : : }
357 : : else
3140 peter_e@gmx.net 358 :CBC 125 : max_chr = (pg_wchar) MAX_SIMPLE_CHR;
359 : : }
360 : :
361 : : /*
362 : : * And scan 'em ...
363 : : */
4999 tgl@sss.pgh.pa.us 364 : 139 : nmatches = 0; /* number of consecutive matches */
365 : :
366 [ + + ]: 257931 : for (cur_chr = 0; cur_chr <= max_chr; cur_chr++)
367 : : {
368 [ + + ]: 257792 : if ((*probefunc) (cur_chr))
369 : 71040 : nmatches++;
370 [ + + ]: 186752 : else if (nmatches > 0)
371 : : {
372 [ - + ]: 5885 : if (!store_match(pcc, cur_chr - nmatches, nmatches))
4999 tgl@sss.pgh.pa.us 373 :UBC 0 : goto out_of_memory;
4999 tgl@sss.pgh.pa.us 374 :CBC 5885 : nmatches = 0;
375 : : }
376 : : }
377 : :
378 [ + + ]: 139 : if (nmatches > 0)
379 [ - + ]: 12 : if (!store_match(pcc, cur_chr - nmatches, nmatches))
4999 tgl@sss.pgh.pa.us 380 :UBC 0 : goto out_of_memory;
381 : :
382 : : /*
383 : : * We might have allocated more memory than needed, if so free it
384 : : */
4999 tgl@sss.pgh.pa.us 385 [ + + ]:CBC 139 : if (pcc->cv.nchrs == 0)
386 : : {
387 : 56 : free(pcc->cv.chrs);
388 : 56 : pcc->cv.chrs = NULL;
389 : 56 : pcc->cv.chrspace = 0;
390 : : }
391 [ + - ]: 83 : else if (pcc->cv.nchrs < pcc->cv.chrspace)
392 : : {
393 : 83 : newchrs = (chr *) realloc(pcc->cv.chrs,
394 : 83 : pcc->cv.nchrs * sizeof(chr));
395 [ - + ]: 83 : if (newchrs == NULL)
4999 tgl@sss.pgh.pa.us 396 :UBC 0 : goto out_of_memory;
4999 tgl@sss.pgh.pa.us 397 :CBC 83 : pcc->cv.chrs = newchrs;
398 : 83 : pcc->cv.chrspace = pcc->cv.nchrs;
399 : : }
400 [ - + ]: 139 : if (pcc->cv.nranges == 0)
401 : : {
4999 tgl@sss.pgh.pa.us 402 :UBC 0 : free(pcc->cv.ranges);
403 : 0 : pcc->cv.ranges = NULL;
404 : 0 : pcc->cv.rangespace = 0;
405 : : }
4999 tgl@sss.pgh.pa.us 406 [ + - ]:CBC 139 : else if (pcc->cv.nranges < pcc->cv.rangespace)
407 : : {
408 : 139 : newchrs = (chr *) realloc(pcc->cv.ranges,
409 : 139 : pcc->cv.nranges * sizeof(chr) * 2);
410 [ - + ]: 139 : if (newchrs == NULL)
4999 tgl@sss.pgh.pa.us 411 :UBC 0 : goto out_of_memory;
4999 tgl@sss.pgh.pa.us 412 :CBC 139 : pcc->cv.ranges = newchrs;
413 : 139 : pcc->cv.rangespace = pcc->cv.nranges;
414 : : }
415 : :
416 : : /*
417 : : * Success, link it into cache chain
418 : : */
419 : 139 : pcc->next = pg_ctype_cache_list;
420 : 139 : pg_ctype_cache_list = pcc;
421 : :
422 : 139 : return &pcc->cv;
423 : :
424 : : /*
425 : : * Failure, clean up
426 : : */
4999 tgl@sss.pgh.pa.us 427 :UBC 0 : out_of_memory:
1229 peter@eisentraut.org 428 : 0 : free(pcc->cv.chrs);
429 : 0 : free(pcc->cv.ranges);
4999 tgl@sss.pgh.pa.us 430 : 0 : free(pcc);
431 : :
432 : 0 : return NULL;
433 : : }
|