Skip to content

Commit aaf701d

Browse files
authored
Support additional memory protection flag combinations (#1864)
* Support additional memory protection flag combinations * Enable some mmap tests
1 parent 719a446 commit aaf701d

File tree

3 files changed

+50
-35
lines changed

3 files changed

+50
-35
lines changed

Src/IronPython.Modules/mmap.cs

Lines changed: 42 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -68,40 +68,65 @@ private static Exception WindowsError(int code) {
6868
public static PythonType mmap {
6969
get {
7070
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) {
71-
return DynamicHelpers.GetPythonTypeFromType(typeof(MmapDefault));
71+
return DynamicHelpers.GetPythonTypeFromType(typeof(MmapWindows));
7272
}
7373

7474
return DynamicHelpers.GetPythonTypeFromType(typeof(MmapUnix));
7575
}
7676
}
7777

78+
7879
[PythonType("mmap"), PythonHidden]
7980
public class MmapUnix : MmapDefault {
8081
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)
81-
: base(context, fileno, length, null, NormalizeAccess(flags, prot, access), offset) { }
82+
: base(context, fileno, length, null, ToMmapFileAccess(flags, prot, access), offset) { }
8283

83-
private static int NormalizeAccess(int flags, int prot, int access) {
84+
private static MemoryMappedFileAccess ToMmapFileAccess(int flags, int prot, int access) {
8485
if (access == ACCESS_DEFAULT) {
8586
if ((flags & (MAP_PRIVATE | MAP_SHARED)) == 0) {
8687
throw PythonOps.OSError(PythonErrorNumber.EINVAL, "Invalid argument");
8788
}
8889
if ((prot & PROT_WRITE) != 0) {
89-
return (flags & MAP_PRIVATE) != 0 ? ACCESS_COPY : ACCESS_WRITE;
90-
}
91-
if ((prot & PROT_READ) != 0) {
92-
return ACCESS_READ;
93-
}
94-
throw PythonOps.NotImplementedError("this combination of flags and prot is not supported");
90+
prot |= PROT_READ;
91+
}
92+
return (prot & (PROT_READ | PROT_WRITE | PROT_EXEC)) switch {
93+
PROT_READ => MemoryMappedFileAccess.Read,
94+
PROT_READ | PROT_WRITE => (flags & MAP_PRIVATE) == 0 ? MemoryMappedFileAccess.ReadWrite : MemoryMappedFileAccess.CopyOnWrite,
95+
PROT_READ | PROT_EXEC => MemoryMappedFileAccess.ReadExecute,
96+
PROT_READ | PROT_WRITE | PROT_EXEC when (flags & MAP_PRIVATE) == 0 => MemoryMappedFileAccess.ReadWriteExecute,
97+
_ => throw PythonOps.NotImplementedError("this combination of prot is not supported"),
98+
};
9599
} else if (flags != MAP_SHARED || prot != (PROT_WRITE | PROT_READ)) {
96100
throw PythonOps.ValueError("mmap can't specify both access and flags, prot.");
97-
} else if (access != ACCESS_READ && access != ACCESS_WRITE && access != ACCESS_COPY) {
98-
throw PythonOps.ValueError("mmap invalid access parameter");
101+
} else {
102+
return access switch {
103+
ACCESS_READ => MemoryMappedFileAccess.Read,
104+
ACCESS_WRITE => MemoryMappedFileAccess.ReadWrite,
105+
ACCESS_COPY => MemoryMappedFileAccess.CopyOnWrite,
106+
_ => throw PythonOps.ValueError("mmap invalid access parameter"),
107+
};
99108
}
100-
return access;
101109
}
102110
}
103111

112+
104113
[PythonType("mmap"), PythonHidden]
114+
public class MmapWindows : MmapDefault {
115+
public MmapWindows(CodeContext context, int fileno, long length, string tagname = null, int access = ACCESS_DEFAULT, long offset = 0)
116+
: base(context, fileno, length, tagname, ToMmapFileAccess(access), offset) { }
117+
118+
private static MemoryMappedFileAccess ToMmapFileAccess(int access) {
119+
return access switch {
120+
ACCESS_READ => MemoryMappedFileAccess.Read,
121+
// On Windows, default access is write-through
122+
ACCESS_DEFAULT or ACCESS_WRITE => MemoryMappedFileAccess.ReadWrite,
123+
ACCESS_COPY => MemoryMappedFileAccess.CopyOnWrite,
124+
_ => throw PythonOps.ValueError("mmap invalid access parameter"),
125+
};
126+
}
127+
}
128+
129+
[PythonHidden]
105130
public class MmapDefault : IWeakReferenceable {
106131
private MemoryMappedFile _file;
107132
private MemoryMappedViewAccessor _view;
@@ -115,21 +140,8 @@ public class MmapDefault : IWeakReferenceable {
115140
private volatile bool _isClosed;
116141
private int _refCount = 1;
117142

118-
public MmapDefault(CodeContext/*!*/ context, int fileno, long length, string tagname = null, int access = ACCESS_DEFAULT, long offset = 0) {
119-
switch (access) {
120-
case ACCESS_READ:
121-
_fileAccess = MemoryMappedFileAccess.Read;
122-
break;
123-
case ACCESS_DEFAULT: // On Windows, default access is write-through
124-
case ACCESS_WRITE:
125-
_fileAccess = MemoryMappedFileAccess.ReadWrite;
126-
break;
127-
case ACCESS_COPY:
128-
_fileAccess = MemoryMappedFileAccess.CopyOnWrite;
129-
break;
130-
default:
131-
throw PythonOps.ValueError("mmap invalid access parameter");
132-
}
143+
public MmapDefault(CodeContext/*!*/ context, int fileno, long length, string tagname, MemoryMappedFileAccess fileAccess, long offset) {
144+
_fileAccess = fileAccess;
133145

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

178-
if (_fileAccess == MemoryMappedFileAccess.ReadWrite && !_sourceStream.CanWrite) {
190+
if (_fileAccess is MemoryMappedFileAccess.ReadWrite or MemoryMappedFileAccess.ReadWriteExecute && !_sourceStream.CanWrite) {
179191
throw WindowsError(PythonExceptions._OSError.ERROR_ACCESS_DENIED);
180192
}
181193

@@ -579,7 +591,7 @@ public string readline() {
579591

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

@@ -802,7 +814,7 @@ private long Position {
802814
}
803815

804816
private void EnsureWritable() {
805-
if (_fileAccess == MemoryMappedFileAccess.Read) {
817+
if (_fileAccess is MemoryMappedFileAccess.Read or MemoryMappedFileAccess.ReadExecute) {
806818
throw PythonOps.TypeError("mmap can't modify a read-only memory map.");
807819
}
808820
}

Src/IronPythonTest/Cases/CPythonCasesManifest.ini

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -587,7 +587,7 @@ IsolationLevel=PROCESS # Also weakref failures; https://github.com/IronLanguages
587587
Ignore=true
588588

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

593593
[CPython.test_module]

Src/StdLib/Lib/test/test_mmap.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import os
55
import re
66
import itertools
7+
import struct # IronPython: for platform architecture detection
78
import socket
89
import sys
910
import weakref
@@ -721,7 +722,6 @@ def test_weakref(self):
721722
gc_collect()
722723
self.assertIs(wr(), None)
723724

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

727727
def setUp(self):
@@ -754,7 +754,8 @@ def test_large_offset(self):
754754

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

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

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

0 commit comments

Comments
 (0)