Coverage Report

Created: 2026-10-16 06:48

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/postgres/src/backend/regex/regprefix.c
Line
Count
Source
1
/*-------------------------------------------------------------------------
2
 *
3
 * regprefix.c
4
 *    Extract a common prefix, if any, from a compiled regex.
5
 *
6
 *
7
 * Portions Copyright (c) 2012-2026, PostgreSQL Global Development Group
8
 * Portions Copyright (c) 1998, 1999 Henry Spencer
9
 *
10
 * IDENTIFICATION
11
 *    src/backend/regex/regprefix.c
12
 *
13
 *-------------------------------------------------------------------------
14
 */
15
16
#include "regex/regguts.h"
17
18
19
/*
20
 * forward declarations
21
 */
22
static int  findprefix(struct cnfa *cnfa, struct colormap *cm,
23
             chr *string, size_t *slength);
24
25
26
/*
27
 * pg_regprefix - get common prefix for regular expression
28
 *
29
 * Returns one of:
30
 *  REG_NOMATCH: there is no common prefix of strings matching the regex
31
 *  REG_PREFIX: there is a common prefix of strings matching the regex
32
 *  REG_EXACT: all strings satisfying the regex must match the same string
33
 *  or a REG_XXX error code
34
 *
35
 * In the non-failure cases, *string is set to a palloc'd string containing
36
 * the common prefix or exact value, of length *slength (measured in chrs
37
 * not bytes!).
38
 *
39
 * This function does not analyze all complex cases (such as lookaround
40
 * constraints) exactly.  Therefore it is possible that some strings matching
41
 * the reported prefix or exact-match string do not satisfy the regex.  But
42
 * it should never be the case that a string satisfying the regex does not
43
 * match the reported prefix or exact-match string.
44
 */
45
int
46
pg_regprefix(regex_t *re,
47
       chr **string,
48
       size_t *slength)
49
0
{
50
0
  struct guts *g;
51
0
  struct cnfa *cnfa;
52
0
  int     st;
53
54
  /* sanity checks */
55
0
  if (string == NULL || slength == NULL)
56
0
    return REG_INVARG;
57
0
  *string = NULL;       /* initialize for failure cases */
58
0
  *slength = 0;
59
0
  if (re == NULL || re->re_magic != REMAGIC)
60
0
    return REG_INVARG;
61
0
  if (re->re_csize != sizeof(chr))
62
0
    return REG_MIXED;
63
64
  /* Initialize locale-dependent support */
65
0
  pg_set_regex_collation(re->re_collation);
66
67
  /* setup */
68
0
  g = (struct guts *) re->re_guts;
69
0
  if (g->info & REG_UIMPOSSIBLE)
70
0
    return REG_NOMATCH;
71
72
  /*
73
   * This implementation considers only the search NFA for the topmost regex
74
   * tree node.  Therefore, constraints such as backrefs are not fully
75
   * applied, which is allowed per the function's API spec.
76
   */
77
0
  assert(g->tree != NULL);
78
0
  cnfa = &g->tree->cnfa;
79
80
  /* matchall NFAs never have a fixed prefix */
81
0
  if (cnfa->flags & MATCHALL)
82
0
    return REG_NOMATCH;
83
84
  /*
85
   * Since a correct NFA should never contain any exit-free loops, it should
86
   * not be possible for our traversal to return to a previously visited NFA
87
   * state.  Hence we need at most nstates chrs in the output string.
88
   */
89
0
  *string = (chr *) MALLOC(cnfa->nstates * sizeof(chr));
90
0
  if (*string == NULL)
91
0
    return REG_ESPACE;
92
93
  /* do it */
94
0
  st = findprefix(cnfa, &g->cmap, *string, slength);
95
96
0
  assert(*slength <= cnfa->nstates);
97
98
  /* clean up */
99
0
  if (st != REG_PREFIX && st != REG_EXACT)
100
0
  {
101
0
    FREE(*string);
102
0
    *string = NULL;
103
0
    *slength = 0;
104
0
  }
105
106
0
  return st;
107
0
}
108
109
/*
110
 * findprefix - extract common prefix from cNFA
111
 *
112
 * Results are returned into the preallocated chr array string[], with
113
 * *slength (which must be preset to zero) incremented for each chr.
114
 */
115
static int            /* regprefix return code */
116
findprefix(struct cnfa *cnfa,
117
       struct colormap *cm,
118
       chr *string,
119
       size_t *slength)
