1 #include <assert.h>
2 #include "config.h"
3 #include "parser.h"
4 #include "semantics.h"
5 #include "fileio.h"
6 #include "codegen.h"
7 #include "flatcc/flatcc.h"
8 
9 #define checkfree(s) if (s) { free(s); s = 0; }
10 
flatcc_init_options(flatcc_options_t * opts)11 void flatcc_init_options(flatcc_options_t *opts)
12 {
13     memset(opts, 0, sizeof(*opts));
14 
15     opts->max_schema_size = FLATCC_MAX_SCHEMA_SIZE;
16     opts->max_include_depth = FLATCC_MAX_INCLUDE_DEPTH;
17     opts->max_include_count = FLATCC_MAX_INCLUDE_COUNT;
18     opts->allow_boolean_conversion = FLATCC_ALLOW_BOOLEAN_CONVERSION;
19     opts->allow_enum_key = FLATCC_ALLOW_ENUM_KEY;
20     opts->allow_enum_struct_field = FLATCC_ALLOW_ENUM_STRUCT_FIELD;
21     opts->allow_multiple_key_fields = FLATCC_ALLOW_MULTIPLE_KEY_FIELDS;
22     opts->allow_primary_key = FLATCC_ALLOW_PRIMARY_KEY;
23     opts->allow_scan_for_all_fields = FLATCC_ALLOW_SCAN_FOR_ALL_FIELDS;
24     opts->allow_string_key = FLATCC_ALLOW_STRING_KEY;
25     opts->allow_struct_field_deprecate = FLATCC_ALLOW_STRUCT_FIELD_DEPRECATE;
26     opts->allow_struct_field_key = FLATCC_ALLOW_STRUCT_FIELD_KEY;
27     opts->allow_struct_root = FLATCC_ALLOW_STRUCT_ROOT;
28     opts->ascending_enum = FLATCC_ASCENDING_ENUM;
29     opts->hide_later_enum = FLATCC_HIDE_LATER_ENUM;
30     opts->hide_later_struct = FLATCC_HIDE_LATER_STRUCT;
31     opts->offset_size = FLATCC_OFFSET_SIZE;
32     opts->voffset_size = FLATCC_VOFFSET_SIZE;
33     opts->utype_size = FLATCC_UTYPE_SIZE;
34     opts->bool_size = FLATCC_BOOL_SIZE;
35 
36     opts->require_root_type = FLATCC_REQUIRE_ROOT_TYPE;
37     opts->strict_enum_init = FLATCC_STRICT_ENUM_INIT;
38     /*
39      * Index 0 is table elem count, and index 1 is table size
40      * so max count is reduced by 2, meaning field id's
41      * must be between 0 and vt_max_count - 1.
42      * Usually, the table is 16-bit, so FLATCC_VOFFSET_SIZE = 2.
43      * Strange expression to avoid shift overflow on 64 bit size.
44      */
45     opts->vt_max_count = ((1LL << (FLATCC_VOFFSET_SIZE * 8 - 1)) - 1) * 2;
46 
47     opts->default_schema_ext = FLATCC_DEFAULT_SCHEMA_EXT;
48     opts->default_bin_schema_ext = FLATCC_DEFAULT_BIN_SCHEMA_EXT;
49     opts->default_bin_ext = FLATCC_DEFAULT_BIN_EXT;
50 
51     opts->cgen_no_conflicts = FLATCC_CGEN_NO_CONFLICTS;
52 
53     opts->cgen_pad = FLATCC_CGEN_PAD;
54     opts->cgen_sort = FLATCC_CGEN_SORT;
55     opts->cgen_pragmas = FLATCC_CGEN_PRAGMAS;
56 
57     opts->cgen_common_reader = 0;
58     opts->cgen_common_builder = 0;
59     opts->cgen_reader = 0;
60     opts->cgen_builder = 0;
61     opts->cgen_json_parser = 0;
62     opts->cgen_spacing = FLATCC_CGEN_SPACING;
63 
64     opts->bgen_bfbs = FLATCC_BGEN_BFBS;
65     opts->bgen_qualify_names = FLATCC_BGEN_QUALIFY_NAMES;
66     opts->bgen_length_prefix = FLATCC_BGEN_LENGTH_PREFIX;
67 }
68 
flatcc_create_context(flatcc_options_t * opts,const char * name,flatcc_error_fun error_out,void * error_ctx)69 flatcc_context_t flatcc_create_context(flatcc_options_t *opts, const char *name,
70         flatcc_error_fun error_out, void *error_ctx)
71 {
72     fb_parser_t *P;
73 
74     if (!(P = malloc(sizeof(*P)))) {
75         return 0;
76     }
77     if (fb_init_parser(P, opts, name, error_out, error_ctx, 0)) {
78         free(P);
79         return 0;
80     }
81     return P;
82 }
83 
__flatcc_create_child_context(flatcc_options_t * opts,const char * name,fb_parser_t * P_parent)84 static flatcc_context_t __flatcc_create_child_context(flatcc_options_t *opts, const char *name,
85         fb_parser_t *P_parent)
86 {
87     fb_parser_t *P;
88 
89     if (!(P = malloc(sizeof(*P)))) {
90         return 0;
91     }
92     if (fb_init_parser(P, opts, name, P_parent->error_out, P_parent->error_ctx, P_parent->schema.root_schema)) {
93         free(P);
94         return 0;
95     }
96     return P;
97 }
98 
99 /* TODO: handle include files via some sort of buffer read callback
100  * and possible transfer file based parser to this logic. */
flatcc_parse_buffer(flatcc_context_t ctx,const char * buf,size_t buflen)101 int flatcc_parse_buffer(flatcc_context_t ctx, const char *buf, size_t buflen)
102 {
103     fb_parser_t *P = ctx;
104 
105     /* Currently includes cannot be handled by buffers, so they should done. */
106     P->opts.disable_includes = 1;
107     if ((size_t)buflen > P->opts.max_schema_size && P->opts.max_schema_size > 0) {
108         fb_print_error(P, "input exceeds maximum allowed size\n");
109         return -1;
110     }
111     /* Add self to set of visible schema. */
112     ptr_set_insert_item(&P->schema.visible_schema, &P->schema, ht_keep);
113     return fb_parse(P, buf, buflen, 0) || fb_build_schema(P) ? -1 : 0;
114 }
115 
visit_dep(void * context,void * ptr)116 static void visit_dep(void *context, void *ptr)
117 {
118     fb_schema_t *parent = context;
119     fb_schema_t *dep = ptr;
120 
121     ptr_set_insert_item(&parent->visible_schema, dep, ht_keep);
122 }
123 
add_visible_schema(fb_schema_t * parent,fb_schema_t * dep)124 static void add_visible_schema(fb_schema_t *parent, fb_schema_t *dep)
125 {
126     ptr_set_visit(&dep->visible_schema, visit_dep, parent);
127 }
128 
__parse_include_file(fb_parser_t * P_parent,const char * filename)129 static int __parse_include_file(fb_parser_t *P_parent, const char *filename)
130 {
131     flatcc_context_t *ctx = 0;
132     fb_parser_t *P = 0;
133     fb_root_schema_t *rs;
134     flatcc_options_t *opts = &P_parent->opts;
135     fb_schema_t *dep;
136 
137     rs = P_parent->schema.root_schema;
138     if (rs->include_depth >= opts->max_include_depth && opts->max_include_depth > 0) {
139         fb_print_error(P_parent, "include nesting level too deep\n");
140         return -1;
141     }
142     if (rs->include_count >= opts->max_include_count && opts->max_include_count > 0) {
143         fb_print_error(P_parent, "include count limit exceeded\n");
144         return -1;
145     }
146     if (!(ctx = __flatcc_create_child_context(opts, filename, P_parent))) {
147         return -1;
148     }
149     P = (fb_parser_t *)ctx;
150     /* Don't parse the same file twice, or any other file with same name. */
151     if ((dep = fb_schema_table_find_item(&rs->include_index, &P->schema))) {
152         add_visible_schema(&P_parent->schema, dep);
153         flatcc_destroy_context(ctx);
154         return 0;
155     }
156     P->dependencies = P_parent->dependencies;
157     P_parent->dependencies = P;
158     P->referer_path = P_parent->path;
159     /* Each parser has a root schema instance, but only the root parsers instance is used. */
160     rs->include_depth++;
161     rs->include_count++;
162     if (flatcc_parse_file(ctx, filename)) {
163         return -1;
164     }
165     add_visible_schema(&P_parent->schema, &P->schema);
166     return 0;
167 }
168 
169 /*
170  * The depends file format is a make rule:
171  *
172  * <outputfile> : <dep1-file> <dep2-file> ...
173  *
174  * like -MMD option for gcc/clang:
175  * lib.o.d generated with content:
176  *
177  * lib.o : header1.h header2.h
178  *
179  * We use a file name <basename>.depends for schema <basename>.fbs with content:
180  *
181  * <basename>_reader.h : <included-schema-1> ...
182  *
183  * The .d extension could mean the D language and we don't have sensible
184  * .o.d name because of multiple outputs, so .depends is better.
185  *
186  * (the above above is subject to the configuration of extensions).
187  *
188  * TODO:
189  * perhaps we should optionally add a dependency to the common reader
190  * and builder files when they are generated separately as they should in
191  * concurrent builds.
192  *
193  * TODO:
194  * 1. we should have a file for every output we produce (_builder.h * etc.)
195  * 2. reader might not even be in the output, e.g. verifier only.
196  * 3. multiple outputs doesn't work with ninja build 1.7.1, so just
197  *    use reader for now, and possible add an option for multiple
198  *    outputs later.
199  *
200  *  http://stackoverflow.com/questions/11855386/using-g-with-mmd-in-makefile-to-automatically-generate-dependencies
201  *  https://groups.google.com/forum/#!topic/ninja-build/j-2RfBIOd_8
202  *  https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485
203  *
204  *  Spaces in gnu make:
205  *  https://www.cmcrossroads.com/article/gnu-make-meets-file-names-spaces-them
206  *  See comments on gnu make handling of spaces.
207  *  http://clang.llvm.org/doxygen/DependencyFile_8cpp_source.html
208  */
__flatcc_gen_depends_file(fb_parser_t * P)209 static int __flatcc_gen_depends_file(fb_parser_t *P)
210 {
211     FILE *fp = 0;
212     const char *outpath, *basename;
213     const char *depfile, *deproot, *depext;
214     const char *targetfile, *targetsuffix, *targetroot;
215     char *path = 0, *deppath = 0, *tmppath = 0, *targetpath = 0;
216     int ret = -1;
217 
218     /*
219      * The dependencies list is only correct for root files as it is a
220      * linear list. To deal with children, we would have to filter via
221      * the visible schema hash table, but we don't really need that.
222      */
223     assert(P->referer_path == 0);
224 
225     outpath = P->opts.outpath ? P->opts.outpath : "";
226     basename = P->schema.basename;
227     targetfile = P->opts.gen_deptarget;
228 
229 
230     /* The following is mostly considering build tools generating
231      * a depfile as Ninja build would use it. It is a bit strict
232      * on path variations and currenlty doesn't accept multiple
233      * build products in a build rule (Ninja 1.7.1).
234      *
235      * Make depfile relative to cwd so the user can add output if
236      * needed, otherwise it is not possible, or difficult, to use a path given
237      * by a build tool, relative the cwd. If --depfile is not given,
238      * then -d is given or we would not be here. In that case we add an
239      * extension "<basename>.fbs.d" in the outpath.
240      *
241      * A general problem is that the outpath may be a build root dir or
242      * a current subdir for a custom build rule while the dep file
243      * content needs the same path every time, not just an equivalent
244      * path. For dependencies, we can rely on the input schema path.
245      * The input search paths may because confusion but we choose the
246      * discovered path relative to cwd consistently for each schema file
247      * encountered.
248      *
249      * The target file (<target>: <include1.fbs> <include2.fbs> ...)
250      * is tricky because it is not unique - but we can chose <schema>_reader.h
251      * or <schema>.bfbs prefixed with outpath. The user should choose an
252      * outpath relative to cwd or an absolute path depending on what the
253      * build system prefers. This may not be so easy in praxis, but what
254      * can we do?
255      *
256      * It is important to note the default target and the default
257      * depfile name is not just a convenience. Sometimes it is much
258      * simpler to use this version over an explicit path, sometimes
259      * perhaps not so much.
260      */
261 
262     if (P->opts.gen_depfile) {
263         depfile = P->opts.gen_depfile;
264         deproot = "";
265         depext = "";
266     } else {
267         depfile = basename;
268         deproot = outpath;
269         depext = FLATCC_DEFAULT_DEP_EXT;
270     }
271     if (targetfile) {
272         targetsuffix = "";
273         targetroot = "";
274     } else {
275         targetsuffix = P->opts.bgen_bfbs
276                 ? FLATCC_DEFAULT_BIN_SCHEMA_EXT
277                 : FLATCC_DEFAULT_DEP_TARGET_SUFFIX;
278         targetfile = basename;
279         targetroot = outpath;
280     }
281 
282     checkmem(path = fb_create_join_path(deproot, depfile, depext, 1));
283 
284     checkmem(tmppath = fb_create_join_path(targetroot, targetfile, targetsuffix, 1));
285     /* Handle spaces in dependency file. */
286     checkmem((targetpath = fb_create_make_path(tmppath)));
287     checkfree(tmppath);
288 
289     fp = fopen(path, "wb");
290     if (!fp) {
291         fb_print_error(P, "could not open dependency file for output: %s\n", path);
292         goto done;
293     }
294     fprintf(fp, "%s:", targetpath);
295 
296     /* Don't depend on root schema. */
297     P = P->dependencies;
298     while (P) {
299         checkmem((deppath = fb_create_make_path(P->path)));
300         fprintf(fp, " %s", deppath);
301         P = P->dependencies;
302         checkfree(deppath);
303     }
304     fprintf(fp, "\n");
305     ret = 0;
306 
307 done:
308     checkfree(path);
309     checkfree(tmppath);
310     checkfree(targetpath);
311     checkfree(deppath);
312     if (fp) {
313         fclose(fp);
314     }
315     return ret;
316 }
317 
flatcc_parse_file(flatcc_context_t ctx,const char * filename)318 int flatcc_parse_file(flatcc_context_t ctx, const char *filename)
319 {
320     fb_parser_t *P = ctx;
321     size_t inpath_len, filename_len;
322     char *buf, *path, *include_file;
323     const char *inpath;
324     size_t size;
325     fb_name_t *inc;
326     int i, ret, is_root;
327 
328     filename_len = strlen(filename);
329     /* Don't parse the same file twice, or any other file with same basename. */
330     if (fb_schema_table_insert_item(&P->schema.root_schema->include_index, &P->schema, ht_keep)) {
331         return 0;
332     }
333     buf = 0;
334     path = 0;
335     include_file = 0;
336     ret = -1;
337     is_root = !P->referer_path;
338 
339     /*
340      * For root files, read file relative to working dir first. For
341      * included files (`referer_path` set), first try include paths
342      * in order, then path relative to including file.
343      */
344     if (is_root) {
345         if (!(buf = fb_read_file(filename, P->opts.max_schema_size, &size))) {
346             if (size + P->schema.root_schema->total_source_size > P->opts.max_schema_size && P->opts.max_schema_size > 0) {
347                 fb_print_error(P, "input exceeds maximum allowed size\n");
348                 ret = -1;
349                 goto done;
350             }
351         } else {
352             checkmem((path = fb_copy_path(filename)));
353         }
354     }
355     for (i = 0; !buf && i < P->opts.inpath_count; ++i) {
356         inpath = P->opts.inpaths[i];
357         inpath_len = strlen(inpath);
358         checkmem((path = fb_create_join_path_n(inpath, inpath_len, filename, filename_len, "", 1)));
359         if (!(buf = fb_read_file(path, P->opts.max_schema_size, &size))) {
360             free(path);
361             path = 0;
362             if (size > P->opts.max_schema_size && P->opts.max_schema_size > 0) {
363                 fb_print_error(P, "input exceeds maximum allowed size\n");
364                 ret = -1;
365                 goto done;
366             }
367         }
368     }
369     if (!buf && !is_root) {
370         inpath = P->referer_path;
371         inpath_len = fb_find_basename(inpath, strlen(inpath));
372         checkmem((path = fb_create_join_path_n(inpath, inpath_len, filename, filename_len, "", 1)));
373         if (!(buf = fb_read_file(path, P->opts.max_schema_size, &size))) {
374             free(path);
375             path = 0;
376             if (size > P->opts.max_schema_size && P->opts.max_schema_size > 0) {
377                 fb_print_error(P, "input exceeds maximum allowed size\n");
378                 ret = -1;
379                 goto done;
380             }
381         }
382     }
383     if (!buf) {
384         fb_print_error(P, "error reading included schema file: %s\n", filename);
385         goto done;
386     }
387     P->schema.root_schema->total_source_size += size;
388     P->path = path;
389     /* Parser owns path. */
390     path = 0;
391     /*
392      * Even if we do not have the recursive option set, we still
393      * need to parse all include files to make sense of the current
394      * file.
395      */
396     if (!(ret = fb_parse(P, buf, size, 1))) {
397         /* Parser owns buffer. */
398         buf = 0;
399         inc = P->schema.includes;
400         while (inc) {
401             checkmem((include_file = fb_copy_path_n(inc->name.s.s, inc->name.s.len)));
402             if (__parse_include_file(P, include_file)) {
403                 goto done;
404             }
405             free(include_file);
406             include_file = 0;
407             inc = inc->link;
408         }
409         /* Add self to set of visible schema. */
410         ptr_set_insert_item(&P->schema.visible_schema, &P->schema, ht_keep);
411         if (fb_build_schema(P)) {
412             goto done;
413         }
414         /*
415         * We choose to only generate optional .depends files for root level
416         * files. These will contain all nested files regardless of
417         * recursive file generation flags.
418         */
419         if (P->opts.gen_dep && is_root) {
420             if (__flatcc_gen_depends_file(P)) {
421                 goto done;
422             }
423         }
424         ret = 0;
425     }
426 
427 done:
428     /* Parser owns buffer so don't free it here. */
429     checkfree(path);
430     checkfree(include_file);
431     return ret;
432 }
433 
434 #if FLATCC_REFLECTION
flatcc_generate_binary_schema_to_buffer(flatcc_context_t ctx,void * buf,size_t bufsiz)435 int flatcc_generate_binary_schema_to_buffer(flatcc_context_t ctx, void *buf, size_t bufsiz)
436 {
437     fb_parser_t *P = ctx;
438 
439     if (fb_codegen_bfbs_to_buffer(&P->opts, &P->schema, buf, &bufsiz)) {
440         return (int)bufsiz;
441     }
442     return -1;
443 }
444 
flatcc_generate_binary_schema(flatcc_context_t ctx,size_t * size)445 void *flatcc_generate_binary_schema(flatcc_context_t ctx, size_t *size)
446 {
447     fb_parser_t *P = ctx;
448 
449     return fb_codegen_bfbs_alloc_buffer(&P->opts, &P->schema, size);
450 }
451 #endif
452 
flatcc_generate_files(flatcc_context_t ctx)453 int flatcc_generate_files(flatcc_context_t ctx)
454 {
455     fb_parser_t *P = ctx, *P_leaf;
456     fb_output_t *out, output;
457     int ret = 0;
458     out = &output;
459 
460     if (!P || P->failed) {
461         return -1;
462     }
463     P_leaf = 0;
464     while (P) {
465         P->inverse_dependencies = P_leaf;
466         P_leaf = P;
467         P = P->dependencies;
468     }
469     P = ctx;
470 #if FLATCC_REFLECTION
471     if (P->opts.bgen_bfbs) {
472         if (fb_codegen_bfbs_to_file(&P->opts, &P->schema)) {
473             return -1;
474         }
475     }
476 #endif
477 
478     if (fb_init_output_c(out, &P->opts)) {
479         return -1;
480     }
481     /* This does not require a parse first. */
482     if (!P->opts.gen_append && (ret = fb_codegen_common_c(out))) {
483         goto done;
484     }
485     /* If no file parsed - just common files if at all. */
486     if (!P->has_schema) {
487         goto done;
488     }
489     if (!P->opts.cgen_recursive) {
490         ret = fb_codegen_c(out, &P->schema);
491         goto done;
492     }
493     /* Make sure stdout and outfile output is generated in the right order. */
494     P = P_leaf;
495     while (!ret && P) {
496         ret = P->failed || fb_codegen_c(out, &P->schema);
497         P = P->inverse_dependencies;
498     }
499 done:
500     fb_end_output_c(out);
501     return ret;
502 }
503 
flatcc_destroy_context(flatcc_context_t ctx)504 void flatcc_destroy_context(flatcc_context_t ctx)
505 {
506     fb_parser_t *P = ctx, *dep = 0;
507 
508     while (P) {
509         dep = P->dependencies;
510         fb_clear_parser(P);
511         free(P);
512         P = dep;
513     }
514 }
515