Add call graph fixtures for various languages and scenarios
Some checks failed
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
Export Center CI / export-ci (push) Has been cancelled
Findings Ledger CI / build-test (push) Has been cancelled
Findings Ledger CI / migration-validation (push) Has been cancelled
Findings Ledger CI / generate-manifest (push) Has been cancelled
Lighthouse CI / Lighthouse Audit (push) Has been cancelled
Lighthouse CI / Axe Accessibility Audit (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Reachability Corpus Validation / validate-corpus (push) Has been cancelled
Reachability Corpus Validation / validate-ground-truths (push) Has been cancelled
Scanner Analyzers / Discover Analyzers (push) Has been cancelled
Scanner Analyzers / Validate Test Fixtures (push) Has been cancelled
Signals CI & Image / signals-ci (push) Has been cancelled
Signals Reachability Scoring & Events / reachability-smoke (push) Has been cancelled
Reachability Corpus Validation / determinism-check (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
Signals Reachability Scoring & Events / sign-and-upload (push) Has been cancelled

- Introduced `all-edge-reasons.json` to test edge resolution reasons in .NET.
- Added `all-visibility-levels.json` to validate method visibility levels in .NET.
- Created `dotnet-aspnetcore-minimal.json` for a minimal ASP.NET Core application.
- Included `go-gin-api.json` for a Go Gin API application structure.
- Added `java-spring-boot.json` for the Spring PetClinic application in Java.
- Introduced `legacy-no-schema.json` for legacy application structure without schema.
- Created `node-express-api.json` for an Express.js API application structure.
This commit is contained in:
master
2025-12-16 10:44:24 +02:00
parent 4391f35d8a
commit 5a480a3c2a
223 changed files with 19367 additions and 727 deletions

View File

@@ -108,12 +108,12 @@ internal sealed class JavaCallgraphBuilder
var edgeId = JavaGraphIdentifiers.ComputeEdgeId(callerId, calleeId, edge.InstructionOffset);
var confidence = edge.Confidence == JavaReflectionConfidence.High ? 0.9 : 0.5;
var edgeType = edge.Reason switch
var (edgeType, edgeReason) = edge.Reason switch
{
JavaReflectionReason.ClassForName => JavaEdgeType.Reflection,
JavaReflectionReason.ClassLoaderLoadClass => JavaEdgeType.Reflection,
JavaReflectionReason.ServiceLoaderLoad => JavaEdgeType.ServiceLoader,
_ => JavaEdgeType.Reflection,
JavaReflectionReason.ClassForName => (JavaEdgeType.Reflection, JavaEdgeReason.ReflectionString),
JavaReflectionReason.ClassLoaderLoadClass => (JavaEdgeType.Reflection, JavaEdgeReason.ReflectionString),
JavaReflectionReason.ServiceLoaderLoad => (JavaEdgeType.ServiceLoader, JavaEdgeReason.ServiceLoader),
_ => (JavaEdgeType.Reflection, JavaEdgeReason.ReflectionString),
};
_edges.Add(new JavaCallEdge(
@@ -123,6 +123,7 @@ internal sealed class JavaCallgraphBuilder
CalleePurl: null, // Reflection targets often unknown
CalleeMethodDigest: null,
EdgeType: edgeType,
EdgeReason: edgeReason,
BytecodeOffset: edge.InstructionOffset,
IsResolved: isResolved,
Confidence: confidence));
@@ -229,6 +230,16 @@ internal sealed class JavaCallgraphBuilder
var isSynthetic = (method.AccessFlags & 0x1000) != 0;
var isBridge = (method.AccessFlags & 0x0040) != 0;
// Extract visibility from access flags
var visibility = ExtractVisibility(method.AccessFlags);
// Determine if this method is an entrypoint candidate
// Public non-synthetic methods that aren't constructors or accessors
var isEntrypointCandidate = isPublic &&
!isSynthetic &&
!method.Name.StartsWith("<") &&
!method.Name.StartsWith("lambda$");
var node = new JavaMethodNode(
MethodId: methodId,
ClassName: className,
@@ -241,11 +252,34 @@ internal sealed class JavaCallgraphBuilder
IsStatic: isStatic,
IsPublic: isPublic,
IsSynthetic: isSynthetic,
IsBridge: isBridge);
IsBridge: isBridge,
Visibility: visibility,
IsEntrypointCandidate: isEntrypointCandidate);
_methods.TryAdd(methodId, node);
}
private static JavaVisibility ExtractVisibility(int accessFlags)
{
// ACC_PUBLIC = 0x0001, ACC_PRIVATE = 0x0002, ACC_PROTECTED = 0x0004
if ((accessFlags & 0x0001) != 0)
{
return JavaVisibility.Public;
}
else if ((accessFlags & 0x0002) != 0)
{
return JavaVisibility.Private;
}
else if ((accessFlags & 0x0004) != 0)
{
return JavaVisibility.Protected;
}
else
{
return JavaVisibility.Package; // Package-private (default)
}
}
private void FindSyntheticRoots(string className, JavaClassFileParser.ClassFile classFile, string jarPath)
{
var rootOrder = 0;
@@ -380,13 +414,14 @@ internal sealed class JavaCallgraphBuilder
methodRef.Value.Name,
methodRef.Value.Descriptor);
var edgeType = opcode switch
var (edgeType, edgeReason) = opcode switch
{
0xB8 => JavaEdgeType.InvokeStatic,
0xB6 => JavaEdgeType.InvokeVirtual,
0xB7 => methodRef.Value.Name == "<init>" ? JavaEdgeType.Constructor : JavaEdgeType.InvokeSpecial,
0xB9 => JavaEdgeType.InvokeInterface,
_ => JavaEdgeType.InvokeVirtual,
0xB8 => (JavaEdgeType.InvokeStatic, JavaEdgeReason.DirectCall),
0xB6 => (JavaEdgeType.InvokeVirtual, JavaEdgeReason.VirtualCall),
0xB7 when methodRef.Value.Name == "<init>" => (JavaEdgeType.Constructor, JavaEdgeReason.NewObj),
0xB7 => (JavaEdgeType.InvokeSpecial, JavaEdgeReason.SuperCall),
0xB9 => (JavaEdgeType.InvokeInterface, JavaEdgeReason.InterfaceCall),
_ => (JavaEdgeType.InvokeVirtual, JavaEdgeReason.VirtualCall),
};
// Check if target is resolved (known in our method set)
@@ -403,6 +438,7 @@ internal sealed class JavaCallgraphBuilder
CalleePurl: calleePurl,
CalleeMethodDigest: null, // Would compute if method is in our set
EdgeType: edgeType,
EdgeReason: edgeReason,
BytecodeOffset: instructionOffset,
IsResolved: isResolved,
Confidence: isResolved ? 1.0 : 0.7));
@@ -448,6 +484,7 @@ internal sealed class JavaCallgraphBuilder
CalleePurl: null,
CalleeMethodDigest: null,
EdgeType: JavaEdgeType.InvokeDynamic,
EdgeReason: JavaEdgeReason.DynamicImport,
BytecodeOffset: instructionOffset,
IsResolved: false,
Confidence: 0.3));

