up
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
sdk-generator-smoke / sdk-smoke (push) Has been cancelled
SDK Publish & Sign / sdk-publish (push) Has been cancelled
api-governance / spectral-lint (push) Has been cancelled
oas-ci / oas-validate (push) Has been cancelled
Mirror Thin Bundle Sign & Verify / mirror-sign (push) Has been cancelled

This commit is contained in:
StellaOps Bot
2025-11-27 07:46:56 +02:00
parent d63af51f84
commit ea970ead2a
302 changed files with 43161 additions and 1534 deletions

View File

@@ -1,17 +1,29 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
using System.Linq;
using System.Text.Json;
namespace StellaOps.Scanner.Reachability;
/// <summary>
/// Builds reachability graphs with full schema support including
/// rich node metadata, confidence levels, and source provenance.
/// </summary>
public sealed class ReachabilityGraphBuilder
{
private const string GraphSchemaVersion = "1.0";
private readonly Dictionary<string, RichNode> _richNodes = new(StringComparer.Ordinal);
private readonly HashSet<RichEdge> _richEdges = new();
// Legacy compatibility
private readonly HashSet<string> nodes = new(StringComparer.Ordinal);
private readonly HashSet<ReachabilityEdge> edges = new();
/// <summary>
/// Adds a simple node (legacy API).
/// </summary>
public ReachabilityGraphBuilder AddNode(string symbolId)
{
if (!string.IsNullOrWhiteSpace(symbolId))
@@ -22,6 +34,41 @@ public sealed class ReachabilityGraphBuilder
return this;
}
/// <summary>
/// Adds a rich node with full metadata.
/// </summary>
public ReachabilityGraphBuilder AddNode(
string symbolId,
string lang,
string kind,
string? display = null,
string? sourceFile = null,
int? sourceLine = null,
IReadOnlyDictionary<string, string>? attributes = null)
{
if (string.IsNullOrWhiteSpace(symbolId))
{
return this;
}
var id = symbolId.Trim();
var node = new RichNode(
id,
lang?.Trim() ?? string.Empty,
kind?.Trim() ?? "symbol",
display?.Trim(),
sourceFile?.Trim(),
sourceLine,
attributes?.ToImmutableSortedDictionary(StringComparer.Ordinal) ?? ImmutableSortedDictionary<string, string>.Empty);
_richNodes[id] = node;
nodes.Add(id);
return this;
}
/// <summary>
/// Adds a simple edge (legacy API).
/// </summary>
public ReachabilityGraphBuilder AddEdge(string from, string to, string kind = "call")
{
if (string.IsNullOrWhiteSpace(from) || string.IsNullOrWhiteSpace(to))
@@ -36,6 +83,52 @@ public sealed class ReachabilityGraphBuilder
return this;
}
/// <summary>
/// Adds a rich edge with confidence and provenance.
/// </summary>
/// <param name="from">Source symbol ID.</param>
/// <param name="to">Target symbol ID.</param>
/// <param name="edgeType">Edge type: call, import, inherits, loads, dynamic, reflects, dlopen, ffi, wasm, spawn.</param>
/// <param name="confidence">Confidence level: certain, high, medium, low.</param>
/// <param name="origin">Origin: static or runtime.</param>
/// <param name="provenance">Provenance hint: jvm-bytecode, il, ts-ast, ssa, ebpf, etw, jfr, hook.</param>
/// <param name="evidence">Evidence locator (e.g., "file:path:line").</param>
public ReachabilityGraphBuilder AddEdge(
string from,
string to,
string edgeType,
EdgeConfidence confidence,
string origin = "static",
string? provenance = null,
string? evidence = null)
{
if (string.IsNullOrWhiteSpace(from) || string.IsNullOrWhiteSpace(to))
{
return this;
}
var fromId = from.Trim();
var toId = to.Trim();
var type = string.IsNullOrWhiteSpace(edgeType) ? "call" : edgeType.Trim();
var richEdge = new RichEdge(
fromId,
toId,
type,
confidence,
origin?.Trim() ?? "static",
provenance?.Trim(),
evidence?.Trim());
_richEdges.Add(richEdge);
nodes.Add(fromId);
nodes.Add(toId);
// Also add to legacy set for compatibility
edges.Add(new ReachabilityEdge(fromId, toId, type));
return this;
}
public string BuildJson(bool indented = true)
{
var payload = new ReachabilityGraphPayload
@@ -54,21 +147,102 @@ public sealed class ReachabilityGraphBuilder
return JsonSerializer.Serialize(payload, options);
}
/// <summary>
/// Converts the builder contents to a union graph using rich metadata when available.
/// </summary>
public ReachabilityUnionGraph ToUnionGraph(string language)
{
ArgumentException.ThrowIfNullOrWhiteSpace(language);
var nodeList = nodes
.Select(id => new ReachabilityUnionNode(id, language, "symbol"))
.ToList();
var lang = language.Trim();
var edgeList = edges
.Select(edge => new ReachabilityUnionEdge(edge.From, edge.To, edge.Kind))
.ToList();
// Build nodes: prefer rich metadata, fall back to simple nodes
var nodeList = new List<ReachabilityUnionNode>();
foreach (var id in nodes.OrderBy(n => n, StringComparer.Ordinal))
{
if (_richNodes.TryGetValue(id, out var rich))
{
var source = rich.SourceFile is not null
? new ReachabilitySource("static", null, rich.SourceLine.HasValue ? $"file:{rich.SourceFile}:{rich.SourceLine}" : $"file:{rich.SourceFile}")
: null;
nodeList.Add(new ReachabilityUnionNode(
id,
rich.Lang,
rich.Kind,
rich.Display,
source,
rich.Attributes.Count > 0 ? rich.Attributes : null));
}
else
{
nodeList.Add(new ReachabilityUnionNode(id, lang, "symbol"));
}
}
// Build edges: prefer rich metadata, fall back to simple edges
var edgeSet = new HashSet<(string, string, string)>();
var edgeList = new List<ReachabilityUnionEdge>();
foreach (var rich in _richEdges.OrderBy(e => e.From, StringComparer.Ordinal)
.ThenBy(e => e.To, StringComparer.Ordinal)
.ThenBy(e => e.EdgeType, StringComparer.Ordinal))
{
var key = (rich.From, rich.To, rich.EdgeType);
if (!edgeSet.Add(key))
{
continue;
}
var source = new ReachabilitySource(
rich.Origin,
rich.Provenance,
rich.Evidence);
edgeList.Add(new ReachabilityUnionEdge(
rich.From,
rich.To,
rich.EdgeType,
ConfidenceToString(rich.Confidence),
source));
}
// Add any legacy edges not already covered
foreach (var edge in edges.OrderBy(e => e.From, StringComparer.Ordinal)
.ThenBy(e => e.To, StringComparer.Ordinal)
.ThenBy(e => e.Kind, StringComparer.Ordinal))
{
var key = (edge.From, edge.To, edge.Kind);
if (!edgeSet.Add(key))
{
continue;
}
edgeList.Add(new ReachabilityUnionEdge(edge.From, edge.To, edge.Kind));
}
return new ReachabilityUnionGraph(nodeList, edgeList);
}
/// <summary>
/// Gets the count of nodes in the graph.
/// </summary>
public int NodeCount => nodes.Count;
/// <summary>
/// Gets the count of edges in the graph.
/// </summary>
public int EdgeCount => edges.Count + _richEdges.Count(re => !edges.Contains(new ReachabilityEdge(re.From, re.To, re.EdgeType)));
private static string ConfidenceToString(EdgeConfidence confidence) => confidence switch
{
EdgeConfidence.Certain => "certain",
EdgeConfidence.High => "high",
EdgeConfidence.Medium => "medium",
EdgeConfidence.Low => "low",
_ => "certain"
};
public static ReachabilityGraphBuilder FromFixture(string variantPath)
{
ArgumentException.ThrowIfNullOrWhiteSpace(variantPath);
@@ -133,4 +307,80 @@ public sealed class ReachabilityGraphBuilder
public List<ReachabilityNode> Nodes { get; set; } = new();
public List<ReachabilityEdgePayload> Edges { get; set; } = new();
}
private sealed record RichNode(
string SymbolId,
string Lang,
string Kind,
string? Display,
string? SourceFile,
int? SourceLine,
ImmutableSortedDictionary<string, string> Attributes);
private sealed record RichEdge(
string From,
string To,
string EdgeType,
EdgeConfidence Confidence,
string Origin,
string? Provenance,
string? Evidence);
}
/// <summary>
/// Confidence levels for reachability edges per the union schema.
/// </summary>
public enum EdgeConfidence
{
/// <summary>
/// Edge is certain (direct call, import statement).
/// </summary>
Certain,
/// <summary>
/// High confidence (type-constrained virtual call).
/// </summary>
High,
/// <summary>
/// Medium confidence (interface dispatch, some dynamic patterns).
/// </summary>
Medium,
/// <summary>
/// Low confidence (reflection, string-based loading).
/// </summary>
Low
}
/// <summary>
/// Well-known edge types per the reachability union schema.
/// </summary>
public static class EdgeTypes
{
public const string Call = "call";
public const string Import = "import";
public const string Inherits = "inherits";
public const string Loads = "loads";
public const string Dynamic = "dynamic";
public const string Reflects = "reflects";
public const string Dlopen = "dlopen";
public const string Ffi = "ffi";
public const string Wasm = "wasm";
public const string Spawn = "spawn";
}
/// <summary>
/// Well-known provenance hints per the reachability union schema.
/// </summary>
public static class Provenance
{
public const string JvmBytecode = "jvm-bytecode";
public const string Il = "il";
public const string TsAst = "ts-ast";
public const string Ssa = "ssa";
public const string Ebpf = "ebpf";
public const string Etw = "etw";
public const string Jfr = "jfr";
public const string Hook = "hook";
}