Avoids circular dependency in profile groups

Adds cycle detection to prevent infinite loops when evaluating profile groups.

This ensures that profile group configurations don't result in stack overflow errors when groups reference each other, directly or indirectly.
This commit is contained in:
DHR60 2025-09-29 17:09:09 +08:00
parent ae46b39110
commit 4bf86665b7
3 changed files with 50 additions and 2 deletions

View file

@ -109,6 +109,42 @@ public class ProfileItem : ReactiveObject
return true; return true;
} }
public async Task<bool> HasCycle(HashSet<string> visited, HashSet<string> stack)
{
if (ConfigType < EConfigType.Group)
return false;
if (stack.Contains(IndexId))
return true;
if (visited.Contains(IndexId))
return false;
visited.Add(IndexId);
stack.Add(IndexId);
if (ProfileGroupItemManager.Instance.TryGet(IndexId, out var group)
&& !group.ChildItems.IsNullOrEmpty())
{
var childProfiles = (await Task.WhenAll(
Utils.String2List(group.ChildItems)
.Where(p => !p.IsNullOrEmpty())
.Select(AppManager.Instance.GetProfileItem)
))
.Where(p => p != null)
.ToList();
foreach (var child in childProfiles)
{
if (await child.HasCycle(visited, stack))
return true;
}
}
stack.Remove(IndexId);
return false;
}
#endregion function #endregion function
[PrimaryKey] [PrimaryKey]

View file

@ -217,9 +217,15 @@ public partial class CoreConfigSingboxService
{ {
return -1; return -1;
} }
var hasCycle = await node.HasCycle(new HashSet<string>(), new HashSet<string>());
if (hasCycle)
{
return -1;
}
// remove custom nodes // remove custom nodes
// remove group nodes for proxy chain // remove group nodes for proxy chain
// avoid self-reference
var childProfiles = (await Task.WhenAll( var childProfiles = (await Task.WhenAll(
Utils.String2List(profileGroupItem.ChildItems) Utils.String2List(profileGroupItem.ChildItems)
.Where(p => !p.IsNullOrEmpty()) .Where(p => !p.IsNullOrEmpty())
@ -230,7 +236,6 @@ public partial class CoreConfigSingboxService
&& p.IsValid() && p.IsValid()
&& p.ConfigType != EConfigType.Custom && p.ConfigType != EConfigType.Custom
&& (node.ConfigType == EConfigType.PolicyGroup || p.ConfigType < EConfigType.Group) && (node.ConfigType == EConfigType.PolicyGroup || p.ConfigType < EConfigType.Group)
&& p.IndexId != node.IndexId
) )
.ToList(); .ToList();

View file

@ -493,6 +493,13 @@ public partial class CoreConfigV2rayService
{ {
return -1; return -1;
} }
var hasCycle = await node.HasCycle(new HashSet<string>(), new HashSet<string>());
if (hasCycle)
{
return -1;
}
// remove custom nodes // remove custom nodes
// remove group nodes for proxy chain // remove group nodes for proxy chain
var childProfiles = (await Task.WhenAll( var childProfiles = (await Task.WhenAll(