View File

@@ -31,6 +31,8 @@ public sealed record JavaReachabilityGraph(
/// <param name="IsPublic">Whether the method is public.</param>
/// <param name="IsSynthetic">Whether the method is synthetic (compiler-generated).</param>
/// <param name="IsBridge">Whether the method is a bridge method.</param>
/// <param name="Visibility">Access visibility (public, private, protected, package).</param>
/// <param name="IsEntrypointCandidate">Whether this method could be an entrypoint (public, controller action, etc.).</param>
public sealed record JavaMethodNode(
string MethodId,
string ClassName,
@@ -43,7 +45,27 @@ public sealed record JavaMethodNode(
bool IsStatic,
bool IsPublic,
bool IsSynthetic,
bool IsBridge);
bool IsBridge,
JavaVisibility Visibility,
bool IsEntrypointCandidate);
/// <summary>
/// Access visibility levels for Java methods.
/// </summary>
public enum JavaVisibility
{
/// <summary>Accessible from anywhere.</summary>
Public,
/// <summary>Accessible only within the same class.</summary>
Private,
/// <summary>Accessible within the same package or subclasses.</summary>
Protected,
/// <summary>Package-private (default access).</summary>
Package
}
/// <summary>
/// A call edge in the Java call graph.
@@ -54,6 +76,7 @@ public sealed record JavaMethodNode(
/// <param name="CalleePurl">PURL of the callee if resolvable.</param>
/// <param name="CalleeMethodDigest">Method digest of the callee.</param>
/// <param name="EdgeType">Type of edge (invoke type).</param>
/// <param name="EdgeReason">Semantic reason for the edge (DirectCall, VirtualCall, etc.).</param>
/// <param name="BytecodeOffset">Bytecode offset where call occurs.</param>
/// <param name="IsResolved">Whether the callee was successfully resolved.</param>
/// <param name="Confidence">Confidence level (1.0 for resolved, lower for heuristic).</param>
@@ -64,6 +87,7 @@ public sealed record JavaCallEdge(
string? CalleePurl,
string? CalleeMethodDigest,
JavaEdgeType EdgeType,
JavaEdgeReason EdgeReason,
int BytecodeOffset,
bool IsResolved,
double Confidence);
@@ -98,6 +122,46 @@ public enum JavaEdgeType
Constructor,
}
/// <summary>
/// Semantic reason for why a Java edge exists.
/// Maps to the schema's EdgeReason enum for explainability.
/// </summary>
public enum JavaEdgeReason
{
/// <summary>Direct static method call (invokestatic).</summary>
DirectCall,
/// <summary>Virtual method dispatch (invokevirtual, invokeinterface).</summary>
VirtualCall,
/// <summary>Reflection-based invocation (Class.forName, Method.invoke).</summary>
ReflectionString,
/// <summary>Dependency injection binding (Spring, Guice).</summary>
DiBinding,
/// <summary>Dynamic lambda or method reference (invokedynamic).</summary>
DynamicImport,
/// <summary>Constructor/object instantiation (invokespecial &lt;init&gt;).</summary>
NewObj,
/// <summary>Super or private method call (invokespecial non-init).</summary>
SuperCall,
/// <summary>ServiceLoader-based service discovery.</summary>
ServiceLoader,
/// <summary>Interface method dispatch.</summary>
InterfaceCall,
/// <summary>Native interop (JNI).</summary>
NativeInterop,
/// <summary>Reason could not be determined.</summary>
Unknown
}
/// <summary>
/// A synthetic root in the Java call graph.
/// </summary>