1 /*
2 * src_convention_check.c is used to analyze if we are following our own naming
3 * convention for functions in the API (GMT_*), the core library (gmt_*),
4 * the inter-file functions (gmtlib_*) or the in-file static function
5 * (<gmtfile>_*). We also try to determine in how many files a function is called
6 * in an effort to look for candidates for conversion to static functions.
7 *
8 * Options:
9 * -e Only list the external functions and skip static ones
10 * -f Only list functions and not where they are called
11 * -o Write scan to stdout [/tmp/gmt/scan.txt]
12 * -v Extra progress verbosity
13 * -w Only report functions with a warning for possible wrong name
14 *
15 * Weaknesses: There are 3rd party files we probably should skip, and it is
16 * not yet clear what convention to use in supplements.
17 *
18 * Paul Wessel, April 2020
19 */
20
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include <libgen.h>
25
26 #define NFILES 500
27 #define NFUNCS 10000
28
29 struct FUNCTION {
30 char name[64]; /* Name of function */
31 char file[64]; /* Name if file it is declared in */
32 int api; /* 1 if declared extern in gmt.h */
33 int declared_dev; /* 1 if declared extern in gmt_prototypes.h */
34 int declared_lib; /* 1 if declared extern in gmt_internals.h */
35 int declared_local; /* 1 if function declared static */
36 int rec;
37 unsigned int n_files; /* How many files referenced */
38 unsigned int n_calls; /* How many times referenced */
39 char *in; /* Array to mark which files this functions appears in */
40 };
41
find_function(struct FUNCTION * F,int N,char * name)42 int find_function (struct FUNCTION *F, int N, char *name) {
43 /* Return 1 if function already found, else 0 */
44 int k;
45 for (k = 0; k < N; k++)
46 if (!strcmp (F[k].name, name)) return k;
47 return -1;
48 }
49
compare_n(const void * v1,const void * v2)50 static int compare_n (const void *v1, const void *v2) {
51 /* Compare function for qsort to arrange functions based on how many files they
52 * appear in and (if tied) how many times called */
53 const struct FUNCTION *F1 = v1, *F2 = v2;
54 if (F1->n_files < F2->n_files) return +1;
55 if (F1->n_calls > F2->n_files) return -1;
56 if (F1->n_files < F2->n_calls) return +1;
57 if (F1->n_calls > F2->n_calls) return -1;
58 return (0);
59 }
60
61 /* Include NULL-terminated arrays with function names.
62 * These include files are created on the fly by src_convention_check.sh and then
63 * this code is compiled */
64
65 static char *modules[] = {
66 #include "/tmp/gmt/modules.h"
67 NULL
68 };
69
70 static char *API[] = {
71 #include "/tmp/gmt/api.h"
72 NULL
73 };
74
75 static char *libdev[] = {
76 #include "/tmp/gmt/prototypes.h"
77 NULL
78 };
79
80 static char *libint[] = {
81 #include "/tmp/gmt/internals.h"
82 NULL
83 };
84
is_recognized(char * name,char * list[])85 static int is_recognized (char *name, char *list[]) {
86 /* Return 1 if name appears in the list, else 0 */
87 int k = 0;
88 while (list[k]) {
89 if (!strcmp (name, list[k]))
90 return 1;
91 else k++;
92 }
93 return 0;
94 }
95
get_contraction(char * name,char * prefix)96 static void get_contraction (char *name, char *prefix) {
97 /* Remove underscores to build prefix, e.g., gmt_support -> gmtsupport */
98 unsigned k, j;
99 for (k = j = 0; name[k] != '.'; k++)
100 if (name[k] != '_') prefix[j++] = name[k];
101 prefix[j] = '\0';
102 if (!strcmp (prefix, "postscriptlight")) strcpy (prefix, "psl"); /* Accepted legacy shorthand */
103 }
104
wipe_line(char * line)105 static void wipe_line (char *line) {
106 /* Blank out things in quotes to avoid having " so this func () is bad" be seen as a call */
107 int c, quote;
108 char *p = NULL;
109 for (c = quote = 0; c < strlen (line); c++) {
110 if (line[c] == '\"') { quote = !quote; continue; }
111 if (quote) line[c] = ' ';
112 }
113 if ((p = strstr (line, "/*"))) p[0] = '\0'; /* Chop of trailing comment */
114 }
115
main(int argc,char ** argv)116 int main (int argc, char **argv) {
117 int k, f, w, s, n, c, t, is_static, err, n_funcs = 0, comment = 0, brief = 0, ext = 0, log = 1, verbose = 0, warn_only = 0;
118 int set_dev, set_lib, rec;
119 size_t L;
120 char line[512] = {""}, prefix[64] = {""};
121 char word[6][64], type[4] = {'S', 'D', 'L', 'A'}, *p, *q, message[128] = {""};
122 char *err_msg[4] = {"", "Change to gmt_* ", "Change to gmtlib_* ", "Change to <file>_* "};
123 struct FUNCTION F[NFUNCS];
124 FILE *fp, *out = stdout;
125
126 if (argc == 1) {
127 fprintf (stderr, "usage: src_convention_check [-e] [-f] [-o] *.c > log\n");
128 fprintf (stderr, " -e Only list external functions [all]\n");
129 fprintf (stderr, " -f Only list function stats and not where called [full log]\n");
130 fprintf (stderr, " -o Write main results to stdout [/tmp/gmt/gmt/scan.txt\n");
131 fprintf (stderr, " -v Extra verbose output [minimal verbosity]\n");
132 fprintf (stderr, " -w Only write lines with warnings of wrong naming]\n");
133 exit (1);
134 }
135
136 if (argc >= NFILES) {
137 fprintf (stderr, "src_convention_check: Out of file space - increase NFILES and rebuild\n");
138 exit (-1);
139 }
140
141 /* Check that prototypes all are called gmt_* and that internals are all called gmtlib_* */
142
143 fprintf (stderr, "src_convention_check: 0. Scanning include files for proper names\n");
144 k = 0;
145 while (API[k] != NULL) {
146 if (strncmp (API[k], "GMT_", 4U)) fprintf (stderr, "gmt.h: Wrongly named function %s\n", API[k]);
147 k++;
148 }
149 k = 0;
150 while (libdev[k] != NULL) {
151 if (strncmp (libdev[k], "gmt_", 4U)) fprintf (stderr, "gmt_prototypes.h: Wrongly named function %s\n", libdev[k]);
152 k++;
153 }
154 k = 0;
155 while (libint[k] != NULL) {
156 if (strncmp (libint[k], "gmtlib_", 4U)) fprintf (stderr, "gmt_internals.h: Wrongly named function %s\n", libint[k]);
157 k++;
158 }
159
160 fprintf (stderr, "src_convention_check: 1. Scanning all codes for function declarations\n");
161 for (k = 1; k < argc; k++) { /* For each input file */
162 if (strcmp (argv[k], "-e") == 0) { /* Only list external functions and not static */
163 ext = 1;
164 continue;
165 }
166 if (strcmp (argv[k], "-f") == 0) { /* Only list functions and not where called */
167 brief = 1;
168 continue;
169 }
170 if (strcmp (argv[k], "-o") == 0) { /* Write to stdout */
171 log = 0;
172 continue;
173 }
174 if (strcmp (argv[k], "-v") == 0) { /* Extra verbos */
175 verbose = 1;
176 continue;
177 }
178 if (strcmp (argv[k], "-w") == 0) { /* Warnings only e*/
179 warn_only = 1;
180 continue;
181 }
182
183 if ((fp = fopen (argv[k], "r")) == NULL) {
184 fprintf (stderr, "src_convention_check: Unable to open %s for reading\n", (argv[k]));
185 continue;
186 }
187 if (verbose) fprintf (stderr, "\tsrc_convention_check: Scanning %s\n", argv[k]);
188 set_lib = (strstr (argv[k], "gmt_") != NULL || strstr (argv[k], "common_") != NULL); /* Called in a library file */
189 comment = rec = 0;
190 while (fgets (line, 512, fp)) {
191 rec++;
192 if (!comment && strstr (line, "/*") && strstr (line, "*/") == NULL) /* Start of multi-line comment */
193 comment = 1;
194 else if (comment && strstr (line, "*/")) { /* End of multi-line comment with this line */
195 comment = 0;
196 continue;
197 }
198 if (comment) continue;
199 if (strchr (" \t#/{", line[0])) continue;
200 if (line[1] == '*') continue; /* Comment */
201 if (strchr (line, '(') == NULL) continue;
202 if (!strncmp (line, "EXTERN", 6U)) continue;
203 if (!strncmp (line, "extern", 6U)) continue;
204 if (strstr (line, "typedef")) continue;
205 wipe_line (line);
206 n = sscanf (line, "%s %s %s %s %s %s", word[0], word[1], word[2], word[3], word[4], word[5]);
207 if (n < 2) continue;
208 w = is_static = 0;
209 if (!strcmp (word[w], "if") || !strcmp (word[w], "for") || !strcmp (word[w], "while") || !strcmp (word[w], "else") || !strcmp (word[w], "enum")) continue;
210 if (strchr (word[0], ':')) continue; /* goto label */
211 if (strchr (word[0], '(')) continue; /* function call */
212 if (!strcmp (word[w], "GMT_LOCAL") || !strcmp (word[w], "static")) {
213 w = is_static = 1;
214 }
215 if (ext && is_static) continue; /* Only list external functions since -e is set */
216 if (!strncmp (word[w], "inline", 6U))
217 w++; /* Skip inline */
218 if (!strncmp (word[w], "const", 5U))
219 w++; /* Skip inline */
220 if (!strncmp (word[w], "struct", 6U)) { /* Skip "struct NAME" */
221 w += 2;
222 if (strchr (word[w], '{')) continue; /* Was a structure definition */
223 }
224 else if (!strncmp (word[w], "unsigned", 8U) || !strncmp (word[w], "signed", 6U))
225 w += 2; /* Skip "unsigned short" */
226 else /* skip "double", etc. */
227 w++;
228 if (w >= n) continue;
229 if (!strcmp (word[w], "*") || !strcmp (word[w], "**"))
230 w++; /* Skip a lonely * */
231 if (w >= n) continue;
232 s = (word[w][0] == '*') ? 1 : 0; /* Skip leading * if there is no space */
233 if (strchr (&word[w][s], '[')) continue; /* Got some array */
234 L = strlen (word[w]);
235 if (strcmp (&word[w][s], "parse") == 0 || strcmp (&word[w][s], "usage") == 0 || strcmp (&word[w][s], "New_Ctrl") == 0) continue; /* Let these be named as is */
236 //if (L > 5 && strncmp (&word[w][s], "GMT_", 4U) == 0) continue; /* Skip GMT API functions */
237 if (L > 5 && strncmp (&word[w][s], "PSL_", 4U) == 0) continue; /* Skip PSL functions */
238 if (word[w][L-1] == '_') continue; /* Skip FORTRAN wrappers */
239 if ((p = strchr (word[w], '('))) p[0] = '\0'; /* Change functionname(args) to functionname */
240 if (strcmp (&word[w][s], "main") == 0) continue; /* Skip main functions in modules */
241 if (strlen (&word[w][s]) == 0) continue;
242 if ((f = find_function (F, n_funcs, &word[w][s])) == -1) {
243 f = n_funcs++; /* Add one more */
244 strncpy (F[f].name, &word[w][s], 63);
245 strncpy (F[f].file, argv[k], 63);
246 if (is_recognized (F[f].name, API))
247 F[f].api = 1;
248 else if (is_recognized (F[f].name, libdev))
249 F[f].declared_dev = 1;
250 else if (is_recognized (F[f].name, libint))
251 F[f].declared_lib = 1;
252 if (L > 5 && strncmp (F[f].name, "GMT_", 4U) == 0 && is_recognized (&F[f].name[4], modules))
253 F[f].api = 1;
254 F[f].declared_local = is_static;
255 F[f].rec = rec;
256 F[f].in = calloc (NFILES, 1U);
257 }
258 if (n_funcs == NFUNCS) {
259 fprintf (stderr, "src_convention_check: Out of function space - increase NFUNCS and rebuild\n");
260 exit (-1);
261 }
262 }
263 fclose (fp);
264 }
265 /* Look for function calls */
266 fprintf (stderr, "src_convention_check: 2. Scanning all codes for function calls\n");
267 for (k = 1; k < argc; k++) { /* For each input file */
268 if ((fp = fopen (argv[k], "r")) == NULL) continue;
269 if (verbose) fprintf (stderr, "\tsrc_convention_check: Scanning %s\n", argv[k]);
270 set_dev = is_recognized (argv[k], modules); /* Called in a module */
271 set_lib = (strstr (argv[k], "gmt_") != NULL || strstr (argv[k], "common_") != NULL); /* Called in a library file */
272 rec = 0;
273 while (fgets (line, 512, fp)) {
274 rec++;
275 if (line[0] == '/' || line[1] == '*') continue; /* Comment */
276 if (strchr (" \t", line[0]) == NULL) continue; /* Not a called function */
277 wipe_line (line);
278 if (strchr (line, '(') == NULL) continue; /* Not a function call */
279 t = 0;
280 while (strchr ("\t", line[t])) t++; /* Wind past leading white space */
281 for (f = 0; f < n_funcs; f++) {
282 L = strlen (F[f].name);
283 if ((p = strstr (&line[t], F[f].name)) == NULL) continue;
284 q = p-1; /* Previous char (which we know exists, so q[0] below is valid */
285 if (strlen (p) > (L+2) && (p[L] == '(' || (p[L] == ' ' && p[L+1] == '(')) && strchr (" \t()", q[0])) { /* Found a call to this function */
286 F[f].in[k] = 1;
287 F[f].n_calls++ ;
288 }
289 }
290 }
291 }
292 for (f = 0; f < n_funcs; f++) {
293 for (k = 1; k < argc; k++) /* For each input file */
294 if (F[f].in[k]) F[f].n_files++;
295 }
296 qsort (F, n_funcs, sizeof (struct FUNCTION), compare_n);
297
298 fprintf (stderr, "src_convention_check: Write the report\n");
299 /* Report */
300 if (log) out = fopen ("/tmp/gmt/scan.txt", "w");
301 fprintf (out, "LIBRARY CODES: A = GMT API, D = GMTDEV LIB, L = INTERNAL LIB, S = STATIC\n");
302 fprintf (out, "NFILES FUNCTION NCALLS LIB DECLARED-IN-FILE LINE MESSAGE\n");
303 for (f = 0; f < n_funcs; f++) {
304 err = 0;
305 p = basename (F[f].file);
306 get_contraction (p, prefix);
307 L = strlen (prefix);
308 k = (F[f].declared_local) ? 0 : ((F[f].declared_dev) ? 1 : 2);
309 if (F[f].api) k = 3;
310 if (F[f].declared_local) {
311 if (strncmp (F[f].name, prefix, L)) err = 3;
312 }
313 else if (F[f].declared_dev) {
314 if (strncmp (F[f].name, "gmt_", 4U)) err = 1;
315 }
316 else if (F[f].declared_lib) {
317 if (strncmp (F[f].name, "gmtlib_", 7U)) err = 2;
318 }
319 if (err == 3) {
320 if (F[f].n_files > 1)
321 strcpy (message, err_msg[err]);
322 else {
323 sprintf (message, "Change to %s_* ", prefix);
324 }
325 }
326 else
327 strcpy (message, err_msg[err]);
328 if (!F[f].declared_local && F[f].n_files <= 1 && !F[f].api) {
329 strcat (message, "Static function candidate");
330 err = 3;
331 }
332 if (!warn_only || err) {
333 fprintf (out, "%5d %-40s %4d %c %-32s %6d %s\n", F[f].n_files, F[f].name, F[f].n_calls, type[k], F[f].file, F[f].rec, message);
334 if (brief) { /* Done with this, free memory */
335 free ((void *)F[f].in);
336 continue;
337 }
338 for (k = 1; k < argc; k++) /* For each input file, report each incident */
339 if (F[f].in[k] && strcmp (argv[k], F[f].file)) fprintf (out, "\t\t%s\n", argv[k]);
340 }
341 free ((void *)F[f].in);
342 }
343 if (log) {
344 fclose (out);
345 fprintf (stderr, "src_convention_check: Report written to /tmp/gmt/scan.txt\n");
346 }
347 }