Files
Yaroze.NET/tests/Yaroze.Tests/Analysis/FunctionAnalyzerTests.cs
T
2026-01-24 17:23:50 +01:00

367 lines
12 KiB
C#

using Xunit;
using Yaroze.Core.Analysis;
using Yaroze.Core.CPU;
namespace Yaroze.Tests.Analysis;
public class FunctionAnalyzerTests
{
[Fact]
public void FunctionAnalyzer_DiscoversSingleFunction()
{
// Arrange - Simple function with just a return
byte[] memory = new byte[4096];
uint baseAddress = 0x80000000;
// main:
// jr $ra
// nop
WriteInstruction(memory, 0, 0x03E00008); // JR $ra
WriteInstruction(memory, 4, 0x00000000); // NOP
var analyzer = new FunctionAnalyzer(memory);
// Act
analyzer.AnalyzeFromEntryPoint(baseAddress);
// Assert
Assert.Single(analyzer.Functions);
Assert.True(analyzer.Functions.ContainsKey(baseAddress));
}
[Fact]
public void FunctionAnalyzer_DiscoversCalledFunction()
{
// Arrange
byte[] memory = new byte[4096];
uint baseAddress = 0x80000000;
uint subAddress = baseAddress + 0x10;
// main:
// jal sub
// nop
// jr $ra
// nop
// sub:
// jr $ra
// nop
WriteInstruction(memory, 0, 0x0C000004); // JAL 0x80000010
WriteInstruction(memory, 4, 0x00000000); // NOP
WriteInstruction(memory, 8, 0x03E00008); // JR $ra
WriteInstruction(memory, 12, 0x00000000); // NOP
WriteInstruction(memory, 16, 0x03E00008); // JR $ra (sub)
WriteInstruction(memory, 20, 0x00000000); // NOP
var analyzer = new FunctionAnalyzer(memory);
// Act
analyzer.AnalyzeFromEntryPoint(baseAddress);
// Assert
Assert.Equal(2, analyzer.Functions.Count);
Assert.True(analyzer.Functions.ContainsKey(baseAddress));
Assert.True(analyzer.Functions.ContainsKey(subAddress));
}
[Fact]
public void FunctionAnalyzer_BuildsCallGraph()
{
// Arrange
byte[] memory = new byte[4096];
uint baseAddress = 0x80000000;
uint subAddress = baseAddress + 0x10;
// main calls sub
WriteInstruction(memory, 0, 0x0C000004); // JAL 0x80000010
WriteInstruction(memory, 4, 0x00000000); // NOP
WriteInstruction(memory, 8, 0x03E00008); // JR $ra
WriteInstruction(memory, 12, 0x00000000); // NOP
WriteInstruction(memory, 16, 0x03E00008); // JR $ra (sub)
WriteInstruction(memory, 20, 0x00000000); // NOP
var analyzer = new FunctionAnalyzer(memory);
// Act
analyzer.AnalyzeFromEntryPoint(baseAddress);
// Assert
var mainFunc = analyzer.Functions[baseAddress];
Assert.Contains(subAddress, mainFunc.CallsTo);
var subFunc = analyzer.Functions[subAddress];
Assert.Contains(baseAddress, subFunc.CalledFrom);
}
[Fact]
public void FunctionAnalyzer_HandlesConditionalBranches()
{
// Arrange - Function with branch
byte[] memory = new byte[4096];
uint baseAddress = 0x80000000;
// main:
// beq $t0, $zero, +2
// nop
// addiu $v0, $zero, 1
// jr $ra
// nop
WriteInstruction(memory, 0, 0x11000002); // BEQ $8, $0, +2
WriteInstruction(memory, 4, 0x00000000); // NOP
WriteInstruction(memory, 8, 0x24020001); // ADDIU $v0, $zero, 1
WriteInstruction(memory, 12, 0x03E00008); // JR $ra
WriteInstruction(memory, 16, 0x00000000); // NOP
var analyzer = new FunctionAnalyzer(memory);
// Act
analyzer.AnalyzeFromEntryPoint(baseAddress);
// Assert - Should have one function with all instructions
Assert.Single(analyzer.Functions);
var func = analyzer.Functions[baseAddress];
Assert.Contains(baseAddress + 0, func.Instructions); // BEQ
Assert.Contains(baseAddress + 4, func.Instructions); // NOP
Assert.Contains(baseAddress + 8, func.Instructions); // ADDIU
Assert.Contains(baseAddress + 12, func.Instructions); // JR
}
[Fact]
public void FunctionAnalyzer_AssignsDefaultNames()
{
// Arrange
byte[] memory = new byte[4096];
uint baseAddress = 0x80000000;
WriteInstruction(memory, 0, 0x03E00008); // JR $ra
WriteInstruction(memory, 4, 0x00000000); // NOP
var analyzer = new FunctionAnalyzer(memory);
// Act
analyzer.AnalyzeFromEntryPoint(baseAddress);
// Assert
var func = analyzer.Functions[baseAddress];
Assert.Equal("func_80000000", func.Name);
}
[Fact(Skip = "ExportCallGraphToDot method removed")]
public void FunctionAnalyzer_ExportsToDotFormat()
{
// Arrange
byte[] memory = new byte[4096];
uint baseAddress = 0x80000000;
uint subAddress = baseAddress + 0x10;
// main calls sub
WriteInstruction(memory, 0, 0x0C000004); // JAL 0x80000010
WriteInstruction(memory, 4, 0x00000000); // NOP
WriteInstruction(memory, 8, 0x03E00008); // JR $ra
WriteInstruction(memory, 12, 0x00000000); // NOP
WriteInstruction(memory, 16, 0x03E00008); // JR $ra (sub)
WriteInstruction(memory, 20, 0x00000000); // NOP
var analyzer = new FunctionAnalyzer(memory);
analyzer.AnalyzeFromEntryPoint(baseAddress);
// Act
// string dot = analyzer.ExportCallGraphToDot();
// Assert
// Assert.Contains("digraph", dot);
// Assert.Contains("func_80000000", dot);
// Assert.Contains("func_80000010", dot);
// Assert.Contains("->", dot);
}
[Fact]
public void FunctionAnalyzer_HandlesUnconditionalJump()
{
// Arrange - Function with unconditional jump
byte[] memory = new byte[4096];
uint baseAddress = 0x80000000;
// main:
// j 0x80000010
// nop
// ...
// addiu $v0, $zero, 1
// jr $ra
// nop
WriteInstruction(memory, 0, 0x08000004); // J 0x80000010
WriteInstruction(memory, 4, 0x00000000); // NOP
WriteInstruction(memory, 8, 0x00000000); // NOP (unreachable)
WriteInstruction(memory, 12, 0x00000000); // NOP (unreachable)
WriteInstruction(memory, 16, 0x24020001); // ADDIU $v0, $zero, 1
WriteInstruction(memory, 20, 0x03E00008); // JR $ra
WriteInstruction(memory, 24, 0x00000000); // NOP
var analyzer = new FunctionAnalyzer(memory);
// Act
analyzer.AnalyzeFromEntryPoint(baseAddress);
// Assert
var func = analyzer.Functions[baseAddress];
Assert.Contains(baseAddress + 0, func.Instructions); // J
Assert.Contains(baseAddress + 16, func.Instructions); // ADDIU
Assert.Contains(baseAddress + 20, func.Instructions); // JR
Assert.DoesNotContain(baseAddress + 8, func.Instructions); // Unreachable
}
[Fact]
public void FunctionAnalyzer_HandlesRecursiveCalls()
{
// Arrange - Recursive factorial function
byte[] memory = new byte[4096];
uint baseAddress = 0x80000000;
// factorial:
// beq $a0, $zero, done
// nop
// addiu $a0, $a0, -1
// jal factorial (recursive)
// nop
// done:
// jr $ra
// nop
WriteInstruction(memory, 0, 0x10800003); // BEQ $a0, $zero, +3
WriteInstruction(memory, 4, 0x00000000); // NOP
WriteInstruction(memory, 8, 0x2484FFFF); // ADDIU $a0, $a0, -1
WriteInstruction(memory, 12, 0x0C000000); // JAL 0x80000000 (self)
WriteInstruction(memory, 16, 0x00000000); // NOP
WriteInstruction(memory, 20, 0x03E00008); // JR $ra
WriteInstruction(memory, 24, 0x00000000); // NOP
var analyzer = new FunctionAnalyzer(memory);
// Act
analyzer.AnalyzeFromEntryPoint(baseAddress);
// Assert - Should have one function that calls itself
Assert.Single(analyzer.Functions);
var func = analyzer.Functions[baseAddress];
Assert.Contains(baseAddress, func.CallsTo); // Calls itself
Assert.Contains(baseAddress, func.CalledFrom); // Called by itself
}
[Fact(Skip = "GetOrCreateFunction method removed")]
public void FunctionAnalyzer_GetOrCreateFunction_CreatesNewFunction()
{
// Arrange
byte[] memory = new byte[4096];
uint baseAddress = 0x80000000;
var analyzer = new FunctionAnalyzer(memory);
// Act
// var func1 = analyzer.GetOrCreateFunction(baseAddress);
// var func2 = analyzer.GetOrCreateFunction(baseAddress);
// Assert - Should return same instance
// Assert.Same(func1, func2);
// Assert.Single(analyzer.Functions);
}
[Fact]
public void FunctionAnalyzer_HandlesMultipleFunctions()
{
// Arrange - Program with 3 functions
byte[] memory = new byte[4096];
uint baseAddress = 0x80000000;
uint func1 = baseAddress + 0x20;
uint func2 = baseAddress + 0x40;
// main:
// jal func1
// nop
// jal func2
// nop
// jr $ra
// nop
WriteInstruction(memory, 0, 0x0C000008); // JAL func1
WriteInstruction(memory, 4, 0x00000000); // NOP
WriteInstruction(memory, 8, 0x0C000010); // JAL func2
WriteInstruction(memory, 12, 0x00000000); // NOP
WriteInstruction(memory, 16, 0x03E00008); // JR $ra
WriteInstruction(memory, 20, 0x00000000); // NOP
// func1:
// jr $ra
// nop
WriteInstruction(memory, 32, 0x03E00008); // JR $ra
WriteInstruction(memory, 36, 0x00000000); // NOP
// func2:
// jr $ra
// nop
WriteInstruction(memory, 64, 0x03E00008); // JR $ra
WriteInstruction(memory, 68, 0x00000000); // NOP
var analyzer = new FunctionAnalyzer(memory);
// Act
analyzer.AnalyzeFromEntryPoint(baseAddress);
// Assert
Assert.Equal(3, analyzer.Functions.Count);
Assert.True(analyzer.Functions.ContainsKey(baseAddress));
Assert.True(analyzer.Functions.ContainsKey(func1));
Assert.True(analyzer.Functions.ContainsKey(func2));
var mainFunc = analyzer.Functions[baseAddress];
Assert.Contains(func1, mainFunc.CallsTo);
Assert.Contains(func2, mainFunc.CallsTo);
}
[Fact]
public void Function_ToString_FormatsCorrectly()
{
// Arrange
var func = new Function
{
Address = 0x80000000,
Name = "main"
};
func.Instructions.Add(0x80000000);
func.Instructions.Add(0x80000004);
func.CallsTo.Add(0x80000100);
// Act
string result = func.ToString();
// Assert
Assert.Contains("main", result);
Assert.Contains("0x80000000", result);
Assert.Contains("2", result); // instruction count
Assert.Contains("1", result); // calls count
}
[Fact]
public void FunctionAnalyzer_StopsAtSafetyLimit()
{
// Arrange - Create a scenario that could loop infinitely
byte[] memory = new byte[4096];
uint baseAddress = 0x80000000;
// Infinite loop: j 0x80000000
for (int i = 0; i < 1000; i += 4)
{
WriteInstruction(memory, i, 0x08000000); // J 0x80000000
}
var analyzer = new FunctionAnalyzer(memory);
// Act - Should not hang or throw
analyzer.AnalyzeFromEntryPoint(baseAddress);
// Assert - Should have analyzed something without hanging
Assert.NotEmpty(analyzer.Functions);
}
private void WriteInstruction(byte[] memory, int offset, uint instruction)
{
byte[] bytes = BitConverter.GetBytes(instruction);
Array.Copy(bytes, 0, memory, offset, 4);
}
}