Skip to content

Commit 20ec675

Browse files
committed
Replace cs with csharp
1 parent 50f18a3 commit 20ec675

File tree

1 file changed

+39
-39
lines changed

1 file changed

+39
-39
lines changed

docs/standard/unsafe-code/best-practices.md

Lines changed: 39 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ If the GC interrupts the execution of the `UnreliableCode` method right after th
6363
location stored in `x` but won't know anything about `nativePointer` and will not update the value
6464
it contains. At that point, writing to `nativePointer` is writing to arbitrary memory.
6565

66-
```cs
66+
```csharp
6767
unsafe void UnreliableCode(ref int x)
6868
{
6969
int* nativePointer = (int*)Unsafe.AsPointer(ref x);
@@ -80,7 +80,7 @@ to an unexpected exception, general global state corruption, or process terminat
8080
The recommended solution is instead to use the `fixed` keyword and `&` address-of operator to ensure that the
8181
GC cannot relocate the target reference for the duration of the operation.
8282

83-
```cs
83+
```csharp
8484
unsafe void ReliableCode(ref int x)
8585
{
8686
fixed (int* nativePointer = &x) // `x` cannot be relocated for the duration of this block.
@@ -104,7 +104,7 @@ keyword defines a scope for the pointer obtained from the pinned object, it's st
104104
to escape the `fixed` scope and introduce bugs, as C# doesn't provide any ownership/lifecycle protections for it.
105105
A typical example is the following snippet:
106106

107-
```cs
107+
```csharp
108108
unsafe int* GetPointerToArray(int[] array)
109109
{
110110
fixed (int* pArray = array)
@@ -161,7 +161,7 @@ a managed pointer's value is relevant not only when it's dereferenced by the dev
161161
when it's examined by the GC. Thus, a developer can create invalid unmanaged pointers without consequence
162162
as long as they're not dereferenced, but creating any invalid managed pointer is a bug. Example:
163163

164-
```cs
164+
```csharp
165165
unsafe void UnmanagedPointers(int[] array)
166166
{
167167
fixed (int* p = array)
@@ -178,7 +178,7 @@ unsafe void UnmanagedPointers(int[] array)
178178

179179
However, similar code using byrefs (managed pointers) is invalid.
180180

181-
```cs
181+
```csharp
182182
void ManagedPointers_Incorrect(int[] array)
183183
{
184184
ref int invalidPtr = ref Unsafe.Add(ref array[0], -1000); // Already a bug!
@@ -203,7 +203,7 @@ While all kinds of struct-to-class or class-to-struct casts are an undefined beh
203203
it's also possible to encounter unreliable patterns with struct-to-struct or class-to-class conversions.
204204
A typical example of an unreliable pattern is the following code:
205205

206-
```cs
206+
```csharp
207207
struct S1
208208
{
209209
string a;
@@ -237,7 +237,7 @@ to assign a GC reference (or a byref to struct with GC fields) to a potential he
237237
go through the Write Barrier that ensures that the GC is aware of new connections between objects.
238238
However, unsafe code allows us to bypass these guarantees and introduce unreliable patterns. Example:
239239

240-
```cs
240+
```csharp
241241
unsafe void InvalidCode1(object[] arr1, object[] arr2)
242242
{
243243
fixed (object* p1 = arr1)
@@ -256,7 +256,7 @@ unsafe void InvalidCode1(object[] arr1, object[] arr2)
256256

257257
Similarly, the following code with managed pointers is also unreliable:
258258

259-
```cs
259+
```csharp
260260
struct StructWithGcFields
261261
{
262262
object a;
@@ -290,7 +290,7 @@ Specifically, do not assume that an object is still alive when it might not be.
290290
across different runtimes or even between different Tiers of the same method (Tier0 and Tier1 in RyuJIT).
291291
Finalizers are a common scenario where such assumptions can be incorrect.
292292

293-
```cs
293+
```csharp
294294
public class MyClassWithBadCode
295295
{
296296
public IntPtr _handle;
@@ -308,7 +308,7 @@ obj.DoWork();
308308
In this example, `DestroyHandle` might be called before `DoWork` completes or even before it begins.
309309
Therefore, it's crucial not to assume that objects, such as `this`, will remain alive until the end of the method.
310310

311-
```cs
311+
```csharp
312312
void DoWork()
313313
{
314314
// A pseudo-code of what might happen under the hood:
@@ -353,7 +353,7 @@ Example: A struct containing GC references might be zeroed or overwritten in a n
353353
In C#, all idiomatic memory accesses include bounds checks by default.
354354
The JIT compiler can remove these checks if it can prove that they are unnecessary, as in the example below.
355355

356-
```cs
356+
```csharp
357357
int SumAllElements(int[] array)
358358
{
359359
int sum = 0;
@@ -375,7 +375,7 @@ accurately assessing the performance benefits.
375375

376376
Consider for example the following method.
377377

378-
```cs
378+
```csharp
379379
int FetchAnElement(int[] array, int index)
380380
{
381381
return array[index];
@@ -384,7 +384,7 @@ int FetchAnElement(int[] array, int index)
384384

385385
If the JIT cannot prove that `index` is always legally within the bounds of `array`, it will rewrite the method to look something like the below.
386386

387-
```cs
387+
```csharp
388388
int FetchAnElement_AsJitted(int[] array, int index)
389389
{
390390
if (index < 0 || index >= array.Length)
@@ -395,7 +395,7 @@ int FetchAnElement_AsJitted(int[] array, int index)
395395

396396
To reduce the overhead from that check in hot code, you might be tempted to use unsafe-equivalent APIs (`Unsafe` and `MemoryMarshal`):
397397

398-
```cs
398+
```csharp
399399
int FetchAnElement_Unsafe1(int[] array, int index)
400400
{
401401
// DANGER: The access below is not bounds-checked and could cause an access violation.
@@ -405,7 +405,7 @@ int FetchAnElement_Unsafe1(int[] array, int index)
405405

406406
Or use pinning and raw pointers:
407407

408-
```cs
408+
```csharp
409409
unsafe int FetchAnElement_Unsafe2(int[] array, int index)
410410
{
411411
fixed (int* pArray = array)
@@ -428,15 +428,15 @@ bounds checks when it is safe to do so.
428428
3. ✔️ DO provide additional hints to the JIT, such as manual bounds checks before loops and saving fields to locals, as [.NET Memory Model](https://github.com/dotnet/runtime/blob/main/docs/design/specs/Memory-model.md) might conservatively prevent the JIT from removing bounds checks in some scenarios.
429429
4. ✔️ DO guard code with `Debug.Assert` bounds checks if unsafe code is still necessary. Consider the example below.
430430

431-
```cs
431+
```csharp
432432
Debug.Assert(array is not null);
433433
Debug.Assert((index >= 0) && (index < array.Length));
434434
// Unsafe code here
435435
```
436436

437437
You might even refactor these checks into reusable helper methods.
438438

439-
```cs
439+
```csharp
440440
[MethodImpl(MethodImplOptions.AggressiveInlining)]
441441
static T UnsafeGetElementAt<T>(this T[] array, int index)
442442
{
@@ -453,7 +453,7 @@ Inclusion of `Debug.Assert` doesn't provide any soundness checks for Release bui
453453
You might be tempted to use unsafe code to coalesce memory accesses to improve performance.
454454
A classic example is the following code to write `"False"` into a char array:
455455

456-
```cs
456+
```csharp
457457
// Naive implementation
458458
static void WriteToDestination_Safe(char[] dst)
459459
{
@@ -498,7 +498,7 @@ mov word ptr [rax+0x08], 101
498498

499499
There is an even simpler and more readable version of the code:
500500

501-
```cs
501+
```csharp
502502
"False".CopyTo(dst);
503503
```
504504

@@ -524,7 +524,7 @@ potential performance penalties due to crossing cache and page boundaries), it s
524524

525525
For example, consider the scenario where you're clearing two elements of an array at once:
526526

527-
```cs
527+
```csharp
528528
uint[] arr = _arr;
529529
arr[i + 0] = 0;
530530
arr[i + 1] = 0;
@@ -536,7 +536,7 @@ observe the new value `0` or the old value `0xFFFFFFFF`, never "torn" values lik
536536

537537
However, assume the following unsafe code is used to bypass the bounds check and zero both elements with a single 64-bit store:
538538

539-
```cs
539+
```csharp
540540
ref uint p = ref Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(arr), i);
541541
Unsafe.WriteUnaligned<ulong>(ref Unsafe.As<uint, byte>(ref p), 0UL);
542542
```
@@ -575,7 +575,7 @@ instead of aligned ones such as `Unsafe.Read`/`Unsafe.Write` or `Unsafe.As` if d
575575
Be cautious when you use various serialization-like APIs to copy or read structs to or from byte arrays.
576576
If a struct contains paddings or non-blittable members (for example, `bool` or GC fields), then classic unsafe memory operations such as `Fill`, `CopyTo`, and `SequenceEqual` might accidentally copy sensitive data from the stack to the paddings or treat garbage data as significant during comparisons creating rarely reproducible bugs. A common anti-pattern might look like this:
577577

578-
```cs
578+
```csharp
579579
T UnreliableDeserialization<TObject>(ReadOnlySpan<byte> data) where TObject : unmanaged
580580
{
581581
return MemoryMarshal.Read<TObject>(data); // or Unsafe.ReadUnaligned
@@ -600,14 +600,14 @@ The only correct approach is to use field-by-field loads/store specialized for e
600600
Generally, byrefs (managed pointers) are rarely null and the only safe way to create a null byref as of today is
601601
to initialize a `ref struct` with `default`. Then all its `ref` fields are null managed pointers:
602602

603-
```cs
603+
```csharp
604604
RefStructWithRefField s = default;
605605
ref byte nullRef = ref s.refFld;
606606
```
607607

608608
However, there are several unsafe ways to create null byrefs. Some examples include:
609609

610-
```cs
610+
```csharp
611611
// Null byref by calling Unsafe.NullRef directly:
612612
ref object obj = ref Unsafe.NullRef<object>();
613613

@@ -638,7 +638,7 @@ the intended logic.
638638

639639
1. ✔️ DO always consume `stackalloc` into `ReadOnlySpan<T>`/`Span<T>` on the left side of the expression to provide bounds checks:
640640

641-
```cs
641+
```csharp
642642
// Good:
643643
Span<int> s = stackalloc int[10];
644644
s[2] = 0; // Bounds check is eliminated by JIT for this write.
@@ -654,7 +654,7 @@ the intended logic.
654654
3. ❌ DON'T use large lengths for `stackalloc`. For example, 1024 bytes could be considered a reasonable upper bound.
655655
4. ✔️ DO check the range of variables used as `stackalloc` lengths.
656656

657-
```cs
657+
```csharp
658658
void ProblematicCode(int length)
659659
{
660660
Span<int> s = stackalloc int[length]; // Bad practice: check the range of `length`!
@@ -664,7 +664,7 @@ the intended logic.
664664

665665
Fixed version:
666666

667-
```cs
667+
```csharp
668668
void BetterCode(int length)
669669
{
670670
// The "throw if length < 0" check below is important, as attempting to stackalloc a negative
@@ -685,7 +685,7 @@ the intended logic.
685685
Fixed-size buffers were useful for interop scenarios with data sources from other languages or platforms. They then were replaced by safer and more convenient [inline-arrays](../../csharp/language-reference/proposals/csharp-12.0/inline-arrays.md).
686686
An example of a fixed-size buffer (requires `unsafe` context) is the following snippet:
687687

688-
```cs
688+
```csharp
689689
public struct MyStruct
690690
{
691691
public unsafe fixed byte data[8];
@@ -698,7 +698,7 @@ ms.data[10] = 0; // Out-of-bounds write, undefined behavior.
698698

699699
A modern and a safer alternative is [inline-arrays](../../csharp/language-reference/proposals/csharp-12.0/inline-arrays.md):
700700

701-
```cs
701+
```csharp
702702
[System.Runtime.CompilerServices.InlineArray(8)]
703703
public struct Buffer
704704
{
@@ -725,7 +725,7 @@ ms.buffer[10] = 0; // Compiler knows this is out of range and produces compiler
725725

726726
Avoid defining APIs that accept unmanaged or managed pointers to contiguous data. Instead, use `Span<T>` or `ReadOnlySpan<T>`:
727727

728-
```cs
728+
```csharp
729729
// Poor API designs:
730730
void Consume(ref byte data, int length);
731731
void Consume(byte* data, int length);
@@ -748,7 +748,7 @@ can lead to information disclosure, data corruption, or process termination via
748748
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.
749749
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[]`.
750750

751-
```cs
751+
```csharp
752752
unsafe void NullTerminationExamples(string str, ReadOnlySpan<char> span, char[] array)
753753
{
754754
Debug.Assert(str is not null);
@@ -779,7 +779,7 @@ can lead to information disclosure, data corruption, or process termination via
779779

780780
4. ❌ DON'T pass a pinned `Span<char>` or `ReadOnlySpan<char>` across a p/invoke boundary unless you have also passed an explicit length argument. Otherwise, the code on the other side of the p/invoke boundary might improperly believe the buffer is null-terminated.
781781

782-
```cs
782+
```csharp
783783
unsafe static extern void SomePInvokeMethod(char* pwszData);
784784

785785
unsafe void IncorrectPInvokeExample(ReadOnlySpan<char> data)
@@ -796,7 +796,7 @@ unsafe void IncorrectPInvokeExample(ReadOnlySpan<char> data)
796796

797797
To resolve this, use an alternative p/invoke signature that accepts _both_ the data pointer _and_ the length if possible. Otherwise, if the receiver has no way of accepting a separate length argument, ensure the original data is converted to a `string` before pinning it and passing it across the p/invoke boundary.
798798

799-
```cs
799+
```csharp
800800
unsafe static extern void SomePInvokeMethod(char* pwszData);
801801
unsafe static extern void SomePInvokeMethodWhichTakesLength(char* pwszData, uint cchData);
802802

@@ -825,7 +825,7 @@ unsafe void CorrectPInvokeExample(ReadOnlySpan<char> data)
825825

826826
Strings in C# are immutable by design, and any attempt to mutate them using unsafe code can lead to undefined behavior. Example:
827827

828-
```cs
828+
```csharp
829829
string s = "Hello";
830830
fixed (char* p = s)
831831
{
@@ -836,7 +836,7 @@ Console.WriteLine("Hello"); // prints "_ello" instead of "Hello"
836836

837837
Modifying an interned string (*most* string literals are) will change the value for all other uses. Even without string interning, writing into a newly created string should be replaced with the safer `String.Create` API:
838838

839-
```cs
839+
```csharp
840840
// Bad:
841841
string s = new string('\n', 4); // non-interned string
842842
fixed (char* p = s)
@@ -853,7 +853,7 @@ string s = string.Create(4, state, (chr, state) =>
853853

854854
### Recommendations
855855

856-
1. ❌ DON'T mutate strings. Use the `String.Create` API to create a new string if complex copying logic is needed. Otherwise, use `.ToString()`, `StringBuilder`, `new string(...)` or string interpolation syntax.
856+
1. ❌ DON'T mutate strings. Use the `String.Create` API to create a new string if complex copying logic is needed. Otherwise, use `.ToString()`, `StringBuilder`, `new string(...)`, or string interpolation syntax.
857857

858858
## 18. Raw IL code (for example, System.Reflection.Emit and Mono.Cecil)
859859

@@ -884,7 +884,7 @@ Avoid using such techniques unless absolutely necessary.
884884
allocating temporary buffers for I/O operations or other short-lived scenarios. While the API is straightforward
885885
and doesn't inherently contain unsafe features, it can lead to use-after-free bugs in C#. Example:
886886

887-
```cs
887+
```csharp
888888
var buffer = ArrayPool<byte>.Shared.Rent(1024);
889889
_buffer = buffer; // buffer object escapes the scope
890890
Use(buffer);
@@ -904,7 +904,7 @@ but the bug becomes harder to detect when `Rent` and `Return` are in different s
904904

905905
While ECMA-335 standard defines a Boolean as 0-255 where `true` is any non-zero value, it's better to avoid any explicit conversions between integers and Booleans in order to avoid introducing "denormalized" values as anything other than 0 or 1 likely leads to unreliable behavior.
906906

907-
```cs
907+
```csharp
908908
// Bad:
909909
bool b = Unsafe.As<int, bool>(ref someInteger);
910910
int i = Unsafe.As<bool, int>(ref someBool);
@@ -922,7 +922,7 @@ The JIT present in earlier .NET runtimes did not fully optimize the safe version
922922
2. ✔️ DO use ternary operators (or other branching logic) instead. Modern .NET JITs will optimize them effectively.
923923
3. ❌ DON'T read `bool` using unsafe APIs such as `Unsafe.ReadUnaligned` or `MemoryMarshal.Cast` if you don't trust the input. Consider using ternary operators or equality comparisons instead:
924924

925-
```cs
925+
```csharp
926926
// Bad:
927927
bool b = Unsafe.ReadUnaligned<bool>(ref byteData);
928928

0 commit comments

Comments
 (0)