work work hard work

This commit is contained in:
StellaOps Bot
2025-12-18 00:47:24 +02:00
parent dee252940b
commit b4235c134c
189 changed files with 9627 additions and 3258 deletions

View File

@@ -0,0 +1,181 @@
using System.Collections.Immutable;
namespace StellaOps.Scanner.CallGraph;
public sealed class ReachabilityAnalyzer
{
private readonly TimeProvider _timeProvider;
private readonly int _maxDepth;
public ReachabilityAnalyzer(TimeProvider? timeProvider = null, int maxDepth = 256)
{
_timeProvider = timeProvider ?? TimeProvider.System;
_maxDepth = maxDepth <= 0 ? 256 : maxDepth;
}
public ReachabilityAnalysisResult Analyze(CallGraphSnapshot snapshot)
{
ArgumentNullException.ThrowIfNull(snapshot);
var trimmed = snapshot.Trimmed();
var adjacency = BuildAdjacency(trimmed);
var entrypoints = trimmed.EntrypointIds;
if (entrypoints.IsDefaultOrEmpty)
{
return EmptyResult(trimmed);
}
var origins = new Dictionary<string, string>(StringComparer.Ordinal);
var parents = new Dictionary<string, string?>(StringComparer.Ordinal);
var depths = new Dictionary<string, int>(StringComparer.Ordinal);
var queue = new Queue<string>();
foreach (var entry in entrypoints.OrderBy(e => e, StringComparer.Ordinal))
{
origins[entry] = entry;
parents[entry] = null;
depths[entry] = 0;
queue.Enqueue(entry);
}
while (queue.Count > 0)
{
var current = queue.Dequeue();
if (!depths.TryGetValue(current, out var depth))
{
continue;
}
if (depth >= _maxDepth)
{
continue;
}
if (!adjacency.TryGetValue(current, out var neighbors))
{
continue;
}
foreach (var next in neighbors)
{
if (origins.ContainsKey(next))
{
continue;
}
origins[next] = origins[current];
parents[next] = current;
depths[next] = depth + 1;
queue.Enqueue(next);
}
}
var reachableNodes = origins.Keys.OrderBy(id => id, StringComparer.Ordinal).ToImmutableArray();
var reachableSinks = trimmed.SinkIds
.Where(origins.ContainsKey)
.OrderBy(id => id, StringComparer.Ordinal)
.ToImmutableArray();
var paths = BuildPaths(reachableSinks, origins, parents);
var computedAt = _timeProvider.GetUtcNow();
var provisional = new ReachabilityAnalysisResult(
ScanId: trimmed.ScanId,
GraphDigest: trimmed.GraphDigest,
Language: trimmed.Language,
ComputedAt: computedAt,
ReachableNodeIds: reachableNodes,
ReachableSinkIds: reachableSinks,
Paths: paths,
ResultDigest: string.Empty);
var resultDigest = CallGraphDigests.ComputeResultDigest(provisional);
return provisional with { ResultDigest = resultDigest };
}
private static Dictionary<string, ImmutableArray<string>> BuildAdjacency(CallGraphSnapshot snapshot)
{
var map = new Dictionary<string, List<string>>(StringComparer.Ordinal);
foreach (var edge in snapshot.Edges)
{
if (!map.TryGetValue(edge.SourceId, out var list))
{
list = new List<string>();
map[edge.SourceId] = list;
}
list.Add(edge.TargetId);
}
return map.ToDictionary(
kvp => kvp.Key,
kvp => kvp.Value
.Where(v => !string.IsNullOrWhiteSpace(v))
.Distinct(StringComparer.Ordinal)
.OrderBy(v => v, StringComparer.Ordinal)
.ToImmutableArray(),
StringComparer.Ordinal);
}
private static ReachabilityAnalysisResult EmptyResult(CallGraphSnapshot snapshot)
{
var computedAt = TimeProvider.System.GetUtcNow();
var provisional = new ReachabilityAnalysisResult(
ScanId: snapshot.ScanId,
GraphDigest: snapshot.GraphDigest,
Language: snapshot.Language,
ComputedAt: computedAt,
ReachableNodeIds: ImmutableArray<string>.Empty,
ReachableSinkIds: ImmutableArray<string>.Empty,
Paths: ImmutableArray<ReachabilityPath>.Empty,
ResultDigest: string.Empty);
return provisional with { ResultDigest = CallGraphDigests.ComputeResultDigest(provisional) };
}
private static ImmutableArray<ReachabilityPath> BuildPaths(
ImmutableArray<string> reachableSinks,
Dictionary<string, string> origins,
Dictionary<string, string?> parents)
{
var paths = new List<ReachabilityPath>(reachableSinks.Length);
foreach (var sinkId in reachableSinks)
{
if (!origins.TryGetValue(sinkId, out var origin))
{
continue;
}
var nodeIds = ReconstructPathNodeIds(sinkId, parents);
paths.Add(new ReachabilityPath(origin, sinkId, nodeIds));
}
return paths
.OrderBy(p => p.SinkId, StringComparer.Ordinal)
.ThenBy(p => p.EntrypointId, StringComparer.Ordinal)
.ToImmutableArray();
}
private static ImmutableArray<string> ReconstructPathNodeIds(string sinkId, Dictionary<string, string?> parents)
{
var stack = new Stack<string>();
var cursor = sinkId;
while (true)
{
stack.Push(cursor);
if (!parents.TryGetValue(cursor, out var parent) || parent is null)
{
break;
}
cursor = parent;
}
var builder = ImmutableArray.CreateBuilder<string>(stack.Count);
while (stack.Count > 0)
{
builder.Add(stack.Pop());
}
return builder.ToImmutable();
}
}