cc-wrapper.c 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252
  1. // SPDX-License-Identifier: GPL-2.0-only
  2. /*
  3. * Copyright (c) 2021, The Linux Foundation. All rights reserved.
  4. */
  5. #define _GNU_SOURCE
  6. #include <poll.h>
  7. #include <errno.h>
  8. #include <fcntl.h>
  9. #include <stdio.h>
  10. #include <regex.h>
  11. #include <limits.h>
  12. #include <signal.h>
  13. #include <stdlib.h>
  14. #include <string.h>
  15. #include <unistd.h>
  16. #include <stdbool.h>
  17. #include <sys/wait.h>
  18. /*
  19. * Usage: cc-wrapper [COMMAND]
  20. *
  21. * Invoke [COMMAND] and scan for lines matching C compiler "warning:" lines
  22. * If a warning is found, check if in a database whether it should be ignored.
  23. * If that warning isn't ignored, bail return an error and delete the output file
  24. * so that the overall Make environment fails.
  25. */
  26. /*
  27. * 1. match as much as possible which ends with a slash '/'
  28. * 2. match as many characters as possible ending with .
  29. * 3. match the filetype
  30. * 4. Optionally match some line and column info of the style :123:456, etc.
  31. * 5. Match ': warning:'
  32. *
  33. * Ex: msm-kernel/drivers/firmware/qcom_scm.c:929:11: warning: unused variable 'unused' [-Wunused-variable]
  34. * ( 1 )( 2 )3( 4 )( 5 )
  35. */
  36. #define REGEXPR \
  37. "^\\(.*\\/\\)\\([^\\/][^\\/]*\\.[a-zA-Z0-9][a-zA-Z0-9]*\\(:[0-9,-][0-9,-]*\\)*\\): warning:[^\\n]*"
  38. static regex_t reg;
  39. static int reg_compiled;
  40. struct ignored_warning {
  41. const char * const warning;
  42. const size_t len;
  43. };
  44. #define IGNORE_WARNING(str) { (str), sizeof((str)) - 1 }
  45. const struct ignored_warning ignored_warnings[] = {
  46. IGNORE_WARNING("signal.c:51"),
  47. IGNORE_WARNING("signal.c:95"),
  48. IGNORE_WARNING("userfaultfd.c:1875"),
  49. { NULL, 0 }
  50. };
  51. static bool is_ignored_warning(const char * const text, size_t len)
  52. {
  53. const struct ignored_warning *el = &ignored_warnings[0];
  54. while (el->warning) {
  55. /* Check that printed warning location is at least as long as
  56. * the ignored one. If not, no point in strncmp */
  57. if (len >= el->len && !strncmp(text, el->warning, el->len))
  58. return true;
  59. el++;
  60. }
  61. return false;
  62. }
  63. static int process_buffer(const char *buf)
  64. {
  65. int ret = 0;
  66. regmatch_t m[3];
  67. if (!buf)
  68. return 0;
  69. /* Avoid compiling the regex since it takes a little bit of time -- why not avoid */
  70. /* Check if there is ': warning:' in the line, if so, then we need to use regex */
  71. if (!reg_compiled && strcmp(buf, ": warning:")) {
  72. ret = regcomp(&reg, REGEXPR, 0);
  73. if (ret)
  74. return ret;
  75. reg_compiled = 1;
  76. }
  77. if (reg_compiled) {
  78. while (!regexec(&reg, buf, 3, m, 0)) {
  79. if (!is_ignored_warning(buf + m[2].rm_so, m[2].rm_eo - m[2].rm_so)) {
  80. fprintf(stderr, "\nerror, forbidden warning: %.*s\n",
  81. m[2].rm_eo - m[2].rm_so,
  82. buf + m[2].rm_so);
  83. ret = 1;
  84. }
  85. buf += m[0].rm_eo; /* Skip past everything that got matched */
  86. }
  87. }
  88. return ret;
  89. }
  90. int main(int argc, char **argv)
  91. {
  92. int pipes[2];
  93. int ret, status, len;
  94. int i, have_error = 0;
  95. pid_t pid;
  96. char buf[PIPE_BUF];
  97. /*
  98. * linebuf is used only when encountering a line split over multiple reads
  99. * Use a fixed linebuf size to avoid malloc.
  100. * The assumption here is that warning line will be a path, followed by
  101. * some numbers, followed by ": warning:" text. Thus, we can make a rough estimate
  102. * of the max size a warning line could be in order to capture that it's a warning
  103. * line.
  104. */
  105. size_t linebuf_len = 0;
  106. char linebuf[PATH_MAX + 0x100];
  107. if (argc < 2)
  108. return 0;
  109. ret = pipe(pipes);
  110. if (ret)
  111. return ret;
  112. pid = fork();
  113. if (pid == 0) {
  114. close(pipes[0]);
  115. dup2(pipes[1], STDERR_FILENO);
  116. return execvp(argv[1], argv + 1);
  117. }
  118. close(pipes[1]);
  119. if (pid < 0) {
  120. ret = pid;
  121. goto done;
  122. }
  123. while ((len = read(pipes[0], buf, sizeof(buf) - 1)) > 0) {
  124. char *next = buf;
  125. write(STDERR_FILENO, buf, len);
  126. buf[len] = '\0';
  127. if (linebuf_len) {
  128. /* Process incomplete line buffered from prev iteration */
  129. int buf_line_len;
  130. /* Find first newline in the newly read buffer (or end of buffer) */
  131. next = memchr(buf, '\n', len);
  132. if (!next)
  133. buf_line_len = len;
  134. else
  135. buf_line_len = next + 1 - buf; /* Add one to get back the newline */
  136. if (buf_line_len + linebuf_len >= sizeof(linebuf))
  137. buf_line_len = sizeof(linebuf) - linebuf_len - 1;
  138. /* copy it to the linebuf */
  139. memcpy(linebuf + linebuf_len, buf, buf_line_len);
  140. linebuf_len += buf_line_len;
  141. linebuf[linebuf_len] = '\0';
  142. /* check for warnings */
  143. if (process_buffer(linebuf)) {
  144. have_error = 1;
  145. linebuf_len = 0;
  146. } else if (linebuf[linebuf_len - 1] == '\n' ||
  147. linebuf_len >= sizeof(linebuf) - 1) {
  148. /*
  149. * reset the linebuf if we found a complete line or if the linebuf
  150. * overflowed. We don't care about lines longer than linebuf because
  151. * linebuf is highly unlikely to contain the ": warning:" text.
  152. * This is because linebuf is PATH_MAX + 256 bytes
  153. */
  154. linebuf_len = 0;
  155. }
  156. linebuf[linebuf_len] = '\0';
  157. }
  158. if (next) {
  159. /* continue processing rest of buffer */
  160. int last_line_len;
  161. /* check for warnings */
  162. if (process_buffer(next))
  163. have_error = 1;
  164. /* Find last newline in the buffer */
  165. next = memrchr(next, '\n', len - (next - buf));
  166. if (!next) {
  167. next = buf;
  168. last_line_len = len;
  169. } else {
  170. last_line_len = len - (next - buf);
  171. }
  172. if (last_line_len + linebuf_len >= sizeof(linebuf))
  173. last_line_len = sizeof(linebuf) - linebuf_len - 1;
  174. /*
  175. * copy everything after the last newline to linebuf.
  176. * there may be 0 bytes to copy if buffer ended with newline
  177. * no special handling needed for that case
  178. */
  179. memcpy(linebuf + linebuf_len, next, last_line_len);
  180. linebuf_len += last_line_len;
  181. linebuf[linebuf_len] = '\0';
  182. }
  183. }
  184. /* We've finished reading from pipe, process anything leftover, just in case */
  185. if (process_buffer(linebuf))
  186. have_error = 1;
  187. done:
  188. if (have_error) {
  189. /*
  190. * Found an error? Scan the args passed to compiler and see if
  191. * there is "-o out.o". If so, remove the file so that Make
  192. * will attempt to recompile it next time around. Otherwise, next
  193. * build will go through since out.o was created.
  194. */
  195. for (i = 1 ; i < argc - 1 ; i++) {
  196. if (strcmp(argv[i], "-o") == 0) {
  197. remove(argv[i + 1]);
  198. break;
  199. }
  200. }
  201. }
  202. if (reg_compiled)
  203. regfree(&reg);
  204. close(pipes[0]);
  205. waitpid(pid, &status, 0);
  206. /* Did we have an error? */
  207. if (ret)
  208. return ret;
  209. /* Did cc exit gracefully? If not, then something went wrong with cc */
  210. if (!WIFEXITED(status))
  211. return 1;
  212. /* Did cc exit with success? If not, did we find an error? */
  213. return WEXITSTATUS(status) ?: have_error;
  214. }