// Copyright (C) 2026 SharpEmu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using SharpEmu.Core.Cpu.Native; using SharpEmu.HLE; using SharpEmu.Libs.Kernel; using Xunit; namespace SharpEmu.Libs.Tests.Cpu; public sealed class ImportLoopGuardBoundaryTests { [Theory] [InlineData(typeof(KernelRuntimeCompatExports), "gettimeofday", "n88vx3C5nW8")] [InlineData(typeof(KernelMemoryCompatExports), "clock_gettime", "lLMT9vJAck0")] [InlineData(typeof(KernelRuntimeCompatExports), "sceKernelReadTsc", "-2IRUCO--PM")] [InlineData(typeof(KernelRuntimeCompatExports), "sceKernelGetProcessTime", "4J2sUJmuHZQ")] [InlineData(typeof(KernelRuntimeCompatExports), "sceKernelGetProcessTimeCounter", "fgxnMeTNUtY")] public void TimeQuery_ClearsExpiredHistory(Type exportContainer, string exportName, string expectedNid) { var export = Assert.Single(exportContainer.GetMethods(BindingFlags.Public | BindingFlags.Static) .SelectMany(method => method.GetCustomAttributes()), attribute => attribute.ExportName == exportName); Assert.Equal(expectedNid, export.Nid); AssertBoundaryClearsExpiredHistory(export.Nid); } [Theory] [InlineData("1jfXLRVzisc")] [InlineData("1j3S3n-tTW4")] [InlineData("WKAXJ4XBPQ4")] [InlineData("BmMjYxmew1w")] [InlineData("Op8TBGY5KHg")] [InlineData("27bAgiJmOh0")] [InlineData("yH17Q6NWtVg")] public void ProgressBoundary_ClearsExpiredHistory(string nid) { AssertBoundaryClearsExpiredHistory(nid); } [Theory] [InlineData("BNowx2l588E")] [InlineData("j4ViWNHEgww")] [InlineData("0V5nU-Z6t4U")] [InlineData("aI6lQW5v57k")] [InlineData("unknown-import")] public void OtherImport_DoesNotClearExpiredHistory(string nid) { Assert.False(IsBoundary(nid)); var guard = new GuardFixture(); guard.RecordRepeatingImports(nid); guard.ExpireHistory(); Assert.True(guard.ObserveImport(nid)); Assert.NotEqual(0, guard.ReadField("_importLoopSignatureCount")); } [Fact] public void OrdinaryRepeatingImports_RequireElapsedTimeout() { var guard = new GuardFixture(); guard.RecordRepeatingImports(); guard.WriteField("_importLoopPatternHits", 6); guard.WriteField("_importLoopPatternStartTimestamp", Stopwatch.GetTimestamp()); Assert.False(guard.ObserveImport()); guard.ExpireHistory(); Assert.True(guard.ObserveImport()); } [Fact] public void Boundary_RequiresNewHistoryBeforeAnotherExit() { var guard = new GuardFixture(); guard.RecordRepeatingImports(); guard.ExpireHistory(); Assert.True(guard.ObserveImport()); Assert.False(guard.ObserveImport("lLMT9vJAck0")); var newHistoryStart = Stopwatch.GetTimestamp(); Assert.False(guard.ObserveImport()); Assert.Equal(1, guard.ReadField("_importLoopSignatureCount")); Assert.Equal(0, guard.ReadField("_importLoopPatternStartTimestamp")); guard.RecordRepeatingImports(); Assert.False(guard.ObserveImport()); Assert.Equal(1, guard.ReadField("_importLoopPatternHits")); Assert.True(guard.ReadField("_importLoopPatternStartTimestamp") >= newHistoryStart); guard.ExpireHistory(); Assert.True(guard.ObserveImport()); } [Fact] public void ExpiredHistory_DoesNotBypassDispatchSampling() { var guard = new GuardFixture(); guard.RecordRepeatingImports(); guard.ExpireHistory(); Assert.False(guard.ObserveImport(dispatchIndex: 1281)); Assert.True(guard.ObserveImport()); } [Theory] [InlineData(true, 5)] [InlineData(false, 0)] public void DisabledGuard_DoesNotForceExit(bool disabled, int timeoutSeconds) { var guard = new GuardFixture(); guard.RecordRepeatingImports(); guard.ExpireHistory(); guard.WriteField("_disableImportLoopGuard", disabled); guard.WriteField("_importLoopGuardSeconds", timeoutSeconds); Assert.False(guard.ObserveImport()); } private static void AssertBoundaryClearsExpiredHistory(string nid) { Assert.True(IsBoundary(nid)); var guard = new GuardFixture(); guard.RecordRepeatingImports(); guard.ExpireHistory(); Assert.True(guard.ObserveImport()); Assert.False(guard.ObserveImport(nid)); Assert.Equal(0, guard.ReadField("_importLoopPatternHits")); Assert.Equal(0, guard.ReadField("_importLoopPatternStartTimestamp")); Assert.Equal(0, guard.ReadField("_importLoopSignatureCount")); Assert.Equal(0, guard.ReadField("_importLoopSignatureWriteIndex")); } private static bool IsBoundary(string nid) { var method = typeof(DirectExecutionBackend).GetMethod("IsImportLoopGuardBoundary", BindingFlags.NonPublic | BindingFlags.Static); Assert.NotNull(method); return Assert.IsType(method.Invoke(null, [nid])); } private sealed class GuardFixture { private readonly DirectExecutionBackend _backend; public GuardFixture() { // The guard needs history arrays, not native thread and memory resources. _backend = (DirectExecutionBackend)RuntimeHelpers.GetUninitializedObject(typeof(DirectExecutionBackend)); var historyLength = typeof(DirectExecutionBackend).GetField("ImportLoopHistoryLength", BindingFlags.NonPublic | BindingFlags.Static); Assert.NotNull(historyLength); var capacity = Assert.IsType(historyLength.GetRawConstantValue()); WriteField("_importLoopSignatures", new ulong[capacity]); WriteField("_importLoopNidHashes", new ulong[capacity]); WriteField("_importLoopReturnRips", new ulong[capacity]); WriteField("_importLoopGuardSeconds", 5); } public void RecordRepeatingImports(string nid = "ordinary-import") { for (var importIndex = 0; importIndex < 1024; importIndex++) Assert.False(ObserveImport(nid, dispatchIndex: 1281)); } public void ExpireHistory() { // Seed elapsed history so these tests do not wait for the timeout. WriteField("_importLoopPatternHits", 6); WriteField("_importLoopPatternStartTimestamp", Stopwatch.GetTimestamp() - 6 * Stopwatch.Frequency); } public bool ObserveImport(string nid = "ordinary-import", long dispatchIndex = 1280) { var entryType = typeof(DirectExecutionBackend).GetNestedType("ImportStubEntry", BindingFlags.NonPublic); Assert.NotNull(entryType); var entry = Activator.CreateInstance(entryType, [0UL, nid, null, false, false, false, IsBoundary(nid), 1UL]); var method = typeof(DirectExecutionBackend).GetMethod("ShouldForceGuestExitOnImportLoop", BindingFlags.NonPublic | BindingFlags.Instance); Assert.NotNull(method); return Assert.IsType(method.Invoke(_backend, [entry, 0x1000UL, dispatchIndex, 0UL, 0UL])); } public void WriteField(string fieldName, object value) => FindField(fieldName).SetValue(_backend, value); public TField ReadField(string fieldName) => Assert.IsType(FindField(fieldName).GetValue(_backend)); private static FieldInfo FindField(string fieldName) { var field = typeof(DirectExecutionBackend).GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance); Assert.NotNull(field); return field; } } }