You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: docs/standard/unsafe-code/best-practices.md
+67-19Lines changed: 67 additions & 19 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -45,7 +45,7 @@ The next sections describe common unsafe patterns with ✔️ DO and ❌ DON'T r
45
45
## 1. Untracked managed pointers (`Unsafe.AsPointer` and friends)
46
46
47
47
It's not possible to convert a managed (tracked) pointer to an unmanaged (untracked)
48
-
pointer in safe C#. When such need arises, it might be tempting to use `Unsafe.AsPointer`
48
+
pointer in safe C#. When such need arises, it might be tempting to use <xref:System.Runtime.CompilerServices.Unsafe.AsPointer``1(``0@)?displayProperty=nameWithType>
49
49
to avoid the overhead of a `fixed` statement. While there are valid
50
50
use cases for that, it introduces a risk of creating untracked pointers to moveable objects.
51
51
Example:
@@ -92,9 +92,9 @@ unsafe void ReliableCode(ref int x)
92
92
93
93
### Recommendations
94
94
95
-
1. ❌ DON'T use `ref X` arguments with an implicit contract that `X` is always stack-allocated, pinned, or otherwise not relocatable by the GC. Consider instead taking a [ref struct](../../csharp/language-reference/builtin-types/ref-struct.md) argument or changing the argument to be a raw pointer type (`X*`).
96
-
2. ❌ DON'T use a pointer from `Unsafe.AsPointer` if it can outlive the original object it is pointing to. [Per the API's documentation](xref:System.Runtime.CompilerServices.Unsafe.AsPointer``1(``0@)), it's up to the caller of `Unsafe.AsPointer` to guarantee that the GC cannot relocate the reference. Ensure it's clearly visible to code reviewers that the caller has fulfilled this prerequisite.
97
-
3. ✔️ DO use `GCHandle` or `fixed` scopes instead of `Unsafe.AsPointer` to define explicit scopes for unmanaged pointers and to ensure that the object is always pinned.
95
+
1. ❌ DON'T use `ref X` arguments with an implicit contract that `X` is always stack-allocated, pinned, or otherwise not relocatable by the GC. Same applies to plain objects and Spans - don't introduce non-obvious caller-based contracts about their lifetimes in methods signatures. Consider instead taking a [ref struct](../../csharp/language-reference/builtin-types/ref-struct.md) argument or changing the argument to be a raw pointer type (`X*`).
96
+
2. ❌ DON'T use a pointer from <xref:System.Runtime.CompilerServices.Unsafe.AsPointer``1(``0@)?displayProperty=nameWithType> if it can outlive the original object it is pointing to. [Per the API's documentation](xref:System.Runtime.CompilerServices.Unsafe.AsPointer``1(``0@)), it's up to the caller of <xref:System.Runtime.CompilerServices.Unsafe.AsPointer``1(``0@)?displayProperty=nameWithType> to guarantee that the GC cannot relocate the reference. Ensure it's clearly visible to code reviewers that the caller has fulfilled this prerequisite.
97
+
3. ✔️ DO use [GCHandle](xref:System.Runtime.InteropServices.GCHandle`1) or `fixed` scopes instead of <xref:System.Runtime.CompilerServices.Unsafe.AsPointer``1(``0@)?displayProperty=nameWithType> to define explicit scopes for unmanaged pointers and to ensure that the object is always pinned.
98
98
4. ✔️ DO use unmanaged pointers (with `fixed`) instead of byrefs when you need to align an array to a specific boundary. This ensures the GC won't relocate the object and invalidate any alignment assumptions your logic might rely upon.
99
99
100
100
## 2. Exposing pointers outside of the `fixed` scope
Method(pArray); // Bug if `Method` allows `pArray` to escape, perhaps by assigning it to a field.
114
+
Method(pArray); // Bug if `Method` allows `pArray` to escape,
115
+
// perhaps by assigning it to a field.
115
116
116
117
returnpArray; // Bug!
117
118
@@ -146,6 +147,15 @@ While accessing or relying on internal implementation details is bad practice in
146
147
7. ❌ DON'T simply assume that a reference is non-relocatable. This guidance applies to string and UTF-8 (`"..."u8`) literals, static fields, RVA fields, LOH objects, and so on.
147
148
* These are runtime implementation details that might hold for some runtimes but not for others.
148
149
* Unmanaged pointers to such objects might not stop assemblies from being unloaded, causing the pointers to become dangling. Use `fixed` scopes to ensure correctness.
// Bug! The assembly containing the RVA field might be unloaded at this point
155
+
// and `p` becomes a dangling pointer.
156
+
intvalue=p[0]; // Access violation or other issue.
157
+
```
158
+
149
159
8. ❌ DON'T write code that relies on the implementation details of a specific runtime.
150
160
151
161
## 4. Invalid managed pointers (even if they are never dereferenced)
@@ -225,8 +235,7 @@ And even if the layout is similar, you should still be careful when GC reference
225
235
### Recommendations
226
236
227
237
1. ❌ DON'T cast structs to classes or vice versa.
228
-
2. ❌ DON'T use `Unsafe.As` for struct-to-struct or class-to-class conversions unless you're absolutely sure that the cast is legal.
229
-
* For more information, see the _Remarks_ section of the [`Unsafe.As` API docs](xref:System.Runtime.CompilerServices.Unsafe.As``2(``0@)).
238
+
2. ❌ DON'T use `Unsafe.As` for struct-to-struct or class-to-class conversions unless you're absolutely sure that the cast is legal. For more information, see the _Remarks_ section of the [`Unsafe.As` API docs](xref:System.Runtime.CompilerServices.Unsafe.As``2(``0@)).
230
239
3. ✔️ DO prefer safer field-by-field copying, external libraries such as [AutoMapper](https://github.com/AutoMapper/AutoMapper), or Source Generators for such conversions.
231
240
4. ✔️ DO prefer `Unsafe.BitCast` over `Unsafe.As`, as `BitCast` provides some rudimentary usage checks. Note that these checks do not provide full correctness guarantees, meaning `BitCast` is still considered an unsafe API.
232
241
@@ -331,6 +340,20 @@ void DoWork()
331
340
Therefore, it is recommended to explicitly extend the lifetime of objects using <xref:System.GC.KeepAlive(System.Object)?displayProperty=nameWithType>
332
341
or <xref:System.Runtime.InteropServices.SafeHandle>.
333
342
343
+
Another classic instance of this problem is <xref:System.Runtime.InteropServices.Marshal.GetFunctionPointerForDelegate``1(``0)?displayProperty=nameWithType> API:
// Bug! The delegate might be collected by the GC here.
352
+
// It should be kept alive until the native code is done with it.
353
+
354
+
RegisterCallback(fnPtr);
355
+
```
356
+
334
357
### Recommendations
335
358
336
359
1. ❌ DON'T make assumptions about object lifetimes. For instance, never assume `this` is always alive through the end of the method.
@@ -346,7 +369,7 @@ Example: A struct containing GC references might be zeroed or overwritten in a n
346
369
### Recommendations
347
370
348
371
1. ❌ DON'T access locals across threads (especially if they contain GC references).
349
-
2. ✔️ DO use heap or unmanaged memory (for example, `NativeMemory.Alloc`) instead.
372
+
2. ✔️ DO use heap or unmanaged memory (for example, <xref:System.Runtime.InteropServices.NativeMemory.Alloc*?displayProperty=nameWithType>) instead.
350
373
351
374
## 9. Unsafe bounds check removal
352
375
@@ -550,7 +573,7 @@ See the [.NET Memory Model](https://github.com/dotnet/runtime/blob/main/docs/des
550
573
551
574
Another risk of unaligned memory access is the potential for an application crash in certain scenarios.
552
575
While some .NET runtimes rely on the OS to fixup misaligned accesses, there are still some scenarios on some
553
-
platforms where misaligned access can lead to an `DataMisalignedException` (or `SEHException`).
576
+
platforms where misaligned access can lead to an <xref:System.DataMisalignedException> (or <xref:System.Runtime.InteropServices.SEHException>).
554
577
Some of the examples include:
555
578
556
579
*`Interlocked` operations on misaligned memory on some platforms ([example](https://github.com/dotnet/runtime/issues/91662)).
@@ -563,9 +586,9 @@ Some of the examples include:
563
586
2. ✔️ DO align data manually if necessary, but keep in mind that the GC can relocate objects
564
587
at any time, effectively changing the alignment dynamically. This is especially important for various
565
588
`StoreAligned`/`LoadAligned` APIs in SIMD.
566
-
3. ✔️ DO use explicit unaligned Read/Write APIs such as `Unsafe.ReadUnaligned`/`Unsafe.WriteUnaligned`
567
-
instead of aligned ones such as `Unsafe.Read`/`Unsafe.Write` or `Unsafe.As` if data might be misaligned.
568
-
4. ✔️ DO keep in mind that various memory manipulation APIs such as `Span<T>.CopyTo` also don't provide atomicity guarantees.
589
+
3. ✔️ DO use explicit unaligned Read/Write APIs such as <xref:System.Runtime.CompilerServices.Unsafe.ReadUnaligned*?displayProperty=nameWithType>/<xref:System.Runtime.CompilerServices.Unsafe.WriteUnaligned*?displayProperty=nameWithType>
590
+
instead of aligned ones such as <xref:System.Runtime.CompilerServices.Unsafe.Read``1(System.Void*)?displayProperty=nameWithType>/<xref:System.Runtime.CompilerServices.Unsafe.Write``1(System.Void*,``0)?displayProperty=nameWithType> or <xref:System.Runtime.CompilerServices.Unsafe.As``2(``0@)?displayProperty=nameWithType> if data might be misaligned.
591
+
4. ✔️ DO keep in mind that various memory manipulation APIs such as <xref:System.Span`1.CopyTo(System.Span{`0})?displayProperty=nameWithType> also don't provide atomicity guarantees.
569
592
5. ✔️ DO consult with the [.NET Memory Model](https://github.com/dotnet/runtime/blob/main/docs/design/specs/Memory-model.md) documentation ([see references](#references)) for more details on atomicity guarantees.
570
593
6. ✔️ DO measure performance across all your target platforms, as some platforms impose a significant performance penalty for unaligned memory accesses. You may find that on these platforms, naive code performs better than clever code.
571
594
7. ✔️ DO keep in mind that there are scenarios and platforms where unaligned memory access might lead to an exception.
// Span<int> s = length <= 512 ? stackalloc int[512] : new int[length];
698
+
// Span<int> s = length <= 256 ? stackalloc int[256] : new int[length];
676
699
// Which performs a faster zeroing of the stackalloc, but potentially consumes more stack space.
677
700
Consume(s);
678
701
}
@@ -717,6 +740,8 @@ ms.buffer[7] = 0; // Bounds check elided; index is known to be in range.
717
740
ms.buffer[10] =0; // Compiler knows this is out of range and produces compiler error CS9166.
718
741
```
719
742
743
+
Another reason to avoid fixed-size buffers in favor of inline arrays, which are always zero-initialized by default, is that fixed-size buffers might have non-zeroed contents in certain scenarios.
744
+
720
745
### Recommendations
721
746
722
747
1. ✔️ DO prefer replacing fixed-size buffers with inline arrays or IL marshalling attributes where possible.
@@ -746,7 +771,7 @@ can lead to information disclosure, data corruption, or process termination via
746
771
747
772
1. ❌ DON'T expose methods whose arguments are pointer types (unmanaged pointers `T*` or managed pointers `ref T`) when those arguments are intended to represent buffers. Use safe buffer types like `Span<T>` or `ReadOnlySpan<T>` instead.
748
773
2. ❌ DON'T use implicit contracts for byref arguments, such as requiring all callers to allocate the input on the stack. If such a contract is necessary, consider using [ref struct](../../csharp/language-reference/builtin-types/ref-struct.md) instead.
749
-
3. ❌ DON'T assume buffers are zero-terminated unless the scenario explicitly documents that this is a valid assumption. For example, even though .NET guarantees that `string` instances are null-terminated, the same does not hold of other buffer types like `ReadOnlySpan<char>` or `char[]`.
774
+
3. ❌ DON'T assume buffers are zero-terminated unless the scenario explicitly documents that this is a valid assumption. For example, even though .NET guarantees that `string` instances and `"..."u8` literals are null-terminated, the same does not hold of other buffer types like `ReadOnlySpan<char>` or `char[]`.
@@ -863,11 +888,11 @@ Avoid using such techniques unless absolutely necessary.
863
888
864
889
### Recommendations
865
890
866
-
1. ❌ DON'T emit raw IL code as it comes with no guiderails and it makes it easy to introduce type safety and other issues.
867
-
* Like other dynamic code generation techniques, emitting raw IL is also not AOT-friendly if it's not done at the build time.
891
+
1. ❌ DON'T emit raw IL code as it comes with no guiderails and it makes it easy to introduce type safety and other issues. Like other dynamic code generation techniques, emitting raw IL is also not AOT-friendly if it's not done at the build time.
868
892
2. ✔️ DO use Source Generators instead, if possible.
869
893
3. ✔️ DO prefer [\[UnsafeAccessor\]](xref:System.Runtime.CompilerServices.UnsafeAccessorAttribute) instead of emitting raw IL for writing low overhead serialization code for private members if the need arises.
870
894
4. ✔️ DO file an API proposal against [dotnet/runtime](https://github.com/dotnet/runtime) if some API is missing and you're forced to use raw IL code instead.
895
+
5. ✔️ DO use `ilverify` or similar tools to validate the emitted IL code if you must use raw IL.
871
896
872
897
## 19. Uninitialized locals `[SkipLocalsInit]` and `Unsafe.SkipInit`
873
898
@@ -947,7 +972,7 @@ While most of the suggestions in this document apply to interop scenarios as wel
947
972
948
973
## 23. Thread safety
949
974
950
-
See[Managed threading best practices](../../standard/threading/managed-threading-best-practices.md) and [.NET Memory Model](https://github.com/dotnet/runtime/blob/main/docs/design/specs/Memory-model.md).
975
+
Memory safety and thread safety are orthogonal concepts. Code can be memory safe yet still contain data races, torn reads, or visibility bugs; conversely, code can be thread safe while still invoking undefined behavior through unsafe memory manipulation. For broader guidance, see[Managed threading best practices](../../standard/threading/managed-threading-best-practices.md) and [.NET Memory Model](https://github.com/dotnet/runtime/blob/main/docs/design/specs/Memory-model.md).
951
976
952
977
## 24. Unsafe code around SIMD/Vectorization
953
978
@@ -961,6 +986,29 @@ In the context of the unsafe code it's important to keep in mind:
961
986
962
987
Fuzz testing (or "fuzzing") is an automated software testing technique that involves providing invalid, unexpected, or random data as inputs to a computer program. It provides a way to detect memory safety issues in code that may have gaps in test coverage. You can use tools like [SharpFuzz](https://github.com/Metalnem/sharpfuzz) to set up fuzz testing for .NET code.
963
988
989
+
## 26. Compiler warnings
990
+
991
+
Generally, the C# compiler doesn't provide extensive support such as warnings and analyzers around incorrect unsafe code usage. However, there are some existing warnings that can help detect potential issues and should not be ignored or suppressed without careful consideration. Some examples include:
992
+
993
+
```csharp
994
+
nintptr=0;
995
+
unsafe
996
+
{
997
+
intlocal=0;
998
+
ptr= (nint)(&local);
999
+
}
1000
+
awaitTask.Delay(100);
1001
+
1002
+
// ptr is used here
1003
+
```
1004
+
1005
+
This code produces warning CS9123 ("The '&' operator should not be used on parameters or local variables in async methods"), which implies the code is likely incorrect.
1006
+
1007
+
### Recommendations
1008
+
1009
+
1. ✔️ DO pay attention to compiler warnings and fix the underlying issues instead of suppressing them.
1010
+
2. ❌ DON'T assume that the absence of compiler warnings implies the code is correct. The C# compiler has limited to no support for detecting incorrect unsafe code usage.
1011
+
964
1012
## References
965
1013
966
1014
*[Unsafe code, pointer types, and function pointers](../../csharp/language-reference/unsafe-code.md).
0 commit comments