using System.IO.Compression; 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.Reflection; using StellaOps.Scanner.Analyzers.Lang.Tests.TestUtilities; namespace StellaOps.Scanner.Analyzers.Lang.Java.Tests; public sealed class JavaReflectionAnalyzerTests { [Fact] public void Analyze_ClassForNameLiteral_ProducesEdge() { var root = TestPaths.CreateTemporaryDirectory(); try { var jarPath = Path.Combine(root, "libs", "reflect.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/Reflective.class"); var bytes = JavaClassFileFactory.CreateClassForNameInvoker("com/example/Reflective", "com.example.Plugin"); 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 = JavaReflectionAnalyzer.Analyze(classPath, cancellationToken); var edge = Assert.Single(analysis.Edges); Assert.Equal("com.example.Reflective", edge.SourceClass); Assert.Equal("com.example.Plugin", edge.TargetType); Assert.Equal(JavaReflectionReason.ClassForName, edge.Reason); Assert.Equal(JavaReflectionConfidence.High, edge.Confidence); } finally { TestPaths.SafeDelete(root); } } [Fact] public void Analyze_TcclUsage_ProducesWarning() { var root = TestPaths.CreateTemporaryDirectory(); try { var jarPath = Path.Combine(root, "libs", "tccl.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/Tccl.class"); var bytes = JavaClassFileFactory.CreateTcclChecker("com/example/Tccl"); 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 = JavaReflectionAnalyzer.Analyze(classPath, cancellationToken); Assert.Empty(analysis.Edges); var warning = Assert.Single(analysis.Warnings); Assert.Equal("tccl", warning.WarningCode); Assert.Equal("com.example.Tccl", warning.SourceClass); } finally { TestPaths.SafeDelete(root); } } [Fact] public void Analyze_SpringBootFatJar_ScansEmbeddedAndBootSegments() { var root = TestPaths.CreateTemporaryDirectory(); try { JavaFixtureBuilder.CreateSpringBootFatJar(root, "apps/app-fat.jar"); 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 = JavaReflectionAnalyzer.Analyze(classPath, cancellationToken); // Expect at least one edge originating from BOOT-INF classes Assert.Contains(analysis.Edges, edge => edge.SourceClass == "com.example.App" && edge.Reason == JavaReflectionReason.ClassForName); Assert.Contains(analysis.Edges, edge => edge.SourceClass == "com.example.Lib" && edge.Reason == JavaReflectionReason.ClassForName); } finally { TestPaths.SafeDelete(root); } } [Fact] public void Analyze_ClassResourceLookup_ProducesResourceEdge() { var root = TestPaths.CreateTemporaryDirectory(); try { var jarPath = Path.Combine(root, "libs", "resources.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/Resources.class"); var bytes = JavaClassFileFactory.CreateClassResourceLookup("com/example/Resources", "/META-INF/plugin.properties"); 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 = JavaReflectionAnalyzer.Analyze(classPath, cancellationToken); var edge = Assert.Single(analysis.Edges.Where(edge => edge.Reason == JavaReflectionReason.ResourceLookup)); Assert.Equal("com.example.Resources", edge.SourceClass); Assert.Equal("/META-INF/plugin.properties", edge.TargetType); Assert.Equal(JavaReflectionConfidence.High, edge.Confidence); } finally { TestPaths.SafeDelete(root); } } }