Repository navigation
Clarify properties of memory safety in .NET programs - #375
Conversation
|
|
||
| * Memory safety | ||
| * No access to uninitialized memory | ||
| * Managed references are always valid to dereference, both as references and if converted to a pointer |
There was a problem hiding this comment.
This statement may be interpreted as including "must be non-null", but that's not the case. Null managed references are fine. Do we need to clarify that?
There was a problem hiding this comment.
Imo it's unclear beyond just that. Maybe something like:
| * Managed references are always valid to dereference, both as references and if converted to a pointer | |
| * Managed references always refer to properly-aligned type-compatible valid memory, or null, for the whole period that they could be dereferenced |
This makes the alignment requirement explicit, along with the type-compatibility requirement too (although, it could be worth having a section that actually defines what type-compatible actually is also, as it's not particularly simple - e.g., ref readonly object pointing to string storage is fine, but ref object pointing to string storage is not; ref byte pointing to int storage is fine, but ref int pointing to byte x4 storage is not correctly aligned in general, so you'd somehow have to guarantee being appropriately aligned for the whole duration on top of that, unlike in the other direction (e.g., by fixed, or by being in a type with 4B alignment & starting at 4B alignment, etc.); etc.), along with explicitly allowing null.
It also removes this part:
Managed references are always valid to dereference, both as references and if converted to a pointer
which reads to me as that I have to be able to do var someValue = *(T*)Unsafe.AsPointer(ref myRefT);, which is certainly not always valid even for valid byrefs.
There was a problem hiding this comment.
which reads to me as that I have to be able to do var someValue = (T)Unsafe.AsPointer(ref myRefT);, which is certainly not always valid even for valid byrefs.
My understanding is that this is true for every non null byref. Why wouldn't this be true?
There was a problem hiding this comment.
It's not, because the GC could move it before you actually dereference, which results in a GC hole. This is especially true if you do a bunch of stuff in between the conversion & dereference (but also true for the code sample I provided, just you are unlikely to see it in practice with that particular sample). It's only valid if it's otherwise pinned & otherwise kept alive for the whole duration that the pointer can be dereferenced for (if either of these fail, then you run into issues, as the pointer will neither pin it nor track a relocation, nor keep it alive, if a GC occurs at just the wrong time - whereas having a byref keeps it tracked & using it keeps it alive).
There was a problem hiding this comment.
I wonder whether the cases this is intended to cover would be better explained in the clarifying examples toward the end of the document. All the cases I can think of already follow from requiring safe code to maintain the two core properties. This seems redundant as a core property.
There was a problem hiding this comment.
Maybe we need a section like "this is valid" and another section like "for publicly exposed things, it's unsafe if these properties are not met"? E.g., a ref to end of array is valid & one existing doesn't break the safety invariant on its own, but it's unsafe to publicly expose it.
In particular, to clarify, both aspects are part of the safety model - the first aspect discusses what has to be met to not break relevant runtime invariants, and the second discusses additional requirements that have to be met if you publicly expose it for callers to not break those same runtime invariants without unsafe.
There was a problem hiding this comment.
This makes the alignment requirement explicit, along with the type-compatibility requirement too (although, it could be worth having a section that actually defines what type-compatible actually is also, as it's not particularly simple - e.g.,
ref readonly objectpointing tostringstorage is fine, butref objectpointing tostringstorage is not;ref bytepointing tointstorage is fine, butref intpointing tobyte x4storage is not correctly aligned in general, so you'd somehow have to guarantee being appropriately aligned for the whole duration on top of that, unlike in the other direction (e.g., by fixed, or by being in a type with 4B alignment & starting at 4B alignment, etc.); etc.), along with explicitly allowingnull.
I don't think it's actually illegal to have such references, only to dereference them with improper alignment/managed type. MemoryMarshal.GetArrayDataReference(Array) is a proof of that.
There was a problem hiding this comment.
I don't think it's actually illegal to have such references, only to dereference them with improper alignment/managed type.
MemoryMarshal.GetArrayDataReference(Array)is a proof of that.
You are correct, but if you publicly expose it that would be unsafe. This lines up well with the idea I just posted before you commented :)
There was a problem hiding this comment.
I've tried to rephrase this by adding a new requirements section. I think rules about managed references are reasonable to split out since they're not about any particular API, they're global rules separate from any API.
There was a problem hiding this comment.
One thing I want to help people with: a lot of unsafe code is about users being aware of the requirements that APIs are placing on them. But the system also places requirements on people who write unsafe code. And violating those rules can destabilize the system, regardless of what the caller does. It's good to call these things out because these are basically "the rules for writing valid unsafe code" and they're separate from the rules about using unsafe code.
|
|
||
| * Memory safety | ||
| * No access to uninitialized memory | ||
| * Managed references are either null, or always valid to dereference, both as references and if converted to a pointer of the same type |
There was a problem hiding this comment.
It is valid for a managed reference to point to the end of the array. It is valid unsafe use of managed references.
https://github.com/dotnet/runtime/blob/main/docs/design/specs/Ecma-335-Augments.md goes into detail about this. (look for "Managed pointers which point at the address just past the end of an object, or the address where an element just past the end of an array would be stored, are permitted but not dereferenceable.")
There was a problem hiding this comment.
Yeah, managed references are particularly weird in that they have separate rules for safe code and unsafe code:
- Some things are valid everywhere (normal managed references created by safe APIs).
- Some thigns are only valid in unsafe code (unaligned managed references).
- Some things are never valid (managed references pointing to invalid memory).
Added requirements for managed references in memory safety context.
Clarify requirements for managed references in unsafe code.
|
|
||
| In particular, managed references have the following requirements: | ||
|
|
||
| - Managed references must be aligned in safe code. Managed refereces are often pinned as unmanaged pointers and passed to native code using C/C++ conventions. C/C++ requires that all pointers are properly aligned, the behavior is undefined otherwise and it can lead to memory safety violations. |
There was a problem hiding this comment.
This is almost exact duplicate of what's written a few paragraphs below. Did you mean to consolidate it?
| In particular, managed references have the following requirements: | ||
|
|
||
| - Managed references must be aligned in safe code. Managed refereces are often pinned as unmanaged pointers and passed to native code using C/C++ conventions. C/C++ requires that all pointers are properly aligned, the behavior is undefined otherwise and it can lead to memory safety violations. | ||
| - Managed references may be null, but if they are non-null then they must point to valid memory, _even in unsafe code_. For these purposes, one element past the end of an array is considered valid memory. |
There was a problem hiding this comment.
This is not capturing all nuances.
For example, we also allow "the address computed by adding the address of a field and the sizeof of the type of that field" that is not necessarily a valid memory. On the other hand, we prohibit valid memory that was not explicitly allocated on managed or native heap (for example, it is invalid for a byref to point into the GC control data structures). It may be best to just link to https://github.com/dotnet/runtime/blob/main/docs/design/specs/Ecma-335-Augments.md#ii1442 .
There was a problem hiding this comment.
OK, fair enough -- linking seems easy enough. I suppose that the only things that we need to state here are things that are only allowed in unsafe code, not things that are disallowed in both safe and unsafe code?
Clarified requirements for managed references in safe and unsafe code, emphasizing alignment and valid memory pointers.
Co-authored-by: Jan Kotas <jkotas@microsoft.com>
This adds a line about managed references, which can be useful clarification for unaligned managed references in the example section below.