diff --git a/src/data/dto/NavDto.cpp b/src/data/dto/NavDto.cpp index 2241804..e4a45f2 100644 --- a/src/data/dto/NavDto.cpp +++ b/src/data/dto/NavDto.cpp @@ -91,13 +91,17 @@ std::vector buildStructTree(const std::vector& flat) auto isRootLevel = [&](const StructNode& n) { return n.parentId.empty() || ids.find(n.parentId) == ids.end(); }; + // visited 防环:每个 id 最多进树一次。对正常树(单父)等价于原逻辑; + // 对不可信后端数据的多节点环 / 重复 id 环,避免无限递归(规约:永不信任外部数据)。 + std::set visited; std::function(const std::string&, bool)> build = [&](const std::string& parentId, bool root) { std::vector out; for (const auto& n : flat) { const bool belongs = root ? isRootLevel(n) : (n.parentId == parentId); if (!belongs) continue; - if (n.id == parentId) continue; // 防自环 + if (visited.count(n.id)) continue; // 已进树 → 跳过,防环/防重复 + visited.insert(n.id); StructTreeNode t; t.node = n; t.isTm = isLeaf(n.id); diff --git a/tests/data/test_nav_dto.cpp b/tests/data/test_nav_dto.cpp index 878901f..e7e4368 100644 --- a/tests/data/test_nav_dto.cpp +++ b/tests/data/test_nav_dto.cpp @@ -106,3 +106,16 @@ TEST(NavDto, BuildStructTreeOrphanParentBecomesRoot) { TEST(NavDto, BuildStructTreeEmpty) { EXPECT_TRUE(dto::buildStructTree({}).empty()); } + +TEST(NavDto, BuildStructTreeHandlesCycleWithoutInfiniteRecursion) { + // 不可信数据:重复 id 形成可达环(R→X→Y→重复X…)。必须终止、不崩。 + const std::vector flat = { + {"R", "根", "", "GS", "", 1}, + {"X", "x", "R", "GS", "", 1}, + {"Y", "y", "X", "GS", "", 1}, + {"X", "x2", "Y", "TM", "", 2}, // 重复 id X,父=Y → 若不防环将无限递归 + }; + const auto roots = dto::buildStructTree(flat); // 不挂起即通过 + ASSERT_EQ(roots.size(), 1u); + EXPECT_EQ(roots[0].node.id, "R"); +}