// 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); } }