dte test coverage


Directory: ./
Coverage: low: ≥ 0% medium: ≥ 50.0% high: ≥ 85.0%
Coverage Exec / Excl / Total
Lines: 43.2% 79 / 0 / 183
Functions: 53.3% 8 / 0 / 15
Branches: 15.5% 17 / 18 / 128

src/tag.c
Line Branch Exec Source
1 #include "build-defs.h"
2 #include <errno.h>
3 #include <stdlib.h>
4 #include <string.h>
5 #include <sys/stat.h>
6 #include <sys/types.h>
7 #include <unistd.h>
8 #include "tag.h"
9 #include "command/error.h"
10 #include "util/debug.h"
11 #include "util/numtostr.h"
12 #include "util/path.h"
13 #include "util/time-util.h"
14 #include "util/xmalloc.h"
15 #include "util/xreadwrite.h"
16 #include "util/xstring.h"
17
18 static bool tag_is_local_to_file(const Tag *tag, const char *path)
19 {
20 return tag->local && !!path && strview_equal_cstring(tag->filename, path);
21 }
22
23 static int visibility_cmp(const Tag *a, const Tag *b, const char *filename)
24 {
25 if (!a->local && !b->local) {
26 return 0;
27 }
28
29 // Is tag visibility limited to the current file?
30 bool a_this_file = tag_is_local_to_file(a, filename);
31 bool b_this_file = tag_is_local_to_file(b, filename);
32
33 // Tags local to other file than current are not interesting
34 if (a->local && !a_this_file) {
35 // a is not interesting
36 if (b->local && !b_this_file) {
37 // b is equally uninteresting
38 return 0;
39 }
40 // b is more interesting, sort it before a
41 return 1;
42 }
43 if (b->local && !b_this_file) {
44 // b is not interesting
45 return -1;
46 }
47
48 // Both are NOT UNinteresting
49
50 if (a->local && a_this_file) {
51 if (b->local && b_this_file) {
52 return 0;
53 }
54 // a is more interesting because it's a local symbol
55 return -1;
56 }
57 if (b->local && b_this_file) {
58 // b is more interesting because it's a local symbol
59 return 1;
60 }
61 return 0;
62 }
63
64 static int kind_cmp(const Tag *a, const Tag *b)
65 {
66 if (a->kind == b->kind) {
67 return 0;
68 }
69
70 // Struct member (m) is not very interesting
71 if (a->kind == 'm') {
72 return 1;
73 }
74 if (b->kind == 'm') {
75 return -1;
76 }
77
78 // Global variable (v) is not very interesting
79 if (a->kind == 'v') {
80 return 1;
81 }
82 if (b->kind == 'v') {
83 return -1;
84 }
85
86 // Struct (s), union (u)
87 return 0;
88 }
89
90 static int tag_cmp_r(const void *ap, const void *bp, void *userdata)
91 {
92 const Tag *const *a = ap;
93 const Tag *const *b = bp;
94 const char *filename = userdata;
95 int r = visibility_cmp(*a, *b, filename);
96 return r ? r : kind_cmp(*a, *b);
97 }
98
99 // Find a "tags" file in the directory `path` or any of its parent directories
100 // and return either a successfully opened FD or a negated <errno.h> value
101 1 static int open_tag_file(char *path)
102 {
103 1 static const char tags[] = "tags";
104
1/2
✓ Branch 10 → 3 taken 1 time.
✗ Branch 10 → 11 not taken.
1 while (*path) {
105 1 size_t len = strlen(path);
106 1 char *slash = strrchr(path, '/');
107
1/2
✓ Branch 3 → 4 taken 1 time.
✗ Branch 3 → 5 not taken.
1 if (slash != path + len - 1) {
108 1 path[len++] = '/';
109 }
110 1 memcpy(path + len, tags, sizeof(tags));
111 1 int fd = xopen(path, O_RDONLY | O_CLOEXEC, 0);
112
1/2
✗ Branch 6 → 7 not taken.
✓ Branch 6 → 11 taken 1 time.
1 if (fd >= 0) {
113 return fd;
114 }
115 if (errno != ENOENT) {
116 return -errno;
117 }
118 *slash = '\0';
119 }
120
121 return -ENOENT;
122 }
123
124 static bool tag_file_changed (
125 const TagFile *tf,
126 const char *filename,
127 const struct stat *st
128 ) {
129 return tf->mtime != st->st_mtime || !streq(tf->filename, filename);
130 }
131
132 // Note: does not free `tf` itself
133 11 void tag_file_free(TagFile *tf)
134 {
135 11 free(tf->filename);
136 11 free(tf->buf);
137 11 *tf = (TagFile){.filename = NULL};
138 11 }
139
140 1 bool load_tag_file(TagFile *tf, ErrorBuffer *ebuf)
141 {
142 1 char path[8192];
143
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 1 time.
1 if (unlikely(!getcwd(path, sizeof(path) - STRLEN("/tags")))) {
144 return error_msg_errno(ebuf, "getcwd");
145 }
146
147 1 int fd = open_tag_file(path);
148
1/2
✗ Branch 6 → 7 not taken.
✓ Branch 6 → 11 taken 1 time.
1 if (fd < 0) {
149 int err = -fd;
150 if (err == ENOENT) {
151 return error_msg(ebuf, "no tags file");
152 } else {
153 return error_msg(ebuf, "failed to open '%s': %s", path, strerror(err));
154 }
155 }
156
157 1 struct stat st;
158
1/2
✗ Branch 12 → 13 not taken.
✓ Branch 12 → 16 taken 1 time.
1 if (unlikely(fstat(fd, &st) != 0)) {
159 const char *str = strerror(errno);
160 xclose(fd);
161 return error_msg(ebuf, "fstat: %s", str);
162 }
163
164
1/2
✗ Branch 16 → 17 not taken.
✓ Branch 16 → 19 taken 1 time.
1 if (unlikely(st.st_size <= 0)) {
165 xclose(fd);
166 return error_msg(ebuf, "empty tags file");
167 }
168
169
1/2
✗ Branch 19 → 20 not taken.
✓ Branch 19 → 26 taken 1 time.
1 if (tf->filename) {
170 if (!tag_file_changed(tf, path, &st)) {
171 xclose(fd);
172 return true;
173 }
174 tag_file_free(tf);
175 BUG_ON(tf->filename);
176 }
177
178 1 char *buf = malloc(st.st_size);
179
1/2
✗ Branch 26 → 27 not taken.
✓ Branch 26 → 30 taken 1 time.
1 if (unlikely(!buf)) {
180 xclose(fd);
181 return error_msg(ebuf, "malloc: %s", strerror(ENOMEM));
182 }
183
184 1 ssize_t size = xread_all(fd, buf, st.st_size);
185 1 int err = errno;
186 1 xclose(fd);
187
1/2
✗ Branch 32 → 33 not taken.
✓ Branch 32 → 35 taken 1 time.
1 if (size < 0) {
188 free(buf);
189 return error_msg(ebuf, "read: %s", strerror(err));
190 }
191
192 2 *tf = (TagFile) {
193 1 .filename = xstrdup(path),
194 1 .dirname_len = (xstrrchr(path, '/') - path) + 1, // Includes last slash
195 .buf = buf,
196 .size = size,
197 1 .mtime = st.st_mtime,
198 };
199
200 1 return true;
201 }
202
203 1 static void free_tags_cb(Tag *tag)
204 {
205 1 free_tag(tag);
206 1 free(tag);
207 1 }
208
209 1 static void free_tags(PointerArray *tags)
210 {
211 1 ptr_array_free_cb(tags, FREE_FUNC(free_tags_cb));
212 1 }
213
214 #if !HAVE_QSORT_R
215 static const char *current_filename_global; // NOLINT(*-non-const-global-variables)
216 static int tag_cmp(const void *t1, const void *t2)
217 {
218 return tag_cmp_r(t1, t2, (char*)current_filename_global);
219 }
220 #endif
221
222 1 static void tag_file_find_tags (
223 const TagFile *tf,
224 const char *filename,
225 StringView name,
226 PointerArray *tags
227 ) {
228 1 Tag *tag = xmalloc(sizeof(*tag));
229 1 size_t pos = 0;
230
2/2
✓ Branch 7 → 4 taken 1 time.
✓ Branch 7 → 8 taken 1 time.
2 while (next_tag(tf->buf, tf->size, &pos, name, true, tag)) {
231 1 ptr_array_append(tags, tag);
232 1 tag = xmalloc(sizeof(*tag));
233 }
234 1 free(tag);
235
236
1/2
✓ Branch 8 → 9 taken 1 time.
✗ Branch 8 → 10 not taken.
1 if (tags->count < 2) {
237 1 return;
238 }
239
240 if (filename) {
241 BUG_ON(!path_is_absolute(filename));
242 size_t n = tf->dirname_len;
243 BUG_ON(n == 0);
244 if (strncmp(filename, tf->filename, n) == 0) {
245 filename += n;
246 } else {
247 // Filename doesn't start with directory prefix of tag file
248 filename = NULL;
249 }
250 }
251
252 void **ptrs = tags->ptrs;
253 BUG_ON(!ptrs);
254
255 #if HAVE_QSORT_R
256 qsort_r(ptrs, tags->count, sizeof(*ptrs), tag_cmp_r, (char*)filename);
257 #else
258 current_filename_global = filename;
259 qsort(ptrs, tags->count, sizeof(*ptrs), tag_cmp);
260 current_filename_global = NULL;
261 #endif
262 }
263
264 // Note: this moves ownership of tag->pattern to the generated Message
265 // and assigns NULL to the old pointer
266 1 void add_message_for_tag(MessageList *messages, Tag *tag, StringView dir)
267 {
268 1 BUG_ON(dir.length == 0);
269 1 BUG_ON(dir.data[0] != '/');
270
271 1 static const char prefix[] = "Tag ";
272 1 StringView name = tag->name;
273 1 size_t prefix_len = sizeof(prefix) - 1;
274 1 Message *m = xmalloc(xadd3(sizeof(*m), prefix_len, name.length + 1));
275 1 xmempcpy3(m->msg, prefix, prefix_len, name.data, name.length, "", 1);
276
277 1 char *filename = path_join_sv(dir, tag->filename, false);
278 1 m->loc = new_file_location(filename, 0, 0, 0);
279
280
1/2
✓ Branch 11 → 12 taken 1 time.
✗ Branch 11 → 13 not taken.
1 if (tag->pattern) {
281 1 m->loc->pattern = tag->pattern; // Message takes ownership
282 1 tag->pattern = NULL;
283 } else {
284 m->loc->line = tag->lineno;
285 }
286
287 1 add_message(messages, m);
288 1 }
289
290 1 size_t tag_lookup (
291 TagFile *tf,
292 MessageList *messages,
293 ErrorBuffer *ebuf,
294 StringView name,
295 const char *filename
296 ) {
297 1 BUG_ON(!tf->filename);
298
299 // Filename helps to find correct tags
300 1 PointerArray tags = PTR_ARRAY_INIT;
301 1 tag_file_find_tags(tf, filename, name, &tags);
302
303 1 size_t ntags = tags.count;
304
1/2
✗ Branch 5 → 6 not taken.
✓ Branch 5 → 8 taken 1 time.
1 if (ntags == 0) {
305 error_msg(ebuf, "Tag '%.*s' not found", (int)name.length, name.data);
306 return 0;
307 }
308
309 // Note that `dirname_len` always includes a trailing slash, but the
310 // call to path_join_sv() in add_message_for_tag() handles that fine
311 1 BUG_ON(tf->dirname_len == 0);
312 1 StringView tagfile_dir = string_view(tf->filename, tf->dirname_len);
313
314
2/2
✓ Branch 13 → 11 taken 1 time.
✓ Branch 13 → 14 taken 1 time.
2 for (size_t i = 0; i < ntags; i++) {
315 1 Tag *tag = tags.ptrs[i];
316 1 add_message_for_tag(messages, tag, tagfile_dir);
317 }
318
319 1 free_tags(&tags);
320 1 return ntags;
321 }
322
323 void collect_tags(TagFile *tf, PointerArray *a, StringView prefix)
324 {
325 ErrorBuffer ebuf = {.print_to_stderr = false};
326 if (!load_tag_file(tf, &ebuf)) {
327 return;
328 }
329
330 Tag tag;
331 size_t pos = 0;
332 StringView prev = STRING_VIEW_INIT;
333 while (next_tag(tf->buf, tf->size, &pos, prefix, false, &tag)) {
334 BUG_ON(tag.name.length == 0);
335 if (prev.length == 0 || !strview_equal(tag.name, prev)) {
336 ptr_array_append(a, xstrcut(tag.name.data, tag.name.length));
337 prev = tag.name;
338 }
339 free_tag(&tag);
340 }
341 }
342
343 String dump_tags(TagFile *tf, ErrorBuffer *ebuf)
344 {
345 if (!load_tag_file(tf, ebuf)) {
346 return string_new(0);
347 }
348
349 const struct timespec ts = {.tv_sec = tf->mtime};
350 char sizestr[FILESIZE_STR_MAX];
351 char tstr[TIME_STR_BUFSIZE];
352 String buf = string_new(tf->size);
353
354 string_sprintf (
355 &buf,
356 "Tags file\n---------\n\n"
357 "%s %s\n%s %s\n%s %s\n\n"
358 "Tag entries\n-----------\n\n",
359 " Path:", tf->filename,
360 " Modified:", timespec_to_str(&ts, tstr) ? tstr : "-",
361 " Size:", filesize_to_str(tf->size, sizestr)
362 );
363
364 const StringView prefix = STRING_VIEW_INIT;
365 size_t pos = 0;
366 Tag tag;
367
368 while (next_tag(tf->buf, tf->size, &pos, prefix, false, &tag)) {
369 string_append_buf(&buf, tag.name.data, tag.name.length);
370 string_append_cstring(&buf, " ");
371 string_append_buf(&buf, tag.filename.data, tag.filename.length);
372 if (tag.kind) {
373 string_sprintf(&buf, " kind:%c", tag.kind);
374 }
375 if (tag.local) {
376 string_append_cstring(&buf, " LOCAL");
377 }
378 if (tag.pattern) {
379 string_sprintf(&buf, " /%s/", tag.pattern);
380 } else {
381 string_sprintf(&buf, " lineno:%lu", tag.lineno);
382 }
383 string_append_byte(&buf, '\n');
384 free_tag(&tag);
385 }
386
387 return buf;
388 }
389