/* SPDX-License-Identifier: GPL-3.0-only WITH GCC-exception-3.1 */ /* File format for coverage information Copyright (C) 1996, 1997, 1998, 2000, 2002, 2003, 2004, 2005, 2007, 2008 Free Software Foundation, Inc. Contributed by Bob Manson . Completely remangled by Nathan Sidwell . This file is part of GCC. GCC is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. GCC is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. Under Section 7 of GPL version 3, you are granted additional permissions described in the GCC Runtime Library Exception, version 3.1, as published by the Free Software Foundation. */ /* Routines declared in gcov-io.h. This file should be #included by another source file, after having #included gcov-io.h. */ #if !IN_GCOV static void gcov_write_block(unsigned int); static gcov_unsigned_t *gcov_write_words(unsigned int); #endif static const gcov_unsigned_t *gcov_read_words(unsigned int); #if !IN_LIBGCOV static void gcov_allocate(unsigned int); #endif static inline gcov_unsigned_t from_file(gcov_unsigned_t value) { #if !IN_LIBGCOV if (gcov_var.endian) { value = (value >> 16) | (value << 16); value = ((value & 0xff00ff) << 8) | ((value >> 8) & 0xff00ff); } #endif return value; } /* Open a gcov file. NAME is the name of the file to open and MODE indicates whether a new file should be created, or an existing file opened. If MODE is >= 0 an existing file will be opened, if possible, and if MODE is <= 0, a new file will be created. Use MODE=0 to attempt to reopen an existing file and then fall back on creating a new one. If MODE < 0, the file will be opened in read-only mode. Otherwise it will be opened for modification. Return zero on failure, >0 on opening an existing file and <0 on creating a new one. */ GCOV_LINKAGE int #if IN_LIBGCOV gcov_open(const char *name) #else gcov_open(const char *name, int mode) #endif { #if IN_LIBGCOV const int mode = 0; #endif #if GCOV_LOCKED struct flock s_flock; int fd; s_flock.l_whence = SEEK_SET; s_flock.l_start = 0; s_flock.l_len = 0; /* Until EOF. */ s_flock.l_pid = getpid(); #endif gcc_assert(!gcov_var.file); gcov_var.start = 0; gcov_var.offset = gcov_var.length = 0; gcov_var.overread = -1u; gcov_var.error = 0; #if !IN_LIBGCOV gcov_var.endian = 0; #endif #if GCOV_LOCKED if (mode > 0) { /* Read-only mode - acquire a read-lock. */ s_flock.l_type = F_RDLCK; fd = open(name, O_RDONLY); } else { /* Write mode - acquire a write-lock. */ s_flock.l_type = F_WRLCK; fd = open(name, O_RDWR | O_CREAT, 0666); } if (fd < 0) return 0; while (fcntl(fd, F_SETLKW, &s_flock) && errno == EINTR) continue; gcov_var.file = fdopen(fd, (mode > 0) ? "rb" : "r+b"); if (!gcov_var.file) { close(fd); return 0; } if (mode > 0) gcov_var.mode = 1; else if (mode == 0) { struct stat st; if (fstat(fd, &st) < 0) { fclose(gcov_var.file); gcov_var.file = 0; return 0; } if (st.st_size != 0) gcov_var.mode = 1; else gcov_var.mode = mode * 2 + 1; } else gcov_var.mode = mode * 2 + 1; #else if (mode >= 0) gcov_var.file = fopen(name, (mode > 0) ? "rb" : "r+b"); if (gcov_var.file) gcov_var.mode = 1; else if (mode <= 0) { gcov_var.file = fopen(name, "w+b"); if (gcov_var.file) gcov_var.mode = mode * 2 + 1; } if (!gcov_var.file) return 0; #endif setbuf(gcov_var.file, (char *)0); return 1; } /* Close the current gcov file. Flushes data to disk. Returns nonzero on failure or error flag set. */ GCOV_LINKAGE int gcov_close(void) { if (gcov_var.file) { #if !IN_GCOV if (gcov_var.offset && gcov_var.mode < 0) gcov_write_block(gcov_var.offset); #endif fclose(gcov_var.file); gcov_var.file = 0; gcov_var.length = 0; } #if !IN_LIBGCOV free(gcov_var.buffer); gcov_var.alloc = 0; gcov_var.buffer = 0; #endif gcov_var.mode = 0; return gcov_var.error; } #if !IN_LIBGCOV /* Check if MAGIC is EXPECTED. Use it to determine endianness of the file. Returns +1 for same endian, -1 for other endian and zero for not EXPECTED. */ GCOV_LINKAGE int gcov_magic(gcov_unsigned_t magic, gcov_unsigned_t expected) { if (magic == expected) return 1; magic = (magic >> 16) | (magic << 16); magic = ((magic & 0xff00ff) << 8) | ((magic >> 8) & 0xff00ff); if (magic == expected) { gcov_var.endian = 1; return -1; } return 0; } #endif #if !IN_LIBGCOV static void gcov_allocate(unsigned int length) { size_t new_size = gcov_var.alloc; if (!new_size) new_size = GCOV_BLOCK_SIZE; new_size += length; new_size *= 2; gcov_var.alloc = new_size; gcov_var.buffer = XRESIZEVAR(gcov_unsigned_t, gcov_var.buffer, new_size << 2); } #endif #if !IN_GCOV /* Write out the current block, if needs be. */ static void gcov_write_block(unsigned int size) { if (fwrite(gcov_var.buffer, size << 2, 1, gcov_var.file) != 1) gcov_var.error = 1; gcov_var.start += size; gcov_var.offset -= size; } /* Allocate space to write BYTES bytes to the gcov file. Return a pointer to those bytes, or NULL on failure. */ static gcov_unsigned_t * gcov_write_words(unsigned int words) { gcov_unsigned_t *result; gcc_assert(gcov_var.mode < 0); #if IN_LIBGCOV if (gcov_var.offset >= GCOV_BLOCK_SIZE) { gcov_write_block(GCOV_BLOCK_SIZE); if (gcov_var.offset) { gcc_assert(gcov_var.offset == 1); memcpy(gcov_var.buffer, gcov_var.buffer + GCOV_BLOCK_SIZE, 4); } } #else if (gcov_var.offset + words > gcov_var.alloc) gcov_allocate(gcov_var.offset + words); #endif result = &gcov_var.buffer[gcov_var.offset]; gcov_var.offset += words; return result; } /* Write unsigned VALUE to coverage file. Sets error flag appropriately. */ GCOV_LINKAGE void gcov_write_unsigned(gcov_unsigned_t value) { gcov_unsigned_t *buffer = gcov_write_words(1); buffer[0] = value; } /* Write counter VALUE to coverage file. Sets error flag appropriately. */ #if IN_LIBGCOV GCOV_LINKAGE void gcov_write_counter(gcov_type value) { gcov_unsigned_t *buffer = gcov_write_words(2); buffer[0] = (gcov_unsigned_t) value; if (sizeof(value) > sizeof(gcov_unsigned_t)) buffer[1] = (gcov_unsigned_t) (value >> 32); else buffer[1] = 0; } #endif /* IN_LIBGCOV */ #if !IN_LIBGCOV /* Write STRING to coverage file. Sets error flag on file error, overflow flag on overflow */ GCOV_LINKAGE void gcov_write_string(const char *string) { unsigned int length = 0; unsigned int alloc = 0; gcov_unsigned_t *buffer; if (string) { length = strlen(string); alloc = (length + 4) >> 2; } buffer = gcov_write_words(1 + alloc); buffer[0] = alloc; buffer[alloc] = 0; memcpy(&buffer[1], string, length); } #endif #if !IN_LIBGCOV /* Write a tag TAG and reserve space for the record length. Return a value to be used for gcov_write_length. */ GCOV_LINKAGE gcov_position_t gcov_write_tag(gcov_unsigned_t tag) { gcov_position_t result = gcov_var.start + gcov_var.offset; gcov_unsigned_t *buffer = gcov_write_words(2); buffer[0] = tag; buffer[1] = 0; return result; } /* Write a record length using POSITION, which was returned by gcov_write_tag. The current file position is the end of the record, and is restored before returning. Returns nonzero on overflow. */ GCOV_LINKAGE void gcov_write_length(gcov_position_t position) { unsigned int offset; gcov_unsigned_t length; gcov_unsigned_t *buffer; gcc_assert(gcov_var.mode < 0); gcc_assert(position + 2 <= gcov_var.start + gcov_var.offset); gcc_assert(position >= gcov_var.start); offset = position - gcov_var.start; length = gcov_var.offset - offset - 2; buffer = (gcov_unsigned_t *) &gcov_var.buffer[offset]; buffer[1] = length; if (gcov_var.offset >= GCOV_BLOCK_SIZE) gcov_write_block(gcov_var.offset); } #else /* IN_LIBGCOV */ /* Write a tag TAG and length LENGTH. */ GCOV_LINKAGE void gcov_write_tag_length(gcov_unsigned_t tag, gcov_unsigned_t length) { gcov_unsigned_t *buffer = gcov_write_words(2); buffer[0] = tag; buffer[1] = length; } /* Write a summary structure to the gcov file. Return nonzero on overflow. */ GCOV_LINKAGE void gcov_write_summary(gcov_unsigned_t tag, const struct gcov_summary *summary) { unsigned int ix; const struct gcov_ctr_summary *csum; gcov_write_tag_length(tag, GCOV_TAG_SUMMARY_LENGTH); gcov_write_unsigned(summary->checksum); for (csum = summary->ctrs, ix = GCOV_COUNTERS_SUMMABLE; ix--; csum++) { gcov_write_unsigned(csum->num); gcov_write_unsigned(csum->runs); gcov_write_counter(csum->sum_all); gcov_write_counter(csum->run_max); gcov_write_counter(csum->sum_max); } } #endif /* IN_LIBGCOV */ #endif /*!IN_GCOV */ /* Return a pointer to read BYTES bytes from the gcov file. Returns NULL on failure (read past EOF). */ static const gcov_unsigned_t * gcov_read_words(unsigned int words) { const gcov_unsigned_t *result; unsigned int excess = gcov_var.length - gcov_var.offset; gcc_assert(gcov_var.mode > 0); if (excess < words) { gcov_var.start += gcov_var.offset; #if IN_LIBGCOV if (excess) { gcc_assert(excess == 1); memcpy(gcov_var.buffer, gcov_var.buffer + gcov_var.offset, 4); } #else memmove(gcov_var.buffer, gcov_var.buffer + gcov_var.offset, excess * 4); #endif gcov_var.offset = 0; gcov_var.length = excess; #if IN_LIBGCOV gcc_assert(!gcov_var.length || gcov_var.length == 1); excess = GCOV_BLOCK_SIZE; #else if (gcov_var.length + words > gcov_var.alloc) gcov_allocate(gcov_var.length + words); excess = gcov_var.alloc - gcov_var.length; #endif excess = fread(gcov_var.buffer + gcov_var.length, 1, excess << 2, gcov_var.file) >> 2; gcov_var.length += excess; if (gcov_var.length < words) { gcov_var.overread += words - gcov_var.length; gcov_var.length = 0; return 0; } } result = &gcov_var.buffer[gcov_var.offset]; gcov_var.offset += words; return result; } /* Read unsigned value from a coverage file. Sets error flag on file error, overflow flag on overflow */ GCOV_LINKAGE gcov_unsigned_t gcov_read_unsigned(void) { gcov_unsigned_t value; const gcov_unsigned_t *buffer = gcov_read_words(1); if (!buffer) return 0; value = from_file(buffer[0]); return value; } /* Read counter value from a coverage file. Sets error flag on file error, overflow flag on overflow */ GCOV_LINKAGE gcov_type gcov_read_counter(void) { gcov_type value; const gcov_unsigned_t *buffer = gcov_read_words(2); if (!buffer) return 0; value = from_file(buffer[0]); if (sizeof(value) > sizeof(gcov_unsigned_t)) value |= ((gcov_type) from_file(buffer[1])) << 32; else if (buffer[1]) gcov_var.error = -1; return value; } /* Read string from coverage file. Returns a pointer to a static buffer, or NULL on empty string. You must copy the string before calling another gcov function. */ #if !IN_LIBGCOV GCOV_LINKAGE const char * gcov_read_string(void) { unsigned int length = gcov_read_unsigned(); if (!length) return 0; return (const char *) gcov_read_words(length); } #endif GCOV_LINKAGE void gcov_read_summary(struct gcov_summary *summary) { unsigned int ix; struct gcov_ctr_summary *csum; summary->checksum = gcov_read_unsigned(); for (csum = summary->ctrs, ix = GCOV_COUNTERS_SUMMABLE; ix--; csum++) { csum->num = gcov_read_unsigned(); csum->runs = gcov_read_unsigned(); csum->sum_all = gcov_read_counter(); csum->run_max = gcov_read_counter(); csum->sum_max = gcov_read_counter(); } } #if !IN_LIBGCOV /* Reset to a known position. BASE should have been obtained from gcov_position, LENGTH should be a record length. */ GCOV_LINKAGE void gcov_sync(gcov_position_t base, gcov_unsigned_t length) { gcc_assert(gcov_var.mode > 0); base += length; if (base - gcov_var.start <= gcov_var.length) gcov_var.offset = base - gcov_var.start; else { gcov_var.offset = gcov_var.length = 0; fseek(gcov_var.file, base << 2, SEEK_SET); gcov_var.start = ftell(gcov_var.file) >> 2; } } #endif #if IN_LIBGCOV /* Move to a given position in a gcov file. */ GCOV_LINKAGE void gcov_seek(gcov_position_t base) { gcc_assert(gcov_var.mode < 0); if (gcov_var.offset) gcov_write_block(gcov_var.offset); fseek(gcov_var.file, base << 2, SEEK_SET); gcov_var.start = ftell(gcov_var.file) >> 2; } #endif #if IN_GCOV > 0 /* Return the modification time of the current gcov file. */ GCOV_LINKAGE time_t gcov_time(void) { struct stat status; if (fstat(fileno(gcov_var.file), &status)) return 0; else return status.st_mtime; } #endif /* IN_GCOV */