disallow circular struct references (#8851)
* detect and fail for circular struct dependencies
* pr comments
* pr comment
diff --git a/include/flatbuffers/idl.h b/include/flatbuffers/idl.h
index 06f93f3..9e58d4b 100644
--- a/include/flatbuffers/idl.h
+++ b/include/flatbuffers/idl.h
@@ -395,13 +395,20 @@
};
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 @@
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<std::string> original_location;
std::vector<voffset_t> reserved_ids;
};
@@ -1101,6 +1110,8 @@
// others includes.
std::vector<IncludedFile> GetIncludedFiles() const;
+ bool HasCircularStructDependency();
+
private:
class ParseDepthGuard;
diff --git a/src/flatc.cpp b/src/flatc.cpp
index 6a57085..ee34044 100644
--- a/src/flatc.cpp
+++ b/src/flatc.cpp
@@ -927,6 +927,9 @@
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 0aaccbb..569c767 100644
--- a/src/idl_parser.cpp
+++ b/src/idl_parser.cpp
@@ -2755,6 +2755,42 @@
return {it->second.cbegin(), it->second.cend()};
}
+bool Parser::HasCircularStructDependency() {
+ std::function<bool(StructDef*)> 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 ebd5a79..f915208 100644
--- a/tests/flatc/flatc_schema_tests.py
+++ b/tests/flatc/flatc_schema_tests.py
@@ -71,3 +71,12 @@
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