dte test coverage


Directory: ./
File: src/tag.c
Date: 2024-12-21 16:03:22
Exec Total Coverage
Lines: 5 183 2.7%
Functions: 1 15 6.7%
Branches: 0 110 0.0%

Line Branch Exec Source
1 #include "feature.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 "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 "tags" file from directory path and its parent directories
100 static int open_tag_file(char *path)
101 {
102 static const char tags[] = "tags";
103 while (*path) {
104 size_t len = strlen(path);
105 char *slash = strrchr(path, '/');
106 if (slash != path + len - 1) {
107 path[len++] = '/';
108 }
109 memcpy(path + len, tags, sizeof(tags));
110 int fd = xopen(path, O_RDONLY | O_CLOEXEC, 0);
111 if (fd >= 0) {
112 return fd;
113 }
114 if (errno != ENOENT) {
115 error_msg("failed to open '%s': %s", path, strerror(errno));
116 return -1;
117 }
118 *slash = '\0';
119 }
120
121 error_msg("no tags file");
122 return -1;
123 }
124
125 static bool tag_file_changed (
126 const TagFile *tf,
127 const char *filename,
128 const struct stat *st
129 ) {
130 return tf->mtime != st->st_mtime || !streq(tf->filename, filename);
131 }
132
133 // Note: does not free `tf` itself
134 8 void tag_file_free(TagFile *tf)
135 {
136 8 free(tf->filename);
137 8 free(tf->buf);
138 8 *tf = (TagFile){.filename = NULL};
139 8 }
140
141 static bool load_tag_file(TagFile *tf)
142 {
143 char path[8192];
144 if (unlikely(!getcwd(path, sizeof(path) - STRLEN("/tags")))) {
145 return error_msg_errno("getcwd");
146 }
147
148 int fd = open_tag_file(path);
149 if (fd < 0) {
150 return false;
151 }
152
153 struct stat st;
154 if (unlikely(fstat(fd, &st) != 0)) {
155 const char *str = strerror(errno);
156 xclose(fd);
157 return error_msg("fstat: %s", str);
158 }
159
160 if (unlikely(st.st_size <= 0)) {
161 xclose(fd);
162 return error_msg("empty tags file");
163 }
164
165 if (tf->filename) {
166 if (!tag_file_changed(tf, path, &st)) {
167 xclose(fd);
168 return true;
169 }
170 tag_file_free(tf);
171 BUG_ON(tf->filename);
172 }
173
174 char *buf = malloc(st.st_size);
175 if (unlikely(!buf)) {
176 xclose(fd);
177 return error_msg("malloc: %s", strerror(ENOMEM));
178 }
179
180 ssize_t size = xread_all(fd, buf, st.st_size);
181 int err = errno;
182 xclose(fd);
183 if (size < 0) {
184 free(buf);
185 return error_msg("read: %s", strerror(err));
186 }
187
188 *tf = (TagFile) {
189 .filename = xstrdup(path),
190 .dirname_len = (xstrrchr(path, '/') - path) + 1, // Includes last slash
191 .buf = buf,
192 .size = size,
193 .mtime = st.st_mtime,
194 };
195
196 return true;
197 }
198
199 static void free_tags_cb(Tag *t)
200 {
201 free_tag(t);
202 free(t);
203 }
204
205 static void free_tags(PointerArray *tags)
206 {
207 ptr_array_free_cb(tags, FREE_FUNC(free_tags_cb));
208 }
209
210 #if !HAVE_QSORT_R
211 static const char *current_filename_global; // NOLINT(*-non-const-global-variables)
212 static int tag_cmp(const void *ap, const void *bp)
213 {
214 return tag_cmp_r(ap, bp, (char*)current_filename_global);
215 }
216 #endif
217
218 static void tag_file_find_tags (
219 const TagFile *tf,
220 const char *filename,
221 const StringView *name,
222 PointerArray *tags
223 ) {
224 Tag *t = xnew(Tag, 1);
225 size_t pos = 0;
226 while (next_tag(tf->buf, tf->size, &pos, name, true, t)) {
227 ptr_array_append(tags, t);
228 t = xnew(Tag, 1);
229 }
230 free(t);
231
232 if (tags->count < 2) {
233 return;
234 }
235
236 if (filename) {
237 BUG_ON(!path_is_absolute(filename));
238 size_t n = tf->dirname_len;
239 BUG_ON(n == 0);
240 if (strncmp(filename, tf->filename, n) == 0) {
241 filename += n;
242 } else {
243 // Filename doesn't start with directory prefix of tag file
244 filename = NULL;
245 }
246 }
247
248 void **ptrs = tags->ptrs;
249 BUG_ON(!ptrs);
250
251 #if HAVE_QSORT_R
252 qsort_r(ptrs, tags->count, sizeof(*ptrs), tag_cmp_r, (char*)filename);
253 #else
254 current_filename_global = filename;
255 qsort(ptrs, tags->count, sizeof(*ptrs), tag_cmp);
256 current_filename_global = NULL;
257 #endif
258 }
259
260 // Note: this moves ownership of tag->pattern to the generated Message
261 // and assigns NULL to the old pointer
262 void add_message_for_tag(MessageArray *messages, Tag *tag, const StringView *dir)
263 {
264 BUG_ON(dir->length == 0);
265 BUG_ON(dir->data[0] != '/');
266
267 static const char prefix[] = "Tag ";
268 size_t prefix_len = sizeof(prefix) - 1;
269 size_t msg_len = prefix_len + tag->name.length;
270 Message *m = xmalloc(sizeof(*m) + msg_len + 1);
271
272 memcpy(m->msg, prefix, prefix_len);
273 memcpy(m->msg + prefix_len, tag->name.data, tag->name.length);
274 m->msg[msg_len] = '\0';
275
276 char *filename = path_join_sv(dir, &tag->filename, false);
277 m->loc = new_file_location(filename, 0, 0, 0);
278
279 if (tag->pattern) {
280 m->loc->pattern = tag->pattern; // Message takes ownership
281 tag->pattern = NULL;
282 } else {
283 m->loc->line = tag->lineno;
284 }
285
286 add_message(messages, m);
287 }
288
289 size_t tag_lookup(TagFile *tf, const StringView *name, const char *filename, MessageArray *messages)
290 {
291 clear_messages(messages);
292 if (!load_tag_file(tf)) {
293 return 0;
294 }
295
296 // Filename helps to find correct tags
297 PointerArray tags = PTR_ARRAY_INIT;
298 tag_file_find_tags(tf, filename, name, &tags);
299
300 size_t ntags = tags.count;
301 if (ntags == 0) {
302 error_msg("Tag '%.*s' not found", (int)name->length, name->data);
303 return 0;
304 }
305
306 // Note that `dirname_len` always includes a trailing slash, but the
307 // call to path_join_sv() in add_message_for_tag() handles that fine
308 BUG_ON(tf->dirname_len == 0);
309 StringView tagfile_dir = string_view(tf->filename, tf->dirname_len);
310
311 for (size_t i = 0; i < ntags; i++) {
312 Tag *tag = tags.ptrs[i];
313 add_message_for_tag(messages, tag, &tagfile_dir);
314 }
315
316 free_tags(&tags);
317 return ntags;
318 }
319
320 void collect_tags(TagFile *tf, PointerArray *a, const StringView *prefix)
321 {
322 if (!load_tag_file(tf)) {
323 return;
324 }
325
326 Tag t;
327 size_t pos = 0;
328 StringView prev = STRING_VIEW_INIT;
329 while (next_tag(tf->buf, tf->size, &pos, prefix, false, &t)) {
330 BUG_ON(t.name.length == 0);
331 if (prev.length == 0 || !strview_equal(&t.name, &prev)) {
332 ptr_array_append(a, xstrcut(t.name.data, t.name.length));
333 prev = t.name;
334 }
335 free_tag(&t);
336 }
337 }
338
339 String dump_tags(TagFile *tf)
340 {
341 if (!load_tag_file(tf)) {
342 return string_new(0);
343 }
344
345 const struct timespec ts = {.tv_sec = tf->mtime};
346 char sizestr[FILESIZE_STR_MAX];
347 char tstr[TIME_STR_BUFSIZE];
348 String buf = string_new(tf->size);
349
350 string_sprintf (
351 &buf,
352 "Tags file\n---------\n\n"
353 "%s %s\n%s %s\n%s %s\n\n"
354 "Tag entries\n-----------\n\n",
355 " Path:", tf->filename,
356 " Modified:", timespec_to_str(&ts, tstr, sizeof(tstr)) ? tstr : "-",
357 " Size:", filesize_to_str(tf->size, sizestr)
358 );
359
360 const StringView prefix = STRING_VIEW_INIT;
361 size_t pos = 0;
362 Tag tag;
363
364 while (next_tag(tf->buf, tf->size, &pos, &prefix, false, &tag)) {
365 string_append_buf(&buf, tag.name.data, tag.name.length);
366 string_append_cstring(&buf, " ");
367 string_append_buf(&buf, tag.filename.data, tag.filename.length);
368 if (tag.kind) {
369 string_sprintf(&buf, " kind:%c", tag.kind);
370 }
371 if (tag.local) {
372 string_append_cstring(&buf, " LOCAL");
373 }
374 if (tag.pattern) {
375 string_sprintf(&buf, " /%s/", tag.pattern);
376 } else {
377 string_sprintf(&buf, " lineno:%lu", tag.lineno);
378 }
379 string_append_byte(&buf, '\n');
380 free_tag(&tag);
381 }
382
383 return buf;
384 }
385