dte test coverage


Directory: ./
Coverage: low: ≥ 0% medium: ≥ 50.0% high: ≥ 85.0%
Coverage Exec / Excl / Total
Lines: 83.1% 74 / 0 / 89
Functions: 91.7% 11 / 0 / 12
Branches: 60.7% 17 / 12 / 40

src/regexp.c
Line Branch Exec Source
1 #include <errno.h>
2 #include <stdlib.h>
3 #include "regexp.h"
4 #include "util/arith.h"
5 #include "util/ascii.h"
6 #include "util/debug.h"
7 #include "util/hashmap.h"
8 #include "util/intern.h"
9 #include "util/log.h"
10 #include "util/xmalloc.h"
11 #include "util/xstring.h"
12
13 // NOLINTNEXTLINE(*-avoid-non-const-global-variables)
14 static HashMap interned_regexps = {.flags = HMAP_BORROWED_KEYS};
15
16 bool regexp_error_msg(ErrorBuffer *ebuf, const regex_t *re, const char *pattern, int err)
17 {
18 if (!ebuf) {
19 return false;
20 }
21 char msg[1024];
22 regerror(err, re, msg, sizeof(msg));
23 return error_msg(ebuf, "%s: %s", msg, pattern);
24 }
25
26 3 const regex_t *regexp_compile_or_fatal_error(const char *pattern)
27 {
28 3 const InternedRegexp *ir = regexp_intern(NULL, pattern);
29
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 3 times.
3 FATAL_ERROR_ON(!ir, EINVAL);
30 3 return &ir->re;
31 }
32
33 147 bool regexp_exec (
34 const regex_t *re,
35 StringView text,
36 size_t nmatch,
37 regmatch_t *pmatch,
38 int flags
39 ) {
40 147 BUG_ON(nmatch && !pmatch);
41
42 147 if (HAVE_REG_STARTEND) {
43 // "If REG_STARTEND is specified, pmatch must point to at least
44 // one regmatch_t (even if nmatch is 0 or REG_NOSUB was specified),
45 // to hold the input offsets for REG_STARTEND."
46 // -- https://man.openbsd.org/regexec#:~:text=If-,REG_STARTEND,-is%20specified
47 147 regmatch_t tmp_startend;
48
2/2
✓ Branch 4 → 5 taken 92 times.
✓ Branch 4 → 6 taken 55 times.
147 pmatch = nmatch ? pmatch : &tmp_startend;
49 147 pmatch[0].rm_so = 0;
50 147 pmatch[0].rm_eo = text.length;
51 147 return !regexec(re, text.data, nmatch, pmatch, flags | REGEXP_STARTEND_FLAG);
52 }
53
54 // Buffer must be null-terminated if REG_STARTEND isn't supported
55 char *cstr = strview_clone_cstring(text);
56 int ret = !regexec(re, cstr, nmatch, pmatch, flags);
57 free(cstr);
58 return ret;
59 }
60
61 11 static bool regexp_wb_tokens_supported(const RegexpWordBoundaryTokens *p)
62 {
63 11 static const char text[] = "SSfooEE SSfoo fooEE foo SSfooEE";
64 11 const regoff_t match_start = 20, match_end = 23;
65 11 BUG_ON(ARRAYLEN(text) <= match_end);
66 11 BUG_ON(!mem_equal(text + match_start - 1, STRN(" foo ")));
67
68 11 char patt[32];
69 11 xmempcpy4(patt, p->start, p->len, STRN("(foo)"), p->end, p->len, "", 1);
70 11 regex_t re;
71 11 int err = regcomp(&re, patt, DEFAULT_REGEX_FLAGS);
72
73
1/2
✗ Branch 7 → 8 not taken.
✓ Branch 7 → 11 taken 11 times.
11 if (err) {
74 if (DEBUG_LOGGING_ENABLED) {
75 char msg[1024];
76 regerror(err, &re, msg, sizeof(msg));
77 LOG_DEBUG("regcomp() failed for pattern \"%s\": %s", patt, msg);
78 }
79 return false;
80 }
81
82 11 regmatch_t m[2];
83 11 bool match = !regexec(&re, text, ARRAYLEN(m), m, 0);
84 11 regfree(&re);
85
3/6
✓ Branch 13 → 14 taken 11 times.
✗ Branch 13 → 16 not taken.
✓ Branch 14 → 15 taken 11 times.
✗ Branch 14 → 16 not taken.
✗ Branch 15 → 16 not taken.
✓ Branch 15 → 17 taken 11 times.
11 return match && (m[0].rm_so == match_start) && (m[0].rm_eo == match_end);
86 }
87
88 // Check which word boundary tokens are supported by regcomp(3)
89 // (if any) and initialize `rwbt` with them for later use
90 11 RegexpWordBoundaryTokens regexp_get_word_boundary_tokens(void)
91 {
92 11 static const RegexpWordBoundaryTokens pairs[] = {
93 {"\\<", "\\>", 2},
94 {"[[:<:]]", "[[:>:]]", 7},
95 {"\\b", "\\b", 2},
96 };
97
98 UNROLL_LOOP(ARRAYLEN(pairs))
99
1/2
✓ Branch 8 → 3 taken 11 times.
✗ Branch 8 → 9 not taken.
11 for (size_t i = 0; i < ARRAYLEN(pairs); i++) {
100 11 const RegexpWordBoundaryTokens *p = &pairs[i];
101
1/2
✓ Branch 4 → 5 taken 11 times.
✗ Branch 4 → 7 not taken.
11 if (regexp_wb_tokens_supported(p)) {
102 11 LOG_INFO("regex word boundary tokens detected: %s %s", p->start, p->end);
103 11 return pairs[i];
104 }
105 }
106
107 LOG_WARNING("no regex word boundary tokens detected");
108 return (RegexpWordBoundaryTokens){.len = 0};
109 }
110
111 NONNULL_ARG(1) NONNULL_ARG_IF_NONZERO_LENGTH(3, 4)
112 67 static size_t regexp_escapeb(char *buf, size_t buflen, const char *pat, size_t plen)
113 {
114 67 BUG_ON(buflen < (2 * plen) + 1);
115 size_t o = 0;
116
2/2
✓ Branch 7 → 4 taken 539 times.
✓ Branch 7 → 8 taken 67 times.
606 for (size_t i = 0; i < plen; i++) {
117 539 char ch = pat[i];
118
2/2
✓ Branch 4 → 5 taken 15 times.
✓ Branch 4 → 6 taken 524 times.
539 if (is_regex_special_char(ch)) {
119 15 buf[o++] = '\\';
120 }
121 539 buf[o++] = ch;
122 }
123 67 buf[o] = '\0';
124 67 return o;
125 }
126
127 67 size_t string_append_escaped_regex(String *s, StringView pattern)
128 {
129 67 size_t bufsize = xmul(2, pattern.length) + 1;
130 67 char *buf = string_reserve_space(s, bufsize);
131 67 size_t esc_len = regexp_escapeb(buf, bufsize, pattern.data, pattern.length);
132 67 BUG_ON(esc_len < pattern.length);
133 67 s->len += esc_len;
134 67 return esc_len;
135 }
136
137 1 String regexp_escape(StringView pattern)
138 {
139 1 String escaped = STRING_INIT;
140 1 string_append_escaped_regex(&escaped, pattern);
141 1 return escaped;
142 }
143
144 38 const InternedRegexp *regexp_intern(ErrorBuffer *ebuf, const char *pattern)
145 {
146
1/2
✓ Branch 2 → 3 taken 38 times.
✗ Branch 2 → 14 not taken.
38 if (pattern[0] == '\0') {
147 return NULL;
148 }
149
150 38 InternedRegexp *ir = hashmap_get(&interned_regexps, pattern);
151
2/2
✓ Branch 4 → 5 taken 35 times.
✓ Branch 4 → 14 taken 3 times.
38 if (ir) {
152 return ir;
153 }
154
155 35 ir = xmalloc(sizeof(*ir));
156 35 int err = regcomp(&ir->re, pattern, DEFAULT_REGEX_FLAGS | REG_NEWLINE | REG_NOSUB);
157
1/2
✗ Branch 7 → 8 not taken.
✓ Branch 7 → 10 taken 35 times.
35 if (unlikely(err)) {
158 regexp_error_msg(ebuf, &ir->re, pattern, err);
159 free(ir);
160 return NULL;
161 }
162
163 35 BUG_ON(!(interned_regexps.flags & HMAP_BORROWED_KEYS));
164 35 const char *str = str_intern(pattern);
165 35 ir->str = str;
166 35 return hashmap_insert(&interned_regexps, (char*)str, ir);
167 }
168
169 56 bool regexp_is_interned(const char *pattern)
170 {
171 // Note that this only checks that an entry matching `pattern`
172 // exists and NOT for pointer equality between `pattern` and
173 // the corresponding `InternedRegexp::str`
174 56 return !!hashmap_find(&interned_regexps, pattern);
175 }
176
177 35 static void free_interned_regexp(InternedRegexp *ir)
178 {
179 35 regfree(&ir->re);
180 35 free(ir);
181 35 }
182
183 11 void free_interned_regexps(void)
184 {
185 11 BUG_ON(!(interned_regexps.flags & HMAP_BORROWED_KEYS));
186 11 hashmap_free(&interned_regexps, FREE_FUNC(free_interned_regexp));
187 11 }
188