dte test coverage


Directory: ./
File: src/ctags.c
Date: 2025-06-04 06:50:24
Exec Total Coverage
Lines: 69 69 100.0%
Functions: 5 5 100.0%
Branches: 53 62 85.5%

Line Branch Exec Source
1 #include <stdlib.h>
2 #include "ctags.h"
3 #include "util/ascii.h"
4 #include "util/debug.h"
5 #include "util/str-util.h"
6 #include "util/strtonum.h"
7 #include "util/xmalloc.h"
8 #include "util/xstring.h"
9
10 // Convert an ex(1) style pattern from a tags(5) file to a basic POSIX
11 // regex ("BRE"), so that it can be compiled with regcomp(3)
12 19 static size_t regex_from_ex_pattern (
13 const char *ex_str,
14 size_t len,
15 char **regex_str // out param
16 ) {
17 19 BUG_ON(len == 0);
18 19 const char open_delim = ex_str[0];
19 19 BUG_ON(open_delim != '/' && open_delim != '?');
20 19 char *buf = xmalloc(xmul(2, len));
21
22 // The pattern isn't a real regex; special chars need to be escaped
23
2/2
✓ Branch 0 (20→9) taken 890 times.
✓ Branch 1 (20→21) taken 1 times.
891 for (size_t i = 1, j = 0; i < len; i++) {
24 890 char c = ex_str[i];
25
2/2
✓ Branch 0 (9→10) taken 889 times.
✓ Branch 1 (9→21) taken 1 times.
890 if (c == '\0') {
26 break;
27
2/2
✓ Branch 0 (10→11) taken 3 times.
✓ Branch 1 (10→14) taken 886 times.
889 } else if (c == '\\') {
28
1/2
✓ Branch 0 (11→12) taken 3 times.
✗ Branch 1 (11→21) not taken.
3 if (unlikely(++i >= len)) {
29 break;
30 }
31 3 c = ex_str[i];
32
2/2
✓ Branch 0 (12→13) taken 1 times.
✓ Branch 1 (12→19) taken 2 times.
3 if (c == '\\') {
33 // Escape "\\" as "\\" (any other "\x" becomes just "x")
34 1 buf[j++] = '\\';
35 }
36
4/4
✓ Branch 0 (14→15) taken 859 times.
✓ Branch 1 (14→16) taken 27 times.
✓ Branch 2 (15→16) taken 1 times.
✓ Branch 3 (15→17) taken 858 times.
886 } else if (c == '*' || c == '[' || c == ']') {
37 28 buf[j++] = '\\';
38
2/2
✓ Branch 0 (17→18) taken 17 times.
✓ Branch 1 (17→19) taken 841 times.
858 } else if (c == open_delim) {
39 17 buf[j] = '\0';
40 17 *regex_str = buf;
41 17 return i + 1;
42 }
43 872 buf[j++] = c;
44 }
45
46 // End of string reached without a matching end delimiter; invalid input
47 2 free(buf);
48 2 return 0;
49 }
50
51 21 static size_t parse_ex_cmd(Tag *tag, const char *buf, size_t size)
52 {
53
1/2
✓ Branch 0 (2→3) taken 21 times.
✗ Branch 1 (2→12) not taken.
21 if (unlikely(size == 0)) {
54 return 0;
55 }
56
57 21 size_t n;
58
2/2
✓ Branch 0 (3→4) taken 19 times.
✓ Branch 1 (3→5) taken 2 times.
21 if (buf[0] == '/' || buf[0] == '?') {
59 19 n = regex_from_ex_pattern(buf, size, &tag->pattern);
60 } else {
61 2 n = buf_parse_ulong(buf, size, &tag->lineno);
62 }
63
64
2/2
✓ Branch 0 (6→7) taken 19 times.
✓ Branch 1 (6→12) taken 2 times.
21 if (n == 0) {
65 return 0;
66 }
67
68
4/4
✓ Branch 0 (7→8) taken 17 times.
✓ Branch 1 (7→11) taken 2 times.
✓ Branch 2 (9→10) taken 16 times.
✓ Branch 3 (9→11) taken 1 times.
19 bool trailing_comment = (n + 1 < size) && mem_equal(buf + n, ";\"", 2);
69 19 return n + (trailing_comment ? 2 : 0);
70 }
71
72 21 bool parse_ctags_line(Tag *tag, const char *line, size_t line_len)
73 {
74 21 size_t pos = 0;
75 21 *tag = (Tag){.name = get_delim(line, &pos, line_len, '\t')};
76
2/4
✓ Branch 0 (3→4) taken 21 times.
✗ Branch 1 (3→25) not taken.
✓ Branch 2 (4→5) taken 21 times.
✗ Branch 3 (4→25) not taken.
21 if (tag->name.length == 0 || pos >= line_len) {
77 return false;
78 }
79
80 21 tag->filename = get_delim(line, &pos, line_len, '\t');
81
2/4
✓ Branch 0 (6→7) taken 21 times.
✗ Branch 1 (6→25) not taken.
✓ Branch 2 (7→8) taken 21 times.
✗ Branch 3 (7→25) not taken.
21 if (tag->filename.length == 0 || pos >= line_len) {
82 return false;
83 }
84
85 21 size_t len = parse_ex_cmd(tag, line + pos, line_len - pos);
86
2/2
✓ Branch 0 (9→10) taken 2 times.
✓ Branch 1 (9→12) taken 19 times.
21 if (len == 0) {
87 2 BUG_ON(tag->pattern);
88 return false;
89 }
90
91 19 pos += len;
92
2/2
✓ Branch 0 (12→13) taken 18 times.
✓ Branch 1 (12→25) taken 1 times.
19 if (pos >= line_len) {
93 return true;
94 }
95
96 /*
97 * Extension fields (key:[value]):
98 *
99 * file: visibility limited to this file
100 * struct:NAME tag is member of struct NAME
101 * union:NAME tag is member of union NAME
102 * typeref:struct:NAME::MEMBER_TYPE MEMBER_TYPE is type of the tag
103 */
104
2/2
✓ Branch 0 (13→14) taken 1 times.
✓ Branch 1 (13→24) taken 17 times.
18 if (line[pos++] != '\t') {
105 // free `pattern` allocated by parse_ex_cmd()
106 1 free_tag(tag);
107 1 tag->pattern = NULL;
108 1 return false;
109 }
110
111
2/2
✓ Branch 0 (24→16) taken 38 times.
✓ Branch 1 (24→25) taken 17 times.
55 while (pos < line_len) {
112 38 StringView field = get_delim(line, &pos, line_len, '\t');
113
3/4
✓ Branch 0 (17→18) taken 17 times.
✓ Branch 1 (17→20) taken 21 times.
✓ Branch 2 (18→19) taken 17 times.
✗ Branch 3 (18→20) not taken.
38 if (field.length == 1 && ascii_isalpha(field.data[0])) {
114 17 tag->kind = field.data[0];
115
2/2
✓ Branch 0 (21→22) taken 7 times.
✓ Branch 1 (21→23) taken 14 times.
21 } else if (strview_equal_cstring(&field, "file:")) {
116 7 tag->local = true;
117 }
118 // TODO: struct/union/typeref
119 }
120
121 return true;
122 }
123
124 18 bool next_tag (
125 const char *buf,
126 size_t buf_len,
127 size_t *posp, // in-out param
128 const StringView *prefix,
129 bool exact,
130 Tag *tag // out param
131 ) {
132 18 const char *p = prefix->data;
133 18 size_t plen = prefix->length;
134
2/2
✓ Branch 0 (14→3) taken 55 times.
✓ Branch 1 (14→15) taken 4 times.
59 for (size_t pos = *posp; pos < buf_len; ) {
135 55 StringView line = buf_slice_next_line(buf, &pos, buf_len);
136 55 if (
137
1/2
✓ Branch 0 (4→5) taken 55 times.
✗ Branch 1 (4→13) not taken.
55 line.length > 0 // Line is non-empty
138
2/2
✓ Branch 0 (5→6) taken 37 times.
✓ Branch 1 (5→13) taken 18 times.
55 && line.data[0] != '!' // and not a comment
139
2/2
✓ Branch 0 (7→8) taken 15 times.
✓ Branch 1 (7→13) taken 22 times.
37 && strview_has_strn_prefix(&line, p, plen) // and starts with `prefix`
140
4/4
✓ Branch 0 (8→9) taken 2 times.
✓ Branch 1 (8→10) taken 13 times.
✓ Branch 2 (9→10) taken 1 times.
✓ Branch 3 (9→13) taken 1 times.
15 && (!exact || line.data[plen] == '\t') // and matches `prefix` exactly, if applicable
141
1/2
✓ Branch 0 (11→12) taken 14 times.
✗ Branch 1 (11→13) not taken.
14 && parse_ctags_line(tag, line.data, line.length) // and is a valid tags(5) entry
142 ) {
143 // Advance the position; `tag` has been filled by parse_ctags_line()
144 14 *posp = pos;
145 14 return true;
146 }
147 }
148
149 // No matching tags remaining
150 4 return false;
151 }
152
153 // NOTE: tag itself is not freed
154 22 void free_tag(Tag *tag)
155 {
156 22 free(tag->pattern);
157 22 }
158