1 | /* File format for coverage information |
2 | Copyright (C) 1996-2023 Free Software Foundation, Inc. |
3 | Contributed by Bob Manson <manson@cygnus.com>. |
4 | Completely remangled by Nathan Sidwell <nathan@codesourcery.com>. |
5 | |
6 | This file is part of GCC. |
7 | |
8 | GCC is free software; you can redistribute it and/or modify it under |
9 | the terms of the GNU General Public License as published by the Free |
10 | Software Foundation; either version 3, or (at your option) any later |
11 | version. |
12 | |
13 | GCC is distributed in the hope that it will be useful, but WITHOUT ANY |
14 | WARRANTY; without even the implied warranty of MERCHANTABILITY or |
15 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
16 | for more details. |
17 | |
18 | Under Section 7 of GPL version 3, you are granted additional |
19 | permissions described in the GCC Runtime Library Exception, version |
20 | 3.1, as published by the Free Software Foundation. |
21 | |
22 | You should have received a copy of the GNU General Public License and |
23 | a copy of the GCC Runtime Library Exception along with this program; |
24 | see the files COPYING3 and COPYING.RUNTIME respectively. If not, see |
25 | <http://www.gnu.org/licenses/>. */ |
26 | |
27 | /* Routines declared in gcov-io.h. This file should be #included by |
28 | another source file, after having #included gcov-io.h. */ |
29 | |
30 | static gcov_unsigned_t *gcov_read_words (void *buffer, unsigned); |
31 | |
32 | /* Indicates the last gcov file access error or that no error occurred |
33 | so far. */ |
34 | enum gcov_file_error |
35 | { |
36 | GCOV_FILE_COUNTER_OVERFLOW = -1, |
37 | GCOV_FILE_NO_ERROR = 0, |
38 | GCOV_FILE_WRITE_ERROR = 1, |
39 | GCOV_FILE_EOF = 2 |
40 | }; |
41 | |
42 | struct gcov_var |
43 | { |
44 | FILE *file; |
45 | enum gcov_file_error error; |
46 | int mode; /* < 0 writing, > 0 reading. */ |
47 | int endian; /* Swap endianness. */ |
48 | #ifdef IN_GCOV_TOOL |
49 | gcov_position_t pos; /* File position for stdin support. */ |
50 | #endif |
51 | } gcov_var; |
52 | |
53 | #define GCOV_MODE_STDIN 2 |
54 | |
55 | /* Save the current position in the gcov file. */ |
56 | /* We need to expose this function when compiling for gcov-tool. */ |
57 | #ifndef IN_GCOV_TOOL |
58 | static inline |
59 | #endif |
60 | gcov_position_t |
61 | gcov_position (void) |
62 | { |
63 | #ifdef IN_GCOV_TOOL |
64 | if (gcov_var.mode == GCOV_MODE_STDIN) |
65 | return gcov_var.pos; |
66 | #endif |
67 | return ftell (stream: gcov_var.file); |
68 | } |
69 | |
70 | /* Return nonzero if the error flag is set. */ |
71 | /* We need to expose this function when compiling for gcov-tool. */ |
72 | #ifndef IN_GCOV_TOOL |
73 | static inline |
74 | #endif |
75 | int |
76 | gcov_is_error (void) |
77 | { |
78 | return gcov_var.file ? gcov_var.error : 1; |
79 | } |
80 | |
81 | #if IN_LIBGCOV |
82 | /* Move to beginning of file, initialize for writing, and clear file error |
83 | status. */ |
84 | |
85 | GCOV_LINKAGE inline void |
86 | gcov_rewrite (void) |
87 | { |
88 | gcov_var.mode = -1; |
89 | gcov_var.error = GCOV_FILE_NO_ERROR; |
90 | fseek (gcov_var.file, 0L, SEEK_SET); |
91 | } |
92 | #endif |
93 | |
94 | static inline gcov_unsigned_t |
95 | from_file (gcov_unsigned_t value) |
96 | { |
97 | #if !IN_LIBGCOV || defined (IN_GCOV_TOOL) |
98 | if (gcov_var.endian) |
99 | return __builtin_bswap32 (value); |
100 | #endif |
101 | return value; |
102 | } |
103 | |
104 | /* Open a gcov file. NAME is the name of the file to open and MODE |
105 | indicates whether a new file should be created, or an existing file |
106 | opened. If MODE is >= 0 an existing file will be opened, if |
107 | possible, and if MODE is <= 0, a new file will be created. Use |
108 | MODE=0 to attempt to reopen an existing file and then fall back on |
109 | creating a new one. If MODE > 0, the file will be opened in |
110 | read-only mode. Otherwise it will be opened for modification. |
111 | Return zero on failure, non-zero on success. */ |
112 | |
113 | GCOV_LINKAGE int |
114 | gcov_open (const char *name, int mode) |
115 | { |
116 | #if GCOV_LOCKED |
117 | struct flock s_flock; |
118 | int fd; |
119 | |
120 | s_flock.l_whence = SEEK_SET; |
121 | s_flock.l_start = 0; |
122 | s_flock.l_len = 0; /* Until EOF. */ |
123 | s_flock.l_pid = getpid (); |
124 | #elif GCOV_LOCKED_WITH_LOCKING |
125 | int fd; |
126 | #endif |
127 | |
128 | gcov_nonruntime_assert (!gcov_var.file); |
129 | gcov_var.error = GCOV_FILE_NO_ERROR; |
130 | #if !IN_LIBGCOV || defined (IN_GCOV_TOOL) |
131 | gcov_var.endian = 0; |
132 | #endif |
133 | #ifdef IN_GCOV_TOOL |
134 | gcov_var.pos = 0; |
135 | if (!name) |
136 | { |
137 | gcov_nonruntime_assert (gcov_var.mode > 0); |
138 | gcov_var.file = stdin; |
139 | gcov_var.mode = GCOV_MODE_STDIN; |
140 | return 1; |
141 | } |
142 | #endif |
143 | #if GCOV_LOCKED |
144 | if (mode > 0) |
145 | { |
146 | /* Read-only mode - acquire a read-lock. */ |
147 | s_flock.l_type = F_RDLCK; |
148 | /* pass mode (ignored) for compatibility */ |
149 | fd = open (file: name, O_RDONLY, S_IRUSR | S_IWUSR); |
150 | } |
151 | else |
152 | { |
153 | /* Write mode - acquire a write-lock. */ |
154 | s_flock.l_type = F_WRLCK; |
155 | /* Truncate if force new mode. */ |
156 | fd = open (file: name, O_RDWR | O_CREAT | (mode < 0 ? O_TRUNC : 0), 0666); |
157 | } |
158 | if (fd < 0) |
159 | return 0; |
160 | |
161 | while (fcntl (fd: fd, F_SETLKW, &s_flock) && errno == EINTR) |
162 | continue; |
163 | |
164 | gcov_var.file = fdopen (fd, (mode > 0) ? "rb" : "r+b" ); |
165 | |
166 | if (!gcov_var.file) |
167 | { |
168 | close (fd: fd); |
169 | return 0; |
170 | } |
171 | #elif GCOV_LOCKED_WITH_LOCKING |
172 | if (mode > 0) |
173 | { |
174 | /* pass mode (ignored) for compatibility */ |
175 | fd = open (name, O_RDONLY | O_BINARY, S_IRUSR | S_IWUSR); |
176 | } |
177 | else |
178 | { |
179 | /* Truncate if force new mode. */ |
180 | fd = open (name, O_RDWR | O_BINARY | O_CREAT | (mode < 0 ? O_TRUNC : 0), |
181 | 0666); |
182 | } |
183 | if (fd < 0) |
184 | return 0; |
185 | |
186 | if (_locking (fd, _LK_LOCK, LONG_MAX) < 0) |
187 | { |
188 | close (fd); |
189 | return 0; |
190 | } |
191 | |
192 | gcov_var.file = fdopen (fd, (mode > 0) ? "rb" : "r+b" ); |
193 | |
194 | if (!gcov_var.file) |
195 | { |
196 | close (fd); |
197 | return 0; |
198 | } |
199 | #else |
200 | if (mode >= 0) |
201 | /* Open an existing file. */ |
202 | gcov_var.file = fopen (name, (mode > 0) ? "rb" : "r+b" ); |
203 | |
204 | if (gcov_var.file) |
205 | mode = 1; |
206 | else if (mode <= 0) |
207 | /* Create a new file. */ |
208 | gcov_var.file = fopen (name, "w+b" ); |
209 | |
210 | if (!gcov_var.file) |
211 | return 0; |
212 | #endif |
213 | |
214 | gcov_var.mode = mode ? mode : 1; |
215 | |
216 | return 1; |
217 | } |
218 | |
219 | /* Close the current gcov file. Flushes data to disk. Returns nonzero |
220 | on failure or error flag set. */ |
221 | |
222 | GCOV_LINKAGE int |
223 | gcov_close (void) |
224 | { |
225 | #ifdef IN_GCOV_TOOL |
226 | if (gcov_var.file == stdin) |
227 | gcov_var.file = 0; |
228 | else |
229 | #endif |
230 | if (gcov_var.file) |
231 | { |
232 | if (fclose (stream: gcov_var.file)) |
233 | gcov_var.error = GCOV_FILE_WRITE_ERROR; |
234 | |
235 | gcov_var.file = 0; |
236 | } |
237 | gcov_var.mode = 0; |
238 | return gcov_var.error; |
239 | } |
240 | |
241 | #if !IN_LIBGCOV || defined (IN_GCOV_TOOL) |
242 | /* Check if MAGIC is EXPECTED. Use it to determine endianness of the |
243 | file. Returns +1 for same endian, -1 for other endian and zero for |
244 | not EXPECTED. */ |
245 | |
246 | GCOV_LINKAGE int |
247 | gcov_magic (gcov_unsigned_t magic, gcov_unsigned_t expected) |
248 | { |
249 | if (magic == expected) |
250 | return 1; |
251 | |
252 | if (__builtin_bswap32 (magic) == expected) |
253 | { |
254 | gcov_var.endian = 1; |
255 | return -1; |
256 | } |
257 | return 0; |
258 | } |
259 | #endif |
260 | |
261 | #if !IN_GCOV |
262 | /* Write DATA of LENGTH characters to coverage file. */ |
263 | |
264 | GCOV_LINKAGE void |
265 | gcov_write (const void *data, unsigned length) |
266 | { |
267 | gcov_unsigned_t r = fwrite (data, length, 1, gcov_var.file); |
268 | if (r != 1) |
269 | gcov_var.error = GCOV_FILE_WRITE_ERROR; |
270 | } |
271 | |
272 | /* Write unsigned VALUE to coverage file. */ |
273 | |
274 | GCOV_LINKAGE void |
275 | gcov_write_unsigned (gcov_unsigned_t value) |
276 | { |
277 | gcov_unsigned_t r = fwrite (&value, sizeof (value), 1, gcov_var.file); |
278 | if (r != 1) |
279 | gcov_var.error = GCOV_FILE_WRITE_ERROR; |
280 | } |
281 | |
282 | #if !IN_LIBGCOV |
283 | /* Write STRING to coverage file. Sets error flag on file |
284 | error, overflow flag on overflow */ |
285 | |
286 | GCOV_LINKAGE void |
287 | gcov_write_string (const char *string) |
288 | { |
289 | unsigned length = 0; |
290 | |
291 | if (string) |
292 | length = strlen (string) + 1; |
293 | |
294 | gcov_write_unsigned (length); |
295 | if (length > 0) |
296 | { |
297 | gcov_unsigned_t r = fwrite (string, length, 1, gcov_var.file); |
298 | if (r != 1) |
299 | gcov_var.error = GCOV_FILE_WRITE_ERROR; |
300 | } |
301 | } |
302 | #endif |
303 | |
304 | #if !IN_LIBGCOV |
305 | /* Write FILENAME to coverage file. Sets error flag on file |
306 | error, overflow flag on overflow */ |
307 | |
308 | GCOV_LINKAGE void |
309 | gcov_write_filename (const char *filename) |
310 | { |
311 | if (profile_abs_path_flag && filename && filename[0] |
312 | && !(IS_DIR_SEPARATOR (filename[0]) |
313 | #if HAVE_DOS_BASED_FILE_SYSTEM |
314 | || filename[1] == ':' |
315 | #endif |
316 | )) |
317 | { |
318 | char *buf = getcwd (NULL, 0); |
319 | if (buf != NULL && buf[0]) |
320 | { |
321 | size_t len = strlen (buf); |
322 | buf = (char*)xrealloc (buf, len + strlen (filename) + 2); |
323 | if (!IS_DIR_SEPARATOR (buf[len - 1])) |
324 | strcat (buf, "/" ); |
325 | strcat (buf, filename); |
326 | gcov_write_string (buf); |
327 | free (buf); |
328 | return; |
329 | } |
330 | } |
331 | |
332 | gcov_write_string (filename); |
333 | } |
334 | |
335 | /* Move to a given position in a gcov file. */ |
336 | |
337 | static void |
338 | gcov_seek (gcov_position_t base) |
339 | { |
340 | fseek (gcov_var.file, base, SEEK_SET); |
341 | } |
342 | |
343 | /* Write a tag TAG and reserve space for the record length. Return a |
344 | value to be used for gcov_write_length. */ |
345 | |
346 | GCOV_LINKAGE gcov_position_t |
347 | gcov_write_tag (gcov_unsigned_t tag) |
348 | { |
349 | gcov_position_t result = gcov_position (); |
350 | gcov_write_unsigned (tag); |
351 | gcov_write_unsigned (0); |
352 | |
353 | return result; |
354 | } |
355 | |
356 | /* Write a record length using POSITION, which was returned by |
357 | gcov_write_tag. The current file position is the end of the |
358 | record, and is restored before returning. Returns nonzero on |
359 | overflow. */ |
360 | |
361 | GCOV_LINKAGE void |
362 | gcov_write_length (gcov_position_t position) |
363 | { |
364 | gcov_position_t current_position = gcov_position (); |
365 | gcov_nonruntime_assert (gcov_var.mode < 0); |
366 | gcov_nonruntime_assert (current_position >= position + 2 * GCOV_WORD_SIZE); |
367 | |
368 | gcov_seek (position + GCOV_WORD_SIZE); |
369 | gcov_write_unsigned (current_position - position - 2 * GCOV_WORD_SIZE); |
370 | gcov_seek (current_position); |
371 | } |
372 | |
373 | #else /* IN_LIBGCOV */ |
374 | |
375 | /* Write an object summary structure to the gcov file. */ |
376 | |
377 | GCOV_LINKAGE void |
378 | gcov_write_object_summary (const struct gcov_summary *summary) |
379 | { |
380 | gcov_write_unsigned (GCOV_TAG_OBJECT_SUMMARY); |
381 | gcov_write_unsigned (GCOV_TAG_OBJECT_SUMMARY_LENGTH); |
382 | gcov_write_unsigned (summary->runs); |
383 | gcov_write_unsigned (summary->sum_max); |
384 | } |
385 | |
386 | #endif /* IN_LIBGCOV */ |
387 | |
388 | #endif /*!IN_GCOV */ |
389 | |
390 | /* Return a pointer to read COUNT bytes from the gcov file. Returns |
391 | NULL on failure (read past EOF). */ |
392 | |
393 | static void * |
394 | gcov_read_bytes (void *buffer, unsigned count) |
395 | { |
396 | if (gcov_var.mode <= 0) |
397 | return NULL; |
398 | |
399 | unsigned read = fread (ptr: buffer, size: count, n: 1, stream: gcov_var.file); |
400 | if (read != 1) |
401 | { |
402 | if (feof (stream: gcov_var.file)) |
403 | gcov_var.error = GCOV_FILE_EOF; |
404 | return NULL; |
405 | } |
406 | |
407 | #ifdef IN_GCOV_TOOL |
408 | gcov_var.pos += count; |
409 | #endif |
410 | return buffer; |
411 | } |
412 | |
413 | /* Read WORDS gcov_unsigned_t values from gcov file. */ |
414 | |
415 | static gcov_unsigned_t * |
416 | gcov_read_words (void *buffer, unsigned words) |
417 | { |
418 | return (gcov_unsigned_t *)gcov_read_bytes (buffer, GCOV_WORD_SIZE * words); |
419 | } |
420 | |
421 | /* Read unsigned value from a coverage file. Sets error flag on file |
422 | error, overflow flag on overflow */ |
423 | |
424 | GCOV_LINKAGE gcov_unsigned_t |
425 | gcov_read_unsigned (void) |
426 | { |
427 | gcov_unsigned_t value; |
428 | gcov_unsigned_t allocated_buffer[1]; |
429 | gcov_unsigned_t *buffer = gcov_read_words (buffer: &allocated_buffer, words: 1); |
430 | |
431 | if (!buffer) |
432 | return 0; |
433 | |
434 | value = from_file (value: buffer[0]); |
435 | return value; |
436 | } |
437 | |
438 | /* Read counter value from a coverage file. Sets error flag on file |
439 | error, overflow flag on overflow */ |
440 | |
441 | GCOV_LINKAGE gcov_type |
442 | gcov_read_counter (void) |
443 | { |
444 | gcov_type value; |
445 | gcov_unsigned_t allocated_buffer[2]; |
446 | gcov_unsigned_t *buffer = gcov_read_words (buffer: &allocated_buffer, words: 2); |
447 | |
448 | if (!buffer) |
449 | return 0; |
450 | value = from_file (value: buffer[0]); |
451 | if (sizeof (value) > sizeof (gcov_unsigned_t)) |
452 | value |= ((gcov_type) from_file (value: buffer[1])) << 32; |
453 | else if (buffer[1]) |
454 | gcov_var.error = GCOV_FILE_COUNTER_OVERFLOW; |
455 | |
456 | return value; |
457 | } |
458 | |
459 | /* Mangle filename path of BASE and output new allocated pointer with |
460 | mangled path. */ |
461 | |
462 | char * |
463 | mangle_path (char const *base) |
464 | { |
465 | /* Convert '/' to '#', convert '..' to '^', |
466 | convert ':' to '~' on DOS based file system. */ |
467 | const char *probe; |
468 | char *buffer = (char *)xmalloc (strlen (s: base) + 1); |
469 | char *ptr = buffer; |
470 | |
471 | #if HAVE_DOS_BASED_FILE_SYSTEM |
472 | if (base[0] && base[1] == ':') |
473 | { |
474 | ptr[0] = base[0]; |
475 | ptr[1] = '~'; |
476 | ptr += 2; |
477 | base += 2; |
478 | } |
479 | #endif |
480 | for (; *base; base = probe) |
481 | { |
482 | size_t len; |
483 | |
484 | for (probe = base; *probe; probe++) |
485 | if (*probe == '/') |
486 | break; |
487 | len = probe - base; |
488 | if (len == 2 && base[0] == '.' && base[1] == '.') |
489 | *ptr++ = '^'; |
490 | else |
491 | { |
492 | memcpy (dest: ptr, src: base, n: len); |
493 | ptr += len; |
494 | } |
495 | if (*probe) |
496 | { |
497 | *ptr++ = '#'; |
498 | probe++; |
499 | } |
500 | } |
501 | |
502 | /* Terminate the string. */ |
503 | *ptr = '\0'; |
504 | |
505 | return buffer; |
506 | } |
507 | |
508 | /* We need to expose the below function when compiling for gcov-tool. */ |
509 | |
510 | #if !IN_LIBGCOV || defined (IN_GCOV_TOOL) |
511 | /* Read string from coverage file. Allocate the buffer for the string |
512 | from the heap or die. Return a pointer to the string, or NULL on |
513 | empty string. */ |
514 | |
515 | GCOV_LINKAGE const char * |
516 | gcov_read_string (void) |
517 | { |
518 | unsigned length = gcov_read_unsigned (); |
519 | |
520 | if (!length) |
521 | return 0; |
522 | |
523 | void *buffer = XNEWVEC (char *, length); |
524 | return (const char *) gcov_read_bytes (buffer, count: length); |
525 | } |
526 | #endif |
527 | |
528 | GCOV_LINKAGE void |
529 | gcov_read_summary (struct gcov_summary *summary) |
530 | { |
531 | summary->runs = gcov_read_unsigned (); |
532 | summary->sum_max = gcov_read_unsigned (); |
533 | } |
534 | |
535 | /* We need to expose the below function when compiling for gcov-tool. */ |
536 | |
537 | #if !IN_LIBGCOV || defined (IN_GCOV_TOOL) |
538 | /* Reset to a known position. BASE should have been obtained from |
539 | gcov_position, LENGTH should be a record length. */ |
540 | |
541 | GCOV_LINKAGE void |
542 | gcov_sync (gcov_position_t base, gcov_unsigned_t length) |
543 | { |
544 | gcov_nonruntime_assert (gcov_var.mode > 0); |
545 | base += length; |
546 | #ifdef IN_GCOV_TOOL |
547 | if (gcov_var.mode == GCOV_MODE_STDIN) |
548 | { |
549 | while (gcov_var.pos < base) |
550 | { |
551 | ++gcov_var.pos; |
552 | (void)fgetc (gcov_var.file); |
553 | } |
554 | return; |
555 | } |
556 | #endif |
557 | fseek (stream: gcov_var.file, off: base, SEEK_SET); |
558 | } |
559 | #endif |
560 | |
561 | #if IN_GCOV > 0 |
562 | /* Return the modification time of the current gcov file. */ |
563 | |
564 | GCOV_LINKAGE time_t |
565 | gcov_time (void) |
566 | { |
567 | struct stat status; |
568 | |
569 | if (fstat (fileno (gcov_var.file), buf: &status)) |
570 | return 0; |
571 | else |
572 | return status.st_mtime; |
573 | } |
574 | #endif /* IN_GCOV */ |
575 | |