diff --git a/include/flatbuffers/idl.h b/include/flatbuffers/idl.h index c84999a5485..94b35beb834 100644 --- a/include/flatbuffers/idl.h +++ b/include/flatbuffers/idl.h @@ -787,6 +787,10 @@ struct IDLOptions { /******************************* Python gRPC ********************************/ bool grpc_python_typed_handlers; + // If set, when a float value is received for an integer field, set the value + // to 0 to avoid precision loss. + bool zero_on_float_to_int; + IDLOptions() : gen_jvmstatic(false), use_flexbuffers(false), @@ -861,7 +865,8 @@ struct IDLOptions { set_empty_vectors_to_null(true), grpc_filename_suffix(".fb"), grpc_use_system_headers(true), - grpc_python_typed_handlers(false) {} + grpc_python_typed_handlers(false), + zero_on_float_to_int(false) {} }; // This encapsulates where the parser is in the current source file. diff --git a/src/idl_parser.cpp b/src/idl_parser.cpp index d01e18ef761..8cbb6ebf2d0 100644 --- a/src/idl_parser.cpp +++ b/src/idl_parser.cpp @@ -948,11 +948,13 @@ CheckedError Parser::ParseField(StructDef &struct_def) { // For union fields, add a second auto-generated field to hold the type, // with a special suffix. - // To ensure compatibility with many codes that rely on the BASE_TYPE_UTYPE value to identify union type fields. + // To ensure compatibility with many codes that rely on the BASE_TYPE_UTYPE + // value to identify union type fields. Type union_type(type.enum_def->underlying_type); union_type.base_type = BASE_TYPE_UTYPE; - ECHECK(AddField(struct_def, name + UnionTypeFieldSuffix(),union_type, &typefield)); - + ECHECK(AddField(struct_def, name + UnionTypeFieldSuffix(), union_type, + &typefield)); + } else if (IsVector(type) && type.element == BASE_TYPE_UNION) { advanced_features_ |= reflection::AdvancedUnionFeatures; // Only cpp, js and ts supports the union vector feature so far. @@ -1590,7 +1592,7 @@ CheckedError Parser::ParseTable(const StructDef &struct_def, std::string *value, if (!struct_def.sortbysize || size == SizeOf(field_value.type.base_type)) { switch (field_value.type.base_type) { - // clang-format off +// clang-format off #define FLATBUFFERS_TD(ENUM, IDLTYPE, CTYPE, ...) \ case BASE_TYPE_ ## ENUM: \ builder_.Pad(field->padding); \ @@ -1733,7 +1735,7 @@ CheckedError Parser::ParseVector(const Type &vector_type, uoffset_t *ovalue, // start at the back, since we're building the data backwards. auto &val = field_stack_.back().first; switch (val.type.base_type) { - // clang-format off +// clang-format off #define FLATBUFFERS_TD(ENUM, IDLTYPE, CTYPE,...) \ case BASE_TYPE_ ## ENUM: \ if (IsStruct(val.type)) SerializeStruct(*val.type.struct_def, val); \ @@ -2189,6 +2191,21 @@ CheckedError Parser::ParseSingleValue(const std::string *name, Value &e, } else { // Try a float number. TRY_ECHECK(kTokenFloatConstant, IsFloat(in_type), BASE_TYPE_FLOAT); + + // Special handling for integer types that might come as floats from JSON + // This happens when large integers are parsed as doubles by JSON parsers + // Set the value to 0 to avoid precision issues + if (!match && token_ == kTokenFloatConstant && IsInteger(in_type) && + opts.zero_on_float_to_int) { + double float_val = std::stod(attribute_); + // For integers fields that receive float values, set to 0 + Warning("Float value " + std::to_string(float_val) + " received for <" + + std::string(TypeName(in_type)) + + "> field, setting to 0."); + attribute_ = "0"; + TRY_ECHECK(kTokenFloatConstant, IsInteger(in_type), BASE_TYPE_INT); + } + // Integer token can init any scalar (integer of float). FORCE_ECHECK(kTokenIntegerConstant, IsScalar(in_type), BASE_TYPE_INT); } @@ -2372,12 +2389,8 @@ template void EnumDef::ChangeEnumValue(EnumVal *ev, T new_value) { } namespace EnumHelper { -template struct EnumValType { - typedef int64_t type; -}; -template<> struct EnumValType { - typedef uint64_t type; -}; +template struct EnumValType { typedef int64_t type; }; +template<> struct EnumValType { typedef uint64_t type; }; } // namespace EnumHelper struct EnumValBuilder { @@ -2482,8 +2495,8 @@ CheckedError Parser::ParseEnum(const bool is_union, EnumDef **dest, EnumDef *enum_def; ECHECK(StartEnum(enum_name, is_union, &enum_def)); if (filename != nullptr && !opts.project_root.empty()) { - enum_def->declaration_file = - &GetPooledString(FilePath(opts.project_root, filename, opts.binary_schema_absolute_paths)); + enum_def->declaration_file = &GetPooledString(FilePath( + opts.project_root, filename, opts.binary_schema_absolute_paths)); } enum_def->doc_comment = enum_comment; if (!opts.proto_mode) { @@ -2511,14 +2524,15 @@ CheckedError Parser::ParseEnum(const bool is_union, EnumDef **dest, if (explicit_underlying_type) { // Specify the integer type underlying this enum. ECHECK(ParseType(enum_def->underlying_type)); - if (!IsInteger(enum_def->underlying_type.base_type) || IsBool(enum_def->underlying_type.base_type)) { - return Error("underlying " + std::string(is_union ? "union" : "enum") + "type must be integral"); + if (!IsInteger(enum_def->underlying_type.base_type) || + IsBool(enum_def->underlying_type.base_type)) { + return Error("underlying " + std::string(is_union ? "union" : "enum") + + "type must be integral"); } - + // Make this type refer back to the enum it was derived from. enum_def->underlying_type.enum_def = enum_def; } - } ECHECK(ParseMetaData(&enum_def->attributes)); const auto underlying_type = enum_def->underlying_type.base_type; @@ -2682,8 +2696,7 @@ bool Parser::SupportsOptionalScalars(const flatbuffers::IDLOptions &opts) { IDLOptions::kKotlin | IDLOptions::kKotlinKmp | IDLOptions::kCpp | IDLOptions::kJava | IDLOptions::kCSharp | IDLOptions::kTs | IDLOptions::kBinary | IDLOptions::kGo | IDLOptions::kPython | - IDLOptions::kJson | - IDLOptions::kNim; + IDLOptions::kJson | IDLOptions::kNim; unsigned long langs = opts.lang_to_generate; return (langs > 0 && langs < IDLOptions::kMAX) && !(langs & ~supported_langs); } @@ -2719,8 +2732,8 @@ bool Parser::Supports64BitOffsets() const { } bool Parser::SupportsUnionUnderlyingType() const { - return (opts.lang_to_generate & ~(IDLOptions::kCpp | IDLOptions::kTs | - IDLOptions::kBinary)) == 0; + return (opts.lang_to_generate & + ~(IDLOptions::kCpp | IDLOptions::kTs | IDLOptions::kBinary)) == 0; } Namespace *Parser::UniqueNamespace(Namespace *ns) { @@ -2761,8 +2774,8 @@ CheckedError Parser::ParseDecl(const char *filename) { struct_def->doc_comment = dc; struct_def->fixed = fixed; if (filename && !opts.project_root.empty()) { - struct_def->declaration_file = - &GetPooledString(FilePath(opts.project_root, filename, opts.binary_schema_absolute_paths)); + struct_def->declaration_file = &GetPooledString(FilePath( + opts.project_root, filename, opts.binary_schema_absolute_paths)); } ECHECK(ParseMetaData(&struct_def->attributes)); struct_def->sortbysize = @@ -2855,8 +2868,8 @@ CheckedError Parser::ParseService(const char *filename) { service_def.doc_comment = service_comment; service_def.defined_namespace = current_namespace_; if (filename != nullptr && !opts.project_root.empty()) { - service_def.declaration_file = - &GetPooledString(FilePath(opts.project_root, filename, opts.binary_schema_absolute_paths)); + service_def.declaration_file = &GetPooledString(FilePath( + opts.project_root, filename, opts.binary_schema_absolute_paths)); } if (services_.Add(current_namespace_->GetFullyQualifiedName(service_name), &service_def)) @@ -3937,12 +3950,12 @@ void Parser::Serialize() { std::vector> included_files; for (auto f = files_included_per_file_.begin(); f != files_included_per_file_.end(); f++) { - const auto filename__ = builder_.CreateSharedString(FilePath( opts.project_root, f->first, opts.binary_schema_absolute_paths)); for (auto i = f->second.begin(); i != f->second.end(); i++) { included_files.push_back(builder_.CreateSharedString( - FilePath(opts.project_root, i->filename, opts.binary_schema_absolute_paths))); + FilePath(opts.project_root, i->filename, + opts.binary_schema_absolute_paths))); } const auto included_files__ = builder_.CreateVector(included_files); included_files.clear(); @@ -4456,8 +4469,11 @@ std::string Parser::ConformTo(const Parser &base) { } } // Check underlying type changes - if (enum_def_base->underlying_type.base_type != enum_def.underlying_type.base_type) { - return "underlying type differ for " + std::string(enum_def.is_union ? "union: " : "enum: ") + qualified_name; + if (enum_def_base->underlying_type.base_type != + enum_def.underlying_type.base_type) { + return "underlying type differ for " + + std::string(enum_def.is_union ? "union: " : "enum: ") + + qualified_name; } } return ""; diff --git a/tests/parser_test.cpp b/tests/parser_test.cpp index 33b1d6e1e6d..4fee76b627a 100644 --- a/tests/parser_test.cpp +++ b/tests/parser_test.cpp @@ -928,5 +928,20 @@ void FieldIdentifierTest() { #endif } +void ZeroOnFloatToIntTest() { + flatbuffers::IDLOptions opts; + { + opts.zero_on_float_to_int = true; + flatbuffers::Parser parser(opts); + TEST_EQ(true, parser.Parse("table T{ i: int = 1.0; }")); + } + + { + opts.zero_on_float_to_int = false; + flatbuffers::Parser parser(opts); + TEST_EQ(false, parser.Parse("table T{ i: int = 1.0; }")); + } +} + } // namespace tests } // namespace flatbuffers diff --git a/tests/parser_test.h b/tests/parser_test.h index d7908491298..03f904ec8ef 100644 --- a/tests/parser_test.h +++ b/tests/parser_test.h @@ -26,6 +26,7 @@ void ValidSameNameDifferentNamespaceTest(); void WarningsAsErrorsTest(); void StringVectorDefaultsTest(); void FieldIdentifierTest(); +void ZeroOnFloatToIntTest(); } // namespace tests } // namespace flatbuffers diff --git a/tests/test.cpp b/tests/test.cpp index a374da57518..d3c268e91a7 100644 --- a/tests/test.cpp +++ b/tests/test.cpp @@ -1729,6 +1729,7 @@ int FlatBufferTests(const std::string &tests_data_path) { EmbeddedSchemaAccess(); Offset64Tests(); UnionUnderlyingTypeTest(); + ZeroOnFloatToIntTest(); return 0; } } // namespace