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 }