// Copyright (c) StellaOps. All rights reserved. // Licensed under AGPL-3.0-or-later. See LICENSE in the project root. using System.Collections.Immutable; using FluentAssertions; using Microsoft.Extensions.Logging.Abstractions; using StellaOps.BinaryIndex.Disassembly; using Xunit; namespace StellaOps.BinaryIndex.Semantic.Tests; [Trait("Category", "Unit")] public class IrLiftingServiceTests { private readonly IrLiftingService _sut; public IrLiftingServiceTests() { _sut = new IrLiftingService(NullLogger.Instance); } [Theory] [InlineData(CpuArchitecture.X86)] [InlineData(CpuArchitecture.X86_64)] [InlineData(CpuArchitecture.ARM32)] [InlineData(CpuArchitecture.ARM64)] public void SupportsArchitecture_ShouldReturnTrue_ForSupportedArchitectures(CpuArchitecture arch) { // Act var result = _sut.SupportsArchitecture(arch); // Assert result.Should().BeTrue(); } [Theory] [InlineData(CpuArchitecture.MIPS32)] [InlineData(CpuArchitecture.RISCV64)] [InlineData(CpuArchitecture.Unknown)] public void SupportsArchitecture_ShouldReturnFalse_ForUnsupportedArchitectures(CpuArchitecture arch) { // Act var result = _sut.SupportsArchitecture(arch); // Assert result.Should().BeFalse(); } [Fact] public async Task LiftToIrAsync_ShouldLiftSimpleInstructions() { // Arrange var instructions = new List { CreateInstruction(0x1000, "MOV", InstructionKind.Move, "RAX", "RBX"), CreateInstruction(0x1004, "ADD", InstructionKind.Arithmetic, "RAX", "RCX"), CreateInstruction(0x1008, "RET", InstructionKind.Return) }; // Act var result = await _sut.LiftToIrAsync( instructions, "test_func", 0x1000, CpuArchitecture.X86_64); // Assert result.Should().NotBeNull(); result.Name.Should().Be("test_func"); result.Address.Should().Be(0x1000); result.Statements.Should().HaveCount(3); result.BasicBlocks.Should().NotBeEmpty(); } [Fact] public async Task LiftToIrAsync_ShouldCreateBasicBlocksOnBranches() { // Arrange var instructions = new List { CreateInstruction(0x1000, "MOV", InstructionKind.Move, "RAX", "0"), CreateInstruction(0x1004, "CMP", InstructionKind.Compare, "RAX", "10"), CreateInstruction(0x1008, "JE", InstructionKind.ConditionalBranch, "0x1020"), CreateInstruction(0x100C, "ADD", InstructionKind.Arithmetic, "RAX", "1"), CreateInstruction(0x1010, "RET", InstructionKind.Return) }; // Act var result = await _sut.LiftToIrAsync( instructions, "branch_func", 0x1000, CpuArchitecture.X86_64); // Assert result.BasicBlocks.Should().HaveCountGreaterThan(1); result.Cfg.Edges.Should().NotBeEmpty(); } [Fact] public async Task LiftToIrAsync_ShouldThrow_ForUnsupportedArchitecture() { // Arrange var instructions = new List { CreateInstruction(0x1000, "NOP", InstructionKind.Nop) }; // Act var act = () => _sut.LiftToIrAsync( instructions, "test", 0x1000, CpuArchitecture.MIPS32); // Assert await act.Should().ThrowAsync(); } [Fact] public async Task TransformToSsaAsync_ShouldVersionVariables() { // Arrange var instructions = new List { CreateInstruction(0x1000, "MOV", InstructionKind.Move, "RAX", "0"), CreateInstruction(0x1004, "ADD", InstructionKind.Arithmetic, "RAX", "1"), CreateInstruction(0x1008, "ADD", InstructionKind.Arithmetic, "RAX", "2"), CreateInstruction(0x100C, "RET", InstructionKind.Return) }; var lifted = await _sut.LiftToIrAsync( instructions, "ssa_test", 0x1000, CpuArchitecture.X86_64); // Act var ssa = await _sut.TransformToSsaAsync(lifted); // Assert ssa.Should().NotBeNull(); ssa.Name.Should().Be("ssa_test"); ssa.Statements.Should().HaveCount(4); // RAX should have multiple versions var raxVersions = ssa.Statements .Where(s => s.Destination?.BaseName == "RAX") .Select(s => s.Destination!.Version) .Distinct() .ToList(); raxVersions.Should().HaveCountGreaterThan(1); } [Fact] public async Task TransformToSsaAsync_ShouldBuildDefUseChains() { // Arrange var instructions = new List { CreateInstruction(0x1000, "MOV", InstructionKind.Move, "RAX", "0"), CreateInstruction(0x1004, "ADD", InstructionKind.Arithmetic, "RBX", "RAX"), CreateInstruction(0x1008, "RET", InstructionKind.Return) }; var lifted = await _sut.LiftToIrAsync( instructions, "defuse_test", 0x1000, CpuArchitecture.X86_64); // Act var ssa = await _sut.TransformToSsaAsync(lifted); // Assert ssa.DefUse.Should().NotBeNull(); ssa.DefUse.Definitions.Should().NotBeEmpty(); } private static DisassembledInstruction CreateInstruction( ulong address, string mnemonic, InstructionKind kind, params string[] operands) { var ops = operands.Select((o, i) => { if (long.TryParse(o, out var val)) { return new Operand(OperandType.Immediate, o, val); } if (o.StartsWith("0x", StringComparison.OrdinalIgnoreCase)) { return new Operand(OperandType.Address, o); } return new Operand(OperandType.Register, o, Register: o); }).ToImmutableArray(); return new DisassembledInstruction( address, [0x90], // NOP placeholder mnemonic, string.Join(", ", operands), kind, ops); } }