dte test coverage


Directory: ./
Coverage: low: ≥ 0% medium: ≥ 50.0% high: ≥ 85.0%
Coverage Exec / Excl / Total
Lines: 100.0% 69 / 0 / 69
Functions: 100.0% 5 / 0 / 5
Branches: 85.0% 51 / 6 / 66

src/ctags.c
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(StringView ex, char **regex_str)
14 {
15 19 BUG_ON(ex.length == 0);
16 19 const char open_delim = ex.data[0];
17 19 BUG_ON(open_delim != '/' && open_delim != '?');
18 19 char *buf = xmalloc(xmul(2, ex.length));
19
20 // The pattern isn't a real regex; special chars need to be escaped
21
2/2
✓ Branch 20 → 9 taken 890 times.
✓ Branch 20 → 21 taken 1 time.
910 for (size_t i = 1, j = 0; i < ex.length; i++) {
22 890 char c = ex.data[i];
23
2/2
✓ Branch 9 → 10 taken 889 times.
✓ Branch 9 → 21 taken 1 time.
890 if (c == '\0') {
24 break;
25
2/2
✓ Branch 10 → 11 taken 3 times.
✓ Branch 10 → 14 taken 886 times.
889 } else if (c == '\\') {
26
1/2
✓ Branch 11 → 12 taken 3 times.
✗ Branch 11 → 21 not taken.
3 if (unlikely(++i >= ex.length)) {
27 break;
28 }
29 3 c = ex.data[i];
30
2/2
✓ Branch 12 → 13 taken 1 time.
✓ Branch 12 → 19 taken 2 times.
3 if (c == '\\') {
31 // Escape "\\" as "\\" (any other "\x" becomes just "x")
32 1 buf[j++] = '\\';
33 }
34
4/4
✓ Branch 14 → 15 taken 859 times.
✓ Branch 14 → 16 taken 27 times.
✓ Branch 15 → 16 taken 1 time.
✓ Branch 15 → 17 taken 858 times.
886 } else if (c == '*' || c == '[' || c == ']') {
35 28 buf[j++] = '\\';
36
2/2
✓ Branch 17 → 18 taken 17 times.
✓ Branch 17 → 19 taken 841 times.
858 } else if (c == open_delim) {
37 17 buf[j] = '\0';
38 17 *regex_str = buf;
39 17 return i + 1;
40 }
41 872 buf[j++] = c;
42 }
43
44 // End of string reached without a matching end delimiter; invalid input
45 2 free(buf);
46 2 return 0;
47 }
48
49 21 static size_t parse_ex_cmd(Tag *tag, StringView cmd)
50 {
51
1/2
✓ Branch 2 → 3 taken 21 times.
✗ Branch 2 → 13 not taken.
21 if (unlikely(cmd.length == 0)) {
52 return 0;
53 }
54
55 21 size_t n;
56
2/2
✓ Branch 4 → 5 taken 19 times.
✓ Branch 4 → 6 taken 2 times.
21 if (strview_has_either_prefix(cmd, "/", "?")) {
57 19 n = regex_from_ex_pattern(cmd, &tag->pattern);
58 } else {
59 2 n = buf_parse_ulong(cmd, &tag->lineno);
60 }
61
62
2/2
✓ Branch 7 → 8 taken 19 times.
✓ Branch 7 → 13 taken 2 times.
21 if (n == 0) {
63 return 0;
64 }
65
66 19 strview_remove_prefix(&cmd, n);
67 19 StringView delim = strview(";\"");
68 19 bool trailing_comment = strview_has_sv_prefix(cmd, delim);
69
2/2
✓ Branch 10 → 11 taken 16 times.
✓ Branch 10 → 12 taken 3 times.
19 return n + (trailing_comment ? delim.length : 0);
70 }
71
72 21 bool parse_ctags_line(Tag *tag, StringView line)
73 {
74 21 size_t pos = 0;
75 21 *tag = (Tag){.name = get_delim(line.data, &pos, line.length, '\t')};
76
2/4
✓ Branch 3 → 4 taken 21 times.
✗ Branch 3 → 26 not taken.
✓ Branch 4 → 5 taken 21 times.
✗ Branch 4 → 26 not taken.
21 if (tag->name.length == 0 || pos >= line.length) {
77 return false;
78 }
79
80 21 tag->filename = get_delim(line.data, &pos, line.length, '\t');
81
2/4
✓ Branch 6 → 7 taken 21 times.
✗ Branch 6 → 26 not taken.
✓ Branch 7 → 8 taken 21 times.
✗ Branch 7 → 26 not taken.
21 if (tag->filename.length == 0 || pos >= line.length) {
82 return false;
83 }
84
85 21 size_t len = parse_ex_cmd(tag, strview_from_slice(line.data, pos, line.length));
86
2/2
✓ Branch 10 → 11 taken 2 times.
✓ Branch 10 → 13 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 13 → 14 taken 18 times.
✓ Branch 13 → 26 taken 1 time.
19 if (pos >= line.length) {
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 14 → 15 taken 1 time.
✓ Branch 14 → 25 taken 17 times.
18 if (line.data[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 25 → 17 taken 38 times.
✓ Branch 25 → 26 taken 17 times.
55 while (pos < line.length) {
112 38 StringView field = get_delim(line.data, &pos, line.length, '\t');
113
3/4
✓ Branch 18 → 19 taken 17 times.
✓ Branch 18 → 21 taken 21 times.
✓ Branch 19 → 20 taken 17 times.
✗ Branch 19 → 21 not taken.
38 if (field.length == 1 && ascii_isalpha(field.data[0])) {
114 17 tag->kind = field.data[0];
115
2/2
✓ Branch 22 → 23 taken 7 times.
✓ Branch 22 → 24 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 StringView text, // Tag file contents
126 size_t *posp, // Current position within `text` [in-out param]
127 StringView prefix,
128 bool exact,
129 Tag *tag // [out param]
130 ) {
131
2/2
✓ Branch 14 → 3 taken 55 times.
✓ Branch 14 → 15 taken 4 times.
59 for (size_t pos = *posp; pos < text.length; ) {
132 55 StringView line = buf_slice_next_line(text.data, &pos, text.length);
133 55 if (
134
1/2
✓ Branch 4 → 5 taken 55 times.
✗ Branch 4 → 13 not taken.
55 line.length > 0 // Line is non-empty
135
2/2
✓ Branch 5 → 6 taken 37 times.
✓ Branch 5 → 13 taken 18 times.
55 && line.data[0] != '!' // and not a comment
136
2/2
✓ Branch 7 → 8 taken 15 times.
✓ Branch 7 → 13 taken 22 times.
37 && strview_has_sv_prefix(line, prefix) // and starts with `prefix`
137
4/4
✓ Branch 8 → 9 taken 2 times.
✓ Branch 8 → 10 taken 13 times.
✓ Branch 9 → 10 taken 1 time.
✓ Branch 9 → 13 taken 1 time.
15 && (!exact || line.data[prefix.length] == '\t') // and matches `prefix` exactly, if applicable
138
1/2
✓ Branch 11 → 12 taken 14 times.
✗ Branch 11 → 13 not taken.
14 && parse_ctags_line(tag, line) // and is a valid tags(5) entry
139 ) {
140 // Advance the position; `tag` has been filled by parse_ctags_line()
141 14 *posp = pos;
142 14 return true;
143 }
144 }
145
146 // No matching tags remaining
147 4 return false;
148 }
149
150 // NOTE: tag itself is not freed
151 22 void free_tag(Tag *tag)
152 {
153 22 free(tag->pattern);
154 22 }
155