4 ms·
> and goes on to explain that each arena has its own unique/distinct address space That does not seem to be how it is implemented now. Try the example use-afte
by cormacrelf 4y ago
> and goes on to explain that each arena has its own unique/distinct address space
That does not seem to be how it is implemented now. Try the example use-after-free on https://uptrace.dev/blog/posts/go-memory-arena.html https://uptrace.dev/blog/posts/go-memory-arena.html under 'Address Sanitizer'. There is no error at all when you dereference, it silently keeps working as freed pointers often do (until they don't). Maybe they will add the virtual address space thing later.
Edit: the issue (https://github.com/golang/go/issues/51317 https://github.com/golang/go/issues/51317) in which that comment was made also has some people referring to the arena being freed "when the runtime gets around to it" rather than immediately. I can't imagine why the virtual address space wouldn't be made poisonous inside the arena.Free method, but it's possible they do do it later. So your pointers maybe do stop working at an indeterminate time. Sounds pretty much like C. I even tried adding a few `runtime.GC()` calls before dereferencing but to no avail, couldn't get it to crash.
The Java 20 implementation has no such issues (https://gist.github.com/cormacrelf/8ddd3cc1b086e4ade93c029a98327d86 https://gist.github.com/cormacrelf/8ddd3cc1b086e4ade93c029a9...), partly because there is no API for allocating a generic T in the arena, so all dereferences are through the managed MemorySegment API. It only seems to offer raw memory, which you can use to implement arrays of unboxed C-style structs, which is great for FFI and network buffers etc. This would be a good tradeoff for Go as well, surely.
- klodolph 4y agoIf you want network buffers in Go, you can just use []byte. The Go GC is designed to make this work well, so nobody in Go is asking for a separate buffer type like you have in Java. In Java you have Buffer in the first place because there are good reasons why you might not want to use byte[]. Unboxed C types in Go can be done with cgo (it's not necessarily pleasant, but it works).
- kaba0 4y ago> why you might not want to use byte[] Such as? Java buffers use byte arrays by default as underlying storage.
- klodolph 4y agohttps://docs.oracle.com/javase/7/docs/api/java/nio/ByteBuffer.html https://docs.oracle.com/javase/7/docs/api/java/nio/ByteBuffe... Look for "Direct vs. non-direct buffers". Some buffers are backed by arrays, some are not. You're basically deciding whether you are okay with a higher cost for I/O or a higher-cost for interacting with the data in the buffer.
- kaba0 4y agoI do know about it, I was trying to find out what difference do the parent mean between Go’s byte arrays and Java’s solution(s). Also, I fail to get your conclusion — direct buffers only have a higher cost for initialization/dealloc otherwise reading/writing should be as cheap as it gets. Byte buffers on the other hand do get write barriers but that is also unlikely to be hit that often (and I believe larger byte arrays get allocated in a separate region that doesn’t get moved).
- eptcyka 4y agoWhy do Go enthusiasts look at types that specialize more generic data structures to ensure invariants and scoff? Maybe go only ever needed []byte, and nothing else - it's always just bytes anyway, right? Moreover, when the go team accomplishes the same goals by extending the language instead of using a library, this is well received.
- stickupkid 4y agoSpecifically to the arena, this required integration with the GC, so couldn't be done as an external library. Hence why a language spec was required.
- klodolph 4y ago> Why do Go enthusiasts look at types that specialize more generic data structures to ensure invariants and scoff? I'm sorry, I'm not really sure what difference between Buffer and byte[] in Java I'm supposed to care about, except for the details of how these objects are allocated in memory. There is not really any semantic difference between them, as far as I can tell. Maybe I don't understand what you're saying--I'd love for you to clarify the reasons why you think Go developers are, I guess, a little bit dense, or foolish, or whatever negative adjectives you like to apply to people who use Go (you know, rather than talking about the language itself--it's always fun to make fun of people).
- _old_dude_ 4y agoyes, Java/C# GCs (Android included) and Go GC are very different beasts. Go GC does not move objects in memory (mark and sweep) while other GCs (mark and compact) do, to avoid heap fragmentation and better throughput (see generational GCs). One issue with mark and compact GCs is that you can not move the array of bytes during a system call. A ByteBuffer/Memory object, is an object that allocates its byte arrays using malloc, outside of the heap, so the bytes are not moved by the GC.
- mknyszek 4y agoThe functionality to poison the address space is there, and it does work. What you're encountering is one of two exceptions in the implementation where you might not get an immediate failure: 1. If you use only a very small amount of an arena chunk and free it, it goes back on a reuse list as an optimization. Accessing that chunk's memory, despite the fact that the arena was freed, is entirely memory safe: nothing else will use that memory. That address space will be properly poisoned once the arena chunk is full, or close to it. 2. If the GC is actively marking, arena chunk poisoning is delayed to avoid races with the GC that might cause it to dereference a pointer into poisoned memory. The arena chunk is poisoned as soon as the GC is done. The API explicitly does not guarantee a crash on use-after-free[1] because the Go team wanted a valid implementation of arenas to be simply "new" for New and noop for Free. The point is to just stay memory safe and have a high probability of catching an issue _in production_ where presumably arenas are filled up (otherwise why are you using arenas?). Edit: arguably that optimization should just be turned off for MSAN/ASAN mode for greater user-friendliness, which seems reasonable. I think that was just an oversight. [1]: https://cs.opensource.google/go/go/+/master:src/arena/arena.go;l=19;bpv=1;bpt=0 https://cs.opensource.google/go/go/+/master:src/arena/arena....
- cormacrelf 4y ago> If you use only a very small amount of an arena chunk and free it, it goes back on a reuse list as an optimization. Accessing that chunk's memory, despite the fact that the arena was freed, is entirely memory safe: nothing else will use that memory. I still have a pointer into the chunk. If you reuse the chunk in a different arena, I still have a pointer into the chunk. At no point is it invalidated, and the new arena will now start allocating new stuff from the start of the chunk. Right? And my old pointer still works, and the data inside it is at some point overwritten. How is that memory safe? Are you eventually unmapping the virtual address space and remapping the underlying allocation somewhere else? So the poisoning happens when the chunk is picked up for reuse by a new arena?
- mknyszek 4y ago> At no point is it invalidated, and the new arena will now start allocating new stuff from the start of the chunk. Right? And my old pointer still works, and the data inside it is at some point overwritten. It doesn't allocate at the start of the chunk, it just picks up wherever the last one left off. That allocated memory in the chunk from previous arena allocations is not reused until the chunk as a whole is unmapped and the GC can confirm that no more pointers point into that chunk's address space (if you leave a dangling pointer, you only waste address space). This points-into property is cheap to check, it's equivalent to whether the chunk has been marked by the GC. Again, I think that MSAN/ASAN should probably just be more strict with these kinds of use-after-frees. You won't crash, but it's still technically incorrect. (Not much can be done about the "GC is in the mark phase" case, unfortunately. Otherwise MSAN/ASAN will complain when the GC inevitably tries to access a pointer into a delayed chunk.)