Actual immutability enforcement by placing immutable data into read-only sections
Siarhei Siamashka
siarhei.siamashka at gmail.com
Mon Dec 19 17:23:34 UTC 2022
On Monday, 19 December 2022 at 14:06:50 UTC, bauss wrote:
>> What do you think about it? Does this require a new DIP?
>
> Isn't it going to be difficult to properly implement? Since you
> can't really place data into read-only memory, but you have to
> protect whole pages ex. VirtualProtect() on Windows. Esepcially
> with how immutable data can still be allocated through GC. Or
> am I not understanding something about this at all?
I did mention static immutable and CTFE in my initial message.
Some of the immutable data is generated at compile time and can
safely go into read-only sections. Right now I'm only interested
in trying to improve just this.
But since you mentioned catching write accesses to the immutable
data backed by GC allocations, this can be done with some help
from extra tools or instrumentation. For example, I did use
valgrind to debug the code from
https://forum.dlang.org/post/cmtaeuedmdwxjecpcrjh@forum.dlang.org
```C
#include <stddef.h>
#include <valgrind/memcheck.h>
void vg_mark_block(void *p, size_t size)
{
int valgrind_handle = VALGRIND_CREATE_BLOCK(p, size, "MARKED
BLOCK");
VALGRIND_MAKE_MEM_NOACCESS(p, size);
}
```
```D
extern(C) void vg_mark_block(void *p, size_t size) @nogc;
void main() @nogc {
try {
static immutable e = new Exception("test");
vg_mark_block(cast(void*)e, __traits(classInstanceSize,
typeof(e)));
throw e;
} catch (Exception e) {
assert(e.msg == "test");
}
}
```
```
==3369== Invalid write of size 8
==3369== at 0x4D5BEAE: _d_createTrace (in
/usr/lib64/libphobos2.so.0.99.1)
==3369== by 0x4D5D4F9: _d_throwdwarf (in
/usr/lib64/libphobos2.so.0.99.1)
==3369== by 0x1091C2: _Dmain (in /tmp/test/test)
==3369== by 0x4D5CEBE: void rt.dmain2._d_run_main2(char[][],
ulong, extern (C) int function(char[][])*).runAll().__lambda2()
(in /usr/lib64/libphobos2.so.0.99.1)
==3369== by 0x4D5CD6D: void rt.dmain2._d_run_main2(char[][],
ulong, extern (C) int function(char[][])*).tryExec(scope void
delegate()) (in /usr/lib64/libphobos2.so.0.99.1)
==3369== by 0x4D5CE46: void rt.dmain2._d_run_main2(char[][],
ulong, extern (C) int function(char[][])*).runAll() (in
/usr/lib64/libphobos2.so.0.99.1)
==3369== by 0x4D5CD6D: void rt.dmain2._d_run_main2(char[][],
ulong, extern (C) int function(char[][])*).tryExec(scope void
delegate()) (in /usr/lib64/libphobos2.so.0.99.1)
==3369== by 0x4D5CCD6: _d_run_main2 (in
/usr/lib64/libphobos2.so.0.99.1)
==3369== by 0x4D5CA9F: _d_run_main (in
/usr/lib64/libphobos2.so.0.99.1)
==3369== by 0x10923F: main (in /tmp/test/test)
==3369== Address 0x10c098 is 56 bytes inside a MARKED BLOCK of
size 76 client-defined
==3369== at 0x1095A1: vg_mark_block (in /tmp/test/test)
==3369== by 0x1091B3: _Dmain (in /tmp/test/test)
```
Unfortunately valgrind reports both read and write accesses to
this area in the log, so the noise about "invalid reads" needs to
be filtered out. It doesn't support marking an address range as
read-only out of the box:
https://valgrind.org/docs/manual/mc-manual.html#mc-manual.clientreqs (but maybe this can be improved?).
ASAN instrumentation could be also potentially useful in the
future for catching write accesses to the immutable data backed
by GC allocations. And I'm pleasantly surprised to see that ASAN
is [already available in
LDC](http://johanengelen.github.io/ldc/2017/12/25/LDC-and-AddressSanitizer.html). However just like valgrind, right now ASAN doesn't support poisoning a memory area as read-only: https://www.mail-archive.com/address-sanitizer@googlegroups.com/msg01948.html
More information about the Digitalmars-d
mailing list