226 lines
9.9 KiB
C#
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);
|
|
}
|
|
}
|
|
}
|