]>
Commit | Line | Data |
---|---|---|
f67539c2 TL |
1 | /* |
2 | * Licensed to the Apache Software Foundation (ASF) under one | |
3 | * or more contributor license agreements. See the NOTICE file | |
4 | * distributed with this work for additional information | |
5 | * regarding copyright ownership. The ASF licenses this file | |
6 | * to you under the Apache License, Version 2.0 (the | |
7 | * "License"); you may not use this file except in compliance | |
8 | * with the License. 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, | |
13 | * software distributed under the License is distributed on an | |
14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | |
15 | * KIND, either express or implied. See the License for the | |
16 | * specific language governing permissions and limitations | |
17 | * under the License. | |
18 | */ | |
19 | ||
20 | /** | |
21 | * thrift - a lightweight cross-language rpc/serialization tool | |
22 | * | |
23 | * This file contains the main compiler engine for Thrift, which invokes the | |
24 | * scanner/parser to build the thrift object tree. The interface generation | |
25 | * code for each language lives in a file by the language name under the | |
26 | * generate/ folder, and all parse structures live in parse/ | |
27 | * | |
28 | */ | |
29 | ||
30 | #include <cassert> | |
31 | #include <stdlib.h> | |
32 | #include <stdio.h> | |
33 | #include <stdarg.h> | |
34 | #include <time.h> | |
35 | #include <string> | |
36 | #include <algorithm> | |
37 | #include <sys/types.h> | |
38 | #include <sys/stat.h> | |
39 | #include <errno.h> | |
40 | #include <limits.h> | |
41 | ||
42 | #ifdef _WIN32 | |
43 | #include <windows.h> /* for GetFullPathName */ | |
44 | #endif | |
45 | ||
46 | // Careful: must include globals first for extern definitions | |
47 | #include "thrift/common.h" | |
48 | #include "thrift/globals.h" | |
49 | ||
50 | #include "thrift/platform.h" | |
51 | #include "thrift/main.h" | |
52 | #include "thrift/parse/t_program.h" | |
53 | #include "thrift/parse/t_scope.h" | |
54 | #include "thrift/generate/t_generator.h" | |
55 | #include "thrift/audit/t_audit.h" | |
56 | ||
57 | #include "thrift/version.h" | |
58 | ||
59 | using namespace std; | |
60 | ||
61 | /** | |
62 | * Global program tree | |
63 | */ | |
64 | t_program* g_program; | |
65 | ||
66 | /** | |
67 | * Global scope | |
68 | */ | |
69 | t_scope* g_scope; | |
70 | ||
71 | /** | |
72 | * Parent scope to also parse types | |
73 | */ | |
74 | t_scope* g_parent_scope; | |
75 | ||
76 | /** | |
77 | * Prefix for putting types in parent scope | |
78 | */ | |
79 | string g_parent_prefix; | |
80 | ||
81 | /** | |
82 | * Parsing pass | |
83 | */ | |
84 | PARSE_MODE g_parse_mode; | |
85 | ||
86 | /** | |
87 | * Current directory of file being parsed | |
88 | */ | |
89 | string g_curdir; | |
90 | ||
91 | /** | |
92 | * Current file being parsed | |
93 | */ | |
94 | string g_curpath; | |
95 | ||
96 | /** | |
97 | * Search path for inclusions | |
98 | */ | |
99 | vector<string> g_incl_searchpath; | |
100 | ||
101 | /** | |
102 | * Global debug state | |
103 | */ | |
104 | int g_debug = 0; | |
105 | ||
106 | /** | |
107 | * Strictness level | |
108 | */ | |
109 | int g_strict = 127; | |
110 | ||
111 | /** | |
112 | * Warning level | |
113 | */ | |
114 | int g_warn = 1; | |
115 | ||
116 | /** | |
117 | * Verbose output | |
118 | */ | |
119 | int g_verbose = 0; | |
120 | ||
121 | /** | |
122 | * Global time string | |
123 | */ | |
124 | char* g_time_str; | |
125 | ||
126 | /** | |
127 | * The last parsed doctext comment. | |
128 | */ | |
129 | char* g_doctext; | |
130 | ||
131 | /** | |
132 | * The First doctext comment | |
133 | */ | |
134 | char* g_program_doctext_candidate; | |
135 | ||
136 | /** | |
137 | * Whether or not negative field keys are accepted. | |
138 | */ | |
139 | int g_allow_neg_field_keys; | |
140 | ||
141 | /** | |
142 | * Whether or not 64-bit constants will generate a warning. | |
143 | */ | |
144 | int g_allow_64bit_consts = 0; | |
145 | ||
146 | /** | |
147 | * Flags to control code generation | |
148 | */ | |
149 | bool gen_recurse = false; | |
150 | ||
151 | /** | |
152 | * Flags to control thrift audit | |
153 | */ | |
154 | bool g_audit = false; | |
155 | ||
156 | /** | |
157 | * Flag to control return status | |
158 | */ | |
159 | bool g_return_failure = false; | |
160 | bool g_audit_fatal = true; | |
161 | bool g_generator_failure = false; | |
162 | ||
163 | /** | |
164 | * Win32 doesn't have realpath, so use fallback implementation in that case, | |
165 | * otherwise this just calls through to realpath | |
166 | */ | |
167 | char* saferealpath(const char* path, char* resolved_path) { | |
168 | #ifdef _WIN32 | |
169 | char buf[MAX_PATH]; | |
170 | char* basename; | |
171 | DWORD len = GetFullPathNameA(path, MAX_PATH, buf, &basename); | |
172 | if (len == 0 || len > MAX_PATH - 1) { | |
173 | strcpy(resolved_path, path); | |
174 | } else { | |
175 | strcpy(resolved_path, buf); | |
176 | } | |
177 | ||
178 | // Replace backslashes with forward slashes so the | |
179 | // rest of the code behaves correctly. | |
180 | size_t resolved_len = strlen(resolved_path); | |
181 | for (size_t i = 0; i < resolved_len; i++) { | |
182 | if (resolved_path[i] == '\\') { | |
183 | resolved_path[i] = '/'; | |
184 | } | |
185 | } | |
186 | return resolved_path; | |
187 | #else | |
188 | return realpath(path, resolved_path); | |
189 | #endif | |
190 | } | |
191 | ||
192 | bool check_is_directory(const char* dir_name) { | |
193 | #ifdef _WIN32 | |
194 | DWORD attributes = ::GetFileAttributesA(dir_name); | |
195 | if (attributes == INVALID_FILE_ATTRIBUTES) { | |
196 | fprintf(stderr, | |
197 | "Output directory %s is unusable: GetLastError() = %ld\n", | |
198 | dir_name, | |
199 | GetLastError()); | |
200 | return false; | |
201 | } | |
202 | if ((attributes & FILE_ATTRIBUTE_DIRECTORY) != FILE_ATTRIBUTE_DIRECTORY) { | |
203 | fprintf(stderr, "Output directory %s exists but is not a directory\n", dir_name); | |
204 | return false; | |
205 | } | |
206 | return true; | |
207 | #else | |
208 | struct stat sb; | |
209 | if (stat(dir_name, &sb) < 0) { | |
210 | fprintf(stderr, "Output directory %s is unusable: %s\n", dir_name, strerror(errno)); | |
211 | return false; | |
212 | } | |
213 | if (!S_ISDIR(sb.st_mode)) { | |
214 | fprintf(stderr, "Output directory %s exists but is not a directory\n", dir_name); | |
215 | return false; | |
216 | } | |
217 | return true; | |
218 | #endif | |
219 | } | |
220 | ||
221 | /** | |
222 | * Report an error to the user. This is called yyerror for historical | |
223 | * reasons (lex and yacc expect the error reporting routine to be called | |
224 | * this). Call this function to report any errors to the user. | |
225 | * yyerror takes printf style arguments. | |
226 | * | |
227 | * @param fmt C format string followed by additional arguments | |
228 | */ | |
229 | void yyerror(const char* fmt, ...) { | |
230 | va_list args; | |
231 | fprintf(stderr, "[ERROR:%s:%d] (last token was '%s')\n", g_curpath.c_str(), yylineno, yytext); | |
232 | ||
233 | va_start(args, fmt); | |
234 | vfprintf(stderr, fmt, args); | |
235 | va_end(args); | |
236 | ||
237 | fprintf(stderr, "\n"); | |
238 | } | |
239 | ||
240 | /** | |
241 | * Prints a debug message from the parser. | |
242 | * | |
243 | * @param fmt C format string followed by additional arguments | |
244 | */ | |
245 | void pdebug(const char* fmt, ...) { | |
246 | if (g_debug == 0) { | |
247 | return; | |
248 | } | |
249 | va_list args; | |
250 | printf("[PARSE:%d] ", yylineno); | |
251 | va_start(args, fmt); | |
252 | vprintf(fmt, args); | |
253 | va_end(args); | |
254 | printf("\n"); | |
255 | } | |
256 | ||
257 | /** | |
258 | * Prints a verbose output mode message | |
259 | * | |
260 | * @param fmt C format string followed by additional arguments | |
261 | */ | |
262 | void pverbose(const char* fmt, ...) { | |
263 | if (g_verbose == 0) { | |
264 | return; | |
265 | } | |
266 | va_list args; | |
267 | va_start(args, fmt); | |
268 | vprintf(fmt, args); | |
269 | va_end(args); | |
270 | } | |
271 | ||
272 | /** | |
273 | * Prints a warning message | |
274 | * | |
275 | * @param fmt C format string followed by additional arguments | |
276 | */ | |
277 | void pwarning(int level, const char* fmt, ...) { | |
278 | if (g_warn < level) { | |
279 | return; | |
280 | } | |
281 | va_list args; | |
282 | printf("[WARNING:%s:%d] ", g_curpath.c_str(), yylineno); | |
283 | va_start(args, fmt); | |
284 | vprintf(fmt, args); | |
285 | va_end(args); | |
286 | printf("\n"); | |
287 | } | |
288 | ||
289 | /** | |
290 | * Prints a failure message and exits | |
291 | * | |
292 | * @param fmt C format string followed by additional arguments | |
293 | */ | |
294 | void failure(const char* fmt, ...) { | |
295 | va_list args; | |
296 | fprintf(stderr, "[FAILURE:%s:%d] ", g_curpath.c_str(), yylineno); | |
297 | va_start(args, fmt); | |
298 | vfprintf(stderr, fmt, args); | |
299 | va_end(args); | |
300 | printf("\n"); | |
301 | exit(1); | |
302 | } | |
303 | ||
304 | /** | |
305 | * Converts a string filename into a thrift program name | |
306 | */ | |
307 | string program_name(string filename) { | |
308 | string::size_type slash = filename.rfind("/"); | |
309 | if (slash != string::npos) { | |
310 | filename = filename.substr(slash + 1); | |
311 | } | |
312 | string::size_type dot = filename.rfind("."); | |
313 | if (dot != string::npos) { | |
314 | filename = filename.substr(0, dot); | |
315 | } | |
316 | return filename; | |
317 | } | |
318 | ||
319 | /** | |
320 | * Gets the directory path of a filename | |
321 | */ | |
322 | string directory_name(string filename) { | |
323 | string::size_type slash = filename.rfind("/"); | |
324 | // No slash, just use the current directory | |
325 | if (slash == string::npos) { | |
326 | return "."; | |
327 | } | |
328 | return filename.substr(0, slash); | |
329 | } | |
330 | ||
331 | /** | |
332 | * Finds the appropriate file path for the given filename | |
333 | */ | |
334 | string include_file(string filename) { | |
335 | // Absolute path? Just try that | |
336 | if (filename[0] == '/') { | |
337 | // Realpath! | |
338 | char rp[THRIFT_PATH_MAX]; | |
339 | // cppcheck-suppress uninitvar | |
340 | if (saferealpath(filename.c_str(), rp) == NULL) { | |
341 | pwarning(0, "Cannot open include file %s\n", filename.c_str()); | |
342 | return std::string(); | |
343 | } | |
344 | ||
345 | // Stat this file | |
346 | struct stat finfo; | |
347 | if (stat(rp, &finfo) == 0) { | |
348 | return rp; | |
349 | } | |
350 | } else { // relative path, start searching | |
351 | // new search path with current dir global | |
352 | vector<string> sp = g_incl_searchpath; | |
353 | sp.insert(sp.begin(), g_curdir); | |
354 | ||
355 | // iterate through paths | |
356 | vector<string>::iterator it; | |
357 | for (it = sp.begin(); it != sp.end(); it++) { | |
358 | string sfilename = *(it) + "/" + filename; | |
359 | ||
360 | // Realpath! | |
361 | char rp[THRIFT_PATH_MAX]; | |
362 | // cppcheck-suppress uninitvar | |
363 | if (saferealpath(sfilename.c_str(), rp) == NULL) { | |
364 | continue; | |
365 | } | |
366 | ||
367 | // Stat this files | |
368 | struct stat finfo; | |
369 | if (stat(rp, &finfo) == 0) { | |
370 | return rp; | |
371 | } | |
372 | } | |
373 | } | |
374 | ||
375 | // Uh oh | |
376 | pwarning(0, "Could not find include file %s\n", filename.c_str()); | |
377 | return std::string(); | |
378 | } | |
379 | ||
380 | /** | |
381 | * Clears any previously stored doctext string. | |
382 | * Also prints a warning if we are discarding information. | |
383 | */ | |
384 | void clear_doctext() { | |
385 | if (g_doctext != NULL) { | |
386 | pwarning(2, "Uncaptured doctext at on line %d.", g_doctext_lineno); | |
387 | } | |
388 | free(g_doctext); | |
389 | g_doctext = NULL; | |
390 | } | |
391 | ||
392 | /** | |
393 | * Reset program doctext information after processing a file | |
394 | */ | |
395 | void reset_program_doctext_info() { | |
396 | if (g_program_doctext_candidate != NULL) { | |
397 | free(g_program_doctext_candidate); | |
398 | g_program_doctext_candidate = NULL; | |
399 | } | |
400 | g_program_doctext_lineno = 0; | |
401 | g_program_doctext_status = INVALID; | |
402 | pdebug("%s", "program doctext set to INVALID"); | |
403 | } | |
404 | ||
405 | /** | |
406 | * We are sure the program doctext candidate is really the program doctext. | |
407 | */ | |
408 | void declare_valid_program_doctext() { | |
409 | if ((g_program_doctext_candidate != NULL) && (g_program_doctext_status == STILL_CANDIDATE)) { | |
410 | g_program_doctext_status = ABSOLUTELY_SURE; | |
411 | pdebug("%s", "program doctext set to ABSOLUTELY_SURE"); | |
412 | } else { | |
413 | g_program_doctext_status = NO_PROGRAM_DOCTEXT; | |
414 | pdebug("%s", "program doctext set to NO_PROGRAM_DOCTEXT"); | |
415 | } | |
416 | } | |
417 | ||
418 | /** | |
419 | * Cleans up text commonly found in doxygen-like comments | |
420 | * | |
421 | * Warning: if you mix tabs and spaces in a non-uniform way, | |
422 | * you will get what you deserve. | |
423 | */ | |
424 | char* clean_up_doctext(char* doctext) { | |
425 | // Convert to C++ string, and remove Windows's carriage returns. | |
426 | string docstring = doctext; | |
427 | docstring.erase(remove(docstring.begin(), docstring.end(), '\r'), docstring.end()); | |
428 | ||
429 | // Separate into lines. | |
430 | vector<string> lines; | |
431 | string::size_type pos = string::npos; | |
432 | string::size_type last; | |
433 | while (true) { | |
434 | last = (pos == string::npos) ? 0 : pos + 1; | |
435 | pos = docstring.find('\n', last); | |
436 | if (pos == string::npos) { | |
437 | // First bit of cleaning. If the last line is only whitespace, drop it. | |
438 | string::size_type nonwhite = docstring.find_first_not_of(" \t", last); | |
439 | if (nonwhite != string::npos) { | |
440 | lines.push_back(docstring.substr(last)); | |
441 | } | |
442 | break; | |
443 | } | |
444 | lines.push_back(docstring.substr(last, pos - last)); | |
445 | } | |
446 | ||
447 | // A very profound docstring. | |
448 | if (lines.empty()) { | |
449 | return NULL; | |
450 | } | |
451 | ||
452 | // Clear leading whitespace from the first line. | |
453 | pos = lines.front().find_first_not_of(" \t"); | |
454 | lines.front().erase(0, pos); | |
455 | ||
456 | // If every nonblank line after the first has the same number of spaces/tabs, | |
457 | // then a star, remove them. | |
458 | bool have_prefix = true; | |
459 | bool found_prefix = false; | |
460 | string::size_type prefix_len = 0; | |
461 | vector<string>::iterator l_iter; | |
462 | for (l_iter = lines.begin() + 1; l_iter != lines.end(); ++l_iter) { | |
463 | if (l_iter->empty()) { | |
464 | continue; | |
465 | } | |
466 | ||
467 | pos = l_iter->find_first_not_of(" \t"); | |
468 | if (!found_prefix) { | |
469 | if (pos != string::npos) { | |
470 | if (l_iter->at(pos) == '*') { | |
471 | found_prefix = true; | |
472 | prefix_len = pos; | |
473 | } else { | |
474 | have_prefix = false; | |
475 | break; | |
476 | } | |
477 | } else { | |
478 | // Whitespace-only line. Truncate it. | |
479 | l_iter->clear(); | |
480 | } | |
481 | } else if (l_iter->size() > pos && l_iter->at(pos) == '*' && pos == prefix_len) { | |
482 | // Business as usual. | |
483 | } else if (pos == string::npos) { | |
484 | // Whitespace-only line. Let's truncate it for them. | |
485 | l_iter->clear(); | |
486 | } else { | |
487 | // The pattern has been broken. | |
488 | have_prefix = false; | |
489 | break; | |
490 | } | |
491 | } | |
492 | ||
493 | // If our prefix survived, delete it from every line. | |
494 | if (have_prefix) { | |
495 | // Get the star too. | |
496 | prefix_len++; | |
497 | for (l_iter = lines.begin() + 1; l_iter != lines.end(); ++l_iter) { | |
498 | l_iter->erase(0, prefix_len); | |
499 | } | |
500 | } | |
501 | ||
502 | // Now delete the minimum amount of leading whitespace from each line. | |
503 | prefix_len = string::npos; | |
504 | for (l_iter = lines.begin() + 1; l_iter != lines.end(); ++l_iter) { | |
505 | if (l_iter->empty()) { | |
506 | continue; | |
507 | } | |
508 | pos = l_iter->find_first_not_of(" \t"); | |
509 | if (pos != string::npos && (prefix_len == string::npos || pos < prefix_len)) { | |
510 | prefix_len = pos; | |
511 | } | |
512 | } | |
513 | ||
514 | // If our prefix survived, delete it from every line. | |
515 | if (prefix_len != string::npos) { | |
516 | for (l_iter = lines.begin() + 1; l_iter != lines.end(); ++l_iter) { | |
517 | l_iter->erase(0, prefix_len); | |
518 | } | |
519 | } | |
520 | ||
521 | // Remove trailing whitespace from every line. | |
522 | for (l_iter = lines.begin(); l_iter != lines.end(); ++l_iter) { | |
523 | pos = l_iter->find_last_not_of(" \t"); | |
524 | if (pos != string::npos && pos != l_iter->length() - 1) { | |
525 | l_iter->erase(pos + 1); | |
526 | } | |
527 | } | |
528 | ||
529 | // If the first line is empty, remove it. | |
530 | // Don't do this earlier because a lot of steps skip the first line. | |
531 | if (lines.front().empty()) { | |
532 | lines.erase(lines.begin()); | |
533 | } | |
534 | ||
535 | // Now rejoin the lines and copy them back into doctext. | |
536 | docstring.clear(); | |
537 | for (l_iter = lines.begin(); l_iter != lines.end(); ++l_iter) { | |
538 | docstring += *l_iter; | |
539 | docstring += '\n'; | |
540 | } | |
541 | ||
542 | // assert(docstring.length() <= strlen(doctext)); may happen, see THRIFT-1755 | |
543 | if (docstring.length() <= strlen(doctext)) { | |
544 | strcpy(doctext, docstring.c_str()); | |
545 | } else { | |
546 | free(doctext); // too short | |
547 | doctext = strdup(docstring.c_str()); | |
548 | } | |
549 | return doctext; | |
550 | } | |
551 | ||
552 | /** Set to true to debug docstring parsing */ | |
553 | static bool dump_docs = false; | |
554 | ||
555 | /** | |
556 | * Dumps docstrings to stdout | |
557 | * Only works for top-level definitions and the whole program doc | |
558 | * (i.e., not enum constants, struct fields, or functions. | |
559 | */ | |
560 | void dump_docstrings(t_program* program) { | |
561 | string progdoc = program->get_doc(); | |
562 | if (!progdoc.empty()) { | |
563 | printf("Whole program doc:\n%s\n", progdoc.c_str()); | |
564 | } | |
565 | const vector<t_typedef*>& typedefs = program->get_typedefs(); | |
566 | vector<t_typedef*>::const_iterator t_iter; | |
567 | for (t_iter = typedefs.begin(); t_iter != typedefs.end(); ++t_iter) { | |
568 | t_typedef* td = *t_iter; | |
569 | if (td->has_doc()) { | |
570 | printf("typedef %s:\n%s\n", td->get_name().c_str(), td->get_doc().c_str()); | |
571 | } | |
572 | } | |
573 | const vector<t_enum*>& enums = program->get_enums(); | |
574 | vector<t_enum*>::const_iterator e_iter; | |
575 | for (e_iter = enums.begin(); e_iter != enums.end(); ++e_iter) { | |
576 | t_enum* en = *e_iter; | |
577 | if (en->has_doc()) { | |
578 | printf("enum %s:\n%s\n", en->get_name().c_str(), en->get_doc().c_str()); | |
579 | } | |
580 | } | |
581 | const vector<t_const*>& consts = program->get_consts(); | |
582 | vector<t_const*>::const_iterator c_iter; | |
583 | for (c_iter = consts.begin(); c_iter != consts.end(); ++c_iter) { | |
584 | t_const* co = *c_iter; | |
585 | if (co->has_doc()) { | |
586 | printf("const %s:\n%s\n", co->get_name().c_str(), co->get_doc().c_str()); | |
587 | } | |
588 | } | |
589 | const vector<t_struct*>& structs = program->get_structs(); | |
590 | vector<t_struct*>::const_iterator s_iter; | |
591 | for (s_iter = structs.begin(); s_iter != structs.end(); ++s_iter) { | |
592 | t_struct* st = *s_iter; | |
593 | if (st->has_doc()) { | |
594 | printf("struct %s:\n%s\n", st->get_name().c_str(), st->get_doc().c_str()); | |
595 | } | |
596 | } | |
597 | const vector<t_struct*>& xceptions = program->get_xceptions(); | |
598 | vector<t_struct*>::const_iterator x_iter; | |
599 | for (x_iter = xceptions.begin(); x_iter != xceptions.end(); ++x_iter) { | |
600 | t_struct* xn = *x_iter; | |
601 | if (xn->has_doc()) { | |
602 | printf("xception %s:\n%s\n", xn->get_name().c_str(), xn->get_doc().c_str()); | |
603 | } | |
604 | } | |
605 | const vector<t_service*>& services = program->get_services(); | |
606 | vector<t_service*>::const_iterator v_iter; | |
607 | for (v_iter = services.begin(); v_iter != services.end(); ++v_iter) { | |
608 | t_service* sv = *v_iter; | |
609 | if (sv->has_doc()) { | |
610 | printf("service %s:\n%s\n", sv->get_name().c_str(), sv->get_doc().c_str()); | |
611 | } | |
612 | } | |
613 | } | |
614 | ||
615 | /** | |
616 | * Emits a warning on list<byte>, binary type is typically a much better choice. | |
617 | */ | |
618 | void check_for_list_of_bytes(t_type* list_elem_type) { | |
619 | if ((g_parse_mode == PROGRAM) && (list_elem_type != NULL) && list_elem_type->is_base_type()) { | |
620 | t_base_type* tbase = (t_base_type*)list_elem_type; | |
621 | if (tbase->get_base() == t_base_type::TYPE_I8) { | |
622 | pwarning(1, "Consider using the more efficient \"binary\" type instead of \"list<byte>\"."); | |
623 | } | |
624 | } | |
625 | } | |
626 | ||
627 | static bool g_byte_warning_emitted = false; | |
628 | ||
629 | /** | |
630 | * Emits a one-time warning on byte type, promoting the new i8 type instead | |
631 | */ | |
632 | void emit_byte_type_warning() { | |
633 | if (!g_byte_warning_emitted) { | |
634 | pwarning(1, | |
635 | "The \"byte\" type is a compatibility alias for \"i8\". Use \"i8\" to emphasize the " | |
636 | "signedness of this type.\n"); | |
637 | g_byte_warning_emitted = true; | |
638 | } | |
639 | } | |
640 | ||
641 | /** | |
642 | * Prints deprecation notice for old NS declarations that are no longer supported | |
643 | * If new_form is NULL, old_form is assumed to be a language identifier, such as "cpp" | |
644 | * If new_form is not NULL, both arguments are used exactly as given | |
645 | */ | |
646 | void error_unsupported_namespace_decl(const char* old_form, const char* new_form) { | |
647 | const char* remainder = ""; | |
648 | if( new_form == NULL) { | |
649 | new_form = old_form; | |
650 | remainder = "_namespace"; | |
651 | } | |
652 | failure("Unsupported declaration '%s%s'. Use 'namespace %s' instead.", old_form, remainder, new_form); | |
653 | } | |
654 | ||
655 | /** | |
656 | * Prints the version number | |
657 | */ | |
658 | void version() { | |
659 | printf("Thrift version %s\n", THRIFT_VERSION); | |
660 | } | |
661 | ||
662 | /** | |
663 | * Display the usage message and then exit with an error code. | |
664 | */ | |
665 | void usage() { | |
666 | fprintf(stderr, "Usage: thrift [options] file\n\n"); | |
667 | fprintf(stderr, "Use thrift -help for a list of options\n"); | |
668 | exit(1); | |
669 | } | |
670 | ||
671 | /** | |
672 | * Diplays the help message and then exits with an error code. | |
673 | */ | |
674 | void help() { | |
675 | fprintf(stderr, "Usage: thrift [options] file\n"); | |
676 | fprintf(stderr, "Options:\n"); | |
677 | fprintf(stderr, " -version Print the compiler version\n"); | |
678 | fprintf(stderr, " -o dir Set the output directory for gen-* packages\n"); | |
679 | fprintf(stderr, " (default: current directory)\n"); | |
680 | fprintf(stderr, " -out dir Set the ouput location for generated files.\n"); | |
681 | fprintf(stderr, " (no gen-* folder will be created)\n"); | |
682 | fprintf(stderr, " -I dir Add a directory to the list of directories\n"); | |
683 | fprintf(stderr, " searched for include directives\n"); | |
684 | fprintf(stderr, " -nowarn Suppress all compiler warnings (BAD!)\n"); | |
685 | fprintf(stderr, " -strict Strict compiler warnings on\n"); | |
686 | fprintf(stderr, " -v[erbose] Verbose mode\n"); | |
687 | fprintf(stderr, " -r[ecurse] Also generate included files\n"); | |
688 | fprintf(stderr, " -debug Parse debug trace to stdout\n"); | |
689 | fprintf(stderr, | |
690 | " --allow-neg-keys Allow negative field keys (Used to " | |
691 | "preserve protocol\n"); | |
692 | fprintf(stderr, " compatibility with older .thrift files)\n"); | |
693 | fprintf(stderr, " --allow-64bit-consts Do not print warnings about using 64-bit constants\n"); | |
694 | fprintf(stderr, " --gen STR Generate code with a dynamically-registered generator.\n"); | |
695 | fprintf(stderr, " STR has the form language[:key1=val1[,key2[,key3=val3]]].\n"); | |
696 | fprintf(stderr, " Keys and values are options passed to the generator.\n"); | |
697 | fprintf(stderr, " Many options will not require values.\n"); | |
698 | fprintf(stderr, "\n"); | |
699 | fprintf(stderr, "Options related to audit operation\n"); | |
700 | fprintf(stderr, " --audit OldFile Old Thrift file to be audited with 'file'\n"); | |
701 | fprintf(stderr, " -Iold dir Add a directory to the list of directories\n"); | |
702 | fprintf(stderr, " searched for include directives for old thrift file\n"); | |
703 | fprintf(stderr, " -Inew dir Add a directory to the list of directories\n"); | |
704 | fprintf(stderr, " searched for include directives for new thrift file\n"); | |
705 | fprintf(stderr, "\n"); | |
706 | fprintf(stderr, "Available generators (and options):\n"); | |
707 | ||
708 | t_generator_registry::gen_map_t gen_map = t_generator_registry::get_generator_map(); | |
709 | t_generator_registry::gen_map_t::iterator iter; | |
710 | for (iter = gen_map.begin(); iter != gen_map.end(); ++iter) { | |
711 | fprintf(stderr, | |
712 | " %s (%s):\n", | |
713 | iter->second->get_short_name().c_str(), | |
714 | iter->second->get_long_name().c_str()); | |
715 | fprintf(stderr, "%s", iter->second->get_documentation().c_str()); | |
716 | } | |
717 | exit(1); | |
718 | } | |
719 | ||
720 | /** | |
721 | * You know, when I started working on Thrift I really thought it wasn't going | |
722 | * to become a programming language because it was just a generator and it | |
723 | * wouldn't need runtime type information and all that jazz. But then we | |
724 | * decided to add constants, and all of a sudden that means runtime type | |
725 | * validation and inference, except the "runtime" is the code generator | |
726 | * runtime. | |
727 | */ | |
728 | void validate_const_rec(std::string name, t_type* type, t_const_value* value) { | |
729 | if (type->is_void()) { | |
730 | throw "type error: cannot declare a void const: " + name; | |
731 | } | |
732 | ||
733 | if (type->is_base_type()) { | |
734 | t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); | |
735 | switch (tbase) { | |
736 | case t_base_type::TYPE_STRING: | |
737 | if (value->get_type() != t_const_value::CV_STRING) { | |
738 | throw "type error: const \"" + name + "\" was declared as string"; | |
739 | } | |
740 | break; | |
741 | case t_base_type::TYPE_BOOL: | |
742 | if (value->get_type() != t_const_value::CV_INTEGER) { | |
743 | throw "type error: const \"" + name + "\" was declared as bool"; | |
744 | } | |
745 | break; | |
746 | case t_base_type::TYPE_I8: | |
747 | if (value->get_type() != t_const_value::CV_INTEGER) { | |
748 | throw "type error: const \"" + name + "\" was declared as byte"; | |
749 | } | |
750 | break; | |
751 | case t_base_type::TYPE_I16: | |
752 | if (value->get_type() != t_const_value::CV_INTEGER) { | |
753 | throw "type error: const \"" + name + "\" was declared as i16"; | |
754 | } | |
755 | break; | |
756 | case t_base_type::TYPE_I32: | |
757 | if (value->get_type() != t_const_value::CV_INTEGER) { | |
758 | throw "type error: const \"" + name + "\" was declared as i32"; | |
759 | } | |
760 | break; | |
761 | case t_base_type::TYPE_I64: | |
762 | if (value->get_type() != t_const_value::CV_INTEGER) { | |
763 | throw "type error: const \"" + name + "\" was declared as i64"; | |
764 | } | |
765 | break; | |
766 | case t_base_type::TYPE_DOUBLE: | |
767 | if (value->get_type() != t_const_value::CV_INTEGER | |
768 | && value->get_type() != t_const_value::CV_DOUBLE) { | |
769 | throw "type error: const \"" + name + "\" was declared as double"; | |
770 | } | |
771 | break; | |
772 | default: | |
773 | throw "compiler error: no const of base type " + t_base_type::t_base_name(tbase) + name; | |
774 | } | |
775 | } else if (type->is_enum()) { | |
776 | if (value->get_type() != t_const_value::CV_IDENTIFIER) { | |
777 | throw "type error: const \"" + name + "\" was declared as enum"; | |
778 | } | |
779 | ||
780 | // see if there's a dot in the identifier | |
781 | std::string name_portion = value->get_identifier_name(); | |
782 | ||
783 | const vector<t_enum_value*>& enum_values = ((t_enum*)type)->get_constants(); | |
784 | vector<t_enum_value*>::const_iterator c_iter; | |
785 | bool found = false; | |
786 | ||
787 | for (c_iter = enum_values.begin(); c_iter != enum_values.end(); ++c_iter) { | |
788 | if ((*c_iter)->get_name() == name_portion) { | |
789 | found = true; | |
790 | break; | |
791 | } | |
792 | } | |
793 | if (!found) { | |
794 | throw "type error: const " + name + " was declared as type " + type->get_name() | |
795 | + " which is an enum, but " + value->get_identifier() | |
796 | + " is not a valid value for that enum"; | |
797 | } | |
798 | } else if (type->is_struct() || type->is_xception()) { | |
799 | if (value->get_type() != t_const_value::CV_MAP) { | |
800 | throw "type error: const \"" + name + "\" was declared as struct/xception"; | |
801 | } | |
802 | const vector<t_field*>& fields = ((t_struct*)type)->get_members(); | |
803 | vector<t_field*>::const_iterator f_iter; | |
804 | ||
805 | const map<t_const_value*, t_const_value*, t_const_value::value_compare>& val = value->get_map(); | |
806 | map<t_const_value*, t_const_value*, t_const_value::value_compare>::const_iterator v_iter; | |
807 | for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) { | |
808 | if (v_iter->first->get_type() != t_const_value::CV_STRING) { | |
809 | throw "type error: " + name + " struct key must be string"; | |
810 | } | |
811 | t_type* field_type = NULL; | |
812 | for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { | |
813 | if ((*f_iter)->get_name() == v_iter->first->get_string()) { | |
814 | field_type = (*f_iter)->get_type(); | |
815 | } | |
816 | } | |
817 | if (field_type == NULL) { | |
818 | throw "type error: " + type->get_name() + " has no field " + v_iter->first->get_string(); | |
819 | } | |
820 | ||
821 | validate_const_rec(name + "." + v_iter->first->get_string(), field_type, v_iter->second); | |
822 | } | |
823 | } else if (type->is_map()) { | |
824 | t_type* k_type = ((t_map*)type)->get_key_type(); | |
825 | t_type* v_type = ((t_map*)type)->get_val_type(); | |
826 | const map<t_const_value*, t_const_value*, t_const_value::value_compare>& val = value->get_map(); | |
827 | map<t_const_value*, t_const_value*, t_const_value::value_compare>::const_iterator v_iter; | |
828 | for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) { | |
829 | validate_const_rec(name + "<key>", k_type, v_iter->first); | |
830 | validate_const_rec(name + "<val>", v_type, v_iter->second); | |
831 | } | |
832 | } else if (type->is_list() || type->is_set()) { | |
833 | t_type* e_type; | |
834 | if (type->is_list()) { | |
835 | e_type = ((t_list*)type)->get_elem_type(); | |
836 | } else { | |
837 | e_type = ((t_set*)type)->get_elem_type(); | |
838 | } | |
839 | const vector<t_const_value*>& val = value->get_list(); | |
840 | vector<t_const_value*>::const_iterator v_iter; | |
841 | for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) { | |
842 | validate_const_rec(name + "<elem>", e_type, *v_iter); | |
843 | } | |
844 | } | |
845 | } | |
846 | ||
847 | /** | |
848 | * Check simple identifier names | |
849 | * It's easier to do it this way instead of rewriting the whole grammar etc. | |
850 | */ | |
851 | void validate_simple_identifier(const char* identifier) { | |
852 | string name(identifier); | |
853 | if (name.find(".") != string::npos) { | |
854 | yyerror("Identifier %s can't have a dot.", identifier); | |
855 | exit(1); | |
856 | } | |
857 | } | |
858 | ||
859 | /** | |
860 | * Check the type of the parsed const information against its declared type | |
861 | */ | |
862 | void validate_const_type(t_const* c) { | |
863 | validate_const_rec(c->get_name(), c->get_type(), c->get_value()); | |
864 | } | |
865 | ||
866 | /** | |
867 | * Check the type of a default value assigned to a field. | |
868 | */ | |
869 | void validate_field_value(t_field* field, t_const_value* cv) { | |
870 | validate_const_rec(field->get_name(), field->get_type(), cv); | |
871 | } | |
872 | ||
873 | /** | |
874 | * Check that all the elements of a throws block are actually exceptions. | |
875 | */ | |
876 | bool validate_throws(t_struct* throws) { | |
877 | const vector<t_field*>& members = throws->get_members(); | |
878 | vector<t_field*>::const_iterator m_iter; | |
879 | for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { | |
880 | if (!t_generator::get_true_type((*m_iter)->get_type())->is_xception()) { | |
881 | return false; | |
882 | } | |
883 | } | |
884 | return true; | |
885 | } | |
886 | ||
887 | /** | |
888 | * Skips UTF-8 BOM if there is one | |
889 | */ | |
890 | bool skip_utf8_bom(FILE* f) { | |
891 | ||
892 | // pretty straightforward, but works | |
893 | if (fgetc(f) == 0xEF) { | |
894 | if (fgetc(f) == 0xBB) { | |
895 | if (fgetc(f) == 0xBF) { | |
896 | return true; | |
897 | } | |
898 | } | |
899 | } | |
900 | ||
901 | rewind(f); | |
902 | return false; | |
903 | } | |
904 | ||
905 | /** | |
906 | * Parses a program | |
907 | */ | |
908 | void parse(t_program* program, t_program* parent_program) { | |
909 | // Get scope file path | |
910 | string path = program->get_path(); | |
911 | ||
912 | // Set current dir global, which is used in the include_file function | |
913 | g_curdir = directory_name(path); | |
914 | g_curpath = path; | |
915 | ||
916 | // Open the file | |
917 | // skip UTF-8 BOM if there is one | |
918 | yyin = fopen(path.c_str(), "r"); | |
919 | if (yyin == 0) { | |
920 | failure("Could not open input file: \"%s\"", path.c_str()); | |
921 | } | |
922 | if (skip_utf8_bom(yyin)) | |
923 | pverbose("Skipped UTF-8 BOM at %s\n", path.c_str()); | |
924 | ||
925 | // Create new scope and scan for includes | |
926 | pverbose("Scanning %s for includes\n", path.c_str()); | |
927 | g_parse_mode = INCLUDES; | |
928 | g_program = program; | |
929 | g_scope = program->scope(); | |
930 | try { | |
931 | yylineno = 1; | |
932 | if (yyparse() != 0) { | |
933 | failure("Parser error during include pass."); | |
934 | } | |
935 | } catch (string x) { | |
936 | failure(x.c_str()); | |
937 | } | |
938 | fclose(yyin); | |
939 | ||
940 | // Recursively parse all the include programs | |
941 | vector<t_program*>& includes = program->get_includes(); | |
942 | vector<t_program*>::iterator iter; | |
943 | for (iter = includes.begin(); iter != includes.end(); ++iter) { | |
944 | parse(*iter, program); | |
945 | } | |
946 | ||
947 | // reset program doctext status before parsing a new file | |
948 | reset_program_doctext_info(); | |
949 | ||
950 | // Parse the program file | |
951 | g_parse_mode = PROGRAM; | |
952 | g_program = program; | |
953 | g_scope = program->scope(); | |
954 | g_parent_scope = (parent_program != NULL) ? parent_program->scope() : NULL; | |
955 | g_parent_prefix = program->get_name() + "."; | |
956 | g_curpath = path; | |
957 | ||
958 | // Open the file | |
959 | // skip UTF-8 BOM if there is one | |
960 | yyin = fopen(path.c_str(), "r"); | |
961 | if (yyin == 0) { | |
962 | failure("Could not open input file: \"%s\"", path.c_str()); | |
963 | } | |
964 | if (skip_utf8_bom(yyin)) | |
965 | pverbose("Skipped UTF-8 BOM at %s\n", path.c_str()); | |
966 | ||
967 | pverbose("Parsing %s for types\n", path.c_str()); | |
968 | yylineno = 1; | |
969 | try { | |
970 | if (yyparse() != 0) { | |
971 | failure("Parser error during types pass."); | |
972 | } | |
973 | } catch (string x) { | |
974 | failure(x.c_str()); | |
975 | } | |
976 | fclose(yyin); | |
977 | } | |
978 | ||
979 | /** | |
980 | * Generate code | |
981 | */ | |
982 | void generate(t_program* program, const vector<string>& generator_strings) { | |
983 | // Oooohh, recursive code generation, hot!! | |
984 | if (gen_recurse) { | |
985 | program->set_recursive(true); | |
986 | const vector<t_program*>& includes = program->get_includes(); | |
987 | for (auto include : includes) { | |
988 | // Propagate output path from parent to child programs | |
989 | include->set_out_path(program->get_out_path(), program->is_out_path_absolute()); | |
990 | ||
991 | generate(include, generator_strings); | |
992 | } | |
993 | } | |
994 | ||
995 | // Generate code! | |
996 | try { | |
997 | pverbose("Program: %s\n", program->get_path().c_str()); | |
998 | ||
999 | if (dump_docs) { | |
1000 | dump_docstrings(program); | |
1001 | } | |
1002 | ||
1003 | vector<string>::const_iterator iter; | |
1004 | for (iter = generator_strings.begin(); iter != generator_strings.end(); ++iter) { | |
1005 | t_generator* generator = t_generator_registry::get_generator(program, *iter); | |
1006 | ||
1007 | if (generator == NULL) { | |
1008 | pwarning(1, "Unable to get a generator for \"%s\".\n", iter->c_str()); | |
1009 | g_generator_failure = true; | |
1010 | } else if (generator) { | |
1011 | generator->validate_input(); | |
1012 | pverbose("Generating \"%s\"\n", iter->c_str()); | |
1013 | generator->generate_program(); | |
1014 | delete generator; | |
1015 | } | |
1016 | } | |
1017 | } catch (string s) { | |
1018 | failure("Error: %s\n", s.c_str()); | |
1019 | } catch (const char* exc) { | |
1020 | failure("Error: %s\n", exc); | |
1021 | } catch (const std::invalid_argument& invalid_argument_exception) { | |
1022 | failure("Error: %s\n", invalid_argument_exception.what()); | |
1023 | } | |
1024 | } | |
1025 | ||
1026 | void audit(t_program* new_program, | |
1027 | t_program* old_program, | |
1028 | string new_thrift_include_path, | |
1029 | string old_thrift_include_path) { | |
1030 | vector<string> temp_incl_searchpath = g_incl_searchpath; | |
1031 | if (!old_thrift_include_path.empty()) { | |
1032 | g_incl_searchpath.push_back(old_thrift_include_path); | |
1033 | } | |
1034 | ||
1035 | parse(old_program, NULL); | |
1036 | ||
1037 | g_incl_searchpath = temp_incl_searchpath; | |
1038 | if (!new_thrift_include_path.empty()) { | |
1039 | g_incl_searchpath.push_back(new_thrift_include_path); | |
1040 | } | |
1041 | ||
1042 | parse(new_program, NULL); | |
1043 | ||
1044 | compare_namespace(new_program, old_program); | |
1045 | compare_services(new_program->get_services(), old_program->get_services()); | |
1046 | compare_enums(new_program->get_enums(), old_program->get_enums()); | |
1047 | compare_structs(new_program->get_structs(), old_program->get_structs()); | |
1048 | compare_structs(new_program->get_xceptions(), old_program->get_xceptions()); | |
1049 | compare_consts(new_program->get_consts(), old_program->get_consts()); | |
1050 | } | |
1051 | ||
1052 | /** | |
1053 | * Parse it up.. then spit it back out, in pretty much every language. Alright | |
1054 | * not that many languages, but the cool ones that we care about. | |
1055 | */ | |
1056 | int main(int argc, char** argv) { | |
1057 | int i; | |
1058 | std::string out_path; | |
1059 | bool out_path_is_absolute = false; | |
1060 | ||
1061 | // Setup time string | |
1062 | time_t now = time(NULL); | |
1063 | g_time_str = ctime(&now); | |
1064 | ||
1065 | // Check for necessary arguments, you gotta have at least a filename and | |
1066 | // an output language flag | |
1067 | if (argc < 2) { | |
1068 | usage(); | |
1069 | } | |
1070 | ||
1071 | vector<string> generator_strings; | |
1072 | string old_thrift_include_path; | |
1073 | string new_thrift_include_path; | |
1074 | string old_input_file; | |
1075 | ||
1076 | // Set the current path to a dummy value to make warning messages clearer. | |
1077 | g_curpath = "arguments"; | |
1078 | ||
1079 | // Hacky parameter handling... I didn't feel like using a library sorry! | |
1080 | for (i = 1; i < argc - 1; i++) { | |
1081 | char* arg; | |
1082 | ||
1083 | arg = strtok(argv[i], " "); | |
1084 | while (arg != NULL) { | |
1085 | // Treat double dashes as single dashes | |
1086 | if (arg[0] == '-' && arg[1] == '-') { | |
1087 | ++arg; | |
1088 | } | |
1089 | ||
1090 | if (strcmp(arg, "-help") == 0) { | |
1091 | help(); | |
1092 | } else if (strcmp(arg, "-version") == 0) { | |
1093 | version(); | |
1094 | exit(0); | |
1095 | } else if (strcmp(arg, "-debug") == 0) { | |
1096 | g_debug = 1; | |
1097 | } else if (strcmp(arg, "-nowarn") == 0) { | |
1098 | g_warn = 0; | |
1099 | } else if (strcmp(arg, "-strict") == 0) { | |
1100 | g_strict = 255; | |
1101 | g_warn = 2; | |
1102 | } else if (strcmp(arg, "-v") == 0 || strcmp(arg, "-verbose") == 0) { | |
1103 | g_verbose = 1; | |
1104 | } else if (strcmp(arg, "-r") == 0 || strcmp(arg, "-recurse") == 0) { | |
1105 | gen_recurse = true; | |
1106 | } else if (strcmp(arg, "-allow-neg-keys") == 0) { | |
1107 | g_allow_neg_field_keys = true; | |
1108 | } else if (strcmp(arg, "-allow-64bit-consts") == 0) { | |
1109 | g_allow_64bit_consts = true; | |
1110 | } else if (strcmp(arg, "-gen") == 0) { | |
1111 | arg = argv[++i]; | |
1112 | if (arg == NULL) { | |
1113 | fprintf(stderr, "Missing generator specification\n"); | |
1114 | usage(); | |
1115 | } | |
1116 | generator_strings.emplace_back(arg); | |
1117 | } else if (strcmp(arg, "-I") == 0) { | |
1118 | // An argument of "-I\ asdf" is invalid and has unknown results | |
1119 | arg = argv[++i]; | |
1120 | ||
1121 | if (arg == NULL) { | |
1122 | fprintf(stderr, "Missing Include directory\n"); | |
1123 | usage(); | |
1124 | } | |
1125 | g_incl_searchpath.emplace_back(arg); | |
1126 | } else if ((strcmp(arg, "-o") == 0) || (strcmp(arg, "-out") == 0)) { | |
1127 | out_path_is_absolute = (strcmp(arg, "-out") == 0) ? true : false; | |
1128 | arg = argv[++i]; | |
1129 | if (arg == NULL) { | |
1130 | fprintf(stderr, "-o: missing output directory\n"); | |
1131 | usage(); | |
1132 | } | |
1133 | out_path = arg; | |
1134 | ||
1135 | #ifdef _WIN32 | |
1136 | // strip out trailing \ on Windows | |
1137 | std::string::size_type last = out_path.length() - 1; | |
1138 | if (out_path[last] == '\\') { | |
1139 | out_path.erase(last); | |
1140 | } | |
1141 | #endif | |
1142 | if (!check_is_directory(out_path.c_str())) | |
1143 | return -1; | |
1144 | } else if (strcmp(arg, "-audit") == 0) { | |
1145 | g_audit = true; | |
1146 | arg = argv[++i]; | |
1147 | if (arg == NULL) { | |
1148 | fprintf(stderr, "Missing old thrift file name for audit operation\n"); | |
1149 | usage(); | |
1150 | } | |
1151 | char old_thrift_file_rp[THRIFT_PATH_MAX]; | |
1152 | ||
1153 | // cppcheck-suppress uninitvar | |
1154 | if (saferealpath(arg, old_thrift_file_rp) == NULL) { | |
1155 | failure("Could not open input file with realpath: %s", arg); | |
1156 | } | |
1157 | old_input_file = string(old_thrift_file_rp); | |
1158 | } else if (strcmp(arg, "-audit-nofatal") == 0) { | |
1159 | g_audit_fatal = false; | |
1160 | } else if (strcmp(arg, "-Iold") == 0) { | |
1161 | arg = argv[++i]; | |
1162 | if (arg == NULL) { | |
1163 | fprintf(stderr, "Missing Include directory for old thrift file\n"); | |
1164 | usage(); | |
1165 | } | |
1166 | old_thrift_include_path = string(arg); | |
1167 | } else if (strcmp(arg, "-Inew") == 0) { | |
1168 | arg = argv[++i]; | |
1169 | if (arg == NULL) { | |
1170 | fprintf(stderr, "Missing Include directory for new thrift file\n"); | |
1171 | usage(); | |
1172 | } | |
1173 | new_thrift_include_path = string(arg); | |
1174 | } else { | |
1175 | fprintf(stderr, "Unrecognized option: %s\n", arg); | |
1176 | usage(); | |
1177 | } | |
1178 | ||
1179 | // Tokenize more | |
1180 | arg = strtok(NULL, " "); | |
1181 | } | |
1182 | } | |
1183 | ||
1184 | // display help | |
1185 | if ((strcmp(argv[argc - 1], "-help") == 0) || (strcmp(argv[argc - 1], "--help") == 0)) { | |
1186 | help(); | |
1187 | } | |
1188 | ||
1189 | // if you're asking for version, you have a right not to pass a file | |
1190 | if ((strcmp(argv[argc - 1], "-version") == 0) || (strcmp(argv[argc - 1], "--version") == 0)) { | |
1191 | version(); | |
1192 | exit(0); | |
1193 | } | |
1194 | ||
1195 | // Initialize global types | |
1196 | initGlobals(); | |
1197 | ||
1198 | if (g_audit) { | |
1199 | // Audit operation | |
1200 | ||
1201 | if (old_input_file.empty()) { | |
1202 | fprintf(stderr, "Missing file name of old thrift file for audit\n"); | |
1203 | usage(); | |
1204 | } | |
1205 | ||
1206 | char new_thrift_file_rp[THRIFT_PATH_MAX]; | |
1207 | if (argv[i] == NULL) { | |
1208 | fprintf(stderr, "Missing file name of new thrift file for audit\n"); | |
1209 | usage(); | |
1210 | } | |
1211 | // cppcheck-suppress uninitvar | |
1212 | if (saferealpath(argv[i], new_thrift_file_rp) == NULL) { | |
1213 | failure("Could not open input file with realpath: %s", argv[i]); | |
1214 | } | |
1215 | string new_input_file(new_thrift_file_rp); | |
1216 | ||
1217 | t_program new_program(new_input_file); | |
1218 | t_program old_program(old_input_file); | |
1219 | ||
1220 | audit(&new_program, &old_program, new_thrift_include_path, old_thrift_include_path); | |
1221 | ||
1222 | } else { | |
1223 | // Generate options | |
1224 | ||
1225 | // You gotta generate something! | |
1226 | if (generator_strings.empty()) { | |
1227 | fprintf(stderr, "No output language(s) specified\n"); | |
1228 | usage(); | |
1229 | } | |
1230 | ||
1231 | // Real-pathify it | |
1232 | char rp[THRIFT_PATH_MAX]; | |
1233 | if (argv[i] == NULL) { | |
1234 | fprintf(stderr, "Missing file name\n"); | |
1235 | usage(); | |
1236 | } | |
1237 | // cppcheck-suppress uninitvar | |
1238 | if (saferealpath(argv[i], rp) == NULL) { | |
1239 | failure("Could not open input file with realpath: %s", argv[i]); | |
1240 | } | |
1241 | string input_file(rp); | |
1242 | ||
1243 | // Instance of the global parse tree | |
1244 | t_program* program = new t_program(input_file); | |
1245 | if (out_path.size()) { | |
1246 | program->set_out_path(out_path, out_path_is_absolute); | |
1247 | } | |
1248 | ||
1249 | // Compute the cpp include prefix. | |
1250 | // infer this from the filename passed in | |
1251 | string input_filename = argv[i]; | |
1252 | string include_prefix; | |
1253 | ||
1254 | string::size_type last_slash = string::npos; | |
1255 | if ((last_slash = input_filename.rfind("/")) != string::npos) { | |
1256 | include_prefix = input_filename.substr(0, last_slash); | |
1257 | } | |
1258 | ||
1259 | program->set_include_prefix(include_prefix); | |
1260 | ||
1261 | // Parse it! | |
1262 | parse(program, NULL); | |
1263 | ||
1264 | // The current path is not really relevant when we are doing generation. | |
1265 | // Reset the variable to make warning messages clearer. | |
1266 | g_curpath = "generation"; | |
1267 | // Reset yylineno for the heck of it. Use 1 instead of 0 because | |
1268 | // That is what shows up during argument parsing. | |
1269 | yylineno = 1; | |
1270 | ||
1271 | // Generate it! | |
1272 | generate(program, generator_strings); | |
1273 | delete program; | |
1274 | } | |
1275 | ||
1276 | // Clean up. Who am I kidding... this program probably orphans heap memory | |
1277 | // all over the place, but who cares because it is about to exit and it is | |
1278 | // all referenced and used by this wacky parse tree up until now anyways. | |
1279 | clearGlobals(); | |
1280 | ||
1281 | // Finished | |
1282 | if (g_return_failure && g_audit_fatal) { | |
1283 | exit(2); | |
1284 | } | |
1285 | if (g_generator_failure) { | |
1286 | exit(3); | |
1287 | } | |
1288 | // Finished | |
1289 | return 0; | |
1290 | } |