Files
git.stella-ops.org/src/Scanner/__Tests/StellaOps.Scanner.Analyzers.Lang.Java.Tests/Java/JavaJniAnalyzerTests.cs
2026-01-08 08:54:27 +02:00

226 lines
9.9 KiB
C#

using System.IO.Compression;
using Xunit;
using System.Threading;
using StellaOps.Scanner.Analyzers.Lang.Java.Internal;
using StellaOps.Scanner.Analyzers.Lang.Java.Internal.ClassPath;
using StellaOps.Scanner.Analyzers.Lang.Java.Internal.Jni;
using StellaOps.Scanner.Analyzers.Lang.Tests.TestUtilities;
namespace StellaOps.Scanner.Analyzers.Lang.Java.Tests;
/// <summary>
/// Tests for SCANNER-ANALYZERS-JAVA-21-006: JNI/native hint scanner with edge emission.
/// </summary>
public sealed class JavaJniAnalyzerTests
{
[Fact]
public void Analyze_NativeMethod_ProducesEdge()
{
var root = TestPaths.CreateTemporaryDirectory();
try
{
var jarPath = Path.Combine(root, "libs", "jni.jar");
Directory.CreateDirectory(Path.GetDirectoryName(jarPath)!);
using (var archive = new ZipArchive(new FileStream(jarPath, FileMode.Create, FileAccess.ReadWrite, FileShare.None), ZipArchiveMode.Create, leaveOpen: false))
{
var entry = archive.CreateEntry("com/example/Native.class");
var bytes = JavaClassFileFactory.CreateNativeMethodClass("com/example/Native", "nativeMethod0");
using var stream = entry.Open();
stream.Write(bytes);
}
var cancellationToken = TestContext.Current.CancellationToken;
var context = new LanguageAnalyzerContext(root, TimeProvider.System);
var workspace = JavaWorkspaceNormalizer.Normalize(context, cancellationToken);
var classPath = JavaClassPathBuilder.Build(workspace, cancellationToken);
var analysis = JavaJniAnalyzer.Analyze(classPath, cancellationToken);
var edge = Assert.Single(analysis.Edges);
Assert.Equal("com.example.Native", edge.SourceClass);
Assert.Equal(JavaJniReason.NativeMethod, edge.Reason);
Assert.Equal(JavaJniConfidence.High, edge.Confidence);
Assert.Equal("nativeMethod0", edge.MethodName);
Assert.Equal("()V", edge.MethodDescriptor);
Assert.Null(edge.TargetLibrary);
Assert.Equal(-1, edge.InstructionOffset);
}
finally
{
TestPaths.SafeDelete(root);
}
}
[Fact]
public void Analyze_SystemLoadLibrary_ProducesEdgeWithLibraryName()
{
var root = TestPaths.CreateTemporaryDirectory();
try
{
var jarPath = Path.Combine(root, "libs", "loader.jar");
Directory.CreateDirectory(Path.GetDirectoryName(jarPath)!);
using (var archive = new ZipArchive(new FileStream(jarPath, FileMode.Create, FileAccess.ReadWrite, FileShare.None), ZipArchiveMode.Create, leaveOpen: false))
{
var entry = archive.CreateEntry("com/example/Loader.class");
var bytes = JavaClassFileFactory.CreateSystemLoadLibraryInvoker("com/example/Loader", "nativelib");
using var stream = entry.Open();
stream.Write(bytes);
}
var cancellationToken = TestContext.Current.CancellationToken;
var context = new LanguageAnalyzerContext(root, TimeProvider.System);
var workspace = JavaWorkspaceNormalizer.Normalize(context, cancellationToken);
var classPath = JavaClassPathBuilder.Build(workspace, cancellationToken);
var analysis = JavaJniAnalyzer.Analyze(classPath, cancellationToken);
var edge = Assert.Single(analysis.Edges);
Assert.Equal("com.example.Loader", edge.SourceClass);
Assert.Equal(JavaJniReason.SystemLoadLibrary, edge.Reason);
Assert.Equal(JavaJniConfidence.High, edge.Confidence);
Assert.Equal("nativelib", edge.TargetLibrary);
Assert.Equal("loadNative", edge.MethodName);
Assert.True(edge.InstructionOffset >= 0);
}
finally
{
TestPaths.SafeDelete(root);
}
}
[Fact]
public void Analyze_SystemLoad_ProducesEdgeWithPath()
{
var root = TestPaths.CreateTemporaryDirectory();
try
{
var jarPath = Path.Combine(root, "libs", "pathloader.jar");
Directory.CreateDirectory(Path.GetDirectoryName(jarPath)!);
using (var archive = new ZipArchive(new FileStream(jarPath, FileMode.Create, FileAccess.ReadWrite, FileShare.None), ZipArchiveMode.Create, leaveOpen: false))
{
var entry = archive.CreateEntry("com/example/PathLoader.class");
var bytes = JavaClassFileFactory.CreateSystemLoadInvoker("com/example/PathLoader", "/usr/lib/libnative.so");
using var stream = entry.Open();
stream.Write(bytes);
}
var cancellationToken = TestContext.Current.CancellationToken;
var context = new LanguageAnalyzerContext(root, TimeProvider.System);
var workspace = JavaWorkspaceNormalizer.Normalize(context, cancellationToken);
var classPath = JavaClassPathBuilder.Build(workspace, cancellationToken);
var analysis = JavaJniAnalyzer.Analyze(classPath, cancellationToken);
var edge = Assert.Single(analysis.Edges);
Assert.Equal("com.example.PathLoader", edge.SourceClass);
Assert.Equal(JavaJniReason.SystemLoad, edge.Reason);
Assert.Equal(JavaJniConfidence.High, edge.Confidence);
Assert.Equal("/usr/lib/libnative.so", edge.TargetLibrary);
}
finally
{
TestPaths.SafeDelete(root);
}
}
[Fact]
public void Analyze_MultipleJniUsages_ProducesMultipleEdges()
{
var root = TestPaths.CreateTemporaryDirectory();
try
{
var jarPath = Path.Combine(root, "libs", "multi.jar");
Directory.CreateDirectory(Path.GetDirectoryName(jarPath)!);
using (var archive = new ZipArchive(new FileStream(jarPath, FileMode.Create, FileAccess.ReadWrite, FileShare.None), ZipArchiveMode.Create, leaveOpen: false))
{
// Class with native method
var nativeEntry = archive.CreateEntry("com/example/NativeWrapper.class");
var nativeBytes = JavaClassFileFactory.CreateNativeMethodClass("com/example/NativeWrapper", "init");
using (var stream = nativeEntry.Open())
{
stream.Write(nativeBytes);
}
// Class with loadLibrary
var loaderEntry = archive.CreateEntry("com/example/LibLoader.class");
var loaderBytes = JavaClassFileFactory.CreateSystemLoadLibraryInvoker("com/example/LibLoader", "jniwrapper");
using (var stream = loaderEntry.Open())
{
stream.Write(loaderBytes);
}
}
var cancellationToken = TestContext.Current.CancellationToken;
var context = new LanguageAnalyzerContext(root, TimeProvider.System);
var workspace = JavaWorkspaceNormalizer.Normalize(context, cancellationToken);
var classPath = JavaClassPathBuilder.Build(workspace, cancellationToken);
var analysis = JavaJniAnalyzer.Analyze(classPath, cancellationToken);
Assert.Equal(2, analysis.Edges.Length);
Assert.Contains(analysis.Edges, e => e.Reason == JavaJniReason.NativeMethod && e.SourceClass == "com.example.NativeWrapper");
Assert.Contains(analysis.Edges, e => e.Reason == JavaJniReason.SystemLoadLibrary && e.TargetLibrary == "jniwrapper");
}
finally
{
TestPaths.SafeDelete(root);
}
}
[Fact]
public void Analyze_EmptyClassPath_ReturnsEmpty()
{
var root = TestPaths.CreateTemporaryDirectory();
try
{
var cancellationToken = TestContext.Current.CancellationToken;
var context = new LanguageAnalyzerContext(root, TimeProvider.System);
var workspace = JavaWorkspaceNormalizer.Normalize(context, cancellationToken);
var classPath = JavaClassPathBuilder.Build(workspace, cancellationToken);
var analysis = JavaJniAnalyzer.Analyze(classPath, cancellationToken);
Assert.Same(JavaJniAnalysis.Empty, analysis);
}
finally
{
TestPaths.SafeDelete(root);
}
}
[Fact]
public void Analyze_EdgesIncludeReasonCodesAndConfidence()
{
var root = TestPaths.CreateTemporaryDirectory();
try
{
var jarPath = Path.Combine(root, "libs", "reasons.jar");
Directory.CreateDirectory(Path.GetDirectoryName(jarPath)!);
using (var archive = new ZipArchive(new FileStream(jarPath, FileMode.Create, FileAccess.ReadWrite, FileShare.None), ZipArchiveMode.Create, leaveOpen: false))
{
var entry = archive.CreateEntry("com/example/JniClass.class");
var bytes = JavaClassFileFactory.CreateSystemLoadLibraryInvoker("com/example/JniClass", "mylib");
using var stream = entry.Open();
stream.Write(bytes);
}
var cancellationToken = TestContext.Current.CancellationToken;
var context = new LanguageAnalyzerContext(root, TimeProvider.System);
var workspace = JavaWorkspaceNormalizer.Normalize(context, cancellationToken);
var classPath = JavaClassPathBuilder.Build(workspace, cancellationToken);
var analysis = JavaJniAnalyzer.Analyze(classPath, cancellationToken);
var edge = Assert.Single(analysis.Edges);
// Verify reason code is set
Assert.Equal(JavaJniReason.SystemLoadLibrary, edge.Reason);
// Verify confidence is set
Assert.Equal(JavaJniConfidence.High, edge.Confidence);
// Verify details are present
Assert.NotNull(edge.Details);
Assert.Contains("mylib", edge.Details);
}
finally
{
TestPaths.SafeDelete(root);
}
}
}