120
0
{
121
0
  int     st;
122
0
  int     nextst;
123
0
  color   thiscolor;
124
0
  chr     c;
125
0
  struct carc *ca;
126
127
  /*
128
   * The "pre" state must have only BOS/BOL outarcs, else pattern isn't
129
   * anchored left.  If we have both BOS and BOL, they must go to the same
130
   * next state.
131
   */
132
0
  st = cnfa->pre;
133
0
  nextst = -1;
134
0
  for (ca = cnfa->states[st]; ca->co != COLORLESS; ca++)
135
0
  {
136
0
    if (ca->co == cnfa->bos[0] || ca->co == cnfa->bos[1])
137
0
    {
138
0
      if (nextst == -1)
139
0
        nextst = ca->to;
140
0
      else if (nextst != ca->to)
141
0
        return REG_NOMATCH;
142
0
    }
143
0
    else
144
0
      return REG_NOMATCH;
145
0
  }
146
0
  if (nextst == -1)
147
0
    return REG_NOMATCH;
148
149
  /*
150
   * Scan through successive states, stopping as soon as we find one with
151
   * more than one acceptable transition character (either multiple colors
152
   * on out-arcs, or a color with more than one member chr).
153
   *
154
   * We could find a state with multiple out-arcs that are all labeled with
155
   * the same singleton color; this comes from patterns like "^ab(cde|cxy)".
156
   * In that case we add the chr "c" to the output string but then exit the
157
   * loop with nextst == -1.  This leaves a little bit on the table: if the
158
   * pattern is like "^ab(cde|cdy)", we won't notice that "d" could be added
159
   * to the prefix.  But chasing multiple parallel state chains doesn't seem
160
   * worth the trouble.
161
   */
162
0
  do
163
0
  {
164
0
    st = nextst;
165
0
    nextst = -1;
166
0
    thiscolor = COLORLESS;
167
0
    for (ca = cnfa->states[st]; ca->co != COLORLESS; ca++)
168
0
    {
169
      /* We can ignore BOS/BOL arcs */
170
0
      if (ca->co == cnfa->bos[0] || ca->co == cnfa->bos[1])
171
0
        continue;
172
173
      /*
174
       * ... but EOS/EOL arcs terminate the search, as do RAINBOW arcs
175
       * and LACONs
176
       */
177
0
      if (ca->co == cnfa->eos[0] || ca->co == cnfa->eos[1] ||
178
0
        ca->co == RAINBOW || ca->co >= cnfa->ncolors)
179
0
      {
180
0
        thiscolor = COLORLESS;
181
0
        break;
182
0
      }
183
0
      if (thiscolor == COLORLESS)
184
0
      {
185
        /* First plain outarc */
186
0
        thiscolor = ca->co;
187
0
        nextst = ca->to;
188
0
      }
189
0
      else if (thiscolor == ca->co)
190
0
      {
191
        /* Another plain outarc for same color */
192
0
        nextst = -1;
193
0
      }
194
0
      else
195
0
      {
196
        /* More than one plain outarc color terminates the search */
197
0
        thiscolor = COLORLESS;
198
0
        break;
199
0
      }
200
0
    }
201
    /* Done if we didn't find exactly one color on plain outarcs */
202
0
    if (thiscolor == COLORLESS)
203
0
      break;
204
    /* The color must be a singleton */
205
0
    if (cm->cd[thiscolor].nschrs != 1)
206
0
      break;
207
    /* Must not have any high-color-map entries */
208
0
    if (cm->cd[thiscolor].nuchrs != 0)
209
0
      break;
210
211
    /*
212
     * Identify the color's sole member chr and add it to the prefix
213
     * string.  In general the colormap data structure doesn't provide a
214
     * way to find color member chrs, except by trying GETCOLOR() on each
215
     * possible chr value, which won't do at all.  However, for the cases
216
     * we care about it should be sufficient to test the "firstchr" value,
217
     * that is the first chr ever added to the color.  There are cases
218
     * where this might no longer be a member of the color (so we do need
219
     * to test), but none of them are likely to arise for a character that
220
     * is a member of a common prefix.  If we do hit such a corner case,
221
     * we just fall out without adding anything to the prefix string.
222
     */
223
0
    c = cm->cd[thiscolor].firstchr;
224
0
    if (GETCOLOR(cm, c) != thiscolor)
225
0
      break;
226
227
0
    string[(*slength)++] = c;
228
229
    /* Advance to next state, but only if we have a unique next state */
230
0
  } while (nextst != -1);
231
232
  /*
233
   * If we ended at a state that only has EOS/EOL outarcs leading to the
234
   * "post" state, then we have an exact-match string.  Note this is true
235
   * even if the string is of zero length.
236
   */
237
0
  nextst = -1;
238
0
  for (ca = cnfa->states[st]; ca->co != COLORLESS; ca++)
239
0
  {
240
0
    if (ca->co == cnfa->eos[0] || ca->co == cnfa->eos[1])
241
0
    {
242
0
      if (nextst == -1)
243
0
        nextst = ca->to;
244
0
      else if (nextst != ca->to)
245
0
      {
246
0
        nextst = -1;
247
0
        break;
248
0
      }
249
0
    }
250
0
    else
251
0
    {
252
0
      nextst = -1;
253
0
      break;
254
0
    }
255
0
  }
256
0
  if (nextst == cnfa->post)
257
0
    return REG_EXACT;
258
259
  /*
260
   * Otherwise, if we were unable to identify any prefix characters, say
261
   * NOMATCH --- the pattern is anchored left, but doesn't specify any
262
   * particular first character.
263
   */
264
0
  if (*slength > 0)
265
0
    return REG_PREFIX;
266
267
0
  return REG_NOMATCH;
268
0
}