audit work, fixed StellaOps.sln warnings/errors, fixed tests, sprints work, new advisories

This commit is contained in:
master
2026-01-07 18:49:59 +02:00
parent 04ec098046
commit 608a7f85c0
866 changed files with 56323 additions and 6231 deletions

View File

@@ -0,0 +1,22 @@
# BinaryIndex Benchmarks Charter
## Mission
- Maintain deterministic benchmark and accuracy tests for BinaryIndex analyzers.
## Responsibilities
- Keep benchmark datasets local and fixed.
- Ensure benchmark thresholds are stable and documented.
- Separate benchmark runs from unit coverage.
## Required Reading
- docs/modules/binary-index/architecture.md
- docs/modules/platform/architecture-overview.md
- docs/07_HIGH_LEVEL_ARCHITECTURE.md
## Working Agreement
- No network calls; offline fixtures only.
- Fixed seeds and deterministic ordering.
- Avoid machine-specific timing assertions; use bounded thresholds.
## Definition of Done
- Benchmarks reproducible on CI and offline environments.

View File

@@ -12,8 +12,8 @@
<ItemGroup>
<PackageReference Include="FluentAssertions" />
<PackageReference Include="Moq" />
<PackageReference Include="NSubstitute" />
<PackageReference Include="Testcontainers" />
<PackageReference Include="xunit.runner.visualstudio" />
</ItemGroup>
<ItemGroup>

View File

@@ -0,0 +1,162 @@
// -----------------------------------------------------------------------------
// NameDemanglerTests.cs
// Sprint: SPRINT_20260106_001_003_BINDEX_symbol_table_diff
// Tasks: SYM-016, SYM-017 - Unit tests for name demangling
// Description: Unit tests for C++ and Rust name demangler
// -----------------------------------------------------------------------------
using StellaOps.BinaryIndex.Builders.SymbolDiff;
using Xunit;
namespace StellaOps.BinaryIndex.Builders.Tests.SymbolDiff;
[Trait("Category", "Unit")]
public sealed class NameDemanglerTests
{
private readonly NameDemangler _demangler = new();
// Scheme detection tests
[Theory]
[InlineData("_Z3foov", ManglingScheme.ItaniumCxx)]
[InlineData("_ZN3foo3barEv", ManglingScheme.ItaniumCxx)]
[InlineData("?foo@@YAXXZ", ManglingScheme.MicrosoftCxx)]
[InlineData("?foo@bar@@YAXXZ", ManglingScheme.MicrosoftCxx)]
[InlineData("_ZN4test17h0123456789abcdefE", ManglingScheme.Rust)]
[InlineData("_RNvC5crate4main", ManglingScheme.Rust)]
[InlineData("$s4main3fooyyF", ManglingScheme.Swift)]
[InlineData("_$s4main3fooyyF", ManglingScheme.Swift)]
[InlineData("foo", ManglingScheme.None)]
[InlineData("printf", ManglingScheme.None)]
[InlineData("", ManglingScheme.None)]
public void DetectScheme_IdentifiesCorrectScheme(string name, ManglingScheme expected)
{
var result = _demangler.DetectScheme(name);
Assert.Equal(expected, result);
}
// C++ Itanium ABI tests
[Theory]
[InlineData("_Z3foov", "foo")]
[InlineData("_Z3bari", "bar")]
[InlineData("_Z6myFunc", "myFunc")]
public void Demangle_ItaniumCxx_SimpleNames(string mangled, string expected)
{
var result = _demangler.Demangle(mangled);
Assert.Equal(expected, result);
}
[Theory]
[InlineData("_ZN3foo3barEv", "foo::bar")]
[InlineData("_ZN5outer5inner4funcEv", "outer::inner::func")]
public void Demangle_ItaniumCxx_NestedNames(string mangled, string expected)
{
var result = _demangler.Demangle(mangled);
Assert.Equal(expected, result);
}
// Microsoft C++ tests
[Theory]
[InlineData("?foo@@YAXXZ", "foo")]
[InlineData("?bar@MyClass@@QAEXXZ", "MyClass::bar")]
public void Demangle_MicrosoftCxx_SimpleNames(string mangled, string expected)
{
var result = _demangler.Demangle(mangled);
Assert.Equal(expected, result);
}
// Rust legacy mangling tests
[Fact]
public void Demangle_RustLegacy_BasicName()
{
// _ZN<len>name...E format
var mangled = "_ZN4test4mainE";
var result = _demangler.Demangle(mangled);
Assert.NotNull(result);
Assert.Contains("test", result);
}
[Fact]
public void Demangle_RustLegacy_WithHash_StripsHash()
{
// Rust hashes are 17h + 16 hex digits
var mangled = "_ZN4core3ptr17h0123456789abcdefE";
var result = _demangler.Demangle(mangled);
Assert.NotNull(result);
Assert.DoesNotContain("h0123456789abcdef", result);
}
[Fact]
public void Demangle_RustLegacy_DecodesEscapes()
{
// Test Rust escape sequences
var mangled = "_ZN4test8$LT$impl$GT$E";
var result = _demangler.Demangle(mangled);
Assert.NotNull(result);
// Should decode $LT$ to < and $GT$ to >
Assert.Contains("<", result);
Assert.Contains(">", result);
}
// Rust v0 mangling tests
[Fact]
public void Demangle_RustV0_ReturnsPlaceholder()
{
// Rust v0 starts with _R
var mangled = "_RNvC5crate4main";
var result = _demangler.Demangle(mangled);
Assert.NotNull(result);
Assert.StartsWith("<rust-v0>", result);
}
// Swift tests
[Fact]
public void Demangle_Swift_ReturnsPlaceholder()
{
var mangled = "$s4main3fooyyF";
var result = _demangler.Demangle(mangled);
Assert.NotNull(result);
Assert.StartsWith("<swift>", result);
}
// Edge cases
[Fact]
public void Demangle_NullInput_ReturnsNull()
{
var result = _demangler.Demangle(null!);
Assert.Null(result);
}
[Fact]
public void Demangle_EmptyInput_ReturnsNull()
{
var result = _demangler.Demangle(string.Empty);
Assert.Null(result);
}
[Fact]
public void Demangle_PlainCName_ReturnsNull()
{
var result = _demangler.Demangle("printf");
Assert.Null(result);
}
[Fact]
public void Demangle_InvalidMangledName_ReturnsNull()
{
// Invalid Itanium format (no proper length prefix)
var result = _demangler.Demangle("_Zinvalid");
Assert.Null(result);
}
}

