1 /*
2  * %CopyrightBegin%
3  *
4  * Copyright Ericsson AB 2006-2017. All Rights Reserved.
5  *
6  * Licensed under the Apache License, Version 2.0 (the "License");
7  * you may not use this file except in compliance with the License.
8  * You may obtain a copy of the License at
9  *
10  *     http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  *
18  * %CopyrightEnd%
19  */
20 /*
21  * Purpose: Dialyzer front-end.
22  */
23 
24 #include "etc_common.h"
25 
26 #define NO 0
27 #define YES 1
28 
29 #define ASIZE(a) (sizeof(a)/sizeof(a[0]))
30 
31 static int debug = 0;		/* Bit flags for debug printouts. */
32 
33 static char** eargv_base;	/* Base of vector. */
34 static char** eargv;		/* First argument for erl. */
35 
36 static int eargc;		/* Number of arguments in eargv. */
37 
38 #ifdef __WIN32__
39 #  define QUOTE(s) possibly_quote(s)
40 #  define IS_DIRSEP(c) ((c) == '/' || (c) == '\\')
41 #  define ERL_NAME "erl.exe"
42 #else
43 #  define QUOTE(s) s
44 #  define IS_DIRSEP(c) ((c) == '/')
45 #  define ERL_NAME "erl"
46 #endif
47 
48 #define UNSHIFT(s) eargc++, eargv--; eargv[0] = QUOTE(s)
49 #define PUSH(s) eargv[eargc++] = QUOTE(s)
50 #define PUSH2(s, t) PUSH(s); PUSH(t)
51 #define PUSH3(s, t, u) PUSH2(s, t); PUSH(u)
52 
53 /*
54  * Local functions.
55  */
56 
57 static void error(char* format, ...);
58 static void* emalloc(size_t size);
59 #ifdef HAVE_COPYING_PUTENV
60 static void efree(void *p);
61 #endif
62 static char* strsave(char* string);
63 static void push_words(char* src);
64 static int run_erlang(char* name, char** argv);
65 static char* get_default_emulator(char* progname);
66 #ifdef __WIN32__
67 static char* possibly_quote(char* arg);
68 static void* erealloc(void *p, size_t size);
69 #endif
70 
71 /*
72  * Supply a strerror() function if libc doesn't.
73  */
74 #ifndef HAVE_STRERROR
75 
76 extern int sys_nerr;
77 
78 #ifndef SYS_ERRLIST_DECLARED
79 extern const char * const sys_errlist[];
80 #endif /* !SYS_ERRLIST_DECLARED */
81 
strerror(int errnum)82 char *strerror(int errnum)
83 {
84   static char *emsg[1024];
85 
86   if (errnum != 0) {
87     if (errnum > 0 && errnum < sys_nerr)
88       sprintf((char *) &emsg[0], "(%s)", sys_errlist[errnum]);
89     else
90       sprintf((char *) &emsg[0], "errnum = %d ", errnum);
91   }
92   else {
93     emsg[0] = '\0';
94   }
95   return (char *) &emsg[0];
96 }
97 #endif /* !HAVE_STRERROR */
98 
99 static char *
get_env(char * key)100 get_env(char *key)
101 {
102 #ifdef __WIN32__
103     DWORD size = 32;
104     char  *value=NULL;
105     wchar_t *wcvalue = NULL;
106     wchar_t wckey[256];
107     int len;
108 
109     MultiByteToWideChar(CP_UTF8, 0, key, -1, wckey, 256);
110 
111     while (1) {
112 	DWORD nsz;
113 	if (wcvalue)
114 	    free(wcvalue);
115 	wcvalue = (wchar_t*) emalloc(size*sizeof(wchar_t));
116 	SetLastError(0);
117 	nsz = GetEnvironmentVariableW(wckey, wcvalue, size);
118 	if (nsz == 0 && GetLastError() == ERROR_ENVVAR_NOT_FOUND) {
119 	    free(wcvalue);
120 	    return NULL;
121 	}
122 	if (nsz <= size) {
123 	    len = WideCharToMultiByte(CP_UTF8, 0, wcvalue, -1, NULL, 0, NULL, NULL);
124 	    value = emalloc(len*sizeof(char));
125 	    WideCharToMultiByte(CP_UTF8, 0, wcvalue, -1, value, len, NULL, NULL);
126 	    free(wcvalue);
127 	    return value;
128 	}
129 	size = nsz;
130     }
131 #else
132     return getenv(key);
133 #endif
134 }
135 
136 static void
free_env_val(char * value)137 free_env_val(char *value)
138 {
139 #ifdef __WIN32__
140     if (value)
141 	free(value);
142 #endif
143 }
144 
145 static void
set_env(char * key,char * value)146 set_env(char *key, char *value)
147 {
148 #ifdef __WIN32__
149     WCHAR wkey[MAXPATHLEN];
150     WCHAR wvalue[MAXPATHLEN];
151     MultiByteToWideChar(CP_UTF8, 0, key, -1, wkey, MAXPATHLEN);
152     MultiByteToWideChar(CP_UTF8, 0, value, -1, wvalue, MAXPATHLEN);
153     if (!SetEnvironmentVariableW(wkey, wvalue))
154         error("SetEnvironmentVariable(\"%s\", \"%s\") failed!", key, value);
155 #else
156     size_t size = strlen(key) + 1 + strlen(value) + 1;
157     char *str = emalloc(size);
158     sprintf(str, "%s=%s", key, value);
159     if (putenv(str) != 0)
160         error("putenv(\"%s\") failed!", str);
161 #ifdef HAVE_COPYING_PUTENV
162     efree(str);
163 #endif
164 #endif
165 }
166 
167 
168 #ifdef __WIN32__
wmain(int argc,wchar_t ** wcargv)169 int wmain(int argc, wchar_t **wcargv)
170 {
171     char** argv;
172 #else
173 int main(int argc, char** argv)
174 {
175 #endif
176     int eargv_size;
177     int eargc_base;		/* How many arguments in the base of eargv. */
178     char* emulator;
179     char *env;
180     int need_shell = 0;
181 
182 #ifdef __WIN32__
183     int len;
184     int i;
185     /* Convert argv to utf8 */
186     argv = emalloc((argc+1) * sizeof(char*));
187     for (i=0; i<argc; i++) {
188 	len = WideCharToMultiByte(CP_UTF8, 0, wcargv[i], -1, NULL, 0, NULL, NULL);
189 	argv[i] = emalloc(len*sizeof(char));
190 	WideCharToMultiByte(CP_UTF8, 0, wcargv[i], -1, argv[i], len, NULL, NULL);
191     }
192     argv[argc] = NULL;
193 #endif
194 
195     env = get_env("DIALYZER_EMULATOR");
196     emulator = env ? env : get_default_emulator(argv[0]);
197 
198     if (strlen(emulator) >= MAXPATHLEN)
199         error("Value of environment variable DIALYZER_EMULATOR is too large");
200 
201     /*
202      * Add scriptname to env
203      */
204     set_env("ESCRIPT_NAME", argv[0]);
205 
206     /*
207      * Allocate the argv vector to be used for arguments to Erlang.
208      * Arrange for starting to pushing information in the middle of
209      * the array, to allow easy addition of commands in the beginning.
210      */
211 
212     eargv_size = argc*4+100;
213     eargv_base = (char **) emalloc(eargv_size*sizeof(char*));
214     eargv = eargv_base;
215     eargc = 0;
216     push_words(emulator);
217     if (emulator != env) {
218         free(emulator);
219     }
220     eargc_base = eargc;
221     eargv = eargv + eargv_size/2;
222     eargc = 0;
223 
224     free_env_val(env);
225 
226     /*
227      * Push initial arguments.
228      */
229 
230     if (argc > 1 && strcmp(argv[1], "-smp") == 0) {
231 	PUSH("-smpauto");
232 	argc--, argv++;
233     }
234 
235     if (argc > 2 && strcmp(argv[1], "+S") == 0) {
236 	PUSH3("-smp", "+S", argv[2]);
237 	argc--, argv++;
238 	argc--, argv++;
239     }
240 
241     if (argc > 2 && strcmp(argv[1], "+P") == 0) {
242 	PUSH2("+P", argv[2]);
243 	argc--, argv++;
244 	argc--, argv++;
245     } else PUSH2("+P", "1000000");
246 
247     if (argc > 2 && strcmp(argv[1], "+sbt") == 0) {
248 	PUSH2("+sbt", argv[2]);
249 	argc--, argv++;
250 	argc--, argv++;
251     }
252 
253     PUSH("+B");
254     PUSH2("-boot", "no_dot_erlang");
255     PUSH3("-run", "dialyzer", "plain_cl");
256     PUSH("-extra");
257 
258     /*
259      * Push everything except --shell.
260      */
261 
262     while (argc > 1) {
263 	if (strcmp(argv[1], "--shell") == 0) {
264 	    need_shell = 1;
265 	} else {
266 	    PUSH(argv[1]);
267 	}
268 	argc--, argv++;
269     }
270 
271     if (!need_shell) {
272 	UNSHIFT("-noinput");
273     }
274 
275     /*
276      * Move up the commands for invoking the emulator and adjust eargv
277      * accordingly.
278      */
279 
280     while (--eargc_base >= 0) {
281 	UNSHIFT(eargv_base[eargc_base]);
282     }
283 
284     /*
285      * Invoke Erlang with the collected options.
286      */
287 
288     PUSH(NULL);
289     return run_erlang(eargv[0], eargv);
290 }
291 
292 static void
293 push_words(char* src)
294 {
295     char sbuf[MAXPATHLEN];
296     char* dst;
297 
298     dst = sbuf;
299     while ((*dst++ = *src++) != '\0') {
300 	if (isspace((int)*src)) {
301 	    *dst = '\0';
302 	    PUSH(strsave(sbuf));
303 	    dst = sbuf;
304 	    do {
305 		src++;
306 	    } while (isspace((int)*src));
307 	}
308     }
309     if (sbuf[0])
310 	PUSH(strsave(sbuf));
311 }
312 
313 #ifdef __WIN32__
314 wchar_t *make_commandline(char **argv)
315 {
316     static wchar_t *buff = NULL;
317     static int siz = 0;
318     int num = 0, len;
319     char **arg;
320     wchar_t *p;
321 
322     if (*argv == NULL) {
323 	return L"";
324     }
325     for (arg = argv; *arg != NULL; ++arg) {
326 	num += strlen(*arg)+1;
327     }
328     if (!siz) {
329 	siz = num;
330 	buff = (wchar_t *) emalloc(siz*sizeof(wchar_t));
331     } else if (siz < num) {
332 	siz = num;
333 	buff = (wchar_t *) erealloc(buff,siz*sizeof(wchar_t));
334     }
335     p = buff;
336     num=0;
337     for (arg = argv; *arg != NULL; ++arg) {
338 	len = MultiByteToWideChar(CP_UTF8, 0, *arg, -1, p, siz);
339 	p+=(len-1);
340 	*p++=L' ';
341     }
342     *(--p) = L'\0';
343 
344     if (debug) {
345 	printf("Processed command line:%S\n",buff);
346     }
347     return buff;
348 }
349 
350 int my_spawnvp(char **argv)
351 {
352     STARTUPINFOW siStartInfo;
353     PROCESS_INFORMATION piProcInfo;
354     DWORD ec;
355 
356     memset(&siStartInfo,0,sizeof(STARTUPINFOW));
357     siStartInfo.cb = sizeof(STARTUPINFOW);
358     siStartInfo.dwFlags = STARTF_USESTDHANDLES;
359     siStartInfo.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
360     siStartInfo.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
361     siStartInfo.hStdError = GetStdHandle(STD_ERROR_HANDLE);
362     siStartInfo.wShowWindow = SW_HIDE;
363     siStartInfo.dwFlags |= STARTF_USESHOWWINDOW;
364 
365 
366     if (!CreateProcessW(NULL,
367 		       make_commandline(argv),
368 		       NULL,
369 		       NULL,
370 		       TRUE,
371 		       0,
372 		       NULL,
373 		       NULL,
374 		       &siStartInfo,
375 		       &piProcInfo)) {
376 	return -1;
377     }
378     CloseHandle(piProcInfo.hThread);
379 
380     WaitForSingleObject(piProcInfo.hProcess,INFINITE);
381     if (!GetExitCodeProcess(piProcInfo.hProcess,&ec)) {
382 	return 0;
383     }
384     return (int) ec;
385 }
386 #endif /* __WIN32__ */
387 
388 
389 static int
390 run_erlang(char* progname, char** argv)
391 {
392 #ifdef __WIN32__
393     int status;
394 #endif
395 
396     if (debug) {
397 	int i = 0;
398 	while (argv[i] != NULL)
399 	    printf(" %s", argv[i++]);
400 	printf("\n");
401     }
402 
403 #ifdef __WIN32__
404     /*
405      * Alas, we must wait here for the program to finish.
406      * Otherwise, the shell from which we was executed will think
407      * we are finished and print a prompt and read keyboard input.
408      */
409 
410     status = my_spawnvp(argv)/*_spawnvp(_P_WAIT,progname,argv)*/;
411     if (status == -1) {
412 	fprintf(stderr, "dialyzer: Error executing '%s': %d", progname,
413 		GetLastError());
414     }
415     return status;
416 #else
417     execvp(progname, argv);
418     error("Error %d executing \'%s\'.", errno, progname);
419     return 2;
420 #endif
421 }
422 
423 static void
424 error(char* format, ...)
425 {
426     char sbuf[1024];
427     va_list ap;
428 
429     va_start(ap, format);
430     erts_vsnprintf(sbuf, sizeof(sbuf), format, ap);
431     va_end(ap);
432     fprintf(stderr, "dialyzer: %s\n", sbuf);
433     exit(1);
434 }
435 
436 static void*
437 emalloc(size_t size)
438 {
439   void *p = malloc(size);
440   if (p == NULL)
441     error("Insufficient memory");
442   return p;
443 }
444 
445 #ifdef __WIN32__
446 static void *
447 erealloc(void *p, size_t size)
448 {
449     void *res = realloc(p, size);
450     if (res == NULL)
451     error("Insufficient memory");
452     return res;
453 }
454 #endif
455 
456 #ifdef HAVE_COPYING_PUTENV
457 static void
458 efree(void *p)
459 {
460     free(p);
461 }
462 #endif
463 
464 static char*
465 strsave(char* string)
466 {
467     char* p = emalloc(strlen(string)+1);
468     strcpy(p, string);
469     return p;
470 }
471 
472 static int
473 file_exists(char *progname)
474 {
475 #ifdef __WIN32__
476     wchar_t wcsbuf[MAXPATHLEN];
477     MultiByteToWideChar(CP_UTF8, 0, progname, -1, wcsbuf, MAXPATHLEN);
478     return (_waccess(wcsbuf, 0) != -1);
479 #else
480     return (access(progname, 1) != -1);
481 #endif
482 }
483 
484 static char*
485 get_default_emulator(char* progname)
486 {
487     char sbuf[MAXPATHLEN];
488     char* s;
489 
490     if (strlen(progname) >= sizeof(sbuf))
491         return strsave(ERL_NAME);
492 
493     strcpy(sbuf, progname);
494     for (s = sbuf+strlen(sbuf); s >= sbuf; s--) {
495 	if (IS_DIRSEP(*s)) {
496 	    strcpy(s+1, ERL_NAME);
497 	    if(file_exists(sbuf))
498 		return strsave(sbuf);
499 	    break;
500 	}
501     }
502     return strsave(ERL_NAME);
503 }
504 
505 #ifdef __WIN32__
506 static char*
507 possibly_quote(char* arg)
508 {
509     int mustQuote = NO;
510     int n = 0;
511     char* s;
512     char* narg;
513 
514     if (arg == NULL) {
515 	return arg;
516     }
517 
518     /*
519      * Scan the string to find out if it needs quoting and return
520      * the original argument if not.
521      */
522 
523     for (s = arg; *s; s++, n++) {
524 	switch(*s) {
525 	case ' ':
526 	    mustQuote = YES;
527 	    continue;
528 	case '"':
529 	    mustQuote = YES;
530 	    n++;
531 	    continue;
532 	case '\\':
533 	    if(s[1] == '"')
534 		n++;
535 	    continue;
536 	default:
537 	    continue;
538 	}
539     }
540     if (!mustQuote) {
541 	return arg;
542     }
543 
544     /*
545      * Insert the quotes and put a backslash in front of every quote
546      * inside the string.
547      */
548 
549     s = narg = emalloc(n+2+1);
550     for (*s++ = '"'; *arg; arg++, s++) {
551 	if (*arg == '"' || (*arg == '\\' && arg[1] == '"')) {
552 	    *s++ = '\\';
553 	}
554 	*s = *arg;
555     }
556     if (s[-1] == '\\') {
557 	*s++ ='\\';
558     }
559     *s++ = '"';
560     *s = '\0';
561     return narg;
562 }
563 #endif /* __WIN32__ */
564