4 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
6 * Copyright (c) 1999 James Howard and Dag-Erling Coïdan Smørgrav
7 * Copyright (C) 2008-2011 Gabor Kovesdan <gabor@FreeBSD.org>
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
13 * 1. Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in the
17 * documentation and/or other materials provided with the distribution.
19 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
23 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
45 #include "hashtable.h"
46 #include "tre-fastmatch.h"
48 static int fastcmp(const fastmatch_t *fg, const void *data,
52 * Clean up if pattern compilation fails.
54 #define FAIL_COMP(errcode) \
61 hashtable_free(fg->qsBc_table); \
67 * Skips n characters in the input string and assigns the start
68 * address to startptr. Note: as per IEEE Std 1003.1-2008
69 * matching is based on bit pattern not character representations
70 * so we can handle MB strings as byte sequences just like
73 #define SKIP_CHARS(n) \
77 startptr = str_wide + n; \
80 startptr = str_byte + n; \
84 * Converts the wide string pattern to SB/MB string and stores
85 * it in fg->pattern. Sets fg->len to the byte length of the
88 #define STORE_MBS_PAT \
92 siz = wcstombs(NULL, fg->wpattern, 0); \
93 if (siz == (size_t)-1) \
96 fg->pattern = malloc(siz + 1); \
97 if (fg->pattern == NULL) \
99 wcstombs(fg->pattern, fg->wpattern, siz); \
100 fg->pattern[siz] = '\0'; \
103 #define CONV_MBS_PAT(src, dest, destsz) \
105 destsz = wcstombs(NULL, src, 0); \
106 if (destsz == (size_t)-1) \
108 dest = malloc(destsz + 1); \
111 wcstombs(dest, src, destsz); \
112 dest[destsz] = '\0'; \
115 #define IS_OUT_OF_BOUNDS \
117 ? ((type == STR_WIDE) ? ((j + fg->wlen) > len) \
118 : ((j + fg->len) > len)) \
122 * Checks whether the new position after shifting in the input string
123 * is out of the bounds and break out from the loop if so.
125 #define CHECKBOUNDS \
126 if (IS_OUT_OF_BOUNDS) \
130 * Shifts in the input string after a mismatch. The position of the
131 * mismatch is stored in the mismatch variable.
138 unsigned int bc = 0, gs = 0, ts; \
145 if (u != 0 && (unsigned)mismatch == fg->wlen - 1 - shift) \
147 v = fg->wlen - 1 - mismatch; \
148 r = hashtable_get(fg->qsBc_table, \
149 &str_wide[!fg->reversed ? (size_t)j + fg->wlen \
150 : (size_t)j - 1], &bc); \
151 gs = fg->bmGs[mismatch]; \
153 bc = (r == HASH_OK) ? bc : fg->defBc; \
154 DPRINT(("tre_fast_match: mismatch on character" CHF ", " \
156 ((const tre_char_t *)startptr)[mismatch + 1], \
162 if (u != 0 && (unsigned)mismatch == fg->len - 1 - shift) \
164 v = fg->len - 1 - mismatch; \
165 gs = fg->sbmGs[mismatch]; \
167 bc = fg->qsBc[((const unsigned char *)str_byte) \
168 [!fg->reversed ? (size_t)j + fg->len \
170 DPRINT(("tre_fast_match: mismatch on character %c, " \
172 ((const unsigned char *)startptr)[mismatch + 1], \
179 ts = (u >= v) ? (u - v) : 0; \
180 shift = MAX(ts, bc); \
181 shift = MAX(shift, gs); \
183 u = MIN((type == STR_WIDE ? fg->wlen : fg->len) - shift, v); \
187 shift = MAX(shift, u + 1); \
191 DPRINT(("tre_fast_match: shifting %u characters\n", shift)); \
192 j = !fg->reversed ? j + shift : j - shift; \
196 * Normal Quick Search would require a shift based on the position the
197 * next character after the comparison is within the pattern. With
198 * wildcards, the position of the last dot effects the maximum shift
200 * The closer to the end the wild card is the slower the search.
213 * Fills in the bad character shift array for SB/MB strings.
218 _FILL_QSBC_REVERSED \
226 for (unsigned int i = 0; i <= UCHAR_MAX; i++) \
227 fg->qsBc[i] = fg->len - hasdot; \
228 for (unsigned int i = hasdot + 1; i < fg->len; i++) \
230 fg->qsBc[(unsigned char)fg->pattern[i]] = fg->len - i; \
231 DPRINT(("BC shift for char %c is %zu\n", fg->pattern[i], \
235 char c = islower((unsigned char)fg->pattern[i]) ? \
236 toupper((unsigned char)fg->pattern[i]) : \
237 tolower((unsigned char)fg->pattern[i]); \
238 fg->qsBc[(unsigned char)c] = fg->len - i; \
239 DPRINT(("BC shift for char %c is %zu\n", c, fg->len - i)); \
243 #define _FILL_QSBC_REVERSED \
244 for (unsigned int i = 0; i <= UCHAR_MAX; i++) \
245 fg->qsBc[i] = firstdot + 1; \
246 for (int i = firstdot - 1; i >= 0; i--) \
248 fg->qsBc[(unsigned char)fg->pattern[i]] = i + 1; \
249 DPRINT(("Reverse BC shift for char %c is %d\n", fg->pattern[i], \
253 char c = islower((unsigned char)fg->pattern[i]) ? \
254 toupper((unsigned char)fg->pattern[i]) : \
255 tolower((unsigned char)fg->pattern[i]); \
256 fg->qsBc[(unsigned char)c] = i + 1; \
257 DPRINT(("Reverse BC shift for char %c is %d\n", c, i + 1)); \
262 * Fills in the bad character shifts into a hastable for wide strings.
263 * With wide characters it is not possible any more to use a normal
264 * array because there are too many characters and we could not
265 * provide enough memory. Fortunately, we only have to store distinct
266 * values for so many characters as the number of distinct characters
267 * in the pattern, so we can store them in a hashtable and store a
268 * default shift value for the rest.
270 #define FILL_QSBC_WIDE \
273 _FILL_QSBC_WIDE_REVERSED \
280 #define _FILL_QSBC_WIDE \
281 /* Adjust the shift based on location of the last dot ('.'). */ \
282 fg->defBc = fg->wlen - whasdot; \
284 /* Preprocess pattern. */ \
285 fg->qsBc_table = hashtable_init(fg->wlen * (fg->icase ? 8 : 4), \
286 sizeof(tre_char_t), sizeof(int)); \
287 if (!fg->qsBc_table) \
288 FAIL_COMP(REG_ESPACE); \
289 for (unsigned int i = whasdot + 1; i < fg->wlen; i++) \
291 int k = fg->wlen - i; \
294 r = hashtable_put(fg->qsBc_table, &fg->wpattern[i], &k); \
295 if ((r == HASH_FAIL) || (r == HASH_FULL)) \
296 FAIL_COMP(REG_ESPACE); \
297 DPRINT(("BC shift for wide char " CHF " is %d\n", fg->wpattern[i],\
301 tre_char_t wc = iswlower(fg->wpattern[i]) ? \
302 towupper(fg->wpattern[i]) : towlower(fg->wpattern[i]); \
303 r = hashtable_put(fg->qsBc_table, &wc, &k); \
304 if ((r == HASH_FAIL) || (r == HASH_FULL)) \
305 FAIL_COMP(REG_ESPACE); \
306 DPRINT(("BC shift for wide char " CHF " is %d\n", wc, k)); \
310 #define _FILL_QSBC_WIDE_REVERSED \
311 /* Adjust the shift based on location of the last dot ('.'). */ \
312 fg->defBc = (size_t)wfirstdot; \
314 /* Preprocess pattern. */ \
315 fg->qsBc_table = hashtable_init(fg->wlen * (fg->icase ? 8 : 4), \
316 sizeof(tre_char_t), sizeof(int)); \
317 if (!fg->qsBc_table) \
318 FAIL_COMP(REG_ESPACE); \
319 for (int i = wfirstdot - 1; i >= 0; i--) \
324 r = hashtable_put(fg->qsBc_table, &fg->wpattern[i], &k); \
325 if ((r == HASH_FAIL) || (r == HASH_FULL)) \
326 FAIL_COMP(REG_ESPACE); \
327 DPRINT(("Reverse BC shift for wide char " CHF " is %d\n", \
328 fg->wpattern[i], k)); \
331 tre_char_t wc = iswlower(fg->wpattern[i]) ? \
332 towupper(fg->wpattern[i]) : towlower(fg->wpattern[i]); \
333 r = hashtable_put(fg->qsBc_table, &wc, &k); \
334 if ((r == HASH_FAIL) || (r == HASH_FULL)) \
335 FAIL_COMP(REG_ESPACE); \
336 DPRINT(("Reverse BC shift for wide char " CHF " is %d\n", wc, \
342 #define DPRINT_BMGS(len, fmt_str, sh) \
343 for (unsigned int i = 0; i < len; i++) \
344 DPRINT((fmt_str, i, sh[i]));
346 #define DPRINT_BMGS(len, fmt_str, sh) \
347 do { } while(/*CONSTCOND*/0)
351 * Fills in the good suffix table for SB/MB strings.
354 if (fg->len > 0 && !fg->hasdot) \
356 fg->sbmGs = malloc(fg->len * sizeof(*fg->sbmGs)); \
362 _FILL_BMGS(fg->sbmGs, fg->pattern, fg->len, false); \
363 DPRINT_BMGS(fg->len, "GS shift for pos %d is %d\n", fg->sbmGs); \
367 * Fills in the good suffix table for wide strings.
369 #define FILL_BMGS_WIDE \
370 if (fg->wlen > 0 && !fg->hasdot) \
372 fg->bmGs = malloc(fg->wlen * sizeof(*fg->bmGs)); \
378 _FILL_BMGS(fg->bmGs, fg->wpattern, fg->wlen, true); \
379 DPRINT_BMGS(fg->wlen, "GS shift (wide) for pos %d is %d\n", \
383 #define _FILL_BMGS(arr, pat, plen, wide) \
392 wp = malloc(plen * sizeof(tre_char_t)); \
395 for (unsigned int i = 0; i < plen; i++) \
396 wp[i] = towlower(pat[i]); \
397 _CALC_BMGS(arr, wp, plen); \
401 _CALC_BMGS(arr, pat, plen); \
410 for (unsigned int i = 0; i < plen; i++) \
411 p[i] = tolower((unsigned char)pat[i]); \
412 _CALC_BMGS(arr, p, plen); \
416 _CALC_BMGS(arr, pat, plen); \
420 #define _CALC_BMGS(arr, pat, plen) \
424 int *suff = malloc(plen * sizeof(int)); \
428 suff[plen - 1] = plen; \
430 for (int i = plen - 2; i >= 0; i--) \
432 if (i > g && suff[i + plen - 1 - f] < i - g) \
433 suff[i] = suff[i + plen - 1 - f]; \
439 while (g >= 0 && pat[g] == pat[g + plen - 1 - f]) \
445 for (unsigned int i = 0; i < plen; i++) \
448 for (int i = plen - 1; i >= 0; i--) \
449 if (suff[i] == i + 1) \
450 for(; (unsigned long)g < plen - 1 - i; g++) \
451 if (arr[g] == plen) \
452 arr[g] = plen - 1 - i; \
453 for (unsigned int i = 0; i <= plen - 2; i++) \
454 arr[plen - 1 - suff[i]] = plen - 1 - i; \
460 * Copies the pattern pat having length n to p and stores
463 #define SAVE_PATTERN(src, srclen, dst, dstlen) \
465 dst = malloc((dstlen + 1) * sizeof(tre_char_t)); \
469 memcpy(dst, src, dstlen * sizeof(tre_char_t)); \
470 dst[dstlen] = TRE_CHAR('\0');
473 * Initializes pattern compiling.
477 memset(fg, 0, sizeof(*fg)); \
478 fg->icase = (cflags & REG_ICASE); \
479 fg->word = (cflags & REG_WORD); \
480 fg->newline = (cflags & REG_NEWLINE); \
481 fg->nosub = (cflags & REG_NOSUB); \
483 /* Cannot handle REG_ICASE with MB string */ \
484 if (fg->icase && (TRE_MB_CUR_MAX > 1) && n > 0) \
486 DPRINT(("Cannot use fast matcher for MBS with REG_ICASE\n")); \
491 * Checks whether we have a 0-length pattern that will match
492 * anything. If literal is set to false, the EOL anchor is also
493 * taken into account.
495 #define CHECK_MATCHALL(literal) \
496 if (!literal && n == 1 && pat[0] == TRE_CHAR('$')) \
504 fg->matchall = true; \
505 fg->pattern = malloc(sizeof(char)); \
507 FAIL_COMP(REG_ESPACE); \
508 fg->pattern[0] = '\0'; \
509 fg->wpattern = malloc(sizeof(tre_char_t)); \
511 FAIL_COMP(REG_ESPACE); \
512 fg->wpattern[0] = TRE_CHAR('\0'); \
513 DPRINT(("Matching every input\n")); \
518 * Returns: REG_OK on success, error code otherwise
521 tre_compile_literal(fastmatch_t *fg, const tre_char_t *pat, size_t n,
524 size_t hasdot = 0, whasdot = 0;
525 ssize_t firstdot = -1, wfirstdot = -1;
529 CHECK_MATCHALL(true);
531 /* Cannot handle word boundaries with MB string */
532 if (fg->word && (TRE_MB_CUR_MAX > 1))
536 SAVE_PATTERN(pat, n, fg->wpattern, fg->wlen);
539 SAVE_PATTERN(pat, n, fg->pattern, fg->len);
542 DPRINT(("tre_compile_literal: pattern: %s, len %zu, icase: %c, word: %c, "
543 "newline %c\n", fg->pattern, fg->len, fg->icase ? 'y' : 'n',
544 fg->word ? 'y' : 'n', fg->newline ? 'y' : 'n'));
557 * Returns: REG_OK on success, error code otherwise
560 tre_compile_fast(fastmatch_t *fg, const tre_char_t *pat, size_t n,
564 size_t pos = 0, hasdot = 0, whasdot = 0;
565 ssize_t firstdot = -1, wfirstdot = -1;
566 bool escaped = false;
567 bool *_escmap = NULL;
571 /* Remove beginning-of-line character ('^'). */
572 if (pat[0] == TRE_CHAR('^'))
579 CHECK_MATCHALL(false);
581 /* Handle word-boundary matching when GNU extensions are enabled */
582 if ((cflags & REG_GNU) && (n >= 14) &&
583 (memcmp(pat, TRE_CHAR("[[:<:]]"), 7 * sizeof(tre_char_t)) == 0) &&
584 (memcmp(pat + n - 7, TRE_CHAR("[[:>:]]"),
585 7 * sizeof(tre_char_t)) == 0))
592 /* Cannot handle word boundaries with MB string */
593 if (fg->word && (TRE_MB_CUR_MAX > 1))
596 tmp = malloc((n + 1) * sizeof(tre_char_t));
600 /* Copies the char into the stored pattern and skips to the next char. */
604 tmp[pos++] = pat[i]; \
609 /* Traverse the input pattern for processing */
610 for (unsigned int i = 0; i < n; i++)
629 if (escaped || (!(cflags & REG_EXTENDED) && (i == 0)))
636 if ((cflags & REG_EXTENDED) && (i == 0))
638 else if ((cflags & REG_EXTENDED) ^ !escaped)
647 _escmap = calloc(n, sizeof(bool));
668 if (!escaped && (i == n - 1))
674 if ((cflags & REG_EXTENDED) ^ escaped)
680 if (!(cflags & REG_EXTENDED) ^ escaped)
682 else if (!(cflags & REG_EXTENDED) && (i == 0))
684 else if ((cflags & REG_EXTENDED) && (i == 0))
690 if ((cflags & REG_EXTENDED) ^ escaped)
705 DPRINT(("tre_compile_fast: compilation of pattern failed, falling"
710 fg->hasdot = wfirstdot > -1;
713 * The pattern has been processed and copied to tmp as a literal string
714 * with escapes, anchors (^$) and the word boundary match character
715 * classes stripped out.
718 SAVE_PATTERN(tmp, pos, fg->wpattern, fg->wlen);
719 fg->wescmap = _escmap;
723 * The position of dots and escaped dots is different in the MB string
724 * than in to the wide string so traverse the converted string, as well,
725 * to store these positions.
727 if (fg->hasdot || (fg->wescmap != NULL))
729 if (fg->wescmap != NULL)
731 fg->escmap = calloc(fg->len, sizeof(bool));
732 if (fg->escmap == NULL)
740 char *_checkpat = NULL;
741 size_t _checklen = 0;
742 unsigned int escofs = 0;
744 * Make a copy here of the original pattern, because fg->pattern has
745 * already been stripped of all escape sequences in the above processing.
746 * This is necessary if we wish to later treat fg->escmap as an actual,
747 * functional replacement of fg->wescmap.
749 CONV_MBS_PAT(pat, _checkpat, _checklen);
750 for (unsigned int i = 0; i < n; i++)
751 if (_checkpat[i] == '\\')
757 else if (_checkpat[i] == '.' && fg->escmap != NULL && escaped)
759 fg->escmap[i - escofs] = true;
762 else if (_checkpat[i] == '.' && !escaped)
773 SAVE_PATTERN(tmp, pos, fg->pattern, fg->len);
774 fg->escmap = _escmap;
779 DPRINT(("tre_compile_fast: pattern: %s, len %zu, bol %c, eol %c, "
780 "icase: %c, word: %c, newline %c\n", fg->pattern, fg->len,
781 fg->bol ? 'y' : 'n', fg->eol ? 'y' : 'n',
782 fg->icase ? 'y' : 'n', fg->word ? 'y' : 'n',
783 fg->newline ? 'y' : 'n'));
785 /* Check whether reverse QS algorithm is more efficient */
786 if ((wfirstdot > -1) && (fg->wlen - whasdot + 1 < (size_t)wfirstdot) &&
790 DPRINT(("tre_compile_fast: using reverse QS algorithm\n"));
806 j = !fg->reversed ? j + shift : j - shift; \
810 #define _BBOUND_COND \
811 ((type == STR_WIDE) ? \
812 ((j == 0) || !(tre_isalnum(str_wide[j - 1]) || \
813 (str_wide[j - 1] == TRE_CHAR('_')))) : \
814 ((j == 0) || !(tre_isalnum(str_byte[j - 1]) || \
815 (str_byte[j - 1] == '_'))))
817 #define _EBOUND_COND \
818 ((type == STR_WIDE) ? \
819 ((j + fg->wlen == len) || !(tre_isalnum(str_wide[j + fg->wlen]) || \
820 (str_wide[j + fg->wlen] == TRE_CHAR('_')))) : \
821 ((j + fg->len == len) || !(tre_isalnum(str_byte[j + fg->len]) || \
822 (str_byte[j + fg->len] == '_'))))
825 * Condition to check whether the match on position j is on a
828 #define IS_ON_WORD_BOUNDARY \
829 (_BBOUND_COND && _EBOUND_COND)
832 * Checks word boundary and shifts one if match is not on a
835 #define CHECK_WORD_BOUNDARY \
836 if (!IS_ON_WORD_BOUNDARY) \
840 ((j == 0) || ((type == STR_WIDE) ? (str_wide[j - 1] == TRE_CHAR('\n'))\
841 : (str_byte[j - 1] == '\n')))
844 * Checks BOL anchor and shifts one if match is not on a
847 #define CHECK_BOL_ANCHOR \
852 ((type == STR_WIDE) \
853 ? ((j + fg->wlen == len) || \
854 (str_wide[j + fg->wlen] == TRE_CHAR('\n'))) \
855 : ((j + fg->len == len) || (str_byte[j + fg->wlen] == '\n')))
858 * Checks EOL anchor and shifts one if match is not on a
861 #define CHECK_EOL_ANCHOR \
866 * Executes matching of the precompiled pattern on the input string.
867 * Returns REG_OK or REG_NOMATCH depending on if we find a match or not.
870 tre_match_fast(const fastmatch_t *fg, const void *data, size_t len,
871 tre_str_type_t type, int nmatch, regmatch_t pmatch[], int eflags)
873 unsigned int shift, u = 0, v = 0;
875 int ret = REG_NOMATCH;
877 const char *str_byte = data;
878 const void *startptr = NULL;
879 const tre_char_t *str_wide = data;
881 /* Calculate length if unspecified. */
882 if (len == (size_t)-1)
886 len = tre_strlen(str_wide);
889 len = strlen(str_byte);
893 /* Shortcut for empty pattern */
896 if (!fg->nosub && nmatch >= 1)
899 pmatch[0].rm_eo = len;
901 if (fg->bol && fg->eol)
902 return (len == 0) ? REG_OK : REG_NOMATCH;
907 /* No point in going farther if we do not have enough data. */
922 * REG_NOTBOL means not anchoring ^ to the beginning of the line, so we
923 * can shift one because there can't be a match at the beginning.
925 if (fg->bol && (eflags & REG_NOTBOL))
929 * Like above, we cannot have a match at the very end when anchoring to
930 * the end and REG_NOTEOL is specified.
932 if (fg->eol && (eflags & REG_NOTEOL))
936 j = len - (type == STR_WIDE ? fg->wlen : fg->len);
939 /* Only try once at the beginning or ending of the line. */
940 if ((fg->bol || fg->eol) && !fg->newline && !(eflags & REG_NOTBOL) &&
941 !(eflags & REG_NOTEOL))
943 /* Simple text comparison. */
944 if (!((fg->bol && fg->eol) &&
945 (type == STR_WIDE ? (len != fg->wlen) : (len != fg->len))))
947 /* Determine where in data to start search at. */
948 j = fg->eol ? len - (type == STR_WIDE ? fg->wlen : fg->len) : 0;
950 mismatch = fastcmp(fg, startptr, type);
951 if (mismatch == REG_OK)
953 if (fg->word && !IS_ON_WORD_BOUNDARY)
955 if (!fg->nosub && nmatch >= 1)
958 pmatch[0].rm_eo = j + (type == STR_WIDE ? fg->wlen : fg->len);
966 /* Quick Search / Turbo Boyer-Moore algorithm. */
970 mismatch = fastcmp(fg, startptr, type);
971 if (mismatch == REG_OK)
979 if (!fg->nosub && nmatch >= 1)
982 pmatch[0].rm_eo = j + ((type == STR_WIDE) ? fg->wlen : fg->len);
986 else if (mismatch > 0)
988 mismatch = -mismatch - 1;
990 } while (!IS_OUT_OF_BOUNDS);
996 * Frees the resources that were allocated when the pattern was compiled.
999 tre_free_fast(fastmatch_t *fg)
1002 DPRINT(("tre_fast_free: freeing structures for pattern %s\n",
1006 hashtable_free(fg->qsBc_table);
1021 * Returns: -(i + 1) on failure (position that it failed with minus sign)
1022 * error code on error
1026 fastcmp(const fastmatch_t *fg, const void *data, tre_str_type_t type)
1028 const char *str_byte = data;
1029 const char *pat_byte = fg->pattern;
1030 const tre_char_t *str_wide = data;
1031 const tre_char_t *pat_wide = fg->wpattern;
1032 const bool *escmap = (type == STR_WIDE) ? fg->wescmap : fg->escmap;
1033 size_t len = (type == STR_WIDE) ? fg->wlen : fg->len;
1036 /* Compare the pattern and the input char-by-char from the last position. */
1037 for (int i = len - 1; i >= 0; i--) {
1043 if (fg->hasdot && pat_wide[i] == TRE_CHAR('.') &&
1044 (!escmap || !escmap[i]) &&
1045 (!fg->newline || (str_wide[i] != TRE_CHAR('\n'))))
1049 if (fg->icase ? (towlower(pat_wide[i]) == towlower(str_wide[i]))
1050 : (pat_wide[i] == str_wide[i]))
1055 if (fg->hasdot && pat_byte[i] == '.' &&
1056 (!escmap || !escmap[i]) &&
1057 (!fg->newline || (str_byte[i] != '\n')))
1061 if (fg->icase ? (tolower((unsigned char)pat_byte[i]) == tolower((unsigned char)str_byte[i]))
1062 : (pat_byte[i] == str_byte[i]))
1065 DPRINT(("fastcmp: mismatch at position %d\n", i));