View File

@@ -0,0 +1,334 @@
// -----------------------------------------------------------------------------
// SymbolTableDiffAnalyzerTests.cs
// Sprint: SPRINT_20260106_001_003_BINDEX_symbol_table_diff
// Tasks: SYM-020 to SYM-025 - Unit tests for symbol diff
// Description: Unit tests for symbol table diff analyzer
// -----------------------------------------------------------------------------
using Microsoft.Extensions.Logging.Abstractions;
using StellaOps.BinaryIndex.Builders.SymbolDiff;
using Xunit;
using NSubstitute;
namespace StellaOps.BinaryIndex.Builders.Tests.SymbolDiff;
[Trait("Category", "Unit")]
public sealed class SymbolTableDiffAnalyzerTests
{
private readonly ISymbolExtractor _mockExtractor;
private readonly INameDemangler _demangler;
private readonly TimeProvider _timeProvider;
private readonly SymbolTableDiffAnalyzer _analyzer;
public SymbolTableDiffAnalyzerTests()
{
_mockExtractor = Substitute.For<ISymbolExtractor>();
_demangler = new NameDemangler();
_timeProvider = TimeProvider.System;
_analyzer = new SymbolTableDiffAnalyzer(
_mockExtractor,
_demangler,
_timeProvider,
NullLogger<SymbolTableDiffAnalyzer>.Instance);
}
[Fact]
public async Task ComputeDiffAsync_DetectsAddedSymbols()
{
// Arrange
var baseTable = CreateSymbolTable("base.so", [
CreateSymbol("foo", SymbolType.Function)
]);
var targetTable = CreateSymbolTable("target.so", [
CreateSymbol("foo", SymbolType.Function),
CreateSymbol("bar", SymbolType.Function)
]);
_mockExtractor.ExtractAsync("base.so", Arg.Any<CancellationToken>()).Returns(baseTable);
_mockExtractor.ExtractAsync("target.so", Arg.Any<CancellationToken>()).Returns(targetTable);
// Act
var diff = await _analyzer.ComputeDiffAsync("base.so", "target.so");
// Assert
Assert.Single(diff.Exports.Added);
Assert.Equal("bar", diff.Exports.Added[0].Name);
Assert.Empty(diff.Exports.Removed);
}
[Fact]
public async Task ComputeDiffAsync_DetectsRemovedSymbols()
{
// Arrange
var baseTable = CreateSymbolTable("base.so", [
CreateSymbol("foo", SymbolType.Function),
CreateSymbol("bar", SymbolType.Function)
]);
var targetTable = CreateSymbolTable("target.so", [
CreateSymbol("foo", SymbolType.Function)
]);
_mockExtractor.ExtractAsync("base.so", Arg.Any<CancellationToken>()).Returns(baseTable);
_mockExtractor.ExtractAsync("target.so", Arg.Any<CancellationToken>()).Returns(targetTable);
// Act
var diff = await _analyzer.ComputeDiffAsync("base.so", "target.so");
// Assert
Assert.Empty(diff.Exports.Added);
Assert.Single(diff.Exports.Removed);
Assert.Equal("bar", diff.Exports.Removed[0].Name);
}
[Fact]
public async Task ComputeDiffAsync_DetectsModifiedSymbols()
{
// Arrange
var baseTable = CreateSymbolTable("base.so", [
CreateSymbol("foo", SymbolType.Function, size: 100)
]);
var targetTable = CreateSymbolTable("target.so", [
CreateSymbol("foo", SymbolType.Function, size: 200)
]);
_mockExtractor.ExtractAsync("base.so", Arg.Any<CancellationToken>()).Returns(baseTable);
_mockExtractor.ExtractAsync("target.so", Arg.Any<CancellationToken>()).Returns(targetTable);
// Act
var diff = await _analyzer.ComputeDiffAsync("base.so", "target.so");
// Assert
Assert.Single(diff.Exports.Modified);
Assert.Equal("foo", diff.Exports.Modified[0].Name);
Assert.Contains(diff.Exports.Modified[0].Changes, c => c.Attribute == "size");
}
[Fact]
public async Task ComputeDiffAsync_DetectsRenames_WhenFingerprintsMatch()
{
// Arrange
var fingerprint = "abc123def456";
var baseTable = CreateSymbolTable("base.so", [
CreateSymbol("old_name", SymbolType.Function, fingerprint: fingerprint)
]);
var targetTable = CreateSymbolTable("target.so", [
CreateSymbol("new_name", SymbolType.Function, fingerprint: fingerprint)
]);
_mockExtractor.ExtractAsync("base.so", Arg.Any<CancellationToken>()).Returns(baseTable);
_mockExtractor.ExtractAsync("target.so", Arg.Any<CancellationToken>()).Returns(targetTable);
// Act
var diff = await _analyzer.ComputeDiffAsync("base.so", "target.so", new SymbolDiffOptions
{
DetectRenames = true,
RenameSimilarityThreshold = 0.5
});
// Assert
Assert.Single(diff.Exports.Renamed);
Assert.Equal("old_name", diff.Exports.Renamed[0].BaseName);
Assert.Equal("new_name", diff.Exports.Renamed[0].TargetName);
}
[Fact]
public async Task ComputeDiffAsync_ComputesDiffId_Deterministically()
{
// Arrange
var baseTable = CreateSymbolTable("base.so", [
CreateSymbol("foo", SymbolType.Function)
]);
var targetTable = CreateSymbolTable("target.so", [
CreateSymbol("foo", SymbolType.Function),
CreateSymbol("bar", SymbolType.Function)
]);
_mockExtractor.ExtractAsync("base.so", Arg.Any<CancellationToken>()).Returns(baseTable);
_mockExtractor.ExtractAsync("target.so", Arg.Any<CancellationToken>()).Returns(targetTable);
// Act
var diff1 = await _analyzer.ComputeDiffAsync("base.so", "target.so");
var diff2 = await _analyzer.ComputeDiffAsync("base.so", "target.so");
// Assert
Assert.Equal(diff1.DiffId, diff2.DiffId);
Assert.StartsWith("sha256:", diff1.DiffId);
}
[Fact]
public void AssessAbiCompatibility_FullyCompatible_WhenNoBreakingChanges()
{
// Arrange
var diff = CreateDiff(
added: [CreateSymbolChange("new_func")],
removed: [],
modified: []);
// Act
var abi = _analyzer.AssessAbiCompatibility(diff);
// Assert
Assert.Equal(AbiCompatibilityLevel.FullyCompatible, abi.Level);
Assert.True(abi.IsBackwardCompatible);
Assert.Empty(abi.BreakingChanges);
}
[Fact]
public void AssessAbiCompatibility_Incompatible_WhenSymbolsRemoved()
{
// Arrange
var diff = CreateDiff(
added: [],
removed: [CreateSymbolChange("removed_func", SymbolBinding.Global)],
modified: []);
// Act
var abi = _analyzer.AssessAbiCompatibility(diff);
// Assert
Assert.NotEqual(AbiCompatibilityLevel.FullyCompatible, abi.Level);
Assert.False(abi.IsBackwardCompatible);
Assert.Single(abi.BreakingChanges);
Assert.Equal(AbiBreakType.SymbolRemoved, abi.BreakingChanges[0].Type);
}
[Fact]
public void AssessAbiCompatibility_WarningsForAddedSymbols()
{
// Arrange
var diff = CreateDiff(
added: [CreateSymbolChange("new_func")],
removed: [],
modified: []);
// Act
var abi = _analyzer.AssessAbiCompatibility(diff);
// Assert
Assert.Single(abi.Warnings);
Assert.Equal(AbiWarningType.SymbolAdded, abi.Warnings[0].Type);
}
// Helper methods
private static SymbolTable CreateSymbolTable(string path, IReadOnlyList<ExtractedSymbol> exports)
{
return new SymbolTable
{
Binary = new BinaryRef
{
Path = path,
Sha256 = $"sha256:{Guid.NewGuid():N}",
Architecture = "x86_64",
Format = BinaryFormat.Elf
},
Exports = exports,
Imports = [],
VersionDefinitions = [],
VersionRequirements = [],
NeededLibraries = [],
ExtractedAt = DateTimeOffset.UtcNow
};
}
private static ExtractedSymbol CreateSymbol(
string name,
SymbolType type,
SymbolBinding binding = SymbolBinding.Global,
ulong size = 64,
string? fingerprint = null)
{
return new ExtractedSymbol
{
Name = name,
Type = type,
Binding = binding,
Visibility = SymbolVisibility.Default,
Address = 0x1000,
Size = size,
Fingerprint = fingerprint
};
}
private static SymbolChange CreateSymbolChange(
string name,
SymbolBinding binding = SymbolBinding.Global)
{
return new SymbolChange
{
Name = name,
Type = SymbolType.Function,
Binding = binding,
Visibility = SymbolVisibility.Default,
Address = 0x1000,
Size = 64
};
}
private static SymbolTableDiff CreateDiff(
IReadOnlyList<SymbolChange> added,
IReadOnlyList<SymbolChange> removed,
IReadOnlyList<SymbolModification> modified)
{
return new SymbolTableDiff
{
DiffId = "sha256:test",
Base = new BinaryRef
{
Path = "base.so",
Sha256 = "sha256:base",
Architecture = "x86_64",
Format = BinaryFormat.Elf
},
Target = new BinaryRef
{
Path = "target.so",
Sha256 = "sha256:target",
Architecture = "x86_64",
Format = BinaryFormat.Elf
},
Exports = new SymbolChangeSummary
{
Added = added,
Removed = removed,
Modified = modified,
Renamed = [],
Counts = new SymbolChangeCounts
{
Added = added.Count,
Removed = removed.Count,
Modified = modified.Count,
TotalBase = removed.Count + modified.Count,
TotalTarget = added.Count + modified.Count
}
},
Imports = new SymbolChangeSummary
{
Added = [],
Removed = [],
Modified = [],
Renamed = [],
Counts = new SymbolChangeCounts()
},
Versions = new VersionMapDiff
{
DefinitionsAdded = [],
DefinitionsRemoved = [],
RequirementsAdded = [],
RequirementsRemoved = [],
AssignmentsChanged = [],
Counts = new VersionChangeCounts()
},
AbiCompatibility = new AbiCompatibility
{
Level = AbiCompatibilityLevel.FullyCompatible,
Score = 1.0,
IsBackwardCompatible = true,
IsForwardCompatible = true,
BreakingChanges = [],
Warnings = [],
Summary = new AbiSummary()
},
ComputedAt = DateTimeOffset.UtcNow
};
}
}

