up the blokcing tasks
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
Notify Smoke Test / Notifier Service Tests (push) Has been cancelled
Notify Smoke Test / Notification Smoke Test (push) Has been cancelled
Notify Smoke Test / Notify Unit Tests (push) Has been cancelled
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Export Center CI / export-ci (push) Has been cancelled
Manifest Integrity / Validate Schema Integrity (push) Has been cancelled
Manifest Integrity / Validate Contract Documents (push) Has been cancelled
Manifest Integrity / Validate Pack Fixtures (push) Has been cancelled
Manifest Integrity / Audit SHA256SUMS Files (push) Has been cancelled
Manifest Integrity / Verify Merkle Roots (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Risk Bundle CI / risk-bundle-build (push) Has been cancelled
Scanner Analyzers / Discover Analyzers (push) Has been cancelled
Scanner Analyzers / Validate Test Fixtures (push) Has been cancelled
Risk Bundle CI / risk-bundle-offline-kit (push) Has been cancelled
Risk Bundle CI / publish-checksums (push) Has been cancelled
Scanner Analyzers / Build Analyzers (push) Has been cancelled
Scanner Analyzers / Test Language Analyzers (push) Has been cancelled
Scanner Analyzers / Verify Deterministic Output (push) Has been cancelled
devportal-offline / build-offline (push) Has been cancelled
Mirror Thin Bundle Sign & Verify / mirror-sign (push) Has been cancelled

This commit is contained in:
StellaOps Bot
2025-12-11 02:32:18 +02:00
parent 92bc4d3a07
commit 49922dff5a
474 changed files with 76071 additions and 12411 deletions

View File

@@ -0,0 +1,357 @@
using System.Collections.Immutable;
using System.Security.Cryptography;
using System.Text;
namespace StellaOps.Scanner.Analyzers.Lang.Java.Internal.Runtime;
/// <summary>
/// Resolves runtime edges from parsed Java runtime events.
/// Produces append-only edges for runtime-class, runtime-spi, runtime-load patterns.
/// </summary>
internal static class JavaRuntimeEdgeResolver
{
/// <summary>
/// Resolves runtime edges and entrypoints from parsed events.
/// </summary>
public static JavaRuntimeIngestion ResolveFromEvents(
ImmutableArray<JavaRuntimeEvent> events,
ImmutableArray<JavaRuntimeIngestionWarning> parseWarnings,
string contentHash,
JavaRuntimeIngestionConfig config,
CancellationToken cancellationToken = default)
{
var edges = ImmutableArray.CreateBuilder<JavaRuntimeEdge>();
var entrypoints = ImmutableArray.CreateBuilder<JavaRuntimeEntrypoint>();
var warnings = ImmutableArray.CreateBuilder<JavaRuntimeIngestionWarning>();
warnings.AddRange(parseWarnings);
// Track seen edges for deduplication
var seenEdges = new HashSet<string>();
// Track entrypoint invocation counts
var entrypointCounts = new Dictionary<string, (JavaRuntimeEntrypoint Entry, int Count)>();
// Summary counters
int classLoadCount = 0, serviceLoaderCount = 0, nativeLoadCount = 0;
int reflectionCount = 0, resourceAccessCount = 0, moduleResolveCount = 0;
DateTimeOffset? startTime = null, endTime = null;
foreach (var evt in events)
{
cancellationToken.ThrowIfCancellationRequested();
// Track time bounds
if (startTime is null || evt.Timestamp < startTime)
{
startTime = evt.Timestamp;
}
if (endTime is null || evt.Timestamp > endTime)
{
endTime = evt.Timestamp;
}
switch (evt)
{
case JavaClassLoadEvent classLoad:
classLoadCount++;
ResolveClassLoadEdges(classLoad, edges, seenEdges, config);
break;
case JavaServiceLoaderEvent serviceLoader:
serviceLoaderCount++;
ResolveSpiEdges(serviceLoader, edges, entrypoints, entrypointCounts, seenEdges, config);
break;
case JavaNativeLoadEvent nativeLoad:
nativeLoadCount++;
ResolveNativeLoadEdges(nativeLoad, edges, seenEdges, config);
break;
case JavaReflectionEvent reflection:
reflectionCount++;
ResolveReflectionEdges(reflection, edges, entrypoints, entrypointCounts, seenEdges, config);
break;
case JavaResourceAccessEvent resourceAccess:
resourceAccessCount++;
ResolveResourceEdges(resourceAccess, edges, seenEdges, config);
break;
case JavaModuleResolveEvent moduleResolve:
moduleResolveCount++;
ResolveModuleEdges(moduleResolve, edges, seenEdges, config);
break;
}
}
// Build final entrypoints from tracked counts
var finalEntrypoints = entrypointCounts.Values
.Select(v => v.Entry with { InvocationCount = v.Count })
.ToImmutableArray();
var summary = new JavaRuntimeTraceSummary(
StartTime: startTime ?? DateTimeOffset.MinValue,
EndTime: endTime ?? DateTimeOffset.MinValue,
JavaVersion: null, // Would come from trace metadata if available
JavaVendor: null,
JvmName: null,
JvmArgs: null,
ClassLoadCount: classLoadCount,
ServiceLoaderCount: serviceLoaderCount,
NativeLoadCount: nativeLoadCount,
ReflectionCount: reflectionCount,
ResourceAccessCount: resourceAccessCount,
ModuleResolveCount: moduleResolveCount);
return new JavaRuntimeIngestion(
events,
edges.ToImmutable(),
finalEntrypoints,
summary,
warnings.ToImmutable(),
contentHash);
}
private static void ResolveClassLoadEdges(
JavaClassLoadEvent evt,
ImmutableArray<JavaRuntimeEdge>.Builder edges,
HashSet<string> seenEdges,
JavaRuntimeIngestionConfig config)
{
var edgeKey = $"runtime-class:{evt.InitiatingClass ?? "bootstrap"}:{evt.ClassName}";
if (config.DeduplicateEdges && !seenEdges.Add(edgeKey))
{
return;
}
var reason = evt.ClassLoader switch
{
"bootstrap" => JavaRuntimeEdgeReason.ClassLoadBootstrap,
"platform" or "ext" => JavaRuntimeEdgeReason.ClassLoadPlatform,
"app" or "system" => JavaRuntimeEdgeReason.ClassLoadApplication,
_ => JavaRuntimeEdgeReason.ClassLoadCustom,
};
edges.Add(new JavaRuntimeEdge(
EdgeId: ComputeEdgeId(edgeKey),
SourceClass: evt.InitiatingClass,
TargetClass: evt.ClassName,
EdgeType: JavaRuntimeEdgeType.RuntimeClass,
Reason: reason,
Timestamp: evt.Timestamp,
Source: evt.Source,
SourceHash: evt.SourceHash,
Confidence: 1.0,
Details: $"classloader={evt.ClassLoader}"));
}
private static void ResolveSpiEdges(
JavaServiceLoaderEvent evt,
ImmutableArray<JavaRuntimeEdge>.Builder edges,
ImmutableArray<JavaRuntimeEntrypoint>.Builder entrypoints,
Dictionary<string, (JavaRuntimeEntrypoint Entry, int Count)> entrypointCounts,
HashSet<string> seenEdges,
JavaRuntimeIngestionConfig config)
{
foreach (var provider in evt.Providers)
{
var edgeKey = $"runtime-spi:{evt.ServiceInterface}:{provider.ProviderClass}";
if (!config.DeduplicateEdges || seenEdges.Add(edgeKey))
{
edges.Add(new JavaRuntimeEdge(
EdgeId: ComputeEdgeId(edgeKey),
SourceClass: evt.ServiceInterface,
TargetClass: provider.ProviderClass,
EdgeType: JavaRuntimeEdgeType.RuntimeSpi,
Reason: JavaRuntimeEdgeReason.ServiceLoaderExplicit,
Timestamp: evt.Timestamp,
Source: provider.Source,
SourceHash: provider.SourceHash,
Confidence: 1.0,
Details: $"service={evt.ServiceInterface}"));
}
// Track provider as entrypoint
var entrypointKey = $"spi:{provider.ProviderClass}";
if (entrypointCounts.TryGetValue(entrypointKey, out var existing))
{
entrypointCounts[entrypointKey] = (existing.Entry, existing.Count + 1);
}
else
{
var entrypoint = new JavaRuntimeEntrypoint(
EntrypointId: ComputeEdgeId(entrypointKey),
ClassName: provider.ProviderClass,
MethodName: null,
EntrypointType: JavaRuntimeEntrypointType.ServiceProvider,
FirstSeen: evt.Timestamp,
InvocationCount: 1,
Source: provider.Source,
SourceHash: provider.SourceHash,
Confidence: 1.0);
entrypointCounts[entrypointKey] = (entrypoint, 1);
}
}
}
private static void ResolveNativeLoadEdges(
JavaNativeLoadEvent evt,
ImmutableArray<JavaRuntimeEdge>.Builder edges,
HashSet<string> seenEdges,
JavaRuntimeIngestionConfig config)
{
var edgeKey = $"runtime-native:{evt.InitiatingClass ?? "unknown"}:{evt.LibraryName}";
if (config.DeduplicateEdges && !seenEdges.Add(edgeKey))
{
return;
}
var reason = evt.LoadMethod switch
{
"System.load" => JavaRuntimeEdgeReason.SystemLoad,
"System.loadLibrary" => JavaRuntimeEdgeReason.SystemLoadLibrary,
"Runtime.load" => JavaRuntimeEdgeReason.RuntimeLoad,
"Runtime.loadLibrary" => JavaRuntimeEdgeReason.RuntimeLoadLibrary,
_ => JavaRuntimeEdgeReason.SystemLoadLibrary,
};
if (!evt.Success)
{
reason = JavaRuntimeEdgeReason.NativeLoadFailure;
}
edges.Add(new JavaRuntimeEdge(
EdgeId: ComputeEdgeId(edgeKey),
SourceClass: evt.InitiatingClass,
TargetClass: evt.LibraryName,
EdgeType: JavaRuntimeEdgeType.RuntimeNativeLoad,
Reason: reason,
Timestamp: evt.Timestamp,
Source: evt.ResolvedPath,
SourceHash: evt.PathHash,
Confidence: evt.Success ? 1.0 : 0.5,
Details: evt.Success ? $"resolved={evt.ResolvedPath}" : "load_failed"));
}
private static void ResolveReflectionEdges(
JavaReflectionEvent evt,
ImmutableArray<JavaRuntimeEdge>.Builder edges,
ImmutableArray<JavaRuntimeEntrypoint>.Builder entrypoints,
Dictionary<string, (JavaRuntimeEntrypoint Entry, int Count)> entrypointCounts,
HashSet<string> seenEdges,
JavaRuntimeIngestionConfig config)
{
var edgeKey = $"runtime-reflect:{evt.InitiatingClass ?? "unknown"}:{evt.TargetClass}:{evt.ReflectionMethod}";
if (!config.DeduplicateEdges || seenEdges.Add(edgeKey))
{
var reason = evt.ReflectionMethod switch
{
"Class.forName" => JavaRuntimeEdgeReason.ClassForName,
"Class.newInstance" => JavaRuntimeEdgeReason.ClassNewInstance,
"Constructor.newInstance" => JavaRuntimeEdgeReason.ConstructorNewInstance,
"Method.invoke" => JavaRuntimeEdgeReason.MethodInvoke,
_ => JavaRuntimeEdgeReason.ClassForName,
};
edges.Add(new JavaRuntimeEdge(
EdgeId: ComputeEdgeId(edgeKey),
SourceClass: evt.InitiatingClass,
TargetClass: evt.TargetClass,
EdgeType: JavaRuntimeEdgeType.RuntimeReflection,
Reason: reason,
Timestamp: evt.Timestamp,
Source: null,
SourceHash: null,
Confidence: 0.9, // Reflection edges have slightly lower confidence
Details: $"method={evt.ReflectionMethod}"));
}
// Track reflection target as entrypoint
var entrypointKey = $"reflect:{evt.TargetClass}";
if (entrypointCounts.TryGetValue(entrypointKey, out var existing))
{
entrypointCounts[entrypointKey] = (existing.Entry, existing.Count + 1);
}
else
{
var entrypoint = new JavaRuntimeEntrypoint(
EntrypointId: ComputeEdgeId(entrypointKey),
ClassName: evt.TargetClass,
MethodName: null,
EntrypointType: JavaRuntimeEntrypointType.ReflectionTarget,
FirstSeen: evt.Timestamp,
InvocationCount: 1,
Source: null,
SourceHash: null,
Confidence: 0.9);
entrypointCounts[entrypointKey] = (entrypoint, 1);
}
}
private static void ResolveResourceEdges(
JavaResourceAccessEvent evt,
ImmutableArray<JavaRuntimeEdge>.Builder edges,
HashSet<string> seenEdges,
JavaRuntimeIngestionConfig config)
{
if (!evt.Found)
{
return; // Only track successful resource lookups
}
var edgeKey = $"runtime-resource:{evt.InitiatingClass ?? "unknown"}:{evt.ResourceName}";
if (config.DeduplicateEdges && !seenEdges.Add(edgeKey))
{
return;
}
edges.Add(new JavaRuntimeEdge(
EdgeId: ComputeEdgeId(edgeKey),
SourceClass: evt.InitiatingClass,
TargetClass: evt.ResourceName,
EdgeType: JavaRuntimeEdgeType.RuntimeResource,
Reason: JavaRuntimeEdgeReason.GetResource,
Timestamp: evt.Timestamp,
Source: evt.Source,
SourceHash: evt.SourceHash,
Confidence: 1.0,
Details: null));
}
private static void ResolveModuleEdges(
JavaModuleResolveEvent evt,
ImmutableArray<JavaRuntimeEdge>.Builder edges,
HashSet<string> seenEdges,
JavaRuntimeIngestionConfig config)
{
if (string.IsNullOrEmpty(evt.RequiredBy))
{
return; // Skip root modules without a requiring module
}
var edgeKey = $"runtime-module:{evt.RequiredBy}:{evt.ModuleName}";
if (config.DeduplicateEdges && !seenEdges.Add(edgeKey))
{
return;
}
edges.Add(new JavaRuntimeEdge(
EdgeId: ComputeEdgeId(edgeKey),
SourceClass: evt.RequiredBy,
TargetClass: evt.ModuleName,
EdgeType: JavaRuntimeEdgeType.RuntimeModule,
Reason: JavaRuntimeEdgeReason.ModuleRequires,
Timestamp: evt.Timestamp,
Source: evt.ModuleLocation,
SourceHash: evt.LocationHash,
Confidence: 1.0,
Details: evt.IsOpen ? "open_module" : null));
}
private static string ComputeEdgeId(string input)
{
var bytes = Encoding.UTF8.GetBytes(input);
var hash = SHA256.HashData(bytes);
return $"runtime:{Convert.ToHexString(hash[..8]).ToLowerInvariant()}";
}
}

View File

@@ -0,0 +1,286 @@
using System.Collections.Immutable;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
namespace StellaOps.Scanner.Analyzers.Lang.Java.Internal.Runtime;
/// <summary>
/// Parses NDJSON runtime trace files produced by Java agent or JFR export.
/// Supports both agent-produced traces and JFR .ndjson exports.
/// </summary>
internal static class JavaRuntimeEventParser
{
private static readonly JsonSerializerOptions JsonOptions = new()
{
PropertyNameCaseInsensitive = true,
ReadCommentHandling = JsonCommentHandling.Skip,
};
/// <summary>
/// Parses a runtime trace file and returns all events.
/// </summary>
/// <param name="stream">Stream containing NDJSON trace data.</param>
/// <param name="config">Ingestion configuration.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>Parsed events and warnings.</returns>
public static async Task<(ImmutableArray<JavaRuntimeEvent> Events, ImmutableArray<JavaRuntimeIngestionWarning> Warnings, string ContentHash)>
ParseAsync(Stream stream, JavaRuntimeIngestionConfig config, CancellationToken cancellationToken = default)
{
var events = ImmutableArray.CreateBuilder<JavaRuntimeEvent>();
var warnings = ImmutableArray.CreateBuilder<JavaRuntimeIngestionWarning>();
using var hashAlgorithm = IncrementalHash.CreateHash(HashAlgorithmName.SHA256);
using var reader = new StreamReader(stream, Encoding.UTF8, detectEncodingFromByteOrderMarks: false, leaveOpen: true);
var lineNumber = 0;
var eventCount = 0;
while (await reader.ReadLineAsync(cancellationToken) is { } line)
{
lineNumber++;
// Update content hash
hashAlgorithm.AppendData(Encoding.UTF8.GetBytes(line));
hashAlgorithm.AppendData("\n"u8);
if (string.IsNullOrWhiteSpace(line))
{
continue;
}
// Check max events limit
if (config.MaxEvents > 0 && eventCount >= config.MaxEvents)
{
warnings.Add(new JavaRuntimeIngestionWarning(
"MAX_EVENTS_REACHED",
$"Maximum event limit ({config.MaxEvents}) reached, stopping parse",
lineNumber,
null));
break;
}
try
{
var evt = ParseLine(line, config);
if (evt is not null)
{
events.Add(evt);
eventCount++;
}
}
catch (JsonException ex)
{
warnings.Add(new JavaRuntimeIngestionWarning(
"PARSE_ERROR",
$"Failed to parse JSON: {ex.Message}",
lineNumber,
line.Length > 200 ? line[..200] + "..." : line));
}
}
var hash = Convert.ToHexString(hashAlgorithm.GetCurrentHash()).ToLowerInvariant();
return (events.ToImmutable(), warnings.ToImmutable(), hash);
}
/// <summary>
/// Parses a single NDJSON line into a runtime event.
/// </summary>
private static JavaRuntimeEvent? ParseLine(string line, JavaRuntimeIngestionConfig config)
{
using var doc = JsonDocument.Parse(line);
var root = doc.RootElement;
if (!root.TryGetProperty("type", out var typeElement))
{
return null;
}
var type = typeElement.GetString();
return type switch
{
"java.class.load" => ParseClassLoadEvent(root, config),
"java.service.load" => ParseServiceLoaderEvent(root, config),
"java.native.load" => ParseNativeLoadEvent(root, config),
"java.reflection.access" => ParseReflectionEvent(root, config),
"java.resource.access" => ParseResourceAccessEvent(root, config),
"java.module.resolve" => ParseModuleResolveEvent(root, config),
"java.class.statistics" => config.IncludeStatistics ? ParseClassStatisticsEvent(root) : null,
_ => null, // Unknown event types are silently ignored
};
}
private static JavaClassLoadEvent? ParseClassLoadEvent(JsonElement root, JavaRuntimeIngestionConfig config)
{
var ts = GetTimestamp(root);
var className = root.GetProperty("class_name").GetString() ?? string.Empty;
var classLoader = root.TryGetProperty("class_loader", out var cl) ? cl.GetString() ?? "app" : "app";
var source = root.TryGetProperty("source", out var s) ? s.GetString() : null;
var sourceHash = root.TryGetProperty("source_hash", out var sh) ? sh.GetString() : null;
var initiatingClass = root.TryGetProperty("initiating_class", out var ic) ? ic.GetString() : null;
var threadName = root.TryGetProperty("thread_name", out var tn) ? tn.GetString() : null;
// Filter JDK classes if configured
if (!config.IncludeJdkClasses && IsJdkClass(className))
{
return null;
}
// Compute source hash if not provided and scrubbing is enabled
if (config.ScrubPaths && source is not null && sourceHash is null)
{
sourceHash = ComputePathHash(source);
}
return new JavaClassLoadEvent(ts, className, classLoader, source, sourceHash, initiatingClass, threadName);
}
private static JavaServiceLoaderEvent ParseServiceLoaderEvent(JsonElement root, JavaRuntimeIngestionConfig config)
{
var ts = GetTimestamp(root);
var serviceInterface = root.GetProperty("service_interface").GetString() ?? string.Empty;
var initiatingClass = root.TryGetProperty("initiating_class", out var ic) ? ic.GetString() : null;
var threadName = root.TryGetProperty("thread_name", out var tn) ? tn.GetString() : null;
var providers = ImmutableArray.CreateBuilder<JavaServiceProviderInfo>();
if (root.TryGetProperty("providers", out var providersElement) && providersElement.ValueKind == JsonValueKind.Array)
{
foreach (var p in providersElement.EnumerateArray())
{
var providerClass = p.TryGetProperty("provider_class", out var pc) ? pc.GetString() ?? string.Empty : string.Empty;
var source = p.TryGetProperty("source", out var s) ? s.GetString() : null;
var sourceHash = p.TryGetProperty("source_hash", out var sh) ? sh.GetString() : null;
if (config.ScrubPaths && source is not null && sourceHash is null)
{
sourceHash = ComputePathHash(source);
}
providers.Add(new JavaServiceProviderInfo(providerClass, source, sourceHash));
}
}
return new JavaServiceLoaderEvent(ts, serviceInterface, providers.ToImmutable(), initiatingClass, threadName);
}
private static JavaNativeLoadEvent ParseNativeLoadEvent(JsonElement root, JavaRuntimeIngestionConfig config)
{
var ts = GetTimestamp(root);
var libraryName = root.GetProperty("library_name").GetString() ?? string.Empty;
var resolvedPath = root.TryGetProperty("resolved_path", out var rp) ? rp.GetString() : null;
var pathHash = root.TryGetProperty("path_hash", out var ph) ? ph.GetString() : null;
var loadMethod = root.TryGetProperty("load_method", out var lm) ? lm.GetString() ?? "System.loadLibrary" : "System.loadLibrary";
var initiatingClass = root.TryGetProperty("initiating_class", out var ic) ? ic.GetString() : null;
var threadName = root.TryGetProperty("thread_name", out var tn) ? tn.GetString() : null;
var success = root.TryGetProperty("success", out var sc) && sc.GetBoolean();
if (config.ScrubPaths && resolvedPath is not null && pathHash is null)
{
pathHash = ComputePathHash(resolvedPath);
}
return new JavaNativeLoadEvent(ts, libraryName, resolvedPath, pathHash, loadMethod, initiatingClass, threadName, success);
}
private static JavaReflectionEvent ParseReflectionEvent(JsonElement root, JavaRuntimeIngestionConfig config)
{
var ts = GetTimestamp(root);
var targetClass = root.GetProperty("target_class").GetString() ?? string.Empty;
var reflectionMethod = root.TryGetProperty("reflection_method", out var rm) ? rm.GetString() ?? "Class.forName" : "Class.forName";
var initiatingClass = root.TryGetProperty("initiating_class", out var ic) ? ic.GetString() : null;
var sourceLine = root.TryGetProperty("source_line", out var sl) ? sl.GetString() : null;
var threadName = root.TryGetProperty("thread_name", out var tn) ? tn.GetString() : null;
// Filter JDK classes if configured
if (!config.IncludeJdkClasses && IsJdkClass(targetClass))
{
return new JavaReflectionEvent(ts, targetClass, reflectionMethod, initiatingClass, sourceLine, threadName);
}
return new JavaReflectionEvent(ts, targetClass, reflectionMethod, initiatingClass, sourceLine, threadName);
}
private static JavaResourceAccessEvent ParseResourceAccessEvent(JsonElement root, JavaRuntimeIngestionConfig config)
{
var ts = GetTimestamp(root);
var resourceName = root.GetProperty("resource_name").GetString() ?? string.Empty;
var source = root.TryGetProperty("source", out var s) ? s.GetString() : null;
var sourceHash = root.TryGetProperty("source_hash", out var sh) ? sh.GetString() : null;
var initiatingClass = root.TryGetProperty("initiating_class", out var ic) ? ic.GetString() : null;
var found = root.TryGetProperty("found", out var f) && f.GetBoolean();
if (config.ScrubPaths && source is not null && sourceHash is null)
{
sourceHash = ComputePathHash(source);
}
return new JavaResourceAccessEvent(ts, resourceName, source, sourceHash, initiatingClass, found);
}
private static JavaModuleResolveEvent ParseModuleResolveEvent(JsonElement root, JavaRuntimeIngestionConfig config)
{
var ts = GetTimestamp(root);
var moduleName = root.GetProperty("module_name").GetString() ?? string.Empty;
var moduleLocation = root.TryGetProperty("module_location", out var ml) ? ml.GetString() : null;
var locationHash = root.TryGetProperty("location_hash", out var lh) ? lh.GetString() : null;
var requiredBy = root.TryGetProperty("required_by", out var rb) ? rb.GetString() : null;
var isOpen = root.TryGetProperty("is_open", out var io) && io.GetBoolean();
if (config.ScrubPaths && moduleLocation is not null && locationHash is null)
{
locationHash = ComputePathHash(moduleLocation);
}
return new JavaModuleResolveEvent(ts, moduleName, moduleLocation, locationHash, requiredBy, isOpen);
}
private static JavaClassLoadingStatisticsEvent ParseClassStatisticsEvent(JsonElement root)
{
var ts = GetTimestamp(root);
var loadedClassCount = root.TryGetProperty("loaded_class_count", out var lcc) ? lcc.GetInt64() : 0;
var unloadedClassCount = root.TryGetProperty("unloaded_class_count", out var ucc) ? ucc.GetInt64() : 0;
var classLoaders = root.TryGetProperty("class_loaders", out var cl) ? cl.GetInt32() : 0;
var hiddenClasses = root.TryGetProperty("hidden_classes", out var hc) ? hc.GetInt32() : 0;
return new JavaClassLoadingStatisticsEvent(ts, loadedClassCount, unloadedClassCount, classLoaders, hiddenClasses);
}
private static DateTimeOffset GetTimestamp(JsonElement root)
{
if (root.TryGetProperty("ts", out var ts))
{
if (ts.ValueKind == JsonValueKind.String)
{
return DateTimeOffset.Parse(ts.GetString()!, null, System.Globalization.DateTimeStyles.RoundtripKind);
}
if (ts.ValueKind == JsonValueKind.Number)
{
return DateTimeOffset.FromUnixTimeMilliseconds(ts.GetInt64());
}
}
return DateTimeOffset.UtcNow;
}
private static bool IsJdkClass(string className)
{
// Check for JDK internal packages
return className.StartsWith("java/", StringComparison.Ordinal)
|| className.StartsWith("javax/", StringComparison.Ordinal)
|| className.StartsWith("jdk/", StringComparison.Ordinal)
|| className.StartsWith("sun/", StringComparison.Ordinal)
|| className.StartsWith("com/sun/", StringComparison.Ordinal)
|| className.StartsWith("oracle/", StringComparison.Ordinal);
}
/// <summary>
/// Computes a SHA-256 hash of a path for deterministic path-safe evidence.
/// </summary>
internal static string ComputePathHash(string path)
{
// Normalize path separators to forward slash
var normalized = path.Replace('\\', '/');
var bytes = Encoding.UTF8.GetBytes(normalized);
var hash = SHA256.HashData(bytes);
return Convert.ToHexString(hash).ToLowerInvariant();
}
}

View File

@@ -0,0 +1,172 @@
using System.Text.Json.Serialization;
namespace StellaOps.Scanner.Analyzers.Lang.Java.Internal.Runtime;
/// <summary>
/// Base type for Java runtime events captured via Java agent or JFR.
/// Events are serialized as NDJSON with deterministic key ordering.
/// </summary>
internal abstract record JavaRuntimeEvent(
[property: JsonPropertyName("type")] string Type,
[property: JsonPropertyName("ts")] DateTimeOffset Timestamp);
/// <summary>
/// Class load event captured when a class is loaded by the JVM.
/// </summary>
/// <param name="Ts">Event timestamp (UTC).</param>
/// <param name="ClassName">Fully qualified class name (e.g., "java/lang/String").</param>
/// <param name="ClassLoader">Class loader name (e.g., "app", "platform", "bootstrap").</param>
/// <param name="Source">JAR/location where the class was loaded from.</param>
/// <param name="SourceHash">SHA-256 hash of normalized source path for path-safe evidence.</param>
/// <param name="InitiatingClass">Class that initiated the load (if available).</param>
/// <param name="ThreadName">Name of the thread where load occurred.</param>
internal sealed record JavaClassLoadEvent(
DateTimeOffset Ts,
string ClassName,
string ClassLoader,
string? Source,
string? SourceHash,
string? InitiatingClass,
string? ThreadName) : JavaRuntimeEvent("java.class.load", Ts);
/// <summary>
/// ServiceLoader lookup event captured when ServiceLoader.load() is called.
/// </summary>
/// <param name="Ts">Event timestamp (UTC).</param>
/// <param name="ServiceInterface">Service interface being loaded.</param>
/// <param name="Providers">List of provider classes discovered.</param>
/// <param name="InitiatingClass">Class that called ServiceLoader.load().</param>
/// <param name="ThreadName">Name of the thread where lookup occurred.</param>
internal sealed record JavaServiceLoaderEvent(
DateTimeOffset Ts,
string ServiceInterface,
IReadOnlyList<JavaServiceProviderInfo> Providers,
string? InitiatingClass,
string? ThreadName) : JavaRuntimeEvent("java.service.load", Ts);
/// <summary>
/// Information about a service provider discovered by ServiceLoader.
/// </summary>
/// <param name="ProviderClass">Provider implementation class name.</param>
/// <param name="Source">JAR/module where provider was found.</param>
/// <param name="SourceHash">SHA-256 hash of normalized source path.</param>
internal sealed record JavaServiceProviderInfo(
[property: JsonPropertyName("provider_class")] string ProviderClass,
[property: JsonPropertyName("source")] string? Source,
[property: JsonPropertyName("source_hash")] string? SourceHash);
/// <summary>
/// Native library load event captured when System.load/loadLibrary is called.
/// </summary>
/// <param name="Ts">Event timestamp (UTC).</param>
/// <param name="LibraryName">Library name (from loadLibrary) or path (from load).</param>
/// <param name="ResolvedPath">Actual resolved path to the native library.</param>
/// <param name="PathHash">SHA-256 hash of normalized path for path-safe evidence.</param>
/// <param name="LoadMethod">How the library was loaded: "System.load", "System.loadLibrary", "Runtime.load", "Runtime.loadLibrary".</param>
/// <param name="InitiatingClass">Class that initiated the load.</param>
/// <param name="ThreadName">Name of the thread where load occurred.</param>
/// <param name="Success">Whether the load succeeded.</param>
internal sealed record JavaNativeLoadEvent(
DateTimeOffset Ts,
string LibraryName,
string? ResolvedPath,
string? PathHash,
string LoadMethod,
string? InitiatingClass,
string? ThreadName,
bool Success) : JavaRuntimeEvent("java.native.load", Ts);
/// <summary>
/// Reflection class instantiation event captured via instrumentation.
/// </summary>
/// <param name="Ts">Event timestamp (UTC).</param>
/// <param name="TargetClass">Class being instantiated/accessed via reflection.</param>
/// <param name="ReflectionMethod">Method used: "Class.forName", "Class.newInstance", "Constructor.newInstance", "Method.invoke".</param>
/// <param name="InitiatingClass">Class that performed the reflection call.</param>
/// <param name="SourceLine">Source line information if available (class:line format).</param>
/// <param name="ThreadName">Name of the thread where reflection occurred.</param>
internal sealed record JavaReflectionEvent(
DateTimeOffset Ts,
string TargetClass,
string ReflectionMethod,
string? InitiatingClass,
string? SourceLine,
string? ThreadName) : JavaRuntimeEvent("java.reflection.access", Ts);
/// <summary>
/// Resource access event captured when ClassLoader.getResource* is called.
/// </summary>
/// <param name="Ts">Event timestamp (UTC).</param>
/// <param name="ResourceName">Name of the resource being accessed.</param>
/// <param name="Source">JAR/location where resource was found.</param>
/// <param name="SourceHash">SHA-256 hash of normalized source path.</param>
/// <param name="InitiatingClass">Class that requested the resource.</param>
/// <param name="Found">Whether the resource was found.</param>
internal sealed record JavaResourceAccessEvent(
DateTimeOffset Ts,
string ResourceName,
string? Source,
string? SourceHash,
string? InitiatingClass,
bool Found) : JavaRuntimeEvent("java.resource.access", Ts);
/// <summary>
/// Module resolution event captured when JPMS resolves module dependencies.
/// </summary>
/// <param name="Ts">Event timestamp (UTC).</param>
/// <param name="ModuleName">Name of the module being resolved.</param>
/// <param name="ModuleLocation">Location URI of the module.</param>
/// <param name="LocationHash">SHA-256 hash of normalized location.</param>
/// <param name="RequiredBy">Module that required this module.</param>
/// <param name="IsOpen">Whether this is an open module.</param>
internal sealed record JavaModuleResolveEvent(
DateTimeOffset Ts,
string ModuleName,
string? ModuleLocation,
string? LocationHash,
string? RequiredBy,
bool IsOpen) : JavaRuntimeEvent("java.module.resolve", Ts);
/// <summary>
/// JFR event containing aggregated class loading statistics.
/// </summary>
/// <param name="Ts">Event timestamp (UTC).</param>
/// <param name="LoadedClassCount">Total number of loaded classes.</param>
/// <param name="UnloadedClassCount">Total number of unloaded classes.</param>
/// <param name="ClassLoaders">Number of live class loaders.</param>
/// <param name="HiddenClasses">Number of hidden/anonymous classes.</param>
internal sealed record JavaClassLoadingStatisticsEvent(
DateTimeOffset Ts,
long LoadedClassCount,
long UnloadedClassCount,
int ClassLoaders,
int HiddenClasses) : JavaRuntimeEvent("java.class.statistics", Ts);
/// <summary>
/// Summary metadata for a runtime trace session.
/// </summary>
/// <param name="StartTime">Trace session start time.</param>
/// <param name="EndTime">Trace session end time.</param>
/// <param name="JavaVersion">Java version string.</param>
/// <param name="JavaVendor">Java vendor string.</param>
/// <param name="JvmName">JVM name (e.g., "OpenJDK 64-Bit Server VM").</param>
/// <param name="JvmArgs">Sanitized JVM arguments (secrets redacted).</param>
/// <param name="ClassLoadCount">Total class load events.</param>
/// <param name="ServiceLoaderCount">Total ServiceLoader events.</param>
/// <param name="NativeLoadCount">Total native library load events.</param>
/// <param name="ReflectionCount">Total reflection events.</param>
/// <param name="ResourceAccessCount">Total resource access events.</param>
/// <param name="ModuleResolveCount">Total module resolution events.</param>
internal sealed record JavaRuntimeTraceSummary(
DateTimeOffset StartTime,
DateTimeOffset EndTime,
string? JavaVersion,
string? JavaVendor,
string? JvmName,
IReadOnlyList<string>? JvmArgs,
int ClassLoadCount,
int ServiceLoaderCount,
int NativeLoadCount,
int ReflectionCount,
int ResourceAccessCount,
int ModuleResolveCount);

View File

@@ -0,0 +1,211 @@
using System.Collections.Immutable;
namespace StellaOps.Scanner.Analyzers.Lang.Java.Internal.Runtime;
/// <summary>
/// Result of Java runtime trace ingestion per task 21-010.
/// Contains parsed events and derived runtime edges for entrypoint resolution.
/// </summary>
/// <param name="Events">All parsed runtime events.</param>
/// <param name="RuntimeEdges">Edges derived from runtime observation.</param>
/// <param name="RuntimeEntrypoints">Entrypoints discovered through runtime execution.</param>
/// <param name="Summary">Summary metadata for the trace session.</param>
/// <param name="Warnings">Warnings encountered during parsing/ingestion.</param>
/// <param name="ContentHash">SHA-256 hash of the trace content for deterministic identification.</param>
internal sealed record JavaRuntimeIngestion(
ImmutableArray<JavaRuntimeEvent> Events,
ImmutableArray<JavaRuntimeEdge> RuntimeEdges,
ImmutableArray<JavaRuntimeEntrypoint> RuntimeEntrypoints,
JavaRuntimeTraceSummary Summary,
ImmutableArray<JavaRuntimeIngestionWarning> Warnings,
string ContentHash)
{
public static readonly JavaRuntimeIngestion Empty = new(
ImmutableArray<JavaRuntimeEvent>.Empty,
ImmutableArray<JavaRuntimeEdge>.Empty,
ImmutableArray<JavaRuntimeEntrypoint>.Empty,
new JavaRuntimeTraceSummary(
StartTime: DateTimeOffset.MinValue,
EndTime: DateTimeOffset.MinValue,
JavaVersion: null,
JavaVendor: null,
JvmName: null,
JvmArgs: null,
ClassLoadCount: 0,
ServiceLoaderCount: 0,
NativeLoadCount: 0,
ReflectionCount: 0,
ResourceAccessCount: 0,
ModuleResolveCount: 0),
ImmutableArray<JavaRuntimeIngestionWarning>.Empty,
string.Empty);
}
/// <summary>
/// A runtime edge observed during Java execution.
/// These are append-only edges that augment static analysis with runtime evidence.
/// </summary>
/// <param name="EdgeId">Deterministic edge identifier.</param>
/// <param name="SourceClass">Class that initiated the load/lookup.</param>
/// <param name="TargetClass">Class/resource/library that was loaded.</param>
/// <param name="EdgeType">Type of runtime edge.</param>
/// <param name="Reason">Detailed reason code for the edge.</param>
/// <param name="Timestamp">When the edge was observed.</param>
/// <param name="Source">JAR/module where target was loaded from.</param>
/// <param name="SourceHash">SHA-256 hash of source path.</param>
/// <param name="Confidence">Confidence level (runtime edges are typically 1.0).</param>
/// <param name="Details">Additional details about the edge.</param>
internal sealed record JavaRuntimeEdge(
string EdgeId,
string? SourceClass,
string TargetClass,
JavaRuntimeEdgeType EdgeType,
JavaRuntimeEdgeReason Reason,
DateTimeOffset Timestamp,
string? Source,
string? SourceHash,
double Confidence,
string? Details);
/// <summary>
/// An entrypoint discovered through runtime execution.
/// These are classes/methods that were actually invoked during execution.
/// </summary>
/// <param name="EntrypointId">Deterministic identifier.</param>
/// <param name="ClassName">Fully qualified class name.</param>
/// <param name="MethodName">Method name if applicable.</param>
/// <param name="EntrypointType">Type of runtime entrypoint.</param>
/// <param name="FirstSeen">First observation timestamp.</param>
/// <param name="InvocationCount">Number of times this entrypoint was observed.</param>
/// <param name="Source">JAR/module containing the entrypoint.</param>
/// <param name="SourceHash">SHA-256 hash of source path.</param>
/// <param name="Confidence">Confidence level.</param>
internal sealed record JavaRuntimeEntrypoint(
string EntrypointId,
string ClassName,
string? MethodName,
JavaRuntimeEntrypointType EntrypointType,
DateTimeOffset FirstSeen,
int InvocationCount,
string? Source,
string? SourceHash,
double Confidence);
/// <summary>
/// Warning encountered during runtime ingestion.
/// </summary>
/// <param name="WarningCode">Machine-readable warning code.</param>
/// <param name="Message">Human-readable message.</param>
/// <param name="Line">Line number in trace file if applicable.</param>
/// <param name="Details">Additional context.</param>
internal sealed record JavaRuntimeIngestionWarning(
string WarningCode,
string Message,
int? Line,
string? Details);
/// <summary>
/// Types of runtime edges (observed during execution).
/// </summary>
internal enum JavaRuntimeEdgeType
{
/// <summary>Class was loaded during runtime.</summary>
RuntimeClass,
/// <summary>ServiceLoader discovered provider at runtime.</summary>
RuntimeSpi,
/// <summary>Native library was loaded at runtime.</summary>
RuntimeNativeLoad,
/// <summary>Reflection-based class access at runtime.</summary>
RuntimeReflection,
/// <summary>Resource was accessed at runtime.</summary>
RuntimeResource,
/// <summary>Module was resolved at runtime.</summary>
RuntimeModule,
}
/// <summary>
/// Reason codes for runtime edges (more specific than edge type).
/// </summary>
internal enum JavaRuntimeEdgeReason
{
// Class loading reasons
ClassLoadBootstrap,
ClassLoadPlatform,
ClassLoadApplication,
ClassLoadCustom,
// ServiceLoader reasons
ServiceLoaderExplicit,
ServiceLoaderModuleInfo,
ServiceLoaderMetaInf,
// Native load reasons
SystemLoad,
SystemLoadLibrary,
RuntimeLoad,
RuntimeLoadLibrary,
NativeLoadFailure,
// Reflection reasons
ClassForName,
ClassNewInstance,
ConstructorNewInstance,
MethodInvoke,
// Resource reasons
GetResource,
GetResourceAsStream,
GetResources,
// Module reasons
ModuleRequires,
ModuleOpens,
ModuleExports,
}
/// <summary>
/// Types of runtime entrypoints (discovered during execution).
/// </summary>
internal enum JavaRuntimeEntrypointType
{
/// <summary>Main method was executed.</summary>
MainMethod,
/// <summary>ServiceLoader provider was instantiated.</summary>
ServiceProvider,
/// <summary>Reflection target was accessed.</summary>
ReflectionTarget,
/// <summary>Native method was called (JNI callback).</summary>
NativeCallback,
/// <summary>CDI/Spring bean was instantiated.</summary>
ManagedBean,
/// <summary>Servlet/filter was initialized.</summary>
WebComponent,
}
/// <summary>
/// Configuration for runtime ingestion behavior.
/// </summary>
/// <param name="ScrubPaths">Whether to hash/scrub file paths for privacy.</param>
/// <param name="IncludeJdkClasses">Whether to include JDK internal class loads.</param>
/// <param name="IncludeStatistics">Whether to process statistics events.</param>
/// <param name="MaxEvents">Maximum number of events to process (0 = unlimited).</param>
/// <param name="DeduplicateEdges">Whether to deduplicate identical edges.</param>
internal sealed record JavaRuntimeIngestionConfig(
bool ScrubPaths = true,
bool IncludeJdkClasses = false,
bool IncludeStatistics = true,
int MaxEvents = 0,
bool DeduplicateEdges = true)
{
public static readonly JavaRuntimeIngestionConfig Default = new();
}

View File

@@ -0,0 +1,233 @@
using System.Collections.Immutable;
using StellaOps.Scanner.Analyzers.Lang.Java.Internal.Resolver;
namespace StellaOps.Scanner.Analyzers.Lang.Java.Internal.Runtime;
/// <summary>
/// Main entry point for Java runtime trace ingestion (task 21-010).
/// Ingests NDJSON trace files from Java agent or JFR and produces runtime edges.
/// </summary>
internal static class JavaRuntimeIngestor
{
/// <summary>
/// Ingests a runtime trace file and returns runtime edges and entrypoints.
/// </summary>
/// <param name="stream">Stream containing NDJSON trace data.</param>
/// <param name="config">Ingestion configuration.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>Ingestion result with runtime edges and entrypoints.</returns>
public static async Task<JavaRuntimeIngestion> IngestAsync(
Stream stream,
JavaRuntimeIngestionConfig? config = null,
CancellationToken cancellationToken = default)
{
config ??= JavaRuntimeIngestionConfig.Default;
// Parse events from NDJSON
var (events, warnings, contentHash) = await JavaRuntimeEventParser.ParseAsync(
stream,
config,
cancellationToken);
// Resolve edges from events
return JavaRuntimeEdgeResolver.ResolveFromEvents(
events,
warnings,
contentHash,
config,
cancellationToken);
}
/// <summary>
/// Ingests a runtime trace file from a file path.
/// </summary>
public static async Task<JavaRuntimeIngestion> IngestFromFileAsync(
string filePath,
JavaRuntimeIngestionConfig? config = null,
CancellationToken cancellationToken = default)
{
await using var stream = File.OpenRead(filePath);
return await IngestAsync(stream, config, cancellationToken);
}
/// <summary>
/// Merges runtime edges into an existing entrypoint resolution.
/// Creates a new resolution with combined static and runtime evidence.
/// </summary>
/// <param name="staticResolution">Resolution from static analysis (21-005/006/007/008).</param>
/// <param name="runtimeIngestion">Ingestion result from runtime trace.</param>
/// <returns>Combined resolution with runtime edges appended.</returns>
public static JavaEntrypointResolution MergeRuntimeEdges(
JavaEntrypointResolution staticResolution,
JavaRuntimeIngestion runtimeIngestion)
{
ArgumentNullException.ThrowIfNull(staticResolution);
ArgumentNullException.ThrowIfNull(runtimeIngestion);
// Convert runtime edges to resolved edges
var convertedEdges = runtimeIngestion.RuntimeEdges
.Select(ConvertRuntimeEdge)
.ToImmutableArray();
// Convert runtime entrypoints to resolved entrypoints
var convertedEntrypoints = runtimeIngestion.RuntimeEntrypoints
.Select(ConvertRuntimeEntrypoint)
.ToImmutableArray();
// Merge edges (runtime edges appended after static)
var allEdges = staticResolution.Edges.AddRange(convertedEdges);
// Merge entrypoints (avoid duplicates by class name)
var existingClasses = staticResolution.Entrypoints
.Select(e => e.ClassFqcn)
.ToHashSet();
var newEntrypoints = convertedEntrypoints
.Where(e => !existingClasses.Contains(e.ClassFqcn))
.ToImmutableArray();
var allEntrypoints = staticResolution.Entrypoints.AddRange(newEntrypoints);
// Add runtime warnings as resolution warnings
var runtimeWarnings = runtimeIngestion.Warnings
.Select(w => new JavaResolutionWarning(
w.WarningCode,
w.Message,
null,
w.Details))
.ToImmutableArray();
var allWarnings = staticResolution.Warnings.AddRange(runtimeWarnings);
// Recalculate statistics
var statistics = RecalculateStatistics(
allEntrypoints,
staticResolution.Components,
allEdges,
staticResolution.Statistics.ResolutionDuration);
return new JavaEntrypointResolution(
allEntrypoints,
staticResolution.Components,
allEdges,
statistics,
allWarnings);
}
private static JavaResolvedEdge ConvertRuntimeEdge(JavaRuntimeEdge edge)
{
var edgeType = edge.EdgeType switch
{
JavaRuntimeEdgeType.RuntimeClass => JavaEdgeType.ReflectionLoad,
JavaRuntimeEdgeType.RuntimeSpi => JavaEdgeType.ServiceProvider,
JavaRuntimeEdgeType.RuntimeNativeLoad => JavaEdgeType.JniNativeLib,
JavaRuntimeEdgeType.RuntimeReflection => JavaEdgeType.ReflectionLoad,
JavaRuntimeEdgeType.RuntimeResource => JavaEdgeType.ResourceBundle,
JavaRuntimeEdgeType.RuntimeModule => JavaEdgeType.JpmsRequires,
_ => JavaEdgeType.ReflectionLoad,
};
var reason = edge.Reason switch
{
JavaRuntimeEdgeReason.ClassLoadBootstrap => JavaEdgeReason.ClassLoaderLoadClass,
JavaRuntimeEdgeReason.ClassLoadPlatform => JavaEdgeReason.ClassLoaderLoadClass,
JavaRuntimeEdgeReason.ClassLoadApplication => JavaEdgeReason.ClassLoaderLoadClass,
JavaRuntimeEdgeReason.ClassLoadCustom => JavaEdgeReason.ClassLoaderLoadClass,
JavaRuntimeEdgeReason.ServiceLoaderExplicit => JavaEdgeReason.MetaInfServices,
JavaRuntimeEdgeReason.ServiceLoaderModuleInfo => JavaEdgeReason.ModuleInfoProvides,
JavaRuntimeEdgeReason.ServiceLoaderMetaInf => JavaEdgeReason.MetaInfServices,
JavaRuntimeEdgeReason.SystemLoad => JavaEdgeReason.SystemLoad,
JavaRuntimeEdgeReason.SystemLoadLibrary => JavaEdgeReason.SystemLoadLibrary,
JavaRuntimeEdgeReason.RuntimeLoad => JavaEdgeReason.RuntimeLoadLibrary,
JavaRuntimeEdgeReason.RuntimeLoadLibrary => JavaEdgeReason.RuntimeLoadLibrary,
JavaRuntimeEdgeReason.ClassForName => JavaEdgeReason.ClassForName,
JavaRuntimeEdgeReason.ClassNewInstance => JavaEdgeReason.ConstructorNewInstance,
JavaRuntimeEdgeReason.ConstructorNewInstance => JavaEdgeReason.ConstructorNewInstance,
JavaRuntimeEdgeReason.MethodInvoke => JavaEdgeReason.MethodInvoke,
JavaRuntimeEdgeReason.GetResource => JavaEdgeReason.ResourceReference,
JavaRuntimeEdgeReason.GetResourceAsStream => JavaEdgeReason.ResourceReference,
JavaRuntimeEdgeReason.GetResources => JavaEdgeReason.ResourceReference,
JavaRuntimeEdgeReason.ModuleRequires => JavaEdgeReason.JpmsRequiresTransitive,
_ => JavaEdgeReason.ClassForName,
};
return new JavaResolvedEdge(
EdgeId: edge.EdgeId,
SourceId: edge.SourceClass ?? "runtime",
TargetId: edge.TargetClass,
EdgeType: edgeType,
Reason: reason,
Confidence: edge.Confidence,
SegmentIdentifier: edge.Source ?? "runtime",
Details: $"[runtime] {edge.Details}");
}
private static JavaResolvedEntrypoint ConvertRuntimeEntrypoint(JavaRuntimeEntrypoint entry)
{
var entrypointType = entry.EntrypointType switch
{
JavaRuntimeEntrypointType.MainMethod => JavaEntrypointType.MainClass,
JavaRuntimeEntrypointType.ServiceProvider => JavaEntrypointType.ServiceProvider,
JavaRuntimeEntrypointType.ReflectionTarget => JavaEntrypointType.ServiceProvider, // Mapped to ServiceProvider for simplicity
JavaRuntimeEntrypointType.NativeCallback => JavaEntrypointType.NativeMethod,
JavaRuntimeEntrypointType.ManagedBean => JavaEntrypointType.CdiObserver,
JavaRuntimeEntrypointType.WebComponent => JavaEntrypointType.Servlet,
_ => JavaEntrypointType.ServiceProvider,
};
return new JavaResolvedEntrypoint(
EntrypointId: entry.EntrypointId,
ClassFqcn: entry.ClassName,
MethodName: entry.MethodName,
MethodDescriptor: null,
EntrypointType: entrypointType,
SegmentIdentifier: entry.Source ?? "runtime",
Framework: null,
Confidence: entry.Confidence,
ResolutionPath: ImmutableArray.Create("runtime-trace"),
Metadata: ImmutableDictionary<string, string>.Empty
.Add("runtime.invocation_count", entry.InvocationCount.ToString())
.Add("runtime.first_seen", entry.FirstSeen.ToString("O")));
}
private static JavaResolutionStatistics RecalculateStatistics(
ImmutableArray<JavaResolvedEntrypoint> entrypoints,
ImmutableArray<JavaResolvedComponent> components,
ImmutableArray<JavaResolvedEdge> edges,
TimeSpan originalDuration)
{
var entrypointsByType = entrypoints
.GroupBy(e => e.EntrypointType)
.ToImmutableDictionary(g => g.Key, g => g.Count());
var edgesByType = edges
.GroupBy(e => e.EdgeType)
.ToImmutableDictionary(g => g.Key, g => g.Count());
var entrypointsByFramework = entrypoints
.Where(e => e.Framework is not null)
.GroupBy(e => e.Framework!)
.ToImmutableDictionary(g => g.Key, g => g.Count());
var highConfidence = entrypoints.Count(e => e.Confidence >= 0.8);
var mediumConfidence = entrypoints.Count(e => e.Confidence >= 0.5 && e.Confidence < 0.8);
var lowConfidence = entrypoints.Count(e => e.Confidence < 0.5);
var signedComponents = components.Count(c => c.IsSigned);
var modularComponents = components.Count(c => c.ModuleInfo is not null);
return new JavaResolutionStatistics(
TotalEntrypoints: entrypoints.Length,
TotalComponents: components.Length,
TotalEdges: edges.Length,
EntrypointsByType: entrypointsByType,
EdgesByType: edgesByType,
EntrypointsByFramework: entrypointsByFramework,
HighConfidenceCount: highConfidence,
MediumConfidenceCount: mediumConfidence,
LowConfidenceCount: lowConfidence,
SignedComponents: signedComponents,
ModularComponents: modularComponents,
ResolutionDuration: originalDuration);
}
}