diff --git a/include/flatbuffers/idl.h b/include/flatbuffers/idl.h index 06f93f338..9e58d4be9 100644 --- a/include/flatbuffers/idl.h +++ b/include/flatbuffers/idl.h @@ -395,13 +395,20 @@ struct FieldDef : public Definition { }; struct StructDef : public Definition { + enum class CycleStatus { + NotChecked, + InProgress, + Checked, + }; + StructDef() : fixed(false), predecl(true), sortbysize(true), has_key(false), minalign(1), - bytesize(0) {} + bytesize(0), + cycle_status{CycleStatus::NotChecked} {} void PadLastField(size_t min_align) { auto padding = PaddingBytes(bytesize, min_align); @@ -423,6 +430,8 @@ struct StructDef : public Definition { size_t minalign; // What the whole object needs to be aligned to. size_t bytesize; // Size if fixed. + CycleStatus cycle_status; // used for determining if we have circular references + flatbuffers::unique_ptr original_location; std::vector reserved_ids; }; @@ -1101,6 +1110,8 @@ class Parser : public ParserState { // others includes. std::vector GetIncludedFiles() const; + bool HasCircularStructDependency(); + private: class ParseDepthGuard; diff --git a/src/flatc.cpp b/src/flatc.cpp index 6a5708574..ee34044fd 100644 --- a/src/flatc.cpp +++ b/src/flatc.cpp @@ -927,6 +927,9 @@ std::unique_ptr FlatCompiler::GenerateCode(const FlatCOptions& options, auto err = parser->ConformTo(conform_parser); if (!err.empty()) Error("schemas don\'t conform: " + err, false); } + if (parser->HasCircularStructDependency()) { + Error("schema has circular struct dependencies: " + filename, false); + } if (options.schema_binary || opts.binary_schema_gen_embed) { parser->Serialize(); } diff --git a/src/idl_parser.cpp b/src/idl_parser.cpp index 0aaccbb2c..569c76762 100644 --- a/src/idl_parser.cpp +++ b/src/idl_parser.cpp @@ -2755,6 +2755,42 @@ std::vector Parser::GetIncludedFiles() const { return {it->second.cbegin(), it->second.cend()}; } +bool Parser::HasCircularStructDependency() { + std::function visit = + [&](StructDef* struct_def) { + // Only consider fixed structs and structs we have yet to check + if (!struct_def->fixed || struct_def->cycle_status == StructDef::CycleStatus::Checked) { + return false; + } + + if (struct_def->cycle_status == StructDef::CycleStatus::InProgress) { + // cycle found + return true; + } + + struct_def->cycle_status = StructDef::CycleStatus::InProgress; + + for (const auto& field : struct_def->fields.vec) { + if (field->value.type.base_type == BASE_TYPE_STRUCT) { + if (visit(field->value.type.struct_def)) { + return true; // Cycle detected in recursion. + } + } + } + + struct_def->cycle_status = StructDef::CycleStatus::Checked; + return false; // No cycle detected. + }; + + for (const auto& struct_def : structs_.vec) { + if (visit(struct_def)) { + return true; // Cycle detected. + } + } + + return false; // No cycle detected. +} + bool Parser::SupportsOptionalScalars(const flatbuffers::IDLOptions& opts) { static FLATBUFFERS_CONSTEXPR unsigned long supported_langs = IDLOptions::kRust | IDLOptions::kSwift | IDLOptions::kLobster | diff --git a/tests/flatc/flatc_schema_tests.py b/tests/flatc/flatc_schema_tests.py index ebd5a7916..f91520874 100644 --- a/tests/flatc/flatc_schema_tests.py +++ b/tests/flatc/flatc_schema_tests.py @@ -71,3 +71,12 @@ class SchemaTests: schema_json["enums"][0]["values"][2]["attributes"][1]["value"] == "Value 3 (deprecated)" ) + + def CircularStructDependency(self): + try: + flatc(["-c", "circular_struct_dependency.fbs"]) + assert False, "Expected flatc to fail on circular struct dependency" + except subprocess.CalledProcessError: + pass + + flatc(["-c", "circular_table.fbs"]) \ No newline at end of file