View File

@@ -0,0 +1,22 @@
# BinaryIndex.Decompiler Tests Charter
## Mission
- Validate deterministic decompiler parsing and normalization.
## Responsibilities
- Cover AST parsing, normalization, and comparison paths.
- Keep fixtures deterministic and offline-safe.
## Required Reading
- docs/modules/binary-index/architecture.md
- docs/modules/binary-index/semantic-diffing.md
- docs/modules/platform/architecture-overview.md
- docs/07_HIGH_LEVEL_ARCHITECTURE.md
## Definition of Done
- Tests are deterministic and offline-safe.
- Coverage includes error handling and normalization edge cases.
## Working Agreement
- Use fixed seeds and ids in fixtures.
- Avoid non-deterministic ordering; assert sorted output.

View File

@@ -20,9 +20,6 @@
<PackageReference Include="FluentAssertions" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
<PackageReference Include="Microsoft.Extensions.Logging" />
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="xunit.v3" />
<PackageReference Include="xunit.runner.visualstudio" />
</ItemGroup>
<ItemGroup>

View File

@@ -18,13 +18,7 @@
<ItemGroup>
<PackageReference Include="FluentAssertions" />
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="Moq" />
<PackageReference Include="xunit.v3" />
<PackageReference Include="xunit.runner.visualstudio">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
<PackageReference Include="Microsoft.Extensions.Logging" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" />

