mirror of
https://github.com/par274/sharpemu.git
synced 2026-08-30 13:24:19 +08:00
b226dca3df
Co-authored-by: Acelogic <miguelc4600@gmail.com>
275 lines
8.5 KiB
C#
275 lines
8.5 KiB
C#
// Copyright (C) 2026 SharpEmu Emulator Project
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
using Microsoft.CodeAnalysis;
|
|
using Xunit;
|
|
|
|
namespace SharpEmu.SourceGenerators.Tests;
|
|
|
|
public sealed class SysAbiExportAnalyzerTests
|
|
{
|
|
private static IReadOnlyList<Diagnostic> Analyze(string source, params AdditionalText[] additionalFiles) =>
|
|
RoslynTestHost.RunAnalyzer(RoslynTestHost.Compile(source), additionalFiles);
|
|
|
|
private static void AssertSingle(IReadOnlyList<Diagnostic> diagnostics, string id)
|
|
{
|
|
Assert.Single(diagnostics);
|
|
Assert.Equal(id, diagnostics[0].Id);
|
|
}
|
|
|
|
[Fact]
|
|
public void CleanExportProducesNoDiagnostics()
|
|
{
|
|
var diagnostics = Analyze("""
|
|
using SharpEmu.HLE;
|
|
|
|
public static class Exports
|
|
{
|
|
[SysAbiExport(Nid = "Zxa0VhQVTsk", ExportName = "sceKernelWaitSema", Target = Generation.Gen5)]
|
|
public static int WaitSema(CpuContext ctx) => 0;
|
|
}
|
|
""");
|
|
|
|
Assert.Empty(diagnostics);
|
|
}
|
|
|
|
[Fact]
|
|
public void DuplicateNidIsReported()
|
|
{
|
|
var diagnostics = Analyze("""
|
|
using SharpEmu.HLE;
|
|
|
|
public static class Exports
|
|
{
|
|
[SysAbiExport(Nid = "Zxa0VhQVTsk", ExportName = "sceKernelWaitSema")]
|
|
public static int First(CpuContext ctx) => 0;
|
|
|
|
// Same NID via derivation: duplicates must be caught across both forms.
|
|
[SysAbiExport(ExportName = "sceKernelWaitSema")]
|
|
public static int Second(CpuContext ctx) => 0;
|
|
}
|
|
""");
|
|
|
|
AssertSingle(diagnostics, "SHEM001");
|
|
}
|
|
|
|
[Fact]
|
|
public void DuplicateNidOnTheSameMultiAttributeHandlerIsReported()
|
|
{
|
|
var diagnostics = Analyze("""
|
|
using SharpEmu.HLE;
|
|
|
|
public static class Exports
|
|
{
|
|
[SysAbiExport(Nid = "Zxa0VhQVTsk", ExportName = "sceKernelWaitSema")]
|
|
[SysAbiExport(Nid = "Zxa0VhQVTsk", ExportName = "sceKernelWaitSema")]
|
|
public static int Shared(CpuContext ctx) => 0;
|
|
}
|
|
""");
|
|
|
|
AssertSingle(diagnostics, "SHEM001");
|
|
}
|
|
|
|
[Fact]
|
|
public void EveryAttributeOnAMultiAttributeHandlerIsAnalyzed()
|
|
{
|
|
var diagnostics = Analyze("""
|
|
using SharpEmu.HLE;
|
|
|
|
public static class Exports
|
|
{
|
|
[SysAbiExport(Nid = "Zxa0VhQVTsk", ExportName = "sceKernelWaitSema")]
|
|
[SysAbiExport(Nid = "not_a_nid")]
|
|
public static int Shared(CpuContext ctx) => 0;
|
|
}
|
|
""");
|
|
|
|
AssertSingle(diagnostics, "SHEM002");
|
|
}
|
|
|
|
[Fact]
|
|
public void MalformedNidIsReported()
|
|
{
|
|
var diagnostics = Analyze("""
|
|
using SharpEmu.HLE;
|
|
|
|
public static class Exports
|
|
{
|
|
[SysAbiExport(Nid = "not_a_nid", ExportName = "sceKernelWaitSema")]
|
|
public static int WaitSema(CpuContext ctx) => 0;
|
|
}
|
|
""");
|
|
|
|
AssertSingle(diagnostics, "SHEM002");
|
|
}
|
|
|
|
[Fact]
|
|
public void WrongSignatureIsReported()
|
|
{
|
|
var diagnostics = Analyze("""
|
|
using SharpEmu.HLE;
|
|
|
|
public static class Exports
|
|
{
|
|
[SysAbiExport(Nid = "Zxa0VhQVTsk", ExportName = "sceKernelWaitSema")]
|
|
public static void WaitSema(CpuContext ctx) { }
|
|
}
|
|
""");
|
|
|
|
AssertSingle(diagnostics, "SHEM003");
|
|
}
|
|
|
|
[Fact]
|
|
public void TypedHandlerShapeIsAccepted()
|
|
{
|
|
var diagnostics = Analyze("""
|
|
using SharpEmu.HLE;
|
|
|
|
public static class Exports
|
|
{
|
|
[SysAbiExport(Nid = "12wOHk8ywb0", ExportName = "sceKernelPollSema")]
|
|
public static int PollSema(CpuContext ctx, uint handle, int needCount) => 0;
|
|
|
|
[SysAbiExport(Nid = "4DM06U2BNEY", ExportName = "sceKernelCancelSema")]
|
|
public static int CancelSema(CpuContext ctx, uint a, int b, ulong c, long d, uint e, int f) => 0;
|
|
}
|
|
""");
|
|
|
|
Assert.Empty(diagnostics);
|
|
}
|
|
|
|
[Fact]
|
|
public void TypedHandlerBeyondRegisterArgsOrWithUnsupportedTypeIsReported()
|
|
{
|
|
var diagnostics = Analyze("""
|
|
using SharpEmu.HLE;
|
|
|
|
public static class Exports
|
|
{
|
|
// Seven args exceed the six SysV integer registers.
|
|
[SysAbiExport(Nid = "12wOHk8ywb0", ExportName = "sceKernelPollSema")]
|
|
public static int TooMany(CpuContext ctx, uint a, int b, ulong c, long d, uint e, int f, int g) => 0;
|
|
|
|
// string is not register-representable (that is phase 3's marshalling).
|
|
[SysAbiExport(Nid = "4czppHBiriw", ExportName = "sceKernelSignalSema")]
|
|
public static int WrongKind(CpuContext ctx, string name) => 0;
|
|
}
|
|
""");
|
|
|
|
// Order-insensitive: analyzers run concurrently and diagnostic order is unstable.
|
|
Assert.Equal(2, diagnostics.Count);
|
|
Assert.All(diagnostics, diagnostic => Assert.Equal("SHEM003", diagnostic.Id));
|
|
}
|
|
|
|
[Fact]
|
|
public void GuestCStringParameterIsAccepted()
|
|
{
|
|
var diagnostics = Analyze("""
|
|
using SharpEmu.HLE;
|
|
|
|
public static class Exports
|
|
{
|
|
[SysAbiExport(Nid = "1G3lF1Gg1k8", ExportName = "sceKernelOpen")]
|
|
public static int KernelOpen(CpuContext ctx, [GuestCString(4096)] string path, int flags) => 0;
|
|
}
|
|
""");
|
|
|
|
Assert.Empty(diagnostics);
|
|
}
|
|
|
|
[Fact]
|
|
public void MisusedGuestCStringIsReported()
|
|
{
|
|
var diagnostics = Analyze("""
|
|
using SharpEmu.HLE;
|
|
|
|
public static class Exports
|
|
{
|
|
// On a non-string parameter the attribute is meaningless.
|
|
[SysAbiExport(Nid = "1G3lF1Gg1k8", ExportName = "sceKernelOpen")]
|
|
public static int OnInt(CpuContext ctx, [GuestCString(4096)] int flags) => 0;
|
|
|
|
// A read bounded at zero bytes can never succeed.
|
|
[SysAbiExport(Nid = "6c3rCVE-fTU", ExportName = "_open")]
|
|
public static int ZeroLength(CpuContext ctx, [GuestCString(0)] string path) => 0;
|
|
}
|
|
""");
|
|
|
|
// Order-insensitive: analyzers run concurrently and diagnostic order is unstable.
|
|
Assert.Equal(2, diagnostics.Count);
|
|
Assert.All(diagnostics, diagnostic => Assert.Equal("SHEM008", diagnostic.Id));
|
|
}
|
|
|
|
[Fact]
|
|
public void NidContradictingExportNameIsReported()
|
|
{
|
|
var diagnostics = Analyze("""
|
|
using SharpEmu.HLE;
|
|
|
|
public static class Exports
|
|
{
|
|
// This NID belongs to sceKernelSignalSema, not sceKernelWaitSema.
|
|
[SysAbiExport(Nid = "4czppHBiriw", ExportName = "sceKernelWaitSema")]
|
|
public static int WaitSema(CpuContext ctx) => 0;
|
|
}
|
|
""");
|
|
|
|
AssertSingle(diagnostics, "SHEM004");
|
|
}
|
|
|
|
[Fact]
|
|
public void ExportWithNeitherNidNorNameIsReported()
|
|
{
|
|
var diagnostics = Analyze("""
|
|
using SharpEmu.HLE;
|
|
|
|
public static class Exports
|
|
{
|
|
[SysAbiExport(Target = Generation.Gen5)]
|
|
public static int Mystery(CpuContext ctx) => 0;
|
|
}
|
|
""");
|
|
|
|
AssertSingle(diagnostics, "SHEM005");
|
|
}
|
|
|
|
[Fact]
|
|
public void NameOutsideTheCatalogWarnsOnlyWhenCatalogIsWired()
|
|
{
|
|
const string source = """
|
|
using SharpEmu.HLE;
|
|
|
|
public static class Exports
|
|
{
|
|
[SysAbiExport(ExportName = "sceKernelWiatSema", Target = Generation.Gen5)]
|
|
public static int Typo(CpuContext ctx) => 0;
|
|
}
|
|
""";
|
|
|
|
var withoutCatalog = Analyze(source);
|
|
Assert.Empty(withoutCatalog);
|
|
|
|
var catalog = new InMemoryAdditionalText(
|
|
"/repo/scripts/ps5_names.txt",
|
|
"sceKernelWaitSema\nsceKernelSignalSema\n");
|
|
var withCatalog = Analyze(source, catalog);
|
|
AssertSingle(withCatalog, "SHEM006");
|
|
}
|
|
|
|
[Fact]
|
|
public void PrivateHandlerIsReported()
|
|
{
|
|
var diagnostics = Analyze("""
|
|
using SharpEmu.HLE;
|
|
|
|
public static class Exports
|
|
{
|
|
[SysAbiExport(Nid = "Zxa0VhQVTsk", ExportName = "sceKernelWaitSema")]
|
|
private static int Hidden(CpuContext ctx) => 0;
|
|
}
|
|
""");
|
|
|
|
AssertSingle(diagnostics, "SHEM007");
|
|
}
|
|
}
|