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