View File

@@ -0,0 +1,22 @@
# BinaryIndex.Ensemble Tests Charter
## Mission
- Validate deterministic ensemble decisioning and weight tuning.
## Responsibilities
- Cover decision engine inputs, weights, and tie-breaking.
- Keep fixtures deterministic and offline-safe.
## Required Reading
- docs/modules/binary-index/architecture.md
- docs/modules/binary-index/semantic-diffing.md
- docs/modules/platform/architecture-overview.md
- docs/07_HIGH_LEVEL_ARCHITECTURE.md
## Definition of Done
- Tests are deterministic and offline-safe.
- Coverage includes weight tuning and error handling.
## Working Agreement
- Use fixed seeds and ids in fixtures.
- Avoid non-deterministic ordering; assert sorted output.

View File

@@ -0,0 +1,22 @@
# BinaryIndex.Ghidra Tests Charter
## Mission
- Validate deterministic behavior of the Ghidra integration layer.
## Responsibilities
- Cover service behaviors, process lifecycle, and output parsing.
- Keep fixtures deterministic and offline-safe.
## Required Reading
- docs/modules/binary-index/architecture.md
- docs/modules/binary-index/ghidra-deployment.md
- docs/modules/platform/architecture-overview.md
- docs/07_HIGH_LEVEL_ARCHITECTURE.md
## Definition of Done
- Tests are deterministic and offline-safe.
- Coverage includes error handling and cleanup paths.
## Working Agreement
- Use fixed ids and temp paths in fixtures.
- Avoid non-deterministic ordering; assert sorted output.

