flatcc.c (16870B)
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 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 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 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. */ 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 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 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 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 */ 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 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 goto done; 349 } 350 } else { 351 checkmem((path = fb_copy_path(filename))); 352 } 353 } 354 for (i = 0; !buf && i < P->opts.inpath_count; ++i) { 355 inpath = P->opts.inpaths[i]; 356 inpath_len = strlen(inpath); 357 checkmem((path = fb_create_join_path_n(inpath, inpath_len, filename, filename_len, "", 1))); 358 if (!(buf = fb_read_file(path, P->opts.max_schema_size, &size))) { 359 free(path); 360 path = 0; 361 if (size > P->opts.max_schema_size && P->opts.max_schema_size > 0) { 362 fb_print_error(P, "input exceeds maximum allowed size\n"); 363 goto done; 364 } 365 } 366 } 367 if (!buf && !is_root) { 368 inpath = P->referer_path; 369 inpath_len = fb_find_basename(inpath, strlen(inpath)); 370 checkmem((path = fb_create_join_path_n(inpath, inpath_len, filename, filename_len, "", 1))); 371 if (!(buf = fb_read_file(path, P->opts.max_schema_size, &size))) { 372 free(path); 373 path = 0; 374 if (size > P->opts.max_schema_size && P->opts.max_schema_size > 0) { 375 fb_print_error(P, "input exceeds maximum allowed size\n"); 376 goto done; 377 } 378 } 379 } 380 if (!buf) { 381 fb_print_error(P, "error reading included schema file: %s\n", filename); 382 goto done; 383 } 384 P->schema.root_schema->total_source_size += size; 385 P->path = path; 386 /* Parser owns path. */ 387 path = 0; 388 /* 389 * Even if we do not have the recursive option set, we still 390 * need to parse all include files to make sense of the current 391 * file. 392 */ 393 if (!fb_parse(P, buf, size, 1)) { 394 /* Parser owns buffer. */ 395 buf = 0; 396 inc = P->schema.includes; 397 while (inc) { 398 checkmem((include_file = fb_copy_path_n(inc->name.s.s, (size_t)inc->name.s.len))); 399 if (__parse_include_file(P, include_file)) { 400 goto done; 401 } 402 free(include_file); 403 include_file = 0; 404 inc = inc->link; 405 } 406 /* Add self to set of visible schema. */ 407 ptr_set_insert_item(&P->schema.visible_schema, &P->schema, ht_keep); 408 if (fb_build_schema(P)) { 409 goto done; 410 } 411 /* 412 * We choose to only generate optional .depends files for root level 413 * files. These will contain all nested files regardless of 414 * recursive file generation flags. 415 */ 416 if (P->opts.gen_dep && is_root) { 417 if (__flatcc_gen_depends_file(P)) { 418 goto done; 419 } 420 } 421 ret = 0; 422 } 423 424 done: 425 /* Parser owns buffer so don't free it here. */ 426 checkfree(path); 427 checkfree(include_file); 428 return ret; 429 } 430 431 #if FLATCC_REFLECTION 432 int flatcc_generate_binary_schema_to_buffer(flatcc_context_t ctx, void *buf, size_t bufsiz) 433 { 434 fb_parser_t *P = ctx; 435 436 if (fb_codegen_bfbs_to_buffer(&P->opts, &P->schema, buf, &bufsiz)) { 437 return (int)bufsiz; 438 } 439 return -1; 440 } 441 442 void *flatcc_generate_binary_schema(flatcc_context_t ctx, size_t *size) 443 { 444 fb_parser_t *P = ctx; 445 446 return fb_codegen_bfbs_alloc_buffer(&P->opts, &P->schema, size); 447 } 448 #endif 449 450 int flatcc_generate_files(flatcc_context_t ctx) 451 { 452 fb_parser_t *P = ctx, *P_leaf; 453 fb_output_t *out, output; 454 int ret = 0; 455 out = &output; 456 457 if (!P || P->failed) { 458 return -1; 459 } 460 P_leaf = 0; 461 while (P) { 462 P->inverse_dependencies = P_leaf; 463 P_leaf = P; 464 P = P->dependencies; 465 } 466 P = ctx; 467 #if FLATCC_REFLECTION 468 if (P->opts.bgen_bfbs) { 469 if (fb_codegen_bfbs_to_file(&P->opts, &P->schema)) { 470 return -1; 471 } 472 } 473 #endif 474 475 if (fb_init_output_c(out, &P->opts)) { 476 return -1; 477 } 478 /* This does not require a parse first. */ 479 if (!P->opts.gen_append && (ret = fb_codegen_common_c(out))) { 480 goto done; 481 } 482 /* If no file parsed - just common files if at all. */ 483 if (!P->has_schema) { 484 goto done; 485 } 486 if (!P->opts.cgen_recursive) { 487 ret = fb_codegen_c(out, &P->schema); 488 goto done; 489 } 490 /* Make sure stdout and outfile output is generated in the right order. */ 491 P = P_leaf; 492 while (!ret && P) { 493 ret = P->failed || fb_codegen_c(out, &P->schema); 494 P = P->inverse_dependencies; 495 } 496 done: 497 fb_end_output_c(out); 498 return ret; 499 } 500 501 void flatcc_destroy_context(flatcc_context_t ctx) 502 { 503 fb_parser_t *P = ctx, *dep = 0; 504 505 while (P) { 506 dep = P->dependencies; 507 fb_clear_parser(P); 508 free(P); 509 P = dep; 510 } 511 }