Примечание.
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Вопрос чемпиона: https://github.com/dotnet/csharplang/issues/9704
Сводка
Мы обновляем определение unsafe в C# от ссылки на расположения, в которых используются типы указателей, чтобы быть расположениями, где память, неуправляемая средой выполнения, разыменована. Эти расположения находятся в местах, где происходит небезопасность памяти, и отвечают за большую часть CVEs (распространенные уязвимости и уязвимости), классифицируемые как проблемы безопасности памяти.
// Under the proposed rules:
void M()
{
int i = 1;
int* ptr = &i; // Allowed: creating a pointer is not itself unsafe
unsafe
{
Console.WriteLine(*ptr); // Dereference of memory not managed by the runtime. This is unsafe.
ref int intRef = Unsafe.AsRef(ptr); // Conversion of memory not managed by the runtime to a `ref`. This is unsafe.
}
}
namespace System.Runtime.CompilerServices
{
public static class Unsafe
{
unsafe public static ref T AsRef<T>(void* source) { /* ... */ } // `unsafe` marks the member as *requires-unsafe*.
}
}
Мотивация
В этой функции также можно найти https://github.com/dotnet/designs/blob/main/accepted/2025/memory-safety/caller-unsafe.mdфон, в котором отслеживаются более широкие изменения экосистемы, необходимые в рамках этого предложения.
К ним относятся обновления BCL для правильного аннирования методов как небезопасных, а также обновления инструментов для лучшего понимания того, где происходит небезопасная память. Для C# в частности, мы хотим убедиться, что небезопасная память правильно отслеживается языком; Сегодня может быть трудно взглянуть на программу целостно и понять все расположения, где происходит небезопасная память. Это связано с тем, что различные вспомогательные, такие как System.Runtime.CompilerServices.Unsafe, System.Runtime.InteropServices.Marshalи другие не выражают, что они нарушают безопасность памяти и нуждаются в специальном рассмотрении. Методы, которые затем используют эти вспомогательные средства, не сразу очевидны, и при аудите кода для проблем безопасности памяти (заранее при выполнении проверки или при попытке определить причину уязвимости, которая сообщается), трудно определить расположения, которые могут способствовать проблемам.
Исторически в unsafe C# ссылается на определенное отверстие в памяти: существование типов указателя. Момент, когда тип указателя больше не участвует, C# идеально рад позволить памяти небезопасно лежать в коде. Это проблема, которую мы стремимся решить с этой эволюцией unsafe в C# и экосистеме .NET, метки областей, где может произойти небезопасная память, что упрощает рецензентов и аудиторов, чтобы понять границы потенциальной небезопасности памяти в программе. Важно, что это означает, что мы изменим смысл unsafe, а не просто расширение его. Существование указателя не является само по себе небезопасным; Небезопасное действие разыменовывает указатель. Это расширяется дальше к типам; типы не могут быть по сути небезопасными. Это только действие использования типа, которое может быть небезопасным, а не существованием этого типа.
Чтобы эта информация проходила по системе, поэтому нам необходимо иметь способ пометить методы как unsafe. Сегодня, unsafe как модификатор метода не оказывает внешнего влияния, он позволяет использовать указатели только в сигнатуре и тексте элемента. Идя вперед, unsafe как модификатор на самом деле будет публично изменять смысл элемента; он будет указывать, что член имеет проблемы безопасности памяти, и любые виды использования должны быть вручную проверены программистом с помощью члена. Это расширение существующего значения unsafe: unsafe на теле локализует обязательство аудита для этого органа, в то время как unsafe на подписи распространяет это обязательство вызывающей стороне.
Это потенциально большое критическое изменение для определенных сегментов пользовательской базы C#. Наша надежда заключается в том, что для многих наших пользователей это эффективно прозрачно, и обновление новых правил будет легко. Тем не менее, учитывая, что некоторые крупные поверхности API, такие как большие части отражения, может потребоваться отметить unsafe, мы считаем, что вероятно, что потребуется достойный на пандус новых правилах, чтобы избежать полностью двуфункционирования экосистемы.
Кардинальные изменения
При обновлении компилятора, реализующего эту функцию языка, можно наблюдать следующие критические изменения.
- Если обновленные правила безопасности памяти включены (это может быть параметр по умолчанию или даже единственный вариант в будущей версии .NET):
-
unsafeВ элементе теперь также помечается как небезопасное, то есть вызывающие пользователи должны находиться в контекстеunsafeи переопределения не могут бытьunsafe, если базовый элемент является безопасным. -
unsafeв элементе или типе не вводитсяunsafeконтекст, то есть явныеunsafeблоки должны использоваться вокругunsafeопераций в органах членов и инициализаторах. -
externЭлементы и поля в явном макете требуют явногоunsafe/safeключевое слово в объявлении. -
stackallocдля определенных условий требуетсяunsafeконтекст. -
unsafeмодификатор — это ошибка в объявлениях типов, статических конструкторах и деструкторах, так как она не имеет никакого эффекта.
-
- Под новой langversion:
- Лямбда-вывод может рассмотреть больше кандидатов, что приводит к неоднозначности разрешения перегрузки.
-
safeтеперь является контекстным ключевым словом, которое может нарушить код, который использовал его в качестве типа. - Режим compat в устаревшем режиме может привести к большим ошибкам.
Подробный дизайн
Терминология: мы вызываем элементы , требующие небезопасных (ранее известных как вызывающий небезопасный) если
- в соответствии с обновленными правилами безопасности памяти они помечены
unsafe, - в устаревших правилах безопасности памяти они содержат указатели в сигнатуре.
Syntax
В этом предложении представлено следующее:
- новое
safeконтекстное ключевое слово, которое можно применить кexternэлементам и полям в явном макете, - и
unsafeвыражения (unsafe(expression)), подробные сведения о небезопасных выражениях.
Этот новый синтаксис доступен в новом LangVersion, но независимо от согласия, в локальной среде, что мы пытаемся сделать это так, чтобы все, что вы должны сделать, когда вы решили, вы можете сделать, прежде чем вы согласитесь.
Существующие unsafe правила
Существующая спецификация C# содержит большой раздел, посвященный unsafe: §24 Небезопасный код. Он определяется как условно нормативный, так как он не требуется для допустимого компилятора C# для поддержки unsafe функции. Большая часть того, что в настоящее время считается условно нормативным, больше не будет так после этого изменения, так как большинство определений указателей больше не считается небезопасным в самом себе.
Типы указателей, фиксированные и перемещаемые переменные, все выражения указателя (за исключением косвенного указателя, доступа к элементу указателя и доступа к элементу указателя), а fixed оператор все больше не рассматриваются unsafeи существуют в обычном C# без необходимости использовать в контексте.unsafe Аналогичным образом объявление буфера фиксированного размера или инициализированного stackalloc также является совершенно законным в безопасном C#. Во всех этих случаях доступ к памяти небезопасным.
Важно, что эти расслабления указателя применяются независимо от того, выбирает ли сборкаобновленные правила безопасности памяти.
Только операции, которые фактически разыменовывают или в противном случае напрямую обращаются к памяти, продолжают требовать unsafe контекст.
Это обеспечивает добавочный путь миграции: пользователи могут изменить существующий код, переместив unsafe его внутрь, когда член обрабатывает риск внутренне или наружу, когда вызывающий объект должен участвовать в аудите, прежде чем перевернуть переключатель на уровне сборки.
Учитывая обширный перезапись unsafe раздела кода и других частей спецификации C#, присущей этому изменению, это было бы неприменимо и, скорее всего, не полезно предоставить дифф по строкам существующих правил спецификации. Вместо этого мы предоставим обзор изменений, внесенных в данный раздел, а также конкретные новые правила для того, что разрешено в unsafe контекстах.
Переопределения выражений, требующих небезопасных контекстов
Для следующих выражений unsafe требуется контекст при использовании:
- Косвенные указатели
- Доступ к членам указателей
- Доступ к элементам указателей
- Вызов указателя функции
- Доступ к элементам в буфере фиксированного размера
-
stackallocв указанных ниже условиях
Помимо этих выражений, выражения и операторы могут также условно требовать unsafe контекст, если они зависят от любого символа, помеченного как unsafe. Например, вызов метода, который является небезопасным , приведет к тому, что invocation_expression требует контекста unsafe . Операторы с внедренными вызовами (например using, s, foreachи аналогичные) также могут требовать unsafe контекст, если они используют небезопасный член.
Когда мы говорим "требуется небезопасный контекст" или аналогичный в этом документе, это означает, что конструкция требует использования контекста unsafe .
Замечание
Этот раздел, вероятно, требует расширения, чтобы официально объявить, что каждое выражение и оператор должны учитывать, чтобы требовать unsafe контекст.
Типы указателей
Как уже упоминалось, указатели больше не являются небезопасными. Все ссылки на небезопасные контексты в §24.3 удаляются. Типы указателей существуют в обычном C# и не требуют unsafe их существования. Определения типов должны работать в §8.1 и в следующих разделах, как и другие типы.
Аналогичным образом преобразования указателя должны работать в §10 с ссылками на unsafe контексты, удаленные.
Аналогичным образом выражения указателя, за исключением косвенного обращения указателя, доступа к элементу указателя и доступа к элементу указателя, должны работать в §12 с ссылками на unsafe контексты, удаленные. Нет семантики о значении этих выражений; Единственное изменение заключается в том, что они больше не требуют использования контекста unsafe .
Для непрямого обращения указателя, доступа к элементу указателя и доступа к элементу указателя эти операторы остаются небезопасными, так как эти операции доступа не управляются средой выполнения. Они остаются в §24 и продолжают требовать использования контекста unsafe . Любое использование вне контекста unsafe является ошибкой.
Нет семантики об этих операторах; они по-прежнему продолжают означать точно то же самое, что они делают сегодня. Эти выражения всегда должны происходить в контексте unsafe .
Исправленная инструкция перемещается на §13 с ссылками на unsafe удаленные контексты.
Указатели функций еще не включены в основную спецификацию C#, но они также затронуты; все, кроме вызова указателя функции, перемещается в стандартную спецификацию.
Выражение вызова указателя функции всегда должно происходить в контексте unsafe .
Буферы фиксированного размера
История буферов фиксированного размерааналогична указателям. Определение буфера фиксированного размера не является опасным и переходит к §16.3. Доступ к буферу фиксированного размера в выражении является аналогичным образом безопасным, если выражение не возникает как primary_expression объекта element_access; они оцениваются как pointer_element_access, что небезопасно, как указано в приведенных выше правилах.
Выделение памяти в стеке
Опять же, история выделения стека очень похожа на указатели. Преобразование указателя в указатель больше не является небезопасным; это дефекция stackalloc этого указателя, который небезопасн. Однако мы добавим одно новое правило:
Stackalloc_expression небезопасно, если все следующие операторы верны:
-
Stackalloc_expression преобразуется в объект
Span<T>или объектReadOnlySpan<T>. - У stackalloc_expression нет stackalloc_initializer.
-
Stackalloc_expression используется в примененном элементе
SkipLocalsInitAttribute.
В этих контекстах результирующее пространство стека может иметь неизвестное содержимое памяти, и оно преобразуется в тип, предоставляющий безопасную оболочку вокруг неуправляемого доступа к памяти. Это нарушает контракт Span<T> и ReadOnlySpan<T>поэтому должно быть предметом дополнительного контроля автора и рецензентов такого кода.
В отличие от других изменений unsafe правил, которые являются расслаблениями, это ужесточение, и поэтому оно применяется только при согласии на обновленные правила безопасности памяти, чтобы избежать перерыва.
Замечание
Это означает, что назначение указателя stackallocвсегда безопасно независимо от контекста.
Обратите внимание, что stackalloc управляемый тип остается ошибкой.
sizeof
Для некоторых предопределенных типов sizeof всегда была константой и безопасной (§12.8.19) и остается неизменной.
Для других типов sizeof используется для требования небезопасного контекста (§24.6.9), но теперь безопасно независимо от согласия на обновленные правила безопасности памяти.
Переопределение, наследование и реализация
Это ошибка безопасности памяти, unsafe которая добавляется на уровне члена в любом переопределении или реализации элемента, который не требует небезопасности изначально, так как вызывающие пользователи могут использовать базовое определение и не видеть никакого добавления unsafe в производную реализацию.
Делегаты и лямбда-выражения
Это ошибка безопасности памяти для преобразования небезопасного члена в тип делегата вне контекста unsafe .
Типы делегатов и типы функций не могут быть небезопасными.
Это ошибка во время компиляции, применяемая unsafe к лямбда-символу.
extern
Так как extern методы относятся к собственным расположениям, которые не могут быть гарантированы средой выполнения, компилятор не может определить, являются ли они безопасными или небезопасными.
Даже методы, которые принимают unmanaged только параметры по значению, не могут быть безопасно вызваны C#, так как соглашение о вызовах, используемое для метода, может быть неправильно указано пользователем и должно быть вручную проверено путем проверки.
Таким образом, в соответствии с обновленными правилами безопасности памяти компилятор требует, чтобы каждый extern метод был явно помечен как любой или unsafesafe.
extern Методы из сборок с использованием устаревших правил безопасности памяти не считаются неявными, unsafe так как extern считается подробной реализацией, которая не является частью общедоступной поверхности.
extern не гарантируется сохранение в эталонных сборках.
Обратите внимание, что это отличается от режима compat , который применяется к сборкам устаревших правил, так как методы с указателями в сигнатуре всегда нуждаются в небезопасном контексте на сайте вызова.
Небезопасные модификаторы и контексты
Сегодня, как описано небезопасной спецификацией контекста, ведет себя лексически, unsafe помечая весь текстовый текст, содержащийся unsafe блоком как unsafe контекст (за исключением тел итератора), а также некоторые окружающие контексты в случае объявлений:
class A : Attribute
{
public A(object o) { }
}
class C
{
[A(default(int*[]))] void M1() { } // error: using pointers outside `unsafe` context
[A(default(int*[]))] unsafe void M2() { } // ok
}
При согласии с обновленными правилами безопасности памяти член помечает его как unsafe, расширяя обязательство аудита вызывающему объекту и не вводит unsafe контекст (вместо этого только явные unsafe регионы в теле устанавливают unsafe контекст).
unsafe В следующих объявлениях возникает ошибка, так как она больше не имеет значения:
-
delegate, - статический конструктор,
- Деструктор
- Объявление типа (
class,structи т. д.).
unsafe в конструкторе вводится unsafe контекст внутри инициализатора, т. unsafe е. конструктор может вызывать небезопасныйbase или this конструктор.
Типы с небезопасными конструкторами без параметров не удовлетворяют ограничению new() .
Аналогичным образом и в дополнение структуры с параметрами без требования небезопасных конструкторов не удовлетворяют ограничению struct .
unsafe для элемента не применяется к вложенным анонимным или локальным функциям внутри элемента.
То же самое касается анонимных и локальных функций, объявленных внутри unsafe блока (они по-прежнему находятся в unsafe контексте, как всегда, но они не становятся небезопасными).
Чтобы пометить локальную функцию как небезопасную, ее необходимо пометить вручную как unsafe.
Лямбда-файлы не могут быть помечены как небезопасные ( unsafe ключевое слово запрещено на них).
Если член является partial, обе части должны согласиться с модификатором unsafe , без изменений в правилах C# сегодня.
partial class C1
{
public partial void M1(); // Error: both parts must be unsafe, or neither can be
public partial unsafe void M2();
}
partial class C1
{
public unsafe partial void M1() => Console.WriteLine("hello world");
public partial void M2() => Console.WriteLine("hello world"); // Error: both parts must be unsafe, or neither can be
}
Для свойств и getset/init методы доступа могут быть независимо объявлены unsafeкак ; помечая все свойство как unsafe означает, что get оба метода set/init доступа небезопасны.
В настоящее время невозможно разместить какие-либо модификаторы на методы доступа к событиям, и это предложение не изменяет то, add что, т. е. методы remove доступа к событиям не могут быть независимо объявлены как unsafe.
Только если все событие отмечено как unsafe, это означает, что методы доступа небезопасны; в противном случае они безопасны.
Fields
unsafe в поле также помечается как небезопасное, и не вводит unsafe контекст в инициализаторе.
Режим compat также применяется к полям.
Маркировка свойства или события, так как unsafe не делает его резервное поле небезопасным.
В типе с [StructLayout(LayoutKind.Explicit)][ExtendedLayout]или, все поля экземпляра должны быть помечены либо safe либо unsafe.
Если поле "скрыто" за событием auto-property или field-like, safe/unsafe это требование перемещается в событие auto-property или field-like.
Метаданные
Когда сборка компилируется с новыми правилами безопасности памяти, она помечается MemorySafetyRulesAttribute (подробно ниже), заполненной 15 как языковая версия. Это сигнал для всех подчиненных потребителей, что все члены, определенные в сборке, будут должным образом приписываться RequiresUnsafeAttribute (подробно ниже), если unsafe контекст требуется для их вызова.
Любой член в такой сборке, которая не помечена как RequiresUnsafeAttribute не требует unsafe вызова контекста, независимо от типов в сигнатуре элемента.
Это ошибка MemorySafetyRulesAttribute , применяемая или RequiresUnsafeAttribute к любому символу явно в источнике.
Компилятор игнорирует RequiresUnsafeAttributeпомеченные элементы из сборок, использующих устаревшие правила безопасности памяти (вместо этого используется режим compat ).
Если элемент, отличный от типа, помечается как unsafe, компилятор синтезует RequiresUnsafeAttribute приложение на элементе в метаданных.
Если пользователю требуется небезопасный член создает скрытые элементы, такие как методы get/set авто свойства, как член, доступный для пользователя, так и все скрытые элементы, созданные этим пользователем, являются небезопасными и RequiresUnsafeAttribute применяются ко всем этим элементам.
MemorySafetyRulesAttribute Определение RequiresUnsafeAttribute синтезируется компилятором, если это необходимо для стандартных хорошо известных правил элементов.
namespace System.Runtime.CompilerServices
{
/// <summary>Indicates the language version of the memory safety rules used when the module was compiled.</summary>
[AttributeUsage(AttributeTargets.Module, Inherited = false)]
public sealed class MemorySafetyRulesAttribute : Attribute
{
/// <summary>Initializes a new instance of the <see cref="MemorySafetyRulesAttribute"/> class.</summary>
/// <param name="version">The language version of the memory safety rules used when the module was compiled.</param>
public MemorySafetyRulesAttribute(int version) => Version = version;
/// <summary>Gets the language version of the memory safety rules used when the module was compiled.</summary>
public int Version { get; }
}
[AttributeUsage(AttributeTargets.Event | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Constructor, AllowMultiple = false, Inherited = false)]
public sealed class RequiresUnsafeAttribute : Attribute
{
}
}
Режим compat
В целях compat и уменьшения числа ложных отрицательных значений, возникающих при включении новых правил, у нас есть резервное правило для модулей, которые не были обновлены до новых правил. Для таких модулей элемент считается небезопасным , если он содержит тип указателя или указателя функции где-то среди его типов параметров или возвращаемого типа (можно вложить в тип, отличный от указателя, например int*[]).
Обратите внимание, что это не относится к указателям в типах ограничений (например, where T : I<int*[]>), так как они не требуют небезопасного контекста на сайтах вызовов ранее.
Это не включает заменяемые универсальные параметры (например, метод I<T>.M(T) при замене Tint*[]на ) так как для целевого элемента нет безопасного типа, чтобы использовать этот тип указателя для чего-либо в любом случае.
Для таких compat-mode требуются небезопасные члены , для которых требуется unsafe использовать контекст даже от вызывающих пользователей, которые не согласились с обновленными правилами безопасности памяти.
Это должно избежать "погружения", когда только обновление LangVersion (но не обновление версии правил безопасности памяти) делает большинство операций указателя (включая вызовы функций с указателями в подписи, которые, скорее всего, будут помечены как небезопасные при выборе в обновленных правилах), и, следовательно, делает код менее защищенным в этом окне миграции.
VB
Мы не должны добавлять поддержку в Visual Basic для небезопасных членов, так как unsafe в VB нет контекстов сегодня и нет способа работать с указателями там.
unsafe Выражения
Выражение unsafe представляет минимальный unsafe контекст для оценки одного выражения. Это полезно в ситуациях, когда unsafe блок будет ненужно расширить unsafe область или где unsafe блоки нельзя использовать синтаксически (например, в catch фильтрах, инициализаторах полей, конструкторах и операндов).await
Syntax
Добавляется unsafe_expression как primary_no_array_creation_expression:
unsafe_expression
: 'unsafe' '(' expression ')'
;
Оно распространяется на то же AllowUnsafeBlocks требование, что и ключевое unsafe слово в другом месте.
Семантика
unsafe_expression Определяет unsafe контекст для оценки его выражения. Это означает, что указатели расшифровки, вызовы указателя функции и вызовы небезопасных членов разрешены во всех вложенных выражениях. Тип и значение этого типа — это тип и значение unsafe_expression заключенного выражения.
Контекст, unsafe установленный объектом, unsafe_expression не распространяется за пределы закрывающей скобки.
Примеры мотивации и миграции
Несколько синтактических позиций не допускают unsafe блоки вообще, но могут содержать вложенные выражения, которые вызывают небезопасные члены . Без unsafe выражений миграция такого кода требует извлечения небезопасного вложенного выражения в вспомогательной локальной функции или временной переменной, которая скрывает намерение и увеличивает детализацию.
await для небезопасного метода. Выражение await не может отображаться внутри unsafe блока. Когда ожидающийся метод становится обязательным, временная переменная требуется для хранения задачи, прежде чем ее можно ожидать:
// Without unsafe expressions: must spill to a temporary
Task t;
// SAFETY: Discharges obligations because reasons
unsafe { t = DoWork(); }
await t;
// With unsafe expressions: the unsafe context wraps only the call;
// the await remains outside it and is fully legal
// SAFETY: Discharges obligations because reasons
await unsafe(DoWork());
Перехват фильтров. Фильтр whencatch предложения — это выражение, а не текст оператора. Блок unsafe может окружать только операторы, поэтому нет места, чтобы поместить один вокруг только выражения фильтра. Кроме того, если try текст содержит await выражение, unsafe блок не может окружать всю try/catchинструкцию илиawait не допускается внутри unsafe блока. Если метод, используемый в фильтре, становится небезопасным, единственным вариантом без unsafe выражений является вспомогательный метод:
// Without unsafe expressions: must spill to a local function
static bool FilterHelper(Exception e)
{
// SAFETY: Discharges obligations because reasons
unsafe { return NowUnsafeCall(e); }
}
try
{
await DoWork(); // 'await' here prevents wrapping the whole try/catch in 'unsafe'
}
catch (Exception e) when (NowUnsafeCall(e))
{
}
// With unsafe expressions: inline and minimal scope
try
{
await DoWork();
}
// SAFETY: Discharges obligations because reasons
catch (Exception e) when (unsafe(NowUnsafeCall(e)))
{
}
Инициализаторы полей. В соответствии с обновленными правилами unsafe в поле не вводится unsafe контекст в инициализаторе. Когда инициализатор поля вызывает небезопасный член, unsafe выражение предоставляет контекст, не требуя вспомогательного метода:
// Without unsafe expressions: must spill to a helper method
static int InitialValue()
{
// SAFETY: Discharges obligations because reasons
unsafe { return ReadFromPointer(); }
}
static int _value = InitialValue();
// With unsafe expressions: inline
// SAFETY: Discharges obligations because reasons
static int _value = unsafe(ReadFromPointer());
Инициализаторы конструкторов.
this(...) и base(...) списки аргументов инициализатора — это выражения, а не тела инструкций. Если один из этих аргументов вызывает небезопасный член, снова нет места для вставки unsafe блока, поэтому вызов должен быть перемещен в вспомогательный элемент:
class C(int x)
{
// SAFETY: Discharges obligations because reasons
C() : this(unsafe(GetUnsafeValue()))
{
}
}
class Derived : Base
{
// SAFETY: Discharges obligations because reasons
Derived() : base(unsafe(GetUnsafeValue()))
{
}
}
Встроенные вызовы. Как правило, упаковка только небезопасных вложенных выражений сохраняет область аудита жесткой и избегает извлечения безопасного кода (например, оценки аргументов или вызова содержащего метода) в unsafe контекст:
extern int Add(int i1, int i2); // Some fancy extern addition function
// Code I want to write:
// SAFETY: Discharges obligations because reasons
Console.WriteLine(unsafe(Add(1, 2)));
// Code I have to write without unsafe expressions, option 1
// (unsafe context unnecessarily includes the WriteLine call):
// SAFETY: Discharges obligations because reasons
unsafe
{
Console.WriteLine(Add(1, 2));
}
// Code I have to write without unsafe expressions, option 2
// (very verbose and harder to read):
int result;
// SAFETY: Discharges obligations because reasons
unsafe
{
result = Add(1, 2);
}
Console.WriteLine(result);
Вопросы
(ответ) Использование RequiresUnsafeAttribute для обозначения небезопасных элементов
Вместо использования unsafe ключевого слова для элемента для обозначения небезопасных элементов можно использовать атрибут (), примененный к элементу (RequiresUnsafeAttributeи не изменяющий смысл unsafe модификатора для элементов).
unsafeПреимущества:
- аналогично другим языкам и, следовательно, проще понять,
- более обнаруживаемым, чем атрибут.
Преимущества атрибута (или другого ключевого слова):
- избегает нарушения существующих элементов, помеченных как
unsafe, - добавочное внедрение возможно (член по члену),
- не принудительно помечает весь текст
unsafeкак (даже сunsafeключевым словом мы могли бы изменитьсяunsafe, чтобы не иметь влияния на тела, хотя), - позволяет подавлять все небезопасные ошибки без необходимости пометить сам элемент как небезопасный (примеры).
Обсуждения:
- LDM 2025-11-12: ключевое слово
- LDM 2026-01-26: использование атрибута
- LDM 2026-04-06: ключевое слово
Ответ. Используйте ключевое слово unsafe для обозначения небезопасных членов.
Локальные функции и лямбда-безопасные контексты
Прямо сейчас unsafe в тексте метода лексически ограничивается. Все вложенные локальные функции или лямбда-коды наследуют это, и их тела находятся в небезопасном контексте памяти.
Это поведение, которое мы хотим сохранить на языке?
Обратите внимание, что если мы сохраняем unsafe как модификатор, используемый для предоставления того, что вызывающий объект должен быть небезопасным, это может повлиять на подпись метода.
В настоящее время вложенные анонимные и локальные функции не сохраняют небезопасный контекст их содержащего элемента.
unsafeТип делегата ty
Мы могли бы разрешить пометить типы делегатов и лямбда-типы (и типы функций) как небезопасные.
Для этого потребуется несколько дополнительных правил (вне unsafe контекста):
- запретить использование небезопасных делегатов в качестве аргументов типа,
- запретить преобразовывать этих делегатов в что-либо, которое не требует небезопасного (
Delegate,Expression, иobject), без этого, существует риск принудительного примененияunsafeзаметок в неправильном месте и наличие области, где реальная областьunsafeтай неправильно вызвана.
Преобразование группы лямбда-методов в безопасные типы делегатов
Если мы разрешаем unsafe лямбда-коды и делегаты, следует ли преобразовать небезопасную лямбда-группу или группу методов в небезопасный тип делегата, разрешенный без предупреждения или ошибки в контексте unsafe ? Если мы этого не делаем, то это может быть довольно болезненным для различных частей экосистемы, особенно для любых перечислений, передаваемых через запросы LINQ.
Лямбда-метод группы естественных типов
Сегодня единственное реальное влияние на семантику и кодген (помимо дополнительных метаданных) изменяет function_type лямбда-группы или группы методов при unsafe сигнатуре. Если бы мы не делали это, то не было бы никакого реального влияния ни на то, ни к другому, что может дать пользователям больше уверенности в том, что поведение не изменилось под капюшоном.
Замечание
Если мы решили сохранить возможность unsafe иметь лямбда-выражения, мы должны обновить это предложение, чтобы включить изменение синтаксиса, чтобы позволить лямбда-выражениям быть объявлены unsafe в первую очередь.
Указатели на управляемые типы
C# 11 позволяет указателям на управляемые типы с предупреждением.
Следует ли расслабить это предупреждение об адресе операций?
Мы считаем, что проблема заключается только в том, что пользователь разыменовывает такие указатели, которые падают под обычные небезопасные правила эволюции.
Но что насчет sizeof?
stackalloc как инициализированный
Сегодня спецификация всегда учитывает stackalloc память как неинициализированную, и говорит, что содержимое не определено, если вручную не очищается или не назначается. Рассмотрим ли мы эту ошибку спецификации или нужно ли изменить то, что мы рассматриваем unsafe для stackalloc целей?
stackalloc Правило
LDM должен подтвердить правило, определенноеstackalloc выше, и следует ли применять его независимо от того, следует ли использовать вариант, как и другие изменения, связанные с указателем.
AllowUnsafeBlocks
AllowUnsafeBlocks Значение в настоящее время остается неизменным. Для тогоtrue, чтобы использовать ключевое слово или использовать ключевое слово, unsafeнеобходимо задать значениеSkipLocalsInitAttribute.
Не следует ли требовать его в SkipLocalsInitAttribute соответствии с обновленными правилами, так как BCL может пометить этот атрибут как небезопасный?
Следует ли также требовать его для ключевого safe слова?
Следует ли требовать его как для блоков, так unsafe и unsafe для объявлений членов или для других сочетаний этих элементов?
(ответ) unsafe выражения
Другие языки с более комплексными unsafe функциями добавили unsafe в качестве выражения, которое улучшает эргономику пользователей и позволяет авторам более точно ограничить место unsafe использования. Это то, что мы хотим иметь в C#? Рассмотрим встроенный вызов unsafe члена, который обрабатывает безопасность напрямую: прямо сейчас автору потребуется упаковать всю инструкцию в unsafe блок, расширяя область unsafe контекста, или они должны были бы разорвать внутренний вызов функции в промежуточную переменную.
extern int Add(int i1, int i2); // Some fancy extern addition function
// Code I want to write:
Console.WriteLine(unsafe(Add(1, 2)));
// Code I have to write option 1, unsafe context unnecessary includes the WriteLine call
unsafe
{
Console.WriteLine(Add(1, 2));
}
// Code I have to write option 2, very verbose and harder to read:
int result;
unsafe
{
result = Add(1, 2);
}
Console.WriteLine(result);
Ответ: да. См. подробный раздел проектирования.
Дополнительные unsafe контексты и расслабление
Следует ли смягчить больше ограничений вокруг unsafe и указателей в итераторах и асинхронных методах?
Особенно удобно, await UnsafeMethod() так как теперь пользователям придется переписать это Task t; unsafe { t = UnsafeMethod(); } await t;в .
Дополнительные сведения см. в разделе ref/unsafe в итераторах или асинхронном режиме.
Следует ли также разрешить &UnsafeMethod в безопасном контексте? Сегодня, как предложение, это требует unsafe контекста, если метод помечается как unsafe.
Но так как мы просто получаем свой адрес, который потребует unsafe контекста при разыменовании или вызове, мы могли бы разрешить адрес самого себя в безопасном контексте.
-
LDM 2026-05-27:
awaitвunsafeнеобходимости конкретного предложения
Небезопасные расслабления, воротные на LangVersion
Должны ли мы сделать небезопасный контекст расслабления безусловным на LangVersion?
- LDM 2026-04-06: они не являются условными для правил безопасности памяти
- TODO: что о LangVersion?
(ответ) unsafe в типах
Мы не могли бы автоматически сделать всю лексическую область unsafe типа контекстом unsafe и предупреждать о unsafe типе, так как оно не имеет смысла.
-
LDM 2025-11-12:
unsafeдля типа не будет смысла - LDM 2026-05-13: это будет ошибка (можно пересмотреть на основе отзывов)
Ответ: unsafe тип является ошибкой (можно вернуться на основе отзывов) в соответствии с обновленными правилами.
unsafe при доступе
Мы вновь разрешаем unsafe доступ к свойствам, но не для методов доступа к событиям, в соответствии с другими предварительно существующими модификаторами.
Это также означает, что unsafe свойство является просто ярлыком для unsafe своих методов доступа.
Но в то же время для partialсопоставления модификаторов требуются unsafe модификаторы:
partial class C
{
unsafe partial int P { get; set; } // effectively both `get` and `set` are `unsafe` here
unsafe partial int P { unsafe get => 0; set { } } // still an error: `unsafe` on `get` doesn't match
}
// similar to this pre-existing behavior:
unsafe partial class D
{
unsafe partial void M();
}
unsafe partial class D
{
partial void M() { } // error about missing `unsafe`
}
Может быть, он должен вести себя аналогично readonly, т. е. запретить unsafe как на свойство, так и на его метод доступа одновременно:
partial struct S
{
readonly partial int P { get; set; }
// error: Both partial member declarations must be readonly or neither may be readonly
partial int P { readonly get => 0; set { } }
// error: Cannot specify 'readonly' modifiers on both property or indexer 'S.P2' and its accessor. Remove one of them.
readonly int P2 { readonly get => 0; set { } }
}
(ответ) Разрешить подавление небезопасных ошибок в сценариях пограничных вариантов
Как разрешить подавление небезопасных ошибок в следующих сценариях?
class A : System.Attribute
{
unsafe public A() { } // declaring requires-unsafe constructor
}
class C
{
[A] public void M() { } // error: applying requires-unsafe `A..ctor`
}
class B : A
{
public B() { } // error: calling requires-unsafe `A..ctor` (implicit `: base()`)
}
class X<T> where T : new();
class D
{
public void M(X<A> x) { } // error: using `X` which uses requires-unsafe `A..ctor`
}
Чтобы отключить небезопасные ошибки, необходимо каким-то образом представить unsafe контекст в подписи этих членов.
Но ключевое unsafeunsafe слово больше не вводит контекст.
Мы можем ввести unsafeunsafe контекст в сигнатуре, но принудительное создание конструктора требует небезопасности при вызове базового конструктора , небезопасного конструктора, кажется несчастным.
Мы могли бы сделать небезопасные использование в сигнатурах предупреждений, которые пользователи могут отключить на месте, если они достигли этих редких пограничных случаев.
Существует аналогичная проблема с типами, так как unsafe на них не вводится unsafe контекст:
class A : Attribute
{
unsafe public A() { } // declaring requires-unsafe constructor
}
[A] class C; // error: applying requires-unsafe `A..ctor`
class B() : A(); // error: calling requires-unsafe `A..ctor`
class X<T> where T : new();
class D : X<A>; // error: inheriting from `X` which uses requires-unsafe `A..ctor`
-
LDM 2026-05-13:
- Неисполнимый код, например приложение атрибутов, должен оставаться невыразимой ошибкой (пока не услышите отзыв).
- другие пограничные случаи, такие как
new()также могут оставаться ошибкой, пока не услышите отзыв - Не запрещайте объявлять параметры без защиты от небезопасных конструкторов
-
unsafeИнициализатор конструктора может вызывать небезопасныйbaseилиthisконструктор
коллекции params
class C
{
unsafe public C() { } // declaring requires-unsafe constructor
}
class B
{
public void M(params C c) { }
}
Это объявление может быть просто разрешено, так как для вызова такого метода требуется unsafe контекст в любом случае, так как небезопасная params коллекция создается на сайте вызова.
Хотя это делает объявление фактически небезопасным, поэтому мы могли бы просто требовать заметку или по крайней мере предупреждать unsafe об этом факте.
Обратите внимание, что params случай сбора является ошибкой сегодня в соответствии с тем, как другие аналогичные функции ведут себя здесь (Obsolete, UnmanagedCallersOnlyно это может быть ошибка реализации.
Известные члены
Для простоты и разумности реализации мы предлагаем, что компилятор может быть свободен предположить, что все известные члены (например Array.Length, могут быть безопасными( т. е. не требуют безопасности).
Документы XML
Передача обязательства абонентам приходит с ответственностью за то, чтобы ясно, что это обязательство. Следует ли формализировать это за рамки того, что уже может быть представлено в xml-документах?
Элементы, помеченные unsafe , должны иметь примечания, указывающие, что необходимо для вызывающего средства, чтобы убедиться, что код правильный.
Чтобы упростить просмотр и упрощение различения в документации, новый тег документа XML будет полезным: <safety />
Ожидается, что все предварительные или пост-условия будут помещены в <safety> блок.
Чтобы документировать причины каждого unsafe блока, рекомендуется использовать комментарии, аналогичные // SAFETYRust.
Следует ли также проверять их компилятором (например, иметь некоторое предупреждение вне по умолчанию) или оставить это анализатору?
- LDM 2026-05-27: никаких конкретных решений
Более бессмысленные unsafe предупреждения
Должно ли больше объявлений создать бессмысленное unsafe предупреждение или ошибку?
Например, методы с пустыми телами (или extern) и т. д. У нас уже есть анализатор интегрированной среды разработки для ненужных unsafe .
Должно [ModuleInitializer] unsafe void M() { } быть ошибкой, аналогично статической конструкторе?
(ответ) unsafe Поля
Сегодня нет предложения вокруг unsafe на поле. Однако нам может потребоваться добавить его, чтобы любое чтение или запись в поле, помеченное как unsafe должно быть в контексте unsafe . Это позволит нам лучше избежать проблем, связанных с кодом, такими как:
class SafeWrapper
{
internal byte* _p;
public void DoStuff()
{
unsafe
{
// ... validate that the object state is good ...
// ... perform operation with _p ....
}
}
}
// Elsewhere in safe code:
void M(SafeWrapper w)
{
w._p = stackalloc byte[10];
}
Следует ли также пометить поле резервного свойства unsafeавтоматически как ?
Чтобы быть согласованы с нашим решением для членов, было бы хорошо сделать unsafe в поле также не ввести unsafe контекст.
Если в инициализаторе поля используются небезопасные операции, пользователь всегда может инкапсулировать их в метод или представить unsafe выражения.
-
LDM 2026-05-13: поля можно пометить как небезопасные с помощью
unsafe; инициализаторы не находятся вunsafeконтексте.
(ответ) Явный макет
Должны ли поля в структуры помечены как [StructLayout(Explicit)] или [ExtendedLayout] должны быть помечены как unsafe?
Рекомендация: да.
-
LDM 2026-05-13: да, требовать
unsafeилиsafe, как и дляexterns.
Явные поля макета и резервного копирования
Если компилятор синтезирует резервное поле для события auto-property или field-like в типе Explicit/Extended , следует ли требовать safe/unsafe для свойства или события?
В противном случае пользователю будет вынуждено развернуть эти автоматические объявления в поле вручную и объявления элементов-оболочки.
Что касается основного параметра конструктора, который получает резервное поле?
safe В unsafe настоящее время и модификатор запрещены в объявлении параметров.
[Out] и [SkipLocalsInit].
Так как например, VB не гарантирует [Out] инициализацию параметров в сочетании с [SkipLocalsInit]вызовом таких параметров можно рассматривать unsafe в C#.
С другой стороны, она чувствует, как проблема вызываемого, что она не поддерживает свой [Out] контракт (аналогично это может быть небезопасно во многих других отношениях).
Если мы решили, что эти случаи должны быть unsafe, мы можем исключить методы, которые мы знаем, выбрали (в настоящее время они относятся к C#, которые гарантируют правильное использование [Out] , но если другие языки реализуют новые правила, они должны гарантировать это тоже).
Получение адреса неинициализированной переменной
Сегодня, принимая адрес не определенно назначенной переменной, можно считать, что переменная определенно назначена, предоставляя неинициализированный член. У нас есть несколько вариантов решения этого:
- Необходимо, чтобы переменные были определенно назначены, прежде чем разрешать их использовать оператор адреса.
- Сделайте адрес небезопасной переменной неинициализированной.
Примеры:
static void SkipInit<T>(out T value)
{
// value is considered definitely assigned after the address-of
fixed (void* ptr = &value);
}
int i;
// i is considered definitely assigned after the address-of
_ = &i;
// Incrementing whatever was on the stack
i++;
Значение параметра MemorySafetyRulesAttribute
Что должно быть версией правил безопасности памяти "включено"/"обновлено"?
2?
15?
11?
См. также пакет SDK Unsafe Adoption and More gradual opt-in?.
(ответ) Более постепенное согласие?
Сегодня вы можете выбрать две вещи одновременно: применение небезопасных правил в собственном коде, а также сигнал, опубликованный потребителям (через атрибут уровня сборки), что ваши заметки намеренно. Могут быть пользователи, которые хотят начать получать диагностику принудительного применения по мере того, как они заносят заметки, не готов к публикации, что они полностью заметили свою сборку. Должны ли у нас есть "средний" уровень согласия, который будет отображать небезопасную диагностику в качестве предупреждений, и полное согласие будет способствовать их ошибкам?
- LDM 2026-04-29: да, но это не блокируется для предварительной версии
(ответ) Более детальное согласие
Предоставьте подробный механизм, основанный на регионах, аналогичный тому, который мы создали для ссылочных типов, допускающих значение NULL, где пользователи могут использовать директивы для включения функции для определенных регионов исходного кода? Дополнительные сведения см. в разделе "Отказ и выход" для областей кода.
- LDM 2026-04-29: нет
(ответ) extern неявно небезопасно
В настоящее время это единственное место, где RequiresUnsafeAttribute синтезируется без явного unsafe ключевого слова.
Хорошо ли мы с этим вылитым?
Кроме того, CoreLib предоставляет множество экстерн-методов (FCalls) как безопасные сегодня. Обработка экстерн-методов как неявно небезопасных потребует упаковки неявно небезопасных методов экстерна с помощью безопасной оболочки. Мы можем столкнуться с ситуациями, когда добавление дополнительной оболочки сложно из-за сведений о реализации среды выполнения.
-
LDM 2026-04-01:
externэлементы должны быть явно помечены как безопасные, так и небезопасные - LDM 2026-04-06: то же решение повторилось
-
LDM 2026-04-13: временное решение об использовании
safeключевого слова -
LDM 2026-05-13: используйте
safeключевое слово (по-прежнему открыто для пересмотра хотя)
Ответ: extern члены должны быть помечены unsafe либо ( safe мы добавим новое ключевое слово для него, но можем вернуться к нему перед тем, как функция поставляется на основе отзывов).
(ответ) unsafe контекст по умолчанию в элементах
Мы не могли бы автоматически сделать весь текст unsafe метода контекстом unsafe . Rust сделал это в RFC 2585, с мотивацией, которая помогает уменьшить область unsafe блоков до мест, в которых unsafe фактически используется. Мы могли бы сделать то же самое в C#, как предупреждение или ошибку с аналогичными мотивациями.
-
LDM 2026-04-22: да,
unsafeв подписи не делает телоunsafe
(ответ) new() Зависимости
Хотим ли мы поддерживать new() с небезопасными функциями (что в настоящее время мы не поддерживаем в компиляторе для других функций, таких как Obsolete)?
M<C>(); // should be an error outside `unsafe` context since `M` calls the requires-unsafe `C..ctor`?
void M<T>() where T : new()
{
_ = new T();
}
class C
{
unsafe public C() { }
}
-
LDM 2026-05-13: типы с небезопасными конструкторами без параметров не должны соответствовать ограничению
new().
new() ограничение и usings
Как он должен вести себя в псевдонимах и статических использовании?
- Если это ошибка в объявлении, подавляемая с помощью ключевого
usingunsafeслова, который мы уже поддерживаем там, или - должно ли это быть ошибкой обычно на сайте использования, например, если он используется напрямую без псевдонима или статического использования?
Замечание
Во втором случае необходимо добавить предупреждение "бессмысленное unsafe" для использования псевдонимов и статических использования.
class C
{
unsafe public C() { }
}
class D<T> where T : new()
{
public static void M() { _ = new T(); }
}
using X = D<C>;
using unsafe X = D<C>;
X.M();
using static D<C>;
using static unsafe D<C>;
M();
Обратите внимание, что другие ограничения работают так, как бывший вариант сегодня:
using X = D<C>; // error here
_ = new X(); // ok
_ = new D<C>(); // error here
class C
{
public C(int x) { }
}
class D<T> where T : new();
Должно ли unsafeбыть больше конструкций?
-
dynamic(вероятно, должно соответствовать тому, что BCL решает для API отражения)
(ответ) Как разорвать, как мы хотим разорвать
Текст вопроса
Первоначальное предложение является максимально критически критическим подходом, главным образом, как лакмус тест на то, насколько агрессивно мы хотим быть. Он не предлагает возможности отказаться от использования и выхода разделов кода, изменить смысл unsafe методов, запретить использование unsafe типов, использовать ошибки вместо предупреждений и, как правило, заставляет миграцию выполняться одновременно, во время обновления компилятора (а затем потенциально многократно обновлять зависимости и добавлять unsafe к элементам, которые уже использовались). Однако у нас есть богатый опыт внесения изменений, таких как это, что мы можем опираться на область размера разрывов и разрешить добавочное внедрение. Эти параметры описаны ниже.
Отказ в регионе кода и выходе
Это не первый раз, когда C# переопределил "базовый" регистр ненататированного кода. C# 8.0 представил функцию ссылочного типа, допускающую значение NULL, которая во многих отношениях может рассматриваться как схема unsafe формирования функции. Она имела аналогичные цели (предотвращать ошибки, которые стоят миллиарды долларов путем переопределения способа интерпретации C# по умолчанию) и аналогичного общего набора функций (добавление новых сведений в типы для распространения состояний и предотвращения ошибок). Это также было сильно критическое, и требуется сильный набор возможностей согласия и отказ в работе, чтобы разрешить использование функции с течением времени базами кода. Эта функция является контекстом ссылочного типа, допускающего значение NULL. Это лексическая область, которая сообщает компилятору для заданного региона в коде, как интерпретировать ненатованные ссылки на тип и какие типы предупреждений предоставлять пользователю. Мы могли бы использовать это в качестве модели unsafe , а также добавление контекста правил безопасности или аналогично тому, чтобы разрешить контроль того, применяются ли эти новые правила.
Одним из преимуществ, которые мы имеем с новыми unsafe функциями, является то, что они гораздо менее распространены. Хотя в топ-библиотеках есть достойное количество unsafe вызовов, наши угадываемые по проценту верхних библиотек, которые используются unsafe гораздо ниже, чем "каждая строка кода C# когда-либо написана". Надеюсь, это означает, что, хотя некоторые возможности отказаться от использования, возможно, необходимы, мы не нуждаемся в таком сложном механизме, как имеет значение NULL, с выделенными коммутаторами препроцессора и подобными.
Предупреждения и ошибки
В настоящее время предложение утверждает, что требования к безопасности памяти в настоящее время применяются с помощью предупреждения, а не ошибки. Благодаря этому мы работаем с функцией, допускаемой значением NULL, где предупреждения позволяют базам кода постепенно внедрять новую функцию и не нужно одновременно преобразовывать большие фрагменты кода. Мы ожидаем, что аналогичный процесс потребуется для небезопасных предупреждений: многие базы кода просто смогут включить новые правила глобально и перейти к их жизни. Но мы ожидаем, что базы кода, которые мы больше всего заботимся о принятии новых правил, будут иметь большие объемы кода для анимации, и мы хотим, чтобы они могли двигаться вперед с функцией, а не видеть стену ошибок и отказаться немедленно. При создании предупреждений о требованиях эти базы кода позволяют устранять предупреждения по файлу или методу по методу, отключая предупреждения во всем другом месте.
Разрывы подписи метода
Сейчас мы предлагаем, что unsafe в качестве ключевого слова для метода переход от чего-то, что лексически ограничивается без семантического влияния на то, что имеет семантический эффект, и не лексически ограничен.
Мы можем ограничить этот разрыв, введя новое ключевое слово для того, unsafe когда вызывающий метод или член должен находиться в контексте, например в callerunsafe качестве модификатора.
Значения по умолчанию для генераторов источников
При значении NULL авторы генератора могут явно отказаться от значения NULL независимо от того, включен ли весь проект в функцию по умолчанию, чтобы выходные данные генератора не прервались пользователем, включив значение NULL и предупреждая как ошибку. Следует ли делать то же самое для генераторов источников?
Conclusion
Ответил в LDM 2025-11-05. Мы сообщаем об ошибках безопасности памяти при включении новых правил, и никаких исключений для генераторов источников не будет.
(ответ) Ошибки или предупреждения?
- LDM 2025-11-05: ошибки
(ответ) Доступность генератора источников
- LDM 2025-11-05: нет
(ответ) Требовать safe создания элементов с unsafe блоками или указателями?
- LDM 2026-04-13: нет
(ответ) Режим compat для абонентов, не являющихся абонентами?
- LDM 2026-04-22: да
- LDM 2026-04-29: нет изменений для абонентов, которые выбираются
- LDM 2026-04-29: серьезность: ошибки
Ответ: несоответственные члены с указателями в подписи считаются небезопасными для абонентов.
(ответ) Расширение режима compat?
Следует ли рассматривать nint и System.IntPtr как указатели тоже?
Следует ли рассмотреть extern/DllImport из непредназванных абонентов, так как требуется небезопасный ?
Если у нас есть одеялое предупреждение, когда сборка, отключаемая при выборе, ссылается на сборку без согласия?
- LDM 2026-04-29: нет расширений (анализаторы могут охватывать некоторые менее некоторые небезопасные сигналы)
C# feature specifications