Skip to content

Commit f70461c

Browse files
committed
parser: Add zero_on_float_to_int logic and improve json parsing.
1 parent 31beb0f commit f70461c

File tree

5 files changed

+68
-30
lines changed

5 files changed

+68
-30
lines changed

include/flatbuffers/idl.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -787,6 +787,10 @@ struct IDLOptions {
787787
/******************************* Python gRPC ********************************/
788788
bool grpc_python_typed_handlers;
789789

790+
// If set, when a float value is received for an integer field, set the value
791+
// to 0 to avoid precision loss.
792+
bool zero_on_float_to_int;
793+
790794
IDLOptions()
791795
: gen_jvmstatic(false),
792796
use_flexbuffers(false),
@@ -861,7 +865,8 @@ struct IDLOptions {
861865
set_empty_vectors_to_null(true),
862866
grpc_filename_suffix(".fb"),
863867
grpc_use_system_headers(true),
864-
grpc_python_typed_handlers(false) {}
868+
grpc_python_typed_handlers(false),
869+
zero_on_float_to_int(false) {}
865870
};
866871

867872
// This encapsulates where the parser is in the current source file.

src/idl_parser.cpp

Lines changed: 45 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -948,11 +948,13 @@ CheckedError Parser::ParseField(StructDef &struct_def) {
948948
// For union fields, add a second auto-generated field to hold the type,
949949
// with a special suffix.
950950

951-
// To ensure compatibility with many codes that rely on the BASE_TYPE_UTYPE value to identify union type fields.
951+
// To ensure compatibility with many codes that rely on the BASE_TYPE_UTYPE
952+
// value to identify union type fields.
952953
Type union_type(type.enum_def->underlying_type);
953954
union_type.base_type = BASE_TYPE_UTYPE;
954-
ECHECK(AddField(struct_def, name + UnionTypeFieldSuffix(),union_type, &typefield));
955-
955+
ECHECK(AddField(struct_def, name + UnionTypeFieldSuffix(), union_type,
956+
&typefield));
957+
956958
} else if (IsVector(type) && type.element == BASE_TYPE_UNION) {
957959
advanced_features_ |= reflection::AdvancedUnionFeatures;
958960
// 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,
15901592
if (!struct_def.sortbysize ||
15911593
size == SizeOf(field_value.type.base_type)) {
15921594
switch (field_value.type.base_type) {
1593-
// clang-format off
1595+
// clang-format off
15941596
#define FLATBUFFERS_TD(ENUM, IDLTYPE, CTYPE, ...) \
15951597
case BASE_TYPE_ ## ENUM: \
15961598
builder_.Pad(field->padding); \
@@ -1733,7 +1735,7 @@ CheckedError Parser::ParseVector(const Type &vector_type, uoffset_t *ovalue,
17331735
// start at the back, since we're building the data backwards.
17341736
auto &val = field_stack_.back().first;
17351737
switch (val.type.base_type) {
1736-
// clang-format off
1738+
// clang-format off
17371739
#define FLATBUFFERS_TD(ENUM, IDLTYPE, CTYPE,...) \
17381740
case BASE_TYPE_ ## ENUM: \
17391741
if (IsStruct(val.type)) SerializeStruct(*val.type.struct_def, val); \
@@ -2189,6 +2191,21 @@ CheckedError Parser::ParseSingleValue(const std::string *name, Value &e,
21892191
} else {
21902192
// Try a float number.
21912193
TRY_ECHECK(kTokenFloatConstant, IsFloat(in_type), BASE_TYPE_FLOAT);
2194+
2195+
// Special handling for integer types that might come as floats from JSON
2196+
// This happens when large integers are parsed as doubles by JSON parsers
2197+
// Set the value to 0 to avoid precision issues
2198+
if (!match && token_ == kTokenFloatConstant && IsInteger(in_type) &&
2199+
opts.zero_on_float_to_int) {
2200+
double float_val = std::stod(attribute_);
2201+
// For integers fields that receive float values, set to 0
2202+
Warning("Float value " + std::to_string(float_val) + " received for <" +
2203+
std::string(TypeName(in_type)) +
2204+
"> field, setting to 0.");
2205+
attribute_ = "0";
2206+
TRY_ECHECK(kTokenFloatConstant, IsInteger(in_type), BASE_TYPE_INT);
2207+
}
2208+
21922209
// Integer token can init any scalar (integer of float).
21932210
FORCE_ECHECK(kTokenIntegerConstant, IsScalar(in_type), BASE_TYPE_INT);
21942211
}
@@ -2372,12 +2389,8 @@ template<typename T> void EnumDef::ChangeEnumValue(EnumVal *ev, T new_value) {
23722389
}
23732390

23742391
namespace EnumHelper {
2375-
template<BaseType E> struct EnumValType {
2376-
typedef int64_t type;
2377-
};
2378-
template<> struct EnumValType<BASE_TYPE_ULONG> {
2379-
typedef uint64_t type;
2380-
};
2392+
template<BaseType E> struct EnumValType { typedef int64_t type; };
2393+
template<> struct EnumValType<BASE_TYPE_ULONG> { typedef uint64_t type; };
23812394
} // namespace EnumHelper
23822395

23832396
struct EnumValBuilder {
@@ -2482,8 +2495,8 @@ CheckedError Parser::ParseEnum(const bool is_union, EnumDef **dest,
24822495
EnumDef *enum_def;
24832496
ECHECK(StartEnum(enum_name, is_union, &enum_def));
24842497
if (filename != nullptr && !opts.project_root.empty()) {
2485-
enum_def->declaration_file =
2486-
&GetPooledString(FilePath(opts.project_root, filename, opts.binary_schema_absolute_paths));
2498+
enum_def->declaration_file = &GetPooledString(FilePath(
2499+
opts.project_root, filename, opts.binary_schema_absolute_paths));
24872500
}
24882501
enum_def->doc_comment = enum_comment;
24892502
if (!opts.proto_mode) {
@@ -2511,14 +2524,15 @@ CheckedError Parser::ParseEnum(const bool is_union, EnumDef **dest,
25112524
if (explicit_underlying_type) {
25122525
// Specify the integer type underlying this enum.
25132526
ECHECK(ParseType(enum_def->underlying_type));
2514-
if (!IsInteger(enum_def->underlying_type.base_type) || IsBool(enum_def->underlying_type.base_type)) {
2515-
return Error("underlying " + std::string(is_union ? "union" : "enum") + "type must be integral");
2527+
if (!IsInteger(enum_def->underlying_type.base_type) ||
2528+
IsBool(enum_def->underlying_type.base_type)) {
2529+
return Error("underlying " + std::string(is_union ? "union" : "enum") +
2530+
"type must be integral");
25162531
}
2517-
2532+
25182533
// Make this type refer back to the enum it was derived from.
25192534
enum_def->underlying_type.enum_def = enum_def;
25202535
}
2521-
25222536
}
25232537
ECHECK(ParseMetaData(&enum_def->attributes));
25242538
const auto underlying_type = enum_def->underlying_type.base_type;
@@ -2682,8 +2696,7 @@ bool Parser::SupportsOptionalScalars(const flatbuffers::IDLOptions &opts) {
26822696
IDLOptions::kKotlin | IDLOptions::kKotlinKmp | IDLOptions::kCpp |
26832697
IDLOptions::kJava | IDLOptions::kCSharp | IDLOptions::kTs |
26842698
IDLOptions::kBinary | IDLOptions::kGo | IDLOptions::kPython |
2685-
IDLOptions::kJson |
2686-
IDLOptions::kNim;
2699+
IDLOptions::kJson | IDLOptions::kNim;
26872700
unsigned long langs = opts.lang_to_generate;
26882701
return (langs > 0 && langs < IDLOptions::kMAX) && !(langs & ~supported_langs);
26892702
}
@@ -2719,8 +2732,8 @@ bool Parser::Supports64BitOffsets() const {
27192732
}
27202733

27212734
bool Parser::SupportsUnionUnderlyingType() const {
2722-
return (opts.lang_to_generate & ~(IDLOptions::kCpp | IDLOptions::kTs |
2723-
IDLOptions::kBinary)) == 0;
2735+
return (opts.lang_to_generate &
2736+
~(IDLOptions::kCpp | IDLOptions::kTs | IDLOptions::kBinary)) == 0;
27242737
}
27252738

27262739
Namespace *Parser::UniqueNamespace(Namespace *ns) {
@@ -2761,8 +2774,8 @@ CheckedError Parser::ParseDecl(const char *filename) {
27612774
struct_def->doc_comment = dc;
27622775
struct_def->fixed = fixed;
27632776
if (filename && !opts.project_root.empty()) {
2764-
struct_def->declaration_file =
2765-
&GetPooledString(FilePath(opts.project_root, filename, opts.binary_schema_absolute_paths));
2777+
struct_def->declaration_file = &GetPooledString(FilePath(
2778+
opts.project_root, filename, opts.binary_schema_absolute_paths));
27662779
}
27672780
ECHECK(ParseMetaData(&struct_def->attributes));
27682781
struct_def->sortbysize =
@@ -2855,8 +2868,8 @@ CheckedError Parser::ParseService(const char *filename) {
28552868
service_def.doc_comment = service_comment;
28562869
service_def.defined_namespace = current_namespace_;
28572870
if (filename != nullptr && !opts.project_root.empty()) {
2858-
service_def.declaration_file =
2859-
&GetPooledString(FilePath(opts.project_root, filename, opts.binary_schema_absolute_paths));
2871+
service_def.declaration_file = &GetPooledString(FilePath(
2872+
opts.project_root, filename, opts.binary_schema_absolute_paths));
28602873
}
28612874
if (services_.Add(current_namespace_->GetFullyQualifiedName(service_name),
28622875
&service_def))
@@ -3937,12 +3950,12 @@ void Parser::Serialize() {
39373950
std::vector<Offset<flatbuffers::String>> included_files;
39383951
for (auto f = files_included_per_file_.begin();
39393952
f != files_included_per_file_.end(); f++) {
3940-
39413953
const auto filename__ = builder_.CreateSharedString(FilePath(
39423954
opts.project_root, f->first, opts.binary_schema_absolute_paths));
39433955
for (auto i = f->second.begin(); i != f->second.end(); i++) {
39443956
included_files.push_back(builder_.CreateSharedString(
3945-
FilePath(opts.project_root, i->filename, opts.binary_schema_absolute_paths)));
3957+
FilePath(opts.project_root, i->filename,
3958+
opts.binary_schema_absolute_paths)));
39463959
}
39473960
const auto included_files__ = builder_.CreateVector(included_files);
39483961
included_files.clear();
@@ -4456,8 +4469,11 @@ std::string Parser::ConformTo(const Parser &base) {
44564469
}
44574470
}
44584471
// Check underlying type changes
4459-
if (enum_def_base->underlying_type.base_type != enum_def.underlying_type.base_type) {
4460-
return "underlying type differ for " + std::string(enum_def.is_union ? "union: " : "enum: ") + qualified_name;
4472+
if (enum_def_base->underlying_type.base_type !=
4473+
enum_def.underlying_type.base_type) {
4474+
return "underlying type differ for " +
4475+
std::string(enum_def.is_union ? "union: " : "enum: ") +
4476+
qualified_name;
44614477
}
44624478
}
44634479
return "";

tests/parser_test.cpp

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -928,5 +928,20 @@ void FieldIdentifierTest() {
928928
#endif
929929
}
930930

931+
void ZeroOnFloatToIntTest() {
932+
flatbuffers::IDLOptions opts;
933+
{
934+
opts.zero_on_float_to_int = true;
935+
flatbuffers::Parser parser(opts);
936+
TEST_EQ(true, parser.Parse("table T{ i: int = 1.0; }"));
937+
}
938+
939+
{
940+
opts.zero_on_float_to_int = false;
941+
flatbuffers::Parser parser(opts);
942+
TEST_EQ(false, parser.Parse("table T{ i: int = 1.0; }"));
943+
}
944+
}
945+
931946
} // namespace tests
932947
} // namespace flatbuffers

tests/parser_test.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ void ValidSameNameDifferentNamespaceTest();
2626
void WarningsAsErrorsTest();
2727
void StringVectorDefaultsTest();
2828
void FieldIdentifierTest();
29+
void ZeroOnFloatToIntTest();
2930

3031
} // namespace tests
3132
} // namespace flatbuffers

tests/test.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1729,6 +1729,7 @@ int FlatBufferTests(const std::string &tests_data_path) {
17291729
EmbeddedSchemaAccess();
17301730
Offset64Tests();
17311731
UnionUnderlyingTypeTest();
1732+
ZeroOnFloatToIntTest();
17321733
return 0;
17331734
}
17341735
} // namespace

0 commit comments

Comments
 (0)