xref: /netbsd/external/gpl3/gcc.old/dist/gcc/gcov-io.c (revision ec02198a)
1 /* File format for coverage information
2    Copyright (C) 1996-2020 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 #if !IN_GCOV
31 static void gcov_write_block (unsigned);
32 static gcov_unsigned_t *gcov_write_words (unsigned);
33 #endif
34 static const gcov_unsigned_t *gcov_read_words (unsigned);
35 #if !IN_LIBGCOV
36 static void gcov_allocate (unsigned);
37 #endif
38 
39 /* Optimum number of gcov_unsigned_t's read from or written to disk.  */
40 #define GCOV_BLOCK_SIZE (1 << 10)
41 
42 struct gcov_var
43 {
44   FILE *file;
45   gcov_position_t start;	/* Position of first byte of block */
46   unsigned offset;		/* Read/write position within the block.  */
47   unsigned length;		/* Read limit in the block.  */
48   unsigned overread;		/* Number of words overread.  */
49   int error;			/* < 0 overflow, > 0 disk error.  */
50   int mode;	                /* < 0 writing, > 0 reading */
51   int endian;			/* Swap endianness.  */
52 #if IN_LIBGCOV
53   /* Holds one block plus 4 bytes, thus all coverage reads & writes
54      fit within this buffer and we always can transfer GCOV_BLOCK_SIZE
55      to and from the disk. libgcov never backtracks and only writes 4
56      or 8 byte objects.  */
57   gcov_unsigned_t buffer[GCOV_BLOCK_SIZE + 1];
58 #else
59   /* Holds a variable length block, as the compiler can write
60      strings and needs to backtrack.  */
61   size_t alloc;
62   gcov_unsigned_t *buffer;
63 #endif
64 } gcov_var;
65 
66 /* Save the current position in the gcov file.  */
67 /* We need to expose this function when compiling for gcov-tool.  */
68 #ifndef IN_GCOV_TOOL
69 static inline
70 #endif
71 gcov_position_t
gcov_position(void)72 gcov_position (void)
73 {
74   gcov_nonruntime_assert (gcov_var.mode > 0);
75   return gcov_var.start + gcov_var.offset;
76 }
77 
78 /* Return nonzero if the error flag is set.  */
79 /* We need to expose this function when compiling for gcov-tool.  */
80 #ifndef IN_GCOV_TOOL
81 static inline
82 #endif
83 int
gcov_is_error(void)84 gcov_is_error (void)
85 {
86   return gcov_var.file ? gcov_var.error : 1;
87 }
88 
89 #if IN_LIBGCOV
90 /* Move to beginning of file and initialize for writing.  */
91 GCOV_LINKAGE inline void
gcov_rewrite(void)92 gcov_rewrite (void)
93 {
94   gcov_var.mode = -1;
95   gcov_var.start = 0;
96   gcov_var.offset = 0;
97   fseek (gcov_var.file, 0L, SEEK_SET);
98 }
99 #endif
100 
from_file(gcov_unsigned_t value)101 static inline gcov_unsigned_t from_file (gcov_unsigned_t value)
102 {
103 #if !IN_LIBGCOV || defined (IN_GCOV_TOOL)
104   if (gcov_var.endian)
105     {
106       value = (value >> 16) | (value << 16);
107       value = ((value & 0xff00ff) << 8) | ((value >> 8) & 0xff00ff);
108     }
109 #endif
110   return value;
111 }
112 
113 /* Open a gcov file. NAME is the name of the file to open and MODE
114    indicates whether a new file should be created, or an existing file
115    opened. If MODE is >= 0 an existing file will be opened, if
116    possible, and if MODE is <= 0, a new file will be created. Use
117    MODE=0 to attempt to reopen an existing file and then fall back on
118    creating a new one.  If MODE > 0, the file will be opened in
119    read-only mode.  Otherwise it will be opened for modification.
120    Return zero on failure, non-zero on success.  */
121 
122 GCOV_LINKAGE int
123 #if IN_LIBGCOV
gcov_open(const char * name)124 gcov_open (const char *name)
125 #else
126 gcov_open (const char *name, int mode)
127 #endif
128 {
129 #if IN_LIBGCOV
130   int mode = 0;
131 #endif
132 #if GCOV_LOCKED
133   struct flock s_flock;
134   int fd;
135 
136   s_flock.l_whence = SEEK_SET;
137   s_flock.l_start = 0;
138   s_flock.l_len = 0; /* Until EOF.  */
139   s_flock.l_pid = getpid ();
140 #endif
141 
142   gcov_nonruntime_assert (!gcov_var.file);
143   gcov_var.start = 0;
144   gcov_var.offset = gcov_var.length = 0;
145   gcov_var.overread = -1u;
146   gcov_var.error = 0;
147 #if !IN_LIBGCOV
148   gcov_var.endian = 0;
149 #endif
150 #if GCOV_LOCKED
151   if (mode > 0)
152     {
153       /* Read-only mode - acquire a read-lock.  */
154       s_flock.l_type = F_RDLCK;
155       /* pass mode (ignored) for compatibility */
156       fd = open (name, O_RDONLY, S_IRUSR | S_IWUSR);
157     }
158   else
159      {
160        /* Write mode - acquire a write-lock.  */
161        s_flock.l_type = F_WRLCK;
162        /* Truncate if force new mode.  */
163        fd = open (name, O_RDWR | O_CREAT | (mode < 0 ? O_TRUNC : 0), 0666);
164     }
165   if (fd < 0)
166     return 0;
167 
168   while (fcntl (fd, F_SETLKW, &s_flock) && errno == EINTR)
169     continue;
170 
171   gcov_var.file = fdopen (fd, (mode > 0) ? "rb" : "r+b");
172 
173   if (!gcov_var.file)
174     {
175       close (fd);
176       return 0;
177     }
178 #else
179   if (mode >= 0)
180     /* Open an existing file.  */
181     gcov_var.file = fopen (name, (mode > 0) ? "rb" : "r+b");
182 
183   if (gcov_var.file)
184     mode = 1;
185   else if (mode <= 0)
186     /* Create a new file.  */
187     gcov_var.file = fopen (name, "w+b");
188 
189   if (!gcov_var.file)
190     return 0;
191 #endif
192 
193   gcov_var.mode = mode ? mode : 1;
194 
195   setbuf (gcov_var.file, (char *)0);
196 
197   return 1;
198 }
199 
200 /* Close the current gcov file. Flushes data to disk. Returns nonzero
201    on failure or error flag set.  */
202 
203 GCOV_LINKAGE int
gcov_close(void)204 gcov_close (void)
205 {
206   if (gcov_var.file)
207     {
208 #if !IN_GCOV
209       if (gcov_var.offset && gcov_var.mode < 0)
210 	gcov_write_block (gcov_var.offset);
211 #endif
212       fclose (gcov_var.file);
213       gcov_var.file = 0;
214       gcov_var.length = 0;
215     }
216 #if !IN_LIBGCOV
217   free (gcov_var.buffer);
218   gcov_var.alloc = 0;
219   gcov_var.buffer = 0;
220 #endif
221   gcov_var.mode = 0;
222   return gcov_var.error;
223 }
224 
225 #if !IN_LIBGCOV || defined (IN_GCOV_TOOL)
226 /* Check if MAGIC is EXPECTED. Use it to determine endianness of the
227    file. Returns +1 for same endian, -1 for other endian and zero for
228    not EXPECTED.  */
229 
230 GCOV_LINKAGE int
gcov_magic(gcov_unsigned_t magic,gcov_unsigned_t expected)231 gcov_magic (gcov_unsigned_t magic, gcov_unsigned_t expected)
232 {
233   if (magic == expected)
234     return 1;
235   magic = (magic >> 16) | (magic << 16);
236   magic = ((magic & 0xff00ff) << 8) | ((magic >> 8) & 0xff00ff);
237   if (magic == expected)
238     {
239       gcov_var.endian = 1;
240       return -1;
241     }
242   return 0;
243 }
244 #endif
245 
246 #if !IN_LIBGCOV
247 static void
gcov_allocate(unsigned length)248 gcov_allocate (unsigned length)
249 {
250   size_t new_size = gcov_var.alloc;
251 
252   if (!new_size)
253     new_size = GCOV_BLOCK_SIZE;
254   new_size += length;
255   new_size *= 2;
256 
257   gcov_var.alloc = new_size;
258   gcov_var.buffer = XRESIZEVAR (gcov_unsigned_t, gcov_var.buffer, new_size << 2);
259 }
260 #endif
261 
262 #if !IN_GCOV
263 /* Write out the current block, if needs be.  */
264 
265 static void
gcov_write_block(unsigned size)266 gcov_write_block (unsigned size)
267 {
268   if (fwrite (gcov_var.buffer, size << 2, 1, gcov_var.file) != 1)
269     gcov_var.error = 1;
270   gcov_var.start += size;
271   gcov_var.offset -= size;
272 }
273 
274 /* Allocate space to write BYTES bytes to the gcov file. Return a
275    pointer to those bytes, or NULL on failure.  */
276 
277 static gcov_unsigned_t *
gcov_write_words(unsigned words)278 gcov_write_words (unsigned words)
279 {
280   gcov_unsigned_t *result;
281 
282   gcov_nonruntime_assert (gcov_var.mode < 0);
283 #if IN_LIBGCOV
284   if (gcov_var.offset >= GCOV_BLOCK_SIZE)
285     {
286       gcov_write_block (GCOV_BLOCK_SIZE);
287       if (gcov_var.offset)
288 	{
289 	  memcpy (gcov_var.buffer, gcov_var.buffer + GCOV_BLOCK_SIZE, 4);
290 	}
291     }
292 #else
293   if (gcov_var.offset + words > gcov_var.alloc)
294     gcov_allocate (gcov_var.offset + words);
295 #endif
296   result = &gcov_var.buffer[gcov_var.offset];
297   gcov_var.offset += words;
298 
299   return result;
300 }
301 
302 /* Write unsigned VALUE to coverage file.  Sets error flag
303    appropriately.  */
304 
305 GCOV_LINKAGE void
gcov_write_unsigned(gcov_unsigned_t value)306 gcov_write_unsigned (gcov_unsigned_t value)
307 {
308   gcov_unsigned_t *buffer = gcov_write_words (1);
309 
310   buffer[0] = value;
311 }
312 
313 /* Write counter VALUE to coverage file.  Sets error flag
314    appropriately.  */
315 
316 #if IN_LIBGCOV
317 GCOV_LINKAGE void
gcov_write_counter(gcov_type value)318 gcov_write_counter (gcov_type value)
319 {
320   gcov_unsigned_t *buffer = gcov_write_words (2);
321 
322   buffer[0] = (gcov_unsigned_t) value;
323   if (sizeof (value) > sizeof (gcov_unsigned_t))
324     buffer[1] = (gcov_unsigned_t) (value >> 32);
325   else
326     buffer[1] = 0;
327 }
328 #endif /* IN_LIBGCOV */
329 
330 #if !IN_LIBGCOV
331 /* Write STRING to coverage file.  Sets error flag on file
332    error, overflow flag on overflow */
333 
334 GCOV_LINKAGE void
gcov_write_string(const char * string)335 gcov_write_string (const char *string)
336 {
337   unsigned length = 0;
338   unsigned alloc = 0;
339   gcov_unsigned_t *buffer;
340 
341   if (string)
342     {
343       length = strlen (string);
344       alloc = (length + 4) >> 2;
345     }
346 
347   buffer = gcov_write_words (1 + alloc);
348 
349   buffer[0] = alloc;
350 
351   if (alloc > 0)
352     {
353       buffer[alloc] = 0; /* place nul terminators.  */
354       memcpy (&buffer[1], string, length);
355     }
356 }
357 #endif
358 
359 #if !IN_LIBGCOV
360 /* Write FILENAME to coverage file.  Sets error flag on file
361    error, overflow flag on overflow */
362 
363 GCOV_LINKAGE void
gcov_write_filename(const char * filename)364 gcov_write_filename (const char *filename)
365 {
366   if (profile_abs_path_flag && filename && filename[0]
367       && !(IS_DIR_SEPARATOR (filename[0])
368 #if HAVE_DOS_BASED_FILE_SYSTEM
369 	   || filename[1] == ':'
370 #endif
371 	  ))
372     {
373       char *buf = getcwd (NULL, 0);
374       if (buf != NULL && buf[0])
375 	{
376 	  size_t len = strlen (buf);
377 	  buf = (char*)xrealloc (buf, len + strlen (filename) + 2);
378 	  if (!IS_DIR_SEPARATOR (buf[len - 1]))
379 	    strcat (buf, "/");
380 	  strcat (buf, filename);
381 	  gcov_write_string (buf);
382 	  free (buf);
383 	  return;
384 	}
385     }
386 
387   gcov_write_string (filename);
388 }
389 #endif
390 
391 #if !IN_LIBGCOV
392 /* Write a tag TAG and reserve space for the record length. Return a
393    value to be used for gcov_write_length.  */
394 
395 GCOV_LINKAGE gcov_position_t
gcov_write_tag(gcov_unsigned_t tag)396 gcov_write_tag (gcov_unsigned_t tag)
397 {
398   gcov_position_t result = gcov_var.start + gcov_var.offset;
399   gcov_unsigned_t *buffer = gcov_write_words (2);
400 
401   buffer[0] = tag;
402   buffer[1] = 0;
403 
404   return result;
405 }
406 
407 /* Write a record length using POSITION, which was returned by
408    gcov_write_tag.  The current file position is the end of the
409    record, and is restored before returning.  Returns nonzero on
410    overflow.  */
411 
412 GCOV_LINKAGE void
gcov_write_length(gcov_position_t position)413 gcov_write_length (gcov_position_t position)
414 {
415   unsigned offset;
416   gcov_unsigned_t length;
417   gcov_unsigned_t *buffer;
418 
419   gcov_nonruntime_assert (gcov_var.mode < 0);
420   gcov_nonruntime_assert (position + 2 <= gcov_var.start + gcov_var.offset);
421   gcov_nonruntime_assert (position >= gcov_var.start);
422   offset = position - gcov_var.start;
423   length = gcov_var.offset - offset - 2;
424   buffer = (gcov_unsigned_t *) &gcov_var.buffer[offset];
425   buffer[1] = length;
426   if (gcov_var.offset >= GCOV_BLOCK_SIZE)
427     gcov_write_block (gcov_var.offset);
428 }
429 
430 #else /* IN_LIBGCOV */
431 
432 /* Write a tag TAG and length LENGTH.  */
433 
434 GCOV_LINKAGE void
gcov_write_tag_length(gcov_unsigned_t tag,gcov_unsigned_t length)435 gcov_write_tag_length (gcov_unsigned_t tag, gcov_unsigned_t length)
436 {
437   gcov_unsigned_t *buffer = gcov_write_words (2);
438 
439   buffer[0] = tag;
440   buffer[1] = length;
441 }
442 
443 /* Write a summary structure to the gcov file.  Return nonzero on
444    overflow.  */
445 
446 GCOV_LINKAGE void
gcov_write_summary(gcov_unsigned_t tag,const struct gcov_summary * summary)447 gcov_write_summary (gcov_unsigned_t tag, const struct gcov_summary *summary)
448 {
449   gcov_write_tag_length (tag, GCOV_TAG_SUMMARY_LENGTH);
450   gcov_write_unsigned (summary->runs);
451   gcov_write_unsigned (summary->sum_max);
452 }
453 
454 #endif /* IN_LIBGCOV */
455 
456 #endif /*!IN_GCOV */
457 
458 /* Return a pointer to read BYTES bytes from the gcov file. Returns
459    NULL on failure (read past EOF).  */
460 
461 static const gcov_unsigned_t *
gcov_read_words(unsigned words)462 gcov_read_words (unsigned words)
463 {
464   const gcov_unsigned_t *result;
465   unsigned excess = gcov_var.length - gcov_var.offset;
466 
467   if (gcov_var.mode <= 0)
468     return NULL;
469 
470   if (excess < words)
471     {
472       gcov_var.start += gcov_var.offset;
473       if (excess)
474 	{
475 #if IN_LIBGCOV
476 	  memcpy (gcov_var.buffer, gcov_var.buffer + gcov_var.offset, 4);
477 #else
478 	  memmove (gcov_var.buffer, gcov_var.buffer + gcov_var.offset,
479 		   excess * 4);
480 #endif
481 	}
482       gcov_var.offset = 0;
483       gcov_var.length = excess;
484 #if IN_LIBGCOV
485       excess = GCOV_BLOCK_SIZE;
486 #else
487       if (gcov_var.length + words > gcov_var.alloc)
488 	gcov_allocate (gcov_var.length + words);
489       excess = gcov_var.alloc - gcov_var.length;
490 #endif
491       excess = fread (gcov_var.buffer + gcov_var.length,
492 		      1, excess << 2, gcov_var.file) >> 2;
493       gcov_var.length += excess;
494       if (gcov_var.length < words)
495 	{
496 	  gcov_var.overread += words - gcov_var.length;
497 	  gcov_var.length = 0;
498 	  return 0;
499 	}
500     }
501   result = &gcov_var.buffer[gcov_var.offset];
502   gcov_var.offset += words;
503   return result;
504 }
505 
506 /* Read unsigned value from a coverage file. Sets error flag on file
507    error, overflow flag on overflow */
508 
509 GCOV_LINKAGE gcov_unsigned_t
gcov_read_unsigned(void)510 gcov_read_unsigned (void)
511 {
512   gcov_unsigned_t value;
513   const gcov_unsigned_t *buffer = gcov_read_words (1);
514 
515   if (!buffer)
516     return 0;
517   value = from_file (buffer[0]);
518   return value;
519 }
520 
521 /* Read counter value from a coverage file. Sets error flag on file
522    error, overflow flag on overflow */
523 
524 GCOV_LINKAGE gcov_type
gcov_read_counter(void)525 gcov_read_counter (void)
526 {
527   gcov_type value;
528   const gcov_unsigned_t *buffer = gcov_read_words (2);
529 
530   if (!buffer)
531     return 0;
532   value = from_file (buffer[0]);
533   if (sizeof (value) > sizeof (gcov_unsigned_t))
534     value |= ((gcov_type) from_file (buffer[1])) << 32;
535   else if (buffer[1])
536     gcov_var.error = -1;
537 
538   return value;
539 }
540 
541 /* Mangle filename path of BASE and output new allocated pointer with
542    mangled path.  */
543 
544 char *
mangle_path(char const * base)545 mangle_path (char const *base)
546 {
547   /* Convert '/' to '#', convert '..' to '^',
548      convert ':' to '~' on DOS based file system.  */
549   const char *probe;
550   char *buffer = (char *)xmalloc (strlen (base) + 1);
551   char *ptr = buffer;
552 
553 #if HAVE_DOS_BASED_FILE_SYSTEM
554   if (base[0] && base[1] == ':')
555     {
556       ptr[0] = base[0];
557       ptr[1] = '~';
558       ptr += 2;
559       base += 2;
560     }
561 #endif
562   for (; *base; base = probe)
563     {
564       size_t len;
565 
566       for (probe = base; *probe; probe++)
567 	if (*probe == '/')
568 	  break;
569       len = probe - base;
570       if (len == 2 && base[0] == '.' && base[1] == '.')
571 	*ptr++ = '^';
572       else
573 	{
574 	  memcpy (ptr, base, len);
575 	  ptr += len;
576 	}
577       if (*probe)
578 	{
579 	  *ptr++ = '#';
580 	  probe++;
581 	}
582     }
583 
584   /* Terminate the string.  */
585   *ptr = '\0';
586 
587   return buffer;
588 }
589 
590 /* We need to expose the below function when compiling for gcov-tool.  */
591 
592 #if !IN_LIBGCOV || defined (IN_GCOV_TOOL)
593 /* Read string from coverage file. Returns a pointer to a static
594    buffer, or NULL on empty string. You must copy the string before
595    calling another gcov function.  */
596 
597 GCOV_LINKAGE const char *
gcov_read_string(void)598 gcov_read_string (void)
599 {
600   unsigned length = gcov_read_unsigned ();
601 
602   if (!length)
603     return 0;
604 
605   return (const char *) gcov_read_words (length);
606 }
607 #endif
608 
609 GCOV_LINKAGE void
gcov_read_summary(struct gcov_summary * summary)610 gcov_read_summary (struct gcov_summary *summary)
611 {
612   summary->runs = gcov_read_unsigned ();
613   summary->sum_max = gcov_read_unsigned ();
614 }
615 
616 /* We need to expose the below function when compiling for gcov-tool.  */
617 
618 #if !IN_LIBGCOV || defined (IN_GCOV_TOOL)
619 /* Reset to a known position.  BASE should have been obtained from
620    gcov_position, LENGTH should be a record length.  */
621 
622 GCOV_LINKAGE void
gcov_sync(gcov_position_t base,gcov_unsigned_t length)623 gcov_sync (gcov_position_t base, gcov_unsigned_t length)
624 {
625   gcov_nonruntime_assert (gcov_var.mode > 0);
626   base += length;
627   if (base - gcov_var.start <= gcov_var.length)
628     gcov_var.offset = base - gcov_var.start;
629   else
630     {
631       gcov_var.offset = gcov_var.length = 0;
632       fseek (gcov_var.file, base << 2, SEEK_SET);
633       gcov_var.start = ftell (gcov_var.file) >> 2;
634     }
635 }
636 #endif
637 
638 #if IN_LIBGCOV
639 /* Move to a given position in a gcov file.  */
640 
641 GCOV_LINKAGE void
gcov_seek(gcov_position_t base)642 gcov_seek (gcov_position_t base)
643 {
644   if (gcov_var.offset)
645     gcov_write_block (gcov_var.offset);
646   fseek (gcov_var.file, base << 2, SEEK_SET);
647   gcov_var.start = ftell (gcov_var.file) >> 2;
648 }
649 #endif
650 
651 #if IN_GCOV > 0
652 /* Return the modification time of the current gcov file.  */
653 
654 GCOV_LINKAGE time_t
gcov_time(void)655 gcov_time (void)
656 {
657   struct stat status;
658 
659   if (fstat (fileno (gcov_var.file), &status))
660     return 0;
661   else
662     return status.st_mtime;
663 }
664 #endif /* IN_GCOV */
665