sharpemu/tests/SharpEmu.Libs.Tests/Memory/HostViews/HostViewQueryTests.cs

67 lines
3.3 KiB
C#

// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using SharpEmu.HLE;
using SharpEmu.HLE.Host;
using Xunit;
using static SharpEmu.Libs.Tests.Memory.HostViews.HostViewTestSupport;
namespace SharpEmu.Libs.Tests.Memory.HostViews;
public sealed unsafe class HostViewQueryTests
{
[Fact]
public void QueriesFollowViewProtectionRestorationAndRelease()
{
if (!Supported) return;
var views = HostViewMemory.Create();
var size = HoleSize(views);
var address = ReserveFreeHole(views, size);
try
{
Check(address, HostMemory.MEM_RESERVE, HostMemory.PAGE_NOACCESS);
using var backing = CreateBacking(views);
Assert.True(views.SplitHole(address, Segment));
Assert.True(views.TryMapView(backing, address, 0, Segment, HostPageProtection.ReadWrite, out _));
Check(address, HostMemory.MEM_COMMIT, HostMemory.PAGE_READWRITE);
Check(address + Segment, HostMemory.MEM_RESERVE, HostMemory.PAGE_NOACCESS);
Assert.True(views.ChangeAccess(address, 1, HostPageProtection.ReadOnly));
Check(address, HostMemory.MEM_COMMIT, HostMemory.PAGE_READONLY);
Assert.True(views.ChangeAccess(address, views.PageSize, HostPageProtection.ReadWrite));
Assert.True(views.ChangeAccess(address, views.PageSize, HostPageProtection.ReadOnly));
// One round: a second read-write change would take the unchanged-protection path.
var changed = false;
var allocatedBytes = AllocationMeasurement.SteadyState(null,
() => changed = views.ChangeAccess(address, views.PageSize, HostPageProtection.ReadWrite), rounds: 1);
Assert.True(changed);
Assert.Equal(0, allocatedBytes);
Assert.True(views.ChangeAccess(address, views.PageSize, HostPageProtection.NoAccess));
Check(address, HostMemory.MEM_COMMIT, HostMemory.PAGE_NOACCESS);
Assert.False(views.TryMapView(backing, address, BackingSize, Segment, HostPageProtection.ReadWrite, out _));
Check(address, HostMemory.MEM_COMMIT, HostMemory.PAGE_NOACCESS);
Assert.True(views.UnmapView(address, Segment));
Check(address, HostMemory.MEM_RESERVE, HostMemory.PAGE_NOACCESS);
}
finally
{
Assert.True(views.FreeOwnedRange(address, size));
}
// Another thread (a parallel test, the GC) may reuse the freed range at once. A
// release that failed leaves this test's reservation, never committed memory.
Assert.NotEqual((nuint)0, HostMemory.Query((void*)address, out var released));
Assert.Contains(released.State, new[] { HostMemory.MEM_FREE_STATE, HostMemory.MEM_COMMIT });
}
private static void Check(ulong address, uint state, uint protection)
{
if (OperatingSystem.IsWindows() && state == HostMemory.MEM_RESERVE)
protection = 0;
Assert.NotEqual((nuint)0, HostMemory.Query((void*)address, out var direct));
Assert.Equal(state, direct.State);
Assert.Equal(protection, direct.Protect);
Assert.True(PlatformMemory.Query(address, out var host));
Assert.Equal(state, host.RawState);
Assert.Equal(protection, host.RawProtection);
}
}