View File

@@ -16,13 +16,7 @@
<ItemGroup>
<PackageReference Include="FluentAssertions" />
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="Moq" />
<PackageReference Include="xunit.v3" />
<PackageReference Include="xunit.runner.visualstudio">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
<PackageReference Include="Microsoft.Extensions.Logging" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" />

View File

@@ -21,9 +21,6 @@
<PackageReference Include="FsCheck.Xunit.v3" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
<PackageReference Include="Microsoft.Extensions.Logging" />
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="xunit.v3" />
<PackageReference Include="xunit.runner.visualstudio" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,22 @@
# BinaryIndex.Semantic Tests Charter
## Mission
- Validate deterministic semantic graph extraction and matching.
## Responsibilities
- Cover graph extraction, hashing, canonicalization, and matching.
- Keep fixtures deterministic and offline-safe.
## Required Reading
- docs/modules/binary-index/architecture.md
- docs/modules/binary-index/semantic-diffing.md
- docs/modules/platform/architecture-overview.md
- docs/07_HIGH_LEVEL_ARCHITECTURE.md
## Definition of Done
- Tests are deterministic and offline-safe.
- Coverage includes algorithm options and edge cases.
## Working Agreement
- Use fixed seeds and ids in fixtures.
- Avoid non-deterministic ordering; assert sorted output.

View File

@@ -15,10 +15,6 @@
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" />
<PackageReference Include="Microsoft.Extensions.Options" />
<PackageReference Include="Moq" />
<PackageReference Include="xunit.runner.visualstudio" >
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>