Remarque
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de vous connecter ou de modifier des répertoires.
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de modifier des répertoires.
Problème de champion : https://github.com/dotnet/csharplang/issues/9704
Résumé
Nous mettons à jour la définition de C# à partir des unsafe emplacements où les types de pointeur sont utilisés, pour être des emplacements où la mémoire non managée par le runtime est déréférence. Ces emplacements sont là où la mémoire n’est pas en sécurité, et sont responsables de la majeure partie des CVE (Vulnérabilités et expositions courantes) classées comme problèmes de sécurité de la mémoire.
// 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*.
}
}
Motivation
L’arrière-plan de cette fonctionnalité est également disponible, https://github.com/dotnet/designs/blob/main/accepted/2025/memory-safety/caller-unsafe.mdqui suit les changements d’écosystème plus larges qui seront nécessaires dans le cadre de cette proposition.
Il s’agit notamment des mises à jour BCL pour annoter correctement les méthodes comme étant dangereuses, ainsi que des mises à jour d’outils pour mieux comprendre où se produit la non-sécurité de la mémoire. Pour C# en particulier, nous voulons nous assurer que la mémoire est correctement suivie par la langue ; aujourd’hui, il peut être difficile d’examiner un programme de manière holistique et de comprendre tous les endroits où la mémoire se produit sans sécurité. Cela est dû au fait que divers aides tels que le System.Runtime.CompilerServices.Unsafe, System.Runtime.InteropServices.Marshalet d’autres n’expriment pas qu’ils violent la sécurité de la mémoire et ont besoin d’une considération particulière. Les méthodes qui utilisent ensuite ces assistances ne sont pas immédiatement évidentes, et lors de l’audit du code pour les problèmes de sécurité de la mémoire (à l’avance lors de la révision, ou lorsque vous essayez de déterminer la cause d’une vulnérabilité signalée), il peut être difficile d’identifier les emplacements susceptibles de contribuer aux problèmes.
Historiquement, unsafe en C# a fait référence à un trou de sécurité de mémoire spécifique : l’existence de types de pointeurs. Le moment où un type de pointeur n’est plus impliqué, C# est parfaitement heureux de laisser la mémoire se trouver latent dans le code. C’est ce problème que nous cherchons à résoudre avec cette évolution de unsafe C# et de l’écosystème .NET, l’étiquetage des zones où la mémoire peut potentiellement se produire, ce qui rend plus facile pour les réviseurs et les auditeurs de comprendre les limites du risque de perte de mémoire dans un programme. Important, cela signifie que nous allons changer la signification de unsafe, pas seulement l’augmenter. L’existence d’un pointeur n’est pas elle-même dangereuse ; l’action non sécurisée est dereferencing du pointeur. Cela s’étend davantage aux types eux-mêmes ; les types ne peuvent pas être intrinsèquement dangereux. Il s’agit uniquement de l’action d’utiliser un type qui pourrait être dangereux, et non l’existence de ce type.
Pour que ces informations transitent par le système, nous devons donc avoir un moyen de marquer les méthodes elles-mêmes comme unsafe. Aujourd’hui, unsafe en tant que modificateur de méthode n’a aucun impact externe, il permet uniquement aux pointeurs d’être utilisés dans la signature et le corps du membre. À l’avenir, unsafe en tant que modificateur modifiera publiquement la signification du membre ; il indiquera que le membre a des problèmes de sécurité de mémoire et que toutes les utilisations doivent être validées manuellement par le programmeur à l’aide du membre. Il s’agit d’une extension de la signification existante de unsafe: unsafe sur un organisme localise l’obligation d’audit à ce corps, tandis qu’une unsafe signature étend cette obligation à l’appelant.
Il s’agit d’un changement cassant potentiellement important pour des segments particuliers de la base d’utilisateurs C#. Notre espoir est que, pour beaucoup de nos utilisateurs, cela est effectivement transparent et la mise à jour des nouvelles règles sera transparente. Toutefois, étant donné que certaines grandes surfaces d’API comme de grandes parties de réflexion peuvent avoir besoin d’être marquées unsafe, nous pensons qu’il faudra probablement qu’il y ait une rampe d’accès décent aux nouvelles règles pour éviter de bifurquer entièrement l’écosystème.
Modifications majeures
Les modifications cassantes suivantes peuvent être observées lors de la mise à jour vers un compilateur implémentant cette fonctionnalité de langage.
- Si les règles de sécurité de mémoire mises à jour sont activées (qui peuvent être la valeur par défaut ou même la seule option dans une version ultérieure .NET) :
-
unsafesur un membre le marque également comme étant non sécurisé, ce qui signifie que les appelants doivent se trouver dans ununsafecontexte et les remplacements ne peuvent pas êtreunsafesi le membre de base est sécurisé. -
unsafesur un membre ou un type n’introduit pas automatiquement ununsafecontexte, ce qui signifie que les blocs explicitesunsafedoivent être utilisés autourunsafedes opérations dans les corps membres et les initialiseurs. -
externles membres et les champs dans une disposition explicite nécessitent une disposition expliciteunsafe/safemot clé sur la déclaration. -
stackallocdans certaines conditions nécessite ununsafecontexte. -
unsafemodificateur est une erreur sur les déclarations de type, les constructeurs statiques et les destructeurs, car il n’a aucun effet.
-
- Sous une nouvelle langversion :
- L’inférence lambda peut prendre en compte davantage de candidats, ce qui entraîne des ambiguïtés de résolution de surcharge.
-
safeest maintenant un mot clé contextuel qui peut interrompre le code qui l’a utilisé comme type. - Le mode Compat en mode hérité peut entraîner d’autres erreurs.
Conception détaillée
Terminologie : nous appelons les membres qui ont besoin d’un risque (précédemment appelé appelant-non sécurisé) si
- sous les règles de sécurité de mémoire mises à jour , elles sont marquées
unsafe, - sous les règles de sécurité de mémoire héritées , ils contiennent des pointeurs dans la signature.
Syntaxe
Cette proposition introduit :
- un nouveau
safemot clé contextuel qui peut être appliqué auxexternmembres et aux champs dans une disposition explicite, - et
unsafeexpressions (unsafe(expression)), détaillées dans les expressions non sécurisées.
Cette nouvelle syntaxe est disponible sous le nouveau LangVersion, mais indépendamment de l’opt-in, sous le principe que nous essayons de le faire afin que tout ce que vous soyez obligé de faire lorsque vous êtes choisi, vous êtes autorisé à le faire avant de vous inscrire.
Règles existantes unsafe
La spécification C# existante comporte une grande section consacrée au unsafecode non sécurisé §24. Il est défini de manière conditionnelle, car il n’est pas nécessaire pour un compilateur C# valide de prendre en charge la unsafe fonctionnalité. Une grande partie de ce qui est actuellement considéré comme normative conditionnelle ne sera plus ainsi après ce changement, car la plupart de la définition des pointeurs n’est plus considérée comme dangereuse en soi.
Types de pointeurs, variables fixes et modifiables, toutes les expressions de pointeur (à l’exception de l’indirection de pointeur, de l’accès aux membres du pointeur et de l’accès aux éléments de pointeur), et l’instruction fixed n’est plus considérée unsafecomme étant considérée comme étant en C# normale, sans obligation d’être utilisée dans un unsafe contexte. De même, la déclaration d’une mémoire tampon de taille fixe ou d’une initialisation stackalloc est également parfaitement légale en C#sécurisé. Pour tous ces cas, il n’accède qu’à la mémoire non sécurisée.
Il est important de noter que ces relaxations de pointeur s’appliquent indépendamment du fait qu’un assembly opte pour les règles de sécurité de la mémoire mises à jour.
Seules les opérations qui déréférencent réellement ou qui accèdent directement à la mémoire pointu continuent de nécessiter un unsafe contexte.
Cela permet un chemin de migration incrémentiel : les utilisateurs peuvent remodeler le code existant en se déplaçant unsafe vers l’intérieur lorsque le membre gère le risque en interne ou vers l’extérieur lorsque l’appelant doit participer à l’audit, avant de basculer le commutateur d’adhésion à l’échelle de l’assembly.
Étant donné la réécriture étendue de la unsafe section de code et d’autres parties de spécification C# inhérentes à cette modification, il serait difficile et probable qu’il n’est pas utile de fournir une différence de ligne par ligne des règles existantes de la spécification. Au lieu de cela, nous allons fournir une vue d’ensemble de la modification à apporter dans une section donnée, ainsi que des nouvelles règles spécifiques pour ce qui est autorisé dans unsafe les contextes.
Définition d’expressions nécessitant des contextes non sécurisés
Les expressions suivantes nécessitent un unsafe contexte lorsqu’ils sont utilisés :
- Indirections de pointeur
- Accès aux membres de pointeur
- Accès aux éléments de pointeur
- Appel de pointeur de fonction
- Accès aux éléments sur une mémoire tampon de taille fixe
-
stackallocdans les conditions définies ci-dessous
En plus de ces expressions, les expressions et les instructions peuvent également exiger conditionnellement un unsafe contexte s’ils dépendent d’un symbole marqué comme unsafe. Par exemple, l’appel d’une méthode qui nécessite un risque entraîne la invocation_expression d’exiger un unsafe contexte. Les instructions avec des appels incorporés (tels que usings, foreachet similaires) peuvent également nécessiter un unsafe contexte lorsqu’elles utilisent un membre nécessitant un risque .
Lorsque nous disons « nécessite un contexte non sécurisé » ou similaire dans ce document, cela signifie qu’il génère une erreur indiquant que la construction nécessite un unsafe contexte à utiliser.
Note
Cette section doit probablement être étendue pour déclarer formellement ce que chaque expression et chaque instruction doivent prendre en compte pour exiger un unsafe contexte.
Types de pointeur
Comme mentionné, les pointeurs ne deviennent plus intrinsèquement dangereux. Toutes les références aux contextes non sécurisés dans le §24.3 sont supprimées. Les types de pointeurs existent en C# normal et ne nécessitent unsafe pas de les mettre en place. Les définitions de type doivent être travaillées dans le §8.1 et ses sections suivantes, comme d’autres types.
De même, les conversions de pointeur doivent être effectuées dans le §10, avec des références aux unsafe contextes supprimés.
De même, les expressions de pointeur, à l’exception de l’indirection de pointeur, del’accès aux membres du pointeur et de l’accès aux éléments de pointeur, doivent être utilisées dans le §12, avec des références aux unsafe contextes supprimés. Aucune sémantique ne change sur la signification de ces expressions ; la seule modification est qu’elles n’ont plus besoin d’un unsafe contexte à utiliser.
Pour l’indirection de pointeur, l’accès aux membres du pointeur et l’accès aux éléments de pointeur, ces opérateurs restent non sécurisés, car ces opérateurs n’accèdent pas à la mémoire d’accès qui n’est pas gérée par le runtime. Ils restent dans le §24 et continuent à exiger qu’un unsafe contexte soit utilisé. Toute utilisation en dehors d’un unsafe contexte est une erreur.
Aucune sémantique sur ces opérateurs ne change ; ils continuent de signifier exactement la même chose qu’aujourd’hui. Ces expressions doivent toujours se produire dans un unsafe contexte.
L’instruction fixe passe au §13, avec des références aux unsafe contextes supprimés.
Les pointeurs de fonction ne sont pas encore incorporés dans la spécification C# principale, mais ils sont affectés de la même façon ; tout, mais l’appel de pointeur de fonction est déplacé dans la spécification standard.
Une expression d’appel de pointeur de fonction doit toujours se produire dans un unsafe contexte.
Mémoires tampons de taille fixe
L’histoire des mémoires tampons de taille fixe est similaire aux pointeurs. La définition d’une mémoire tampon de taille fixe n’est pas elle-même dangereuse et passe à §16.3. L’accès à une mémoire tampon de taille fixe dans une expression est de même sécurité, sauf si l’expression se produit en tant que primary_expression d’un element_access; elles sont évaluées comme un pointer_element_access, ce qui est dangereux, conformément aux règles ci-dessus.
Allocation de piles
Là encore, l’histoire de l’allocation de pile est très similaire aux pointeurs. La conversion d’un stackalloc pointeur en pointeur n’est plus dangereuse ; il s’agit de la inférence de ce pointeur non sécurisé. Toutefois, nous ajoutons une nouvelle règle :
Une stackalloc_expression est dangereuse si toutes les instructions suivantes sont vraies :
- La stackalloc_expression est convertie en un
Span<T>ou unReadOnlySpan<T>. - Le stackalloc_expression n’a pas de stackalloc_initializer.
- Le stackalloc_expression est utilisé au sein d’un membre qui a
SkipLocalsInitAttributeappliqué.
Dans ces contextes, l’espace de pile résultant peut avoir un contenu de mémoire inconnu et il est converti en un type qui fournit un wrapper sécurisé autour de l’accès à la mémoire non managée. Cela enfreint le contrat et Span<T>ReadOnlySpan<T>doit donc être soumis à un examen supplémentaire par l’auteur et les réviseurs de ce code.
Contrairement aux autres modifications apportées aux unsafe règles qui sont des relaxations, il s’agit d’un renforcement, et par conséquent, il s’applique uniquement sous opt-in aux règles de sécurité de mémoire mises à jour pour éviter une rupture.
Note
Cela signifie que l’affectation d’un stackalloc pointeur est toujours sécurisée, quel que soit le contexte.
Notez qu’un stackalloc type managé reste une erreur.
sizeof
Pour certains types prédéfinis, sizeof a toujours été constant et sécurisé (§12.8.19) et cela reste inchangé.
Pour d’autres types, sizeof utilisés pour exiger un contexte non sécurisé (§24.6.9), mais sont désormais sécurisés indépendamment des règles de sécurité de mémoire mises à jour.
Substitution, héritage et implémentation
Il s’agit d’une erreur de sécurité de la mémoire à ajouter unsafe au niveau du membre dans toute substitution ou implémentation d’un membre qui n’est pas requise à l’origine, car les appelants peuvent utiliser la définition de base et ne voient aucun ajout unsafe par une implémentation dérivée.
Délégués et lambdas
Il s’agit d’une erreur de sécurité de la mémoire pour convertir un membre non sécurisé en un type délégué en dehors du unsafe contexte.
Les types délégués et les types de fonction ne peuvent pas être non sécurisés.
Il s’agit d’une erreur au moment de la compilation à appliquer unsafe sur un symbole lambda.
extern
Étant donné que extern les méthodes sont des emplacements natifs qui ne peuvent pas être garantis par le runtime, le compilateur ne peut pas indiquer s’ils sont sécurisés ou non.
Même les méthodes qui prennent unmanaged uniquement des paramètres par valeur ne peuvent pas être appelées en toute sécurité par C#, car la convention d’appel utilisée pour la méthode peut être spécifiée de manière incorrecte par l’utilisateur et doit être vérifiée manuellement par révision.
Par conséquent, sous les règles de sécurité de mémoire mises à jour, le compilateur exige que chaque extern méthode soit explicitement marquée comme étant l’une ou l’autre unsafesafe.
extern les méthodes des assemblys utilisant les règles de sécurité de mémoire héritées ne sont pas considérées implicitement unsafe , car extern elles sont considérées comme des détails d’implémentation qui ne font pas partie de la surface publique.
extern n’est pas garanti pour être conservé dans les assemblys de référence.
Notez que cela diffère du mode compat qui s’applique également aux assemblys de règles héritées, car les méthodes avec des pointeurs dans la signature ont toujours besoin d’un contexte non sécurisé sur le site d’appel.
Modificateurs et contextes non sécurisés
Aujourd’hui, comme couvert par la spécification de contexte non sécurisé, unsafe se comporte de manière lexicale, marquant l’intégralité du corps textuel contenu par le unsafe bloc comme contexte unsafe (à l’exception des corps itérateurs), ainsi que certains contextes environnants en cas de déclarations :
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
}
Avec l’adhésion aux règles de sécurité de mémoire mises à jour, sur un membre le unsafe marque comme étant non sécurisé, étendant l’obligation d’audit à l’appelant et n’introduit pas de unsafe contexte (au lieu de cela, seules les régions explicites unsafe dans le corps établissent unsafe des contextes).
unsafe sur les déclarations suivantes génère une erreur, car elle n’a plus de signification :
-
delegate, - constructeur statique,
- Destructeur
- déclaration de type (
class,structetc.).
unsafesur un constructeur introduit un unsafe contexte à l’intérieur de son initialiseur, unsafe qu’un constructeur peut appeler un constructeur non sécurisé base ou this non.
Les types avec des constructeurs sans paramètre nécessitant une sécurité non sécurisée ne répondent pas à la new() contrainte.
De même et en outre, les structs avec des constructeurs sans paramètre nécessitant des contraintes non sécurisées ne répondent pas à la struct contrainte.
unsafe sur un membre n’est appliqué à aucune fonction anonyme ou locale imbriquée à l’intérieur du membre.
Il en va de même pour les fonctions anonymes et locales déclarées à l’intérieur d’un unsafe bloc (elles sont toujours dans un unsafe contexte comme toujours, mais elles ne deviennent pas non sécurisées).
Pour marquer une fonction locale comme non sécurisée, elle doit être marquée manuellement comme unsafe.
Les lambda ne peuvent pas être marqués comme non sécurisés (le unsafe mot clé est interdit sur eux).
Lorsqu’un membre est partial, les deux parties doivent accepter le unsafe modificateur, inchangées par rapport aux règles C# aujourd’hui.
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
}
Pour les propriétés, get et set/init les accesseurs peuvent être déclarés indépendamment comme unsafe; marquer l’intégralité de la propriété comme unsafe signifie que les accesseurs et get les set/init accesseurs sont non sécurisés.
Il n’est actuellement pas possible de placer des modificateurs sur les accesseurs d’événements, et cette proposition ne change pas, c’est-à-dire add que les remove accesseurs d’événements ne peuvent pas être déclarés indépendamment comme unsafe.
Uniquement si l’ensemble de l’événement est marqué comme unsafeétant , cela signifie que les accesseurs sont dangereux ; sinon, ils sont sécurisés.
Fields
unsafe sur un champ le marque également comme étant non sécurisé et n’introduit pas de unsafe contexte dans son initialiseur.
Le mode compat s’applique également aux champs.
Le marquage d’une propriété ou d’un événement comme unsafe ne rend pas son champ de stockage non sécurisé.
Dans un type avec [StructLayout(LayoutKind.Explicit)] ou [ExtendedLayout], tous les champs d’instance doivent être marqués soit safe soit unsafe.
Si le champ est « masqué » derrière une propriété automatique ou un événement de type champ, l’exigence safe/unsafe est déplacée vers l’événement de type propriété automatique ou de type champ à la place.
Metadata
Lorsqu’un assembly est compilé avec les nouvelles règles de sécurité de la mémoire, il est marqué avec MemorySafetyRulesAttribute (détaillé ci-dessous), renseigné comme 15 version de langue. Il s’agit d’un signal pour tous les consommateurs en aval que tous les membres définis dans l’assembly sont correctement attribués avec RequiresUnsafeAttribute (détaillé ci-dessous) si un unsafe contexte est nécessaire pour les appeler.
Tout membre d’un tel assembly qui n’est pas marqué avec RequiresUnsafeAttribute ne nécessite pas d’appel de unsafe contexte, quels que soient les types dans la signature du membre.
Il s’agit d’une erreur d’application explicite du MemorySafetyRulesAttribute symbole ou RequiresUnsafeAttribute de n’importe quel symbole dans la source.
Le compilateur ignore les membres marqués RequiresUnsafeAttributedes assemblys qui utilisent les règles de sécurité de mémoire héritées (au lieu de cela, le mode compat est utilisé là).
Lorsqu’un membre non de type est marqué comme unsafeétant , le compilateur synthétise une RequiresUnsafeAttribute application sur le membre dans les métadonnées.
Lorsqu’un membre nécessitant un risque d’utilisateur génère des membres masqués, tels que les méthodes get/set d’une propriété automatique, le membre accessible à l’utilisateur et tous les membres masqués générés par ce membre sont tous non sécurisés et RequiresUnsafeAttribute sont appliqués à tous ces membres.
La MemorySafetyRulesAttribute définition et RequiresUnsafeAttribute la synthèse sont synthétisées par le compilateur si nécessaire par règles de membre connues standard.
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
{
}
}
Mode Compat
À des fins de compatibilité et pour réduire le nombre de faux négatifs qui se produisent lors de l’activation des nouvelles règles, nous avons une règle de secours pour les modules qui n’ont pas été mis à jour vers les nouvelles règles. Pour ces modules, un membre est considéré comme non sécurisé s’il contient un pointeur ou un type de pointeur de fonction quelque part entre ses types de paramètres ou son type de retour (peut être imbriqué dans un type non pointeur, par exemple int*[]).
Notez que cela ne s’applique pas aux pointeurs dans les types de contraintes (par exemple where T : I<int*[]>), car ceux-ci n’auraient pas besoin d’un contexte non sécurisé sur les sites d’appel précédemment.
Cela n’inclut pas les paramètres génériques substitués (par exemple, la méthode I<T>.M(T) lorsqu’elles sont remplacées T par int*[]) car il n’existe aucun moyen de sécurité de type pour le membre cible d’utiliser ce type de pointeur pour quoi que ce soit.
Ce mode compat exige que les membres non sécurisés nécessitent un unsafe contexte à utiliser même des appelants qui n’ont pas choisi les règles de sécurité de mémoire mises à jour.
Cela doit éviter un « dip » où la mise à jour de LangVersion (mais pas la mise à jour de la version des règles de sécurité de la mémoire) rend la plupart des opérations de pointeur sécurisées (y compris l’appel de fonctions avec des pointeurs dans la signature qui seront probablement marquées comme non sécurisées lorsque vous avez choisi les règles mises à jour) et rend ainsi le code moins protégé dans cette fenêtre de migration.
VB
Nous n’avons pas besoin d’ajouter de prise en charge à Visual Basic pour les membres nécessitant des éléments non sécurisés, car il n’existe aucun unsafe contexte dans VB aujourd’hui et aucun moyen de travailler avec des pointeurs là-bas.
unsafe Expressions
Une unsafe expression introduit un contexte minimal unsafe pour l’évaluation d’une seule expression. Il est utile dans les situations où un unsafe bloc élargirait inutilement l’étendue unsafe ou où unsafe les blocs ne peuvent pas être utilisés de manière syntactique (par exemple, dans les filtres, les catch initialiseurs de champ, les initialiseurs de constructeur et la position d’opérande de await).
Syntaxe
Un unsafe_expression élément est ajouté en tant que primary_no_array_creation_expression :
unsafe_expression
: 'unsafe' '(' expression ')'
;
Elle est soumise à la même AllowUnsafeBlocks exigence que le unsafe mot clé ailleurs.
Sémantique
Un unsafe_expression contexte établit un unsafe contexte pour évaluer son expression. Cela signifie que les déréférencements de pointeur, les appels de pointeur de fonction et les appels aux membres non sécurisés requis sont tous autorisés dans l’expression entourée. Le type et la valeur du unsafe_expression fichier sont le type et la valeur de l’expression placée entre elles.
Le unsafe contexte établi par un ne unsafe_expression s’étend pas au-delà de sa parenthèse fermante.
Exemples de motivation et de migration
Plusieurs positions syntactiques n’acceptent pas du tout les blocs, mais peuvent contenir unsafe des sous-expressions qui appellent des membres non sécurisés. Sans unsafe expressions, la migration de ce code nécessite l’extraction de la sous-expression non sécurisée dans une fonction locale d’assistance ou une variable temporaire, qui masque l’intention et augmente la précision.
awaitsur une méthode nécessitant une sécurité. Une await expression ne peut pas apparaître à l’intérieur d’un unsafe bloc. Lorsque la méthode attendue devient non sécurisée, une variable temporaire est nécessaire pour contenir la tâche avant de pouvoir être attendue :
// 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());
Filtres catch. Le when filtre d’une catch clause est une expression, et non un corps d’instruction. Un unsafe bloc ne peut entourer que les instructions. Il n’y a donc aucun endroit pour en placer une autour de l’expression de filtre. En outre, si le try corps contient une await expression, un unsafe bloc ne peut pas entourer l’ensemble try/catchde l’instruction.await Il n’est pas autorisé à l’intérieur d’un unsafe bloc. Lorsqu’une méthode utilisée dans un filtre devient non sécurisée, la seule alternative sans unsafe expressions est une assistance :
// 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)))
{
}
Initialiseurs de champ. Dans les règles mises à jour, unsafe sur un champ n’introduit pas de unsafe contexte dans son initialiseur. Quand l’initialiseur d’un champ appelle un membre non sécurisé, une unsafe expression fournit le contexte sans nécessiter de méthode d’assistance :
// 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());
Initialiseurs du constructeur.
this(...) et base(...) les listes d’arguments d’initialiseur sont des expressions, et non des corps d’instructions. Si l’un de ces arguments appelle un membre nécessitant une sécurité requise , il n’y a à nouveau aucun endroit pour insérer un unsafe bloc. Par conséquent, l’appel doit être déplacé vers un assistance :
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()))
{
}
}
Appels inline. Plus généralement, l’habillage uniquement des sous-expressions non sécurisées nécessite un contrôle de l’étendue d’audit et évite d’extraire le code sécurisé entourant le code sécurisé (par exemple, l’évaluation d’argument ou un appel de méthode contenant) dans le unsafe contexte :
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);
Questions
(répondu) Utiliser RequiresUnsafeAttribute pour désigner des membres non sécurisés
Au lieu d’utiliser le unsafe mot clé sur le membre pour désigner des membres non sécurisés , nous pourrions utiliser un attribut (RequiresUnsafeAttribute) appliqué au membre (et ne pas modifier la signification du unsafe modificateur sur les membres).
Avantages de unsafe:
- semblable à d’autres langues et, par conséquent, plus facile à comprendre,
- plus détectable qu’un attribut.
Avantages d’un attribut (ou d’un autre mot clé) :
- évite de rompre les membres existants marqués comme
unsafe, - adoption incrémentielle possible (membre par membre),
- ne force pas à marquer l’ensemble du corps
unsafecomme (même avecunsafele mot clé que nous pourrions changerunsafepour ne pas avoir d’effet sur les corps cependant), - permet de supprimer toutes les erreurs nécessitant des problèmes non sécurisés sans avoir à marquer le membre lui-même comme étant non sécurisé (exemples).
Discussions:
- LDM 2025-11-12 : utiliser le mot clé
- LDM 2026-01-26 : utiliser l’attribut
- LDM 2026-04-06 : utiliser le mot clé
Réponse : utilisez le mot clé unsafe pour indiquer les membres non sécurisés .
Fonctions locales/contextes de sécurité lambda
À l’heure actuelle unsafe , le corps d’une méthode est limité lexicalement. Toutes les fonctions locales imbriquées ou les lambdas héritent de ceci, et leurs corps se trouvent dans un contexte non sécurisé en mémoire.
Ce comportement est-il que nous voulons conserver dans la langue ?
Notez que si nous conservons unsafe le modificateur utilisé pour exposer que l’appelant doit être dangereux, cela peut avoir des répercussions sur la signature de la méthode.
Comme actuellement proposé, les fonctions anonymes et locales imbriquées ne conservent pas le contexte non sécurisé de leur membre contenant.
Ty de type unsafedélégué
Nous pourrions autoriser le marquage des types délégués et des lambdas (et des types de fonction) comme étant non sécurisés.
Cela nécessiterait plusieurs règles supplémentaires (en dehors unsafe du contexte) :
- interdire l’utilisation de délégués non sécurisés en tant qu’arguments de type,
- interdire la conversion de ces délégués en tout ce qui n’est pas nécessaire (
DelegateetExpressionobject), sans cela, il y a un risque de forcerunsafeles annotations au mauvais endroit et d’avoir une zone où la zone réelle deunsafety n’est pas correctement appelée.
Conversion de groupe lambda/méthode en types délégués sécurisés
Si nous autorisez unsafe les lambdas et les délégués, la conversion d’un groupe lambda ou de méthode non sécurisé requis en un type délégué non non sécurisé autorisé sans avertissement ou erreur dans un unsafe contexte ? Si ce n’est pas le cas, cela pourrait être assez douloureux pour différentes parties de l’écosystème, en particulier les énumérables qui sont transmis par le biais de requêtes LINQ.
Types naturels de groupe lambda/méthode
Aujourd’hui, le seul impact réel sur la sémantique et le codegen (en plus des métadonnées supplémentaires) change la function_type d’un groupe lambda ou de méthode lorsqu’il unsafe se trouve dans la signature. Si nous étions à éviter cela, alors il n’y aurait aucun impact réel sur l’un ou l’autre, ce qui pourrait donner aux utilisateurs plus de confiance que le comportement n’a pas changé subtilement sous le capot.
Note
Si nous décidons de conserver la possibilité d’avoir unsafe des lambdas, nous devons mettre à jour cette proposition pour inclure une modification de syntaxe pour permettre aux lambdas d’être déclarés unsafe au premier endroit.
Pointeurs vers des types managés
C# 11 permet aux pointeurs vers des types managés avec un avertissement.
Devrions-nous assouplir cet avertissement pour les opérations d’adresse ?
Nous pensons que le problème n’est que lorsque l’utilisateur déréfére ce pointeur, qui tombe sous des règles d’évolution normales non sécurisées.
Mais qu’en est-il ?sizeof
stackalloc comme initialisé
Aujourd’hui, la spécification considère stackalloc toujours la mémoire comme non initialisée et indique que le contenu n’est pas défini, sauf s’il est effacé ou affecté manuellement. Considérons-nous cela comme un bogue de spécification, ou devons-nous modifier ce que nous considérons unsafe à stackalloc des fins ?
stackalloc règle
LDM doit confirmer la stackalloc règle définie ci-dessus et s’il doit s’appliquer indépendamment de l’opt-in comme d’autres modifications liées au pointeur.
AllowUnsafeBlocks
La signification de AllowUnsafeBlocks n’est actuellement pas modifiée : il est nécessaire d’être défini true pour pouvoir utiliser le unsafe mot clé ou SkipLocalsInitAttribute.
Ne devrions-nous pas l’exiger SkipLocalsInitAttribute sous les règles mises à jour, car la liste de contrôle peut marquer cet attribut comme étant non sécurisée ?
Faut-il aussi l’exiger pour le safe mot clé ?
Devrions-nous l’exiger pour les unsafe blocs et unsafe les déclarations membres ou une autre combinaison de celles-ci ?
(répondu) unsafe Expressions
D’autres langages dotés de fonctionnalités plus complètes unsafe ont été ajoutés unsafe en tant qu’expression qui améliore l’ergonomie de l’utilisateur et permet aux auteurs de limiter plus précisément l’emplacement où unsafe il est utilisé. Est-ce quelque chose que nous voulons avoir en C# ? Envisagez un appel inline à un unsafe membre qui gère directement la sécurité : à l’heure actuelle, l’auteur doit inclure l’instruction entière dans un unsafe bloc, développer l’étendue du unsafe contexte ou extraire l’appel de fonction interne dans une variable intermédiaire.
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);
Réponse : oui. Consultez la section de conception détaillée.
Plus unsafe de contextes et de relaxations
Devrions-nous assouplir davantage les restrictions relatives unsafe aux paramètres de pointeur et aux itérateurs et aux méthodes asynchrones ?
En particulier l’autorisation await UnsafeMethod() serait utile parce que maintenant les utilisateurs doivent réécrire cela dans Task t; unsafe { t = UnsafeMethod(); } await t;.
Pour plus d’informations , voir ref/unsafe dans les itérateurs/async .
Devrions-nous également autoriser &UnsafeMethod dans un contexte sûr ? Aujourd’hui, comme la proposition se présente, cela nécessite unsafe un contexte si la méthode est marquée comme unsafe.
Toutefois, étant donné que nous obtenons simplement son adresse, qui aura besoin unsafe de contexte quand la déréférencement/appelé, nous pourrions autoriser l’adresse de lui-même dans un contexte sûr.
-
LDM 2026-05-27 :
awaitdansunsafeles besoins d’une proposition concrète
Relaxations dangereuses fermées sur LangVersion
Devrions-nous rendre les relaxations de contexte non sécurisées inconditionnelles sur LangVersion ?
- LDM 2026-04-06 : ils ne sont pas conditionnels à la version des règles de sécurité de la mémoire
- TODO : Qu’en est-il de LangVersion ?
(répondu) unsafe sur les types
Nous ne pourrions pas envisager de rendre automatiquement l’ensemble de l’étendue lexicale d’un unsafe type à être un unsafe contexte et d’avertir pour un unsafe type, car il n’aurait aucune signification.
-
LDM 2025-11-12 :
unsafesur un type n’aura aucune signification - LDM 2026-05-13 : il s’agit d’une erreur (peut être revisitée en fonction des commentaires)
Réponse : unsafe sur un type est une erreur (peut être revisitée en fonction des commentaires) dans les règles mises à jour.
unsafe sur les accesseurs
Nous allons autoriser unsafe les accesseurs de propriétés, mais pas sur les accesseurs d’événements, conformément à d’autres modificateurs préexistants.
Cela signifie également que unsafe sur une propriété est simplement un raccourci pour unsafe ses accesseurs.
Mais en même temps, partialles modificateurs doivent unsafe correspondre :
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`
}
Peut-être qu’il doit se comporter de la même façon que , c’est-à-dire readonlyinterdire unsafe à la fois la propriété et son accesseur en même temps :
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 { } }
}
(répondu) Autoriser la suppression d’erreurs nécessitant des erreurs non sécurisées dans les scénarios de cas de périphérie
Comment autoriser la suppression d’erreurs non sécurisées dans les scénarios suivants ?
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`
}
Pour supprimer les erreurs nécessitant des erreurs non sécurisées , nous devons en quelque sorte introduire un unsafe contexte dans la signature de ces membres.
Mais le unsafe mot clé n’introduit unsafe de contexte.
Nous pourrions introduire unsafe un unsafe contexte dans la signature, mais forcer l’exécution d’un constructeur nécessite une sécurité lors de la tentative d’appeler un constructeur de base nécessitant un risque de sécurité semble malheureux.
Nous pourrions rendre les utilisations non sécurisées dans les avertissements de signatures que les utilisateurs pourraient supprimer sur place s’ils atteignent ces rares cas de périphérie.
Il existe un problème similaire avec les types, car unsafe ils n’introduisentunsafe pas de contexte non plus :
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 :
- Le code non exécutable, comme l’application d’attributs, doit rester une erreur insuppressible (jusqu’à entendre quelques commentaires)
- d’autres cas de périphérie tels que
new()peuvent également rester une erreur jusqu’à ce que nous entendions des commentaires - ne pas interdire la déclaration de constructeurs sans paramètre nécessitant des contraintes non sécurisées
- seul l’initialiseur d’un
unsafeconstructeur peut appeler un constructeur oubasethis
Collections params
class C
{
unsafe public C() { } // declaring requires-unsafe constructor
}
class B
{
public void M(params C c) { }
}
Cette déclaration peut simplement être autorisée, car l’appel de cette méthode nécessite un unsafe contexte de toute façon, car la collection non sécurisée params est construite sur le site d’appel.
Bien que cette déclaration ne soit pas sûre, nous pourrions simplement exiger l’annotation unsafe ou au moins avertir de ce fait.
Notez que le params cas de collection est une erreur aujourd’hui en ligne avec la façon dont d’autres fonctionnalités similaires se comportent ici (Obsolete, UnmanagedCallersOnly), mais cela peut être un bogue d’implémentation.
Membres connus
Par souci de simplicité et d’intégrité de l’implémentation, nous proposons que le compilateur soit libre de supposer que tous les membres connus (tels queArray.Length) peuvent être supposés être sûrs (c’est-à-dire, pas obligatoires).
Documents Xml
Le passage d’une obligation aux appelants est de prendre la responsabilité d’indiquer clairement ce que cette obligation est. Devrions-nous formaliser cela au-delà de ce qui peut déjà être représenté dans les documents xml ?
Les membres marqués unsafe doivent avoir des commentaires indiquant ce qui est nécessaire à l’appelant pour s’assurer que le code est correct.
Pour faciliter la présentation et la différenciation dans la documentation, une nouvelle balise de documentation XML serait utile : <safety />.
On s’attendrait à ce que toutes les conditions préalables/post-conditions soient placées en <safety> bloc.
Pour documenter le raisonnement de chaque unsafe bloc, nous vous recommandons d’utiliser des // SAFETY commentaires similaires à Rust.
Devrions-nous également vérifier ces éléments par le compilateur (par exemple, avoir un avertissement désactivé par défaut) ou laisser cela à un analyseur ?
- LDM 2026-05-27 : aucune décision concrète
Avertissements sans unsafe signification
D’autres déclarations doivent-elles produire l’avertissement ou l’erreur sans unsafe signification ?
Par exemple, les méthodes avec des corps vides (ou extern), etc. Nous disposons déjà d’un analyseur IDE pour des tâches inutiles unsafe .
Doit-il [ModuleInitializer] unsafe void M() { } s’agir d’une erreur similaire à un constructeur statique ?
(répondu) unsafe Champs
Aujourd’hui, aucune proposition n’est faite unsafe sur un terrain. Toutefois, nous devrons peut-être l’ajouter, de sorte que toute lecture ou écriture dans un champ marqué comme unsafe doit se trouver dans un unsafe contexte. Cela nous permettrait d’annoter mieux les problèmes liés au code, tels que :
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];
}
Devrions-nous également marquer le champ de stockage de la propriété automatique comme unsafe?
Pour être cohérent avec notre décision pour les membres, il serait bon de prendre unsafe sur un terrain non plus d’introduire un unsafe contexte.
Si des opérations non sécurisées sont utilisées dans l’initialiseur du champ, l’utilisateur peut toujours les encapsuler dans une méthode, ou nous pourrions introduire des unsafe expressions.
-
LDM 2026-05-13 : les champs peuvent être marqués comme non sécurisés par le biais
unsafede ; les initialiseurs ne sont pas dans leunsafecontexte.
(répondu) Disposition explicite
Les champs dans les structs marqués comme [StructLayout(Explicit)] ou [ExtendedLayout] obligatoires doivent-ils être marqués comme unsafe?
Recommandation : oui.
-
LDM 2026-05-13 : oui, exiger ou
unsafesafe, comme pourexterns.
Champs de disposition et de stockage explicites
Si le compilateur synthétise un champ de stockage pour une propriété automatique ou un événement semblable à un champ dans un Explicit/Extended type, devons-nous exiger safe/unsafe sur la propriété/l’événement à la place ?
Dans le cas contraire, l’utilisateur doit développer ces déclarations automatiques dans un champ manuel, ainsi que des déclarations de membre wrapper.
Qu’en est-il d’un paramètre de constructeur principal qui obtient un champ de stockage ?
Les deux safe et unsafe modificateurs sont actuellement interdits sur une déclaration de paramètre.
[Out] et [SkipLocalsInit]
Étant donné que, par exemple, VB ne garantit pas que [Out] les paramètres sont initialisés, en combinaison avec [SkipLocalsInit], l’appel de ces paramètres peut être considéré unsafe en C#.
En revanche, il se sent comme le problème de l’appelé qu’il ne respecte pas son [Out] contrat (de la même façon, il pourrait être dangereux de plusieurs autres façons).
Si nous décidons que ces cas doivent être unsafe, nous pouvons exclure les méthodes que nous savons avoir choisies (actuellement celles-ci sont de C# qui garantit l’utilisation correcte de [Out] mais si d’autres langues implémentent les nouvelles règles, elles doivent également garantir cela).
Prise de l’adresse d’une variable non initialisée
Aujourd’hui, la prise de l’adresse d’une variable non affectée peut considérer cette variable définitivement affectée, exposant un membre non initialisé. Nous avons quelques options pour résoudre ce problème :
- Exiger que les variables soient définitivement affectées avant d’autoriser l’utilisation d’un opérateur d’adresse sur eux.
- Prenez l’adresse d’une variable non initialisée non sécurisée.
Exemples :
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++;
Valeur de MemorySafetyRulesAttribute
Quelle doit être la version des règles de sécurité de mémoire « activées »/« mises à jour » ?
2?
15?
11?
Consultez également l’adoption non sécurisée du KIT SDK et l’adhésion plus progressive ?.
(répondu) Opt-in plus progressif ?
Aujourd’hui, l’adhésion vous donne deux choses à la fois : l’application des règles dangereuses dans votre propre code et un signal publié à vos consommateurs (via un attribut au niveau de l’assembly) que vos annotations sont intentionnelles. Il peut y avoir des utilisateurs qui souhaitent commencer à obtenir les diagnostics d’application au fur et à mesure qu’ils annotent, sans être prêts à publier qu’ils ont entièrement annoté leur assembly. Devrions-nous avoir un niveau d’adhésion « intermédiaire » qui ferait apparaître les diagnostics non sécurisés en tant qu’avertissements, et l’opt-in complet les promouvoirait en erreurs ?
- LDM 2026-04-29 : oui, mais ce n’est pas bloquant pour la préversion
(répondu) Option d’adhésion plus fine
Fournissez un mécanisme d’opt-in précis basé sur une région analogue à celui que nous avons créé pour les types de référence nullables, où les utilisateurs peuvent utiliser des directives pour activer la fonctionnalité pour des régions spécifiques du code source ? Voir également Désactiver/désactiver les régions de code.
- LDM 2026-04-29 : non
(répondu) extern implicitement non sécurisé
Il s’agit actuellement du seul endroit où RequiresUnsafeAttribute est synthétisé sans mot clé explicite unsafe .
Est-ce que nous sommes d’accord avec cette valeur hors norme ?
De plus, CoreLib expose de nombreuses méthodes extern (FCalls) comme étant sécurisées aujourd’hui. Le traitement des méthodes extern comme implicitement dangereuses nécessite l’habillage des méthodes extern implicitement dangereuses avec un wrapper sécurisé. Nous pouvons rencontrer des situations où l’ajout du wrapper supplémentaire est difficile en raison des détails de l’implémentation du runtime.
-
LDM 2026-04-01 :
externles membres doivent être explicitement marqués comme sécurisés ou non sécurisés - LDM 2026-04-06 : même décision répétée
-
LDM 2026-04-13 : décision temporaire d’utiliser
safele mot clé -
LDM 2026-05-13 : utiliser
safele mot clé (toujours ouvert à la révision cependant)
Réponse : extern les membres doivent être marqués ou unsafesafe (nous ajoutons un nouveau mot clé pour celui-ci, mais nous pouvons le revoir avant que la fonctionnalité ne soit fournie, en fonction des commentaires).
(répondu) unsafe paramètres par défaut dans les membres
Nous n’avons pas pu envisager de rendre automatiquement le corps entier d’une méthode un unsafeunsafe contexte. Rust l’a fait dans RFC 2585, avec la motivation qu’elle aide à réduire l’étendue des unsafe blocs aux emplacements dans lesquels unsafe est réellement utilisé. Nous pourrions faire la même chose en C#, soit en tant qu’avertissement, soit en tant qu’erreur, avec des motivations similaires.
-
LDM 2026-04-22 : oui,
unsafedans la signature ne rend pas le corpsunsafe
(répondu) new() Contrainte
Voulons-nous prendre en charge new() avec des besoins non sécurisés (quelque chose que nous ne semblez pas prendre en charge actuellement dans le compilateur pour d’autres fonctionnalités, comme 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 : les types avec des constructeurs sans paramètre non sécurisés doivent ne pas satisfaire la
new()contrainte
new() contrainte et usings
Comment doit-il se comporter dans les alias et les utilisations statiques ?
- S’il s’agit d’une erreur lors de la
usingdéclaration, supprimable par le biais duunsafemot clé que nous prenons déjà en charge, ou - doit-il s’agir d’une erreur normalement sur le site d’utilisation, comme si elle serait utilisée directement sans alias ou utilisation statique ?
Note
Dans le deuxième cas, nous devons ajouter un avertissement « sans unsafesignification » pour l’utilisation d’alias et d’utilisations statiques.
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();
Notez que d’autres contraintes se comportent comme l’ancienne option aujourd’hui :
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();
Faut-il que d’autres constructions soient unsafe?
-
dynamic(probablement doit correspondre à ce que BCL décide pour les API de réflexion)
(répondu) Comment cassons-nous voulons faire l’asymétrie
Texte de la question
La proposition initiale est une approche au maximum cassant, principalement comme un test d’intensité que nous voulons être. Il ne propose aucune possibilité de refuser/refuser des sections du code, de modifier la signification des unsafe méthodes, d’interdire l’utilisation des unsafe types, d’utiliser des erreurs au lieu d’avertissements et de forcer généralement la migration à se produire tout en même temps, au moment où le compilateur est mis à niveau (puis potentiellement à plusieurs reprises en tant que dépendances mises à jour et ajouter unsafe aux membres qui étaient déjà en cours d’utilisation). Toutefois, nous avons une grande expérience d’apporter des modifications comme celle-ci que nous pouvons tirer de l’étendue de la taille des ruptures et permettre l’adoption incrémentielle. Ces options sont décrites ci-dessous.
Opt in/out pour les régions de code
Ce n’est pas la première fois que C# a redéfini la casse « base » du code non annoté. C# 8.0 a introduit la fonctionnalité de type de référence nullable, qui peut être considérée comme un blueprint pour la façon dont la unsafe fonctionnalité se forme. Il avait des objectifs similaires (empêchez les bogues qui coûtent des milliards de dollars en redéfinissant la façon dont C# par défaut est interprété) et un ensemble de fonctionnalités général similaire (ajoutez de nouvelles informations aux types pour propager les états et éviter les bogues). Il a également été fortement cassant, et avait besoin d’un ensemble fort de fonctionnalités d’opt-in et d’annulation pour permettre à la fonctionnalité d’être adoptée au fil du temps par les bases de code. Cette fonctionnalité est le « contexte de type référence nullable ». Il s’agit d’une étendue lexicale qui informe le compilateur, pour une région donnée dans le code, à la fois comment interpréter les références de type non annotées et les types d’avertissements à donner à l’utilisateur. Nous pourrions également utiliser ce modèle pour unsafe ajouter un « contexte de règles de sécurité » ou similaire pour permettre de contrôler si ces nouvelles règles sont appliquées ou non.
L’un des avantages que nous avons avec les nouvelles unsafe fonctionnalités est qu’elles sont beaucoup moins répandues. Bien qu’il existe un nombre décent d’appels unsafe dans les bibliothèques supérieures, nos estimations sur le pourcentage de bibliothèques principales qui utilisent unsafe sont beaucoup plus faibles que « chaque ligne de code C# jamais écrite ». Espérons que cela signifie que, même si une certaine possibilité d’opter ou de sortir est éventuellement nécessaire, nous n’avons pas besoin d’un mécanisme aussi compliqué que nullable, avec des commutateurs de préprocesseur dédiés et les autres.
Avertissements et erreurs
La proposition indique actuellement que les exigences de sécurité de la mémoire sont actuellement appliquées par le biais d’un avertissement, plutôt que d’une erreur. Il s’agit de notre expérience d’utilisation de la fonctionnalité nullable, où les avertissements ont permis aux bases de code d’adopter de manière incrémentielle la nouvelle fonctionnalité et n’ont pas besoin de convertir toutes les grandes étendues de code en même temps. Nous nous attendons à ce qu’un processus similaire soit nécessaire pour des avertissements non sécurisés : de nombreuses bases de code pourront simplement activer les nouvelles règles à l’échelle mondiale et passer à leur vie. Mais nous attendons des bases de code que nous nous soucions le plus de l’adoption des nouvelles règles aura de grandes quantités de code à annoter, et nous voulons qu’elles puissent avancer avec la fonctionnalité, plutôt que de voir un mur d’erreurs et abandonner immédiatement. En effectuant les avertissements requis, nous autorisez ces bases de code à corriger les avertissements de fichier par fichier ou de méthode par méthode selon les besoins, en désactivant les avertissements partout ailleurs.
Sauts de signature de méthode
À l’heure actuelle, nous proposons qu’en unsafe tant que mot clé sur la méthode se déplace de quelque chose qui est léxicalement délimité sans impact sémantique sur quelque chose qui a un impact sémantique, et n’est pas limité lexicalement.
Nous pourrions limiter ce saut en introduisant un nouveau mot clé pour quand l’appelant d’une méthode ou d’un membre doit se trouver dans un unsafe contexte ; par exemple, callerunsafe en tant que modificateur.
Valeurs par défaut pour les générateurs sources
Pour nullable, nous obligeons les auteurs de générateurs à choisir explicitement la valeur Nullable, que l’ensemble du projet ait choisi la fonctionnalité par défaut, afin que la sortie du générateur ne soit pas rompue par l’utilisateur qui active nullable et avertit en tant qu’erreur. Devrions-nous faire de même pour les générateurs sources ?
Conclusion
Réponse dans LDM 2025-11-05. Nous allons signaler des erreurs pour les problèmes de sécurité de la mémoire lorsque les nouvelles règles sont activées et aucune exception pour les générateurs sources n’est effectuée.
(répondu) Erreurs ou avertissements ?
- LDM 2025-11-05 : erreurs
(répondu) Affordance du générateur source
- LDM 2025-11-05 : aucun
(répondu) Exiger un safe créateur pour les membres avec unsafe des blocs ou des pointeurs ?
- LDM 2026-04-13 : non
(répondu) Mode Compat pour les appelants non opt-in aussi ?
- LDM 2026-04-22 : oui
- LDM 2026-04-29 : aucune modification pour les appels de choix
- LDM 2026-04-29 : gravité : erreurs
Réponse : les membres non sélectionnés avec des pointeurs dans la signature sont considérés comme non sécurisés pour les appelants opt-in.
(répondu) Étendre le mode compat ?
Devrions-nous également considérer nint et System.IntPtr comme des pointeurs ?
Devrions-nous considérer extern/DllImport des appelants non opt-in comme non sécurisés aussi ?
Devrions-nous avoir un avertissement général lorsqu’un assembly choisi fait référence à un assembly non choisi ?
- LDM 2026-04-29 : aucune extension (les analyseurs pourraient couvrir certains signaux de non-sécurité moins certains)
C# feature specifications