Skip to content

Commit

Permalink
Support additional memory protection flag combinations (#1864)
Browse files Browse the repository at this point in the history
* Support additional memory protection flag combinations

* Enable some mmap tests
  • Loading branch information
BCSharp authored Jan 6, 2025
1 parent 719a446 commit aaf701d
Show file tree
Hide file tree
Showing 3 changed files with 50 additions and 35 deletions.
72 changes: 42 additions & 30 deletions Src/IronPython.Modules/mmap.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,40 +68,65 @@ private static Exception WindowsError(int code) {
public static PythonType mmap {
get {
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) {
return DynamicHelpers.GetPythonTypeFromType(typeof(MmapDefault));
return DynamicHelpers.GetPythonTypeFromType(typeof(MmapWindows));
}

return DynamicHelpers.GetPythonTypeFromType(typeof(MmapUnix));
}
}


[PythonType("mmap"), PythonHidden]
public class MmapUnix : MmapDefault {
public MmapUnix(CodeContext/*!*/ context, int fileno, long length, int flags = MAP_SHARED, int prot = PROT_WRITE | PROT_READ, int access = ACCESS_DEFAULT, long offset = 0)
: base(context, fileno, length, null, NormalizeAccess(flags, prot, access), offset) { }
: base(context, fileno, length, null, ToMmapFileAccess(flags, prot, access), offset) { }

private static int NormalizeAccess(int flags, int prot, int access) {
private static MemoryMappedFileAccess ToMmapFileAccess(int flags, int prot, int access) {
if (access == ACCESS_DEFAULT) {
if ((flags & (MAP_PRIVATE | MAP_SHARED)) == 0) {
throw PythonOps.OSError(PythonErrorNumber.EINVAL, "Invalid argument");
}
if ((prot & PROT_WRITE) != 0) {
return (flags & MAP_PRIVATE) != 0 ? ACCESS_COPY : ACCESS_WRITE;
}
if ((prot & PROT_READ) != 0) {
return ACCESS_READ;
}
throw PythonOps.NotImplementedError("this combination of flags and prot is not supported");
prot |= PROT_READ;
}
return (prot & (PROT_READ | PROT_WRITE | PROT_EXEC)) switch {
PROT_READ => MemoryMappedFileAccess.Read,
PROT_READ | PROT_WRITE => (flags & MAP_PRIVATE) == 0 ? MemoryMappedFileAccess.ReadWrite : MemoryMappedFileAccess.CopyOnWrite,
PROT_READ | PROT_EXEC => MemoryMappedFileAccess.ReadExecute,
PROT_READ | PROT_WRITE | PROT_EXEC when (flags & MAP_PRIVATE) == 0 => MemoryMappedFileAccess.ReadWriteExecute,
_ => throw PythonOps.NotImplementedError("this combination of prot is not supported"),
};
} else if (flags != MAP_SHARED || prot != (PROT_WRITE | PROT_READ)) {
throw PythonOps.ValueError("mmap can't specify both access and flags, prot.");
} else if (access != ACCESS_READ && access != ACCESS_WRITE && access != ACCESS_COPY) {
throw PythonOps.ValueError("mmap invalid access parameter");
} else {
return access switch {
ACCESS_READ => MemoryMappedFileAccess.Read,
ACCESS_WRITE => MemoryMappedFileAccess.ReadWrite,
ACCESS_COPY => MemoryMappedFileAccess.CopyOnWrite,
_ => throw PythonOps.ValueError("mmap invalid access parameter"),
};
}
return access;
}
}


[PythonType("mmap"), PythonHidden]
public class MmapWindows : MmapDefault {
public MmapWindows(CodeContext context, int fileno, long length, string tagname = null, int access = ACCESS_DEFAULT, long offset = 0)
: base(context, fileno, length, tagname, ToMmapFileAccess(access), offset) { }

private static MemoryMappedFileAccess ToMmapFileAccess(int access) {
return access switch {
ACCESS_READ => MemoryMappedFileAccess.Read,
// On Windows, default access is write-through
ACCESS_DEFAULT or ACCESS_WRITE => MemoryMappedFileAccess.ReadWrite,
ACCESS_COPY => MemoryMappedFileAccess.CopyOnWrite,
_ => throw PythonOps.ValueError("mmap invalid access parameter"),
};
}
}

[PythonHidden]
public class MmapDefault : IWeakReferenceable {
private MemoryMappedFile _file;
private MemoryMappedViewAccessor _view;
Expand All @@ -115,21 +140,8 @@ public class MmapDefault : IWeakReferenceable {
private volatile bool _isClosed;
private int _refCount = 1;

public MmapDefault(CodeContext/*!*/ context, int fileno, long length, string tagname = null, int access = ACCESS_DEFAULT, long offset = 0) {
switch (access) {
case ACCESS_READ:
_fileAccess = MemoryMappedFileAccess.Read;
break;
case ACCESS_DEFAULT: // On Windows, default access is write-through
case ACCESS_WRITE:
_fileAccess = MemoryMappedFileAccess.ReadWrite;
break;
case ACCESS_COPY:
_fileAccess = MemoryMappedFileAccess.CopyOnWrite;
break;
default:
throw PythonOps.ValueError("mmap invalid access parameter");
}
public MmapDefault(CodeContext/*!*/ context, int fileno, long length, string tagname, MemoryMappedFileAccess fileAccess, long offset) {
_fileAccess = fileAccess;

if (length < 0) {
throw PythonOps.OverflowError("memory mapped size must be positive");
Expand Down Expand Up @@ -175,7 +187,7 @@ public MmapDefault(CodeContext/*!*/ context, int fileno, long length, string tag
throw PythonOps.OSError(PythonExceptions._OSError.ERROR_INVALID_BLOCK, "Bad file descriptor");
}

if (_fileAccess == MemoryMappedFileAccess.ReadWrite && !_sourceStream.CanWrite) {
if (_fileAccess is MemoryMappedFileAccess.ReadWrite or MemoryMappedFileAccess.ReadWriteExecute && !_sourceStream.CanWrite) {
throw WindowsError(PythonExceptions._OSError.ERROR_ACCESS_DENIED);
}

Expand Down Expand Up @@ -579,7 +591,7 @@ public string readline() {

public void resize(long newsize) {
using (new MmapLocker(this)) {
if (_fileAccess != MemoryMappedFileAccess.ReadWrite) {
if (_fileAccess is not MemoryMappedFileAccess.ReadWrite and not MemoryMappedFileAccess.ReadWriteExecute) {
throw PythonOps.TypeError("mmap can't resize a readonly or copy-on-write memory map.");
}

Expand Down Expand Up @@ -802,7 +814,7 @@ private long Position {
}

private void EnsureWritable() {
if (_fileAccess == MemoryMappedFileAccess.Read) {
if (_fileAccess is MemoryMappedFileAccess.Read or MemoryMappedFileAccess.ReadExecute) {
throw PythonOps.TypeError("mmap can't modify a read-only memory map.");
}
}
Expand Down
2 changes: 1 addition & 1 deletion Src/IronPythonTest/Cases/CPythonCasesManifest.ini
Original file line number Diff line number Diff line change
Expand Up @@ -587,7 +587,7 @@ IsolationLevel=PROCESS # Also weakref failures; https://github.com/IronLanguages
Ignore=true

[CPython.test_mmap]
RunCondition=NOT $(IS_POSIX) OR (NOT $(IS_MONO) AND '$(FRAMEWORK)' <> '.NETCoreApp,Version=v6.0')
RunCondition=NOT $(IS_MONO) AND (NOT $(IS_OSX) OR '$(FRAMEWORK)' <> '.NETCoreApp,Version=v6.0')
IsolationLevel=PROCESS

[CPython.test_module]
Expand Down
11 changes: 7 additions & 4 deletions Src/StdLib/Lib/test/test_mmap.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import os
import re
import itertools
import struct # IronPython: for platform architecture detection
import socket
import sys
import weakref
Expand Down Expand Up @@ -721,7 +722,6 @@ def test_weakref(self):
gc_collect()
self.assertIs(wr(), None)

@unittest.skipIf(sys.implementation.name == "ironpython", "TODO")
class LargeMmapTests(unittest.TestCase):

def setUp(self):
Expand Down Expand Up @@ -754,7 +754,8 @@ def test_large_offset(self):

def test_large_filesize(self):
with self._make_test_file(0x17FFFFFFF, b" ") as f:
if sys.maxsize < 0x180000000:
#if sys.maxsize < 0x180000000: # original CPython test
if struct.calcsize('P') * 8 == 32: # IronPython: better detection of 32-bit platform
# On 32 bit platforms the file is larger than sys.maxsize so
# mapping the whole file should fail -- Issue #16743
with self.assertRaises(OverflowError):
Expand All @@ -774,11 +775,13 @@ def _test_around_boundary(self, boundary):
with mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ) as m:
self.assertEqual(m[start:end], tail)

@unittest.skipUnless(sys.maxsize > _4G, "test cannot run on 32-bit systems")
#@unittest.skipUnless(sys.maxsize > _4G, "test cannot run on 32-bit systems") # original CPython decorator
@unittest.skipUnless(struct.calcsize('P') * 8 > 32, "test cannot run on 32-bit systems") # IronPython: better detection of 32-bit platform
def test_around_2GB(self):
self._test_around_boundary(_2G)

@unittest.skipUnless(sys.maxsize > _4G, "test cannot run on 32-bit systems")
#@unittest.skipUnless(sys.maxsize > _4G, "test cannot run on 32-bit systems") # original CPython decorator
@unittest.skipUnless(struct.calcsize('P') * 8 > 32, "test cannot run on 32-bit systems") # IronPython: better detection of 32-bit platform
def test_around_4GB(self):
self._test_around_boundary(_4G)

Expand Down

0 comments on commit aaf701d

Please sign in to comment.