dte test coverage


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