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