Niebezpieczna ewolucja

Kwestia dotycząca mistrza: https://github.com/dotnet/csharplang/issues/9704

Podsumowanie

Aktualizujemy definicję unsafe w języku C# z odwoływania się do lokalizacji, w których są używane typy wskaźników, do lokalizacji, w których pamięć niezarządzana przez środowisko uruchomieniowe jest wyłuszczana. Te lokalizacje to miejsce, w których występuje niebezpieczność pamięci i są odpowiedzialne za większość cve (typowe luki w zabezpieczeniach i ekspozycji) sklasyfikowane jako problemy z bezpieczeństwem pamięci.

// 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*.
    }
}

Motywacja

Tło tej funkcji można również znaleźć w https://github.com/dotnet/designs/blob/main/accepted/2025/memory-safety/caller-unsafe.mdpliku , który śledzi szersze zmiany ekosystemu, które będą potrzebne w ramach tej propozycji. Obejmują one aktualizacje listy BCL w celu poprawnego dodawania adnotacji do metod jako niebezpiecznych, a także aktualizacji narzędzi w celu lepszego zrozumienia miejsca wystąpienia niebezpiecznej pamięci. W szczególności w języku C# chcemy upewnić się, że niebezpieczna pamięć jest prawidłowo śledzona przez język; obecnie może być trudno przyjrzeć się programowi całościowo i zrozumieć wszystkie lokalizacje, w których występuje niebezpieczna pamięć. Dzieje się tak, ponieważ różne pomocniki, takie jak System.Runtime.CompilerServices.Unsafe, System.Runtime.InteropServices.Marshali inne, nie wyrażają, że naruszają bezpieczeństwo pamięci i wymagają szczególnej uwagi. Metody, które następnie używają tych pomocników, nie są od razu oczywiste, a podczas inspekcji kodu pod kątem problemów z bezpieczeństwem pamięci (przed upływem czasu podczas przeprowadzania przeglądu lub podczas próby określenia przyczyny zgłaszanej luki w zabezpieczeniach) może być trudno wskazać lokalizacje, które mogą mieć wpływ na problemy.

Historycznie unsafe w języku C# odniósł się do określonego otworu bezpieczeństwa pamięci: istnienia typów wskaźników. Moment, w którym typ wskaźnika nie jest już zaangażowany, język C# jest całkowicie szczęśliwy, aby nie zezwalać na niebezpieczne miejsce pamięci ukryte w kodzie. Ten problem polega na tym, że chcemy rozwiązać ten problem z tą ewolucją unsafe języka C# i ekosystemu .NET, oznaczając obszary, w których potencjalnie może wystąpić zagrożenie pamięcią, co ułatwia recenzentom i audytorom zrozumienie granic potencjalnej niebezpiecznej pamięci w programie. Co ważne, oznacza to, że zmienimy znaczenie , unsafea nie tylko ją rozszerzymy. Istnienie wskaźnika nie jest niebezpieczne; niebezpieczna akcja wyłuszcza wskaźnik. To rozszerza się bardziej na same typy; typy nie mogą być z natury niebezpieczne. Jest to tylko akcja użycia typu, który może być niebezpieczny, a nie istnienie tego typu.

Aby te informacje przepływły przez system, musimy zatem samodzielnie oznaczyć metody jako unsafe. Obecnie, unsafe jako modyfikator metody nie ma wpływu na zewnątrz, umożliwia używanie wskaźników tylko w podpisie i treści elementu członkowskiego. W przyszłości modyfikator rzeczywiście publicznie zmieni znaczenie elementu członkowskiego. Oznacza to, unsafe że członek ma obawy dotyczące bezpieczeństwa pamięci, a wszelkie zastosowania muszą zostać ręcznie zweryfikowane przez programistę przy użyciu elementu członkowskiego. Jest to rozszerzenie istniejącego znaczenia unsafe: unsafe na organ lokalizuje obowiązek kontroli tego organu, podczas gdy unsafe podpis rozszerza ten obowiązek do rozmówcy.

Jest to potencjalnie duża zmiana powodująca niezgodność dla określonych segmentów bazy użytkowników języka C#. Mamy nadzieję, że dla wielu naszych użytkowników będzie to skutecznie przejrzyste, a aktualizacja nowych reguł będzie bezproblemowa. Jednak biorąc pod uwagę, że niektóre duże powierzchnie interfejsu API, takie jak duże części odbicia, mogą być oznaczone unsafe, uważamy, że prawdopodobnie konieczne będzie przyzwoite na drodze do nowych zasad, aby uniknąć całkowitej analizy ekosystemu.

Zmiany przełomowe

Podczas aktualizowania do kompilatora implementującego tę funkcję języka można zaobserwować następujące zmiany powodujące niezgodność.

  • Jeśli zaktualizowane reguły bezpieczeństwa pamięci są włączone (które mogą być domyślne, a nawet jedyną opcją w przyszłej wersji .NET):
    • unsafe na elemencie członkowskim oznacza to również jako niebezpieczne, co oznacza, że osoby wywołujące muszą znajdować się w unsafe kontekście i przesłonięcia nie mogą być unsafe bezpieczne, jeśli podstawowy element członkowski jest bezpieczny.
    • unsafe element członkowski lub typ nie wprowadza unsafe automatycznie kontekstu, co oznacza, że jawne unsafe bloki muszą być używane wokół unsafe operacji w ciałach członkowskich i inicjatorach.
    • extern elementy członkowskie i pola w układzie jawnym wymagają jawnego unsafe/safe słowo kluczowe w deklaracji .
    • stackalloc w pewnych warunkach wymaga kontekstu unsafe .
    • unsafe modyfikator jest błędem deklaracji typów, konstruktorów statycznych i destruktorów, ponieważ nie ma żadnego wpływu.
  • W ramach nowej wersji langversion:

Szczegółowy projekt

Terminologia: nazywamy członkami wymaga niebezpiecznego (wcześniej znanego jako element wywołujący-niebezpieczny), jeśli

Syntax

W tym wniosku wprowadzono:

Ta nowa składnia jest dostępna w ramach nowej wersji LangVersion, ale niezależnie od zgody, w ramach założenia, że próbujemy to zrobić tak, aby wszystko, co jest wymagane do zrobienia po wyrażeniu zgody, użytkownik może zrobić, zanim wyrazisz zgodę.

Istniejące unsafe reguły

Istniejąca specyfikacja języka C# zawiera dużą sekcję poświęconą unsafe: §24 Niebezpieczny kod. Jest on definiowany jako warunkowo normatywny, ponieważ nie jest wymagany, aby prawidłowy kompilator języka C# obsługiwał unsafe tę funkcję. Wiele z tego, co jest obecnie uważane za warunkowo normatywne, nie będzie już tak po tej zmianie, ponieważ większość definicji wskaźników nie jest już uważana za niebezpieczną w sobie. Typy wskaźników, zmienne stałe i przenoszone, wszystkie wyrażenia wskaźnika (z wyjątkiem pośredniego wskaźnika, dostępu do elementu wskaźnika i dostępu do elementu wskaźnika), a fixed instrukcja nie jest już brana pod uwagę unsafei nie istnieje w normalnym języku C# bez konieczności użycia w unsafe kontekście. Podobnie deklarowanie buforu o stałym rozmiarze lub zainicjowanie stackalloc jest również całkowicie legalne w bezpiecznym języku C#. W przypadku wszystkich tych przypadków dostęp do pamięci jest niebezpieczny.

Co ważne, te wskaźniki relaksu mają zastosowanie niezależnie od tego, czy zestaw zdecyduje się na zaktualizowane zasady bezpieczeństwa pamięci. Tylko operacje, które rzeczywiście wyłuszczyć lub w inny sposób bezpośrednio uzyskać dostęp do pamięci, nadal wymagają unsafe kontekstu. Umożliwia to ścieżkę migracji przyrostowej: użytkownicy mogą zmienić kształt istniejącego kodu, przenosząc się unsafe do wewnątrz, gdy członek obsłuży ryzyko wewnętrznie lub na zewnątrz, gdy obiekt wywołujący musi uczestniczyć w inspekcji, przed przerzuceniem przełącznika zgody na cały zestaw.

Biorąc pod uwagę obszerne ponowne przepisywane zarówno unsafe w sekcji kodu, jak i innych częściach specyfikacji języka C# związane z tą zmianą, byłoby to niewygodne i prawdopodobnie nie jest przydatne, aby zapewnić różnicę wierszy istniejących reguł specyfikacji. Zamiast tego przedstawimy przegląd zmian w danej sekcji, a także konkretne nowe reguły dotyczące tego, co jest dozwolone w unsafe kontekstach.

Ponowne definiowanie wyrażeń wymagających niebezpiecznych kontekstów

Następujące wyrażenia wymagają unsafe kontekstu w przypadku użycia:

Oprócz tych wyrażeń wyrażenia i instrukcje mogą również warunkowo wymagać kontekstu, jeśli zależą one od dowolnego symbolu oznaczonego unsafe jako unsafe. Na przykład wywołanie metody wymagającej niebezpiecznej spowoduje, że invocation_expression będzie wymagać unsafe kontekstu. Instrukcje z osadzonymi wywołaniami (takimi jak usings, foreachi podobne) mogą również wymagać unsafe kontekstu, gdy używają elementu członkowskiego wymagającego niebezpiecznego .

Jeśli mówimy "wymaga niebezpiecznego kontekstu" lub podobnego w tym dokumencie, oznacza to, że emituje błąd, który konstrukcja wymaga unsafe użycia kontekstu.

Note

Ta sekcja prawdopodobnie wymaga rozszerzenia, aby formalnie zadeklarować, co każde wyrażenie i instrukcja muszą rozważyć, aby wymagać unsafe kontekstu.

Typy wskaźników

Jak wspomniano, wskaźniki nie stają się już z natury niebezpieczne. Wszelkie odwołania do niebezpiecznych kontekstów w §24.3 są usuwane. Typy wskaźników istnieją w normalnym języku C# i nie wymagają unsafe wprowadzenia ich do istnienia. Definicje typów powinny być przepracowane w §8.1 i w kolejnych sekcjach, jak w innych typach.

Podobnie konwersje wskaźników powinny być przepracowane w §10, z odwołaniami do unsafe kontekstów usuniętych.

Podobnie wyrażenia wskaźnika, z wyjątkiem pośredniego wskaźnika, dostępu do elementu wskaźnika i dostępu do elementu wskaźnika, powinny być przepracowane w §12, z odwołaniami do unsafe kontekstów usuniętych. Semantyka nie zmienia znaczenia tych wyrażeń; jedyną zmianą unsafe jest to, że nie wymagają już kontekstu do użycia.

W przypadku pośredniego wskaźnika, dostępu do elementu wskaźnika i dostępu do elementu wskaźnika te operatory pozostają niebezpieczne, ponieważ te pamięci dostępu, które nie są zarządzane przez środowisko uruchomieniowe. Pozostają one w §24 i nadal wymagają unsafe użycia kontekstu. Każde użycie poza kontekstem unsafe jest błędem. Nie zmienia się semantyka tych operatorów; nadal oznaczają dokładnie to samo, co robią dzisiaj. Te wyrażenia muszą zawsze występować w unsafe kontekście.

Stała instrukcja przenosi się do §13, z odwołaniami do unsafe kontekstów usuniętych.

Wskaźniki funkcji nie są jeszcze włączone do głównej specyfikacji języka C#, ale mają one podobny wpływ; wszystkie elementy, ale wywołanie wskaźnika funkcji jest przenoszone do standardowej specyfikacji. Wyrażenie wywołania wskaźnika funkcji musi zawsze występować w unsafe kontekście.

bufory o stałym rozmiarze

Historia buforów o stałym rozmiarze jest podobna do wskaźników. Definicja buforu o stałym rozmiarze nie jest niebezpieczna i przenosi się do §16.3. Uzyskiwanie dostępu do buforu o stałym rozmiarze w wyrażeniu jest podobnie bezpieczne, chyba że wyrażenie występuje jako primary_expression elementu element_access; są one oceniane jako pointer_element_access, co jest niebezpieczne, zgodnie z powyższymi regułami.

Alokacja stosu

Ponownie historia alokacji stosu jest bardzo podobna do wskaźników. Konwertowanie wskaźnika stackalloc na wskaźnik nie jest już niebezpieczne. Jest to odroczenie tego wskaźnika, które jest niebezpieczne. Dodajemy jednak jedną nową regułę:

Jeśli wszystkie następujące instrukcje są prawdziwe, stackalloc_expression jest niebezpieczna:

  • Stackalloc_expression jest konwertowany na element Span<T> lub ReadOnlySpan<T>.
  • Stackalloc_expression nie ma stackalloc_initializer.
  • Stackalloc_expression jest używany w ramach zastosowanego elementu członkowskiegoSkipLocalsInitAttribute.

W tych kontekstach wynikowa przestrzeń stosu może mieć nieznaną zawartość pamięci i jest konwertowana na typ, który zapewnia bezpieczną otokę wokół niezarządzanego dostępu do pamięci. Narusza to umowę Span<T> i ReadOnlySpan<T>, dlatego musi podlegać dodatkowej kontroli autorom i recenzentom takiego kodu.

W przeciwieństwie do unsafe innych zmian w przepisach, które są złagodzeniem, jest to zaostrzenie, dlatego stosuje się tylko w ramach zgody na zaktualizowane zasady bezpieczeństwa pamięci, aby uniknąć przerwy.

Note

Oznacza to, że przypisywanie elementu stackalloc do wskaźnika jest zawsze bezpieczne, niezależnie od kontekstu.

Należy pamiętać, że typ stackalloc zarządzany pozostaje błędem.

sizeof

W przypadku niektórych wstępnie zdefiniowanych typów sizeof zawsze była stała i bezpieczna (§12.8.19) i pozostaje niezmieniona. W przypadku innych typów sizeof używane do wymagania niebezpiecznego kontekstu (§24.6.9), ale jest teraz bezpieczne niezależnie od zgody na zaktualizowane reguły bezpieczeństwa pamięci.

Zastępowanie, dziedziczenie i implementacja

Jest to błąd bezpieczeństwa pamięci, który ma zostać dodany unsafe na poziomie elementu członkowskiego w każdym zastąpieniu lub implementacji elementu członkowskiego, który nie jest pierwotnie niebezpieczny , ponieważ wywołujący mogą używać definicji podstawowej i nie widzieć żadnych dodatków unsafe przez pochodną implementację.

Delegaci i wyrażenia lambda

Jest to błąd bezpieczeństwa pamięci umożliwiający przekonwertowanie elementu członkowskiego wymagającego niebezpiecznego na typ delegata poza kontekstem unsafe . Typy delegatów i typy funkcji nie mogą być niebezpieczne. Jest to błąd czasu kompilacji, który ma być stosowany unsafe w symbolu lambda.

extern

Ponieważ extern metody dotyczą lokalizacji natywnych, których nie można zagwarantować przez środowisko uruchomieniowe, kompilator nie może stwierdzić, czy są bezpieczne, czy niebezpieczne. Nawet metody, które przyjmują unmanaged tylko parametry według wartości, nie mogą być bezpiecznie wywoływane przez język C#, ponieważ konwencja wywołania używana dla metody może być niepoprawnie określona przez użytkownika i musi zostać ręcznie zweryfikowana przez przegląd.

W związku z tym w ramach zaktualizowanych reguł bezpieczeństwa pamięci kompilator wymaga jawnego oznaczenia każdej extern metody jako unsafe lub safe.

extern metody z zestawów korzystających ze starszych reguł bezpieczeństwa pamięci nie są traktowane niejawnie unsafe , ponieważ extern są uważane za szczegóły implementacji, które nie są częścią publicznej powierzchni. extern nie ma gwarancji zachowania w zestawach referencyjnych.

Należy pamiętać, że różni się to od trybu zgodności , który dotyczy zestawów starszych reguł, ponieważ metody ze wskaźnikami w podpisie zawsze potrzebują niebezpiecznego kontekstu w lokacji wywołania.

Niebezpieczne modyfikatory i konteksty

Obecnie, zgodnie ze specyfikacją niebezpieczną kontekstu, unsafe zachowuje się w sposób leksykalny, oznaczając całą treść tekstową zawartą unsafe w unsafe bloku jako kontekst (z wyjątkiem treści iteratora), a także niektóre otaczające konteksty w przypadku deklaracji:

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
}

Po wyrażeniu zgody na zaktualizowane reguły bezpieczeństwa pamięci na element członkowski oznacza go jako unsafe, rozszerzając obowiązek inspekcji obiektu wywołującego i nie wprowadza unsafe kontekstu (zamiast tego tylko jawne unsafe regiony w treści ustanawia unsafe konteksty).

unsafe w następujących deklaracjach występuje błąd, ponieważ nie ma już znaczenia:

  • delegate.
  • konstruktor statyczny,
  • Destruktora
  • deklaracja typu (class, structitp.).

unsafe w konstruktorze wprowadza unsafe kontekst wewnątrz inicjatora, tj. unsafe konstruktor może wywołać konstruktor wymaga-niebezpiecznybase lub this konstruktor.

Typy bez parametrów wymagają niebezpiecznych konstruktorów nie spełniają new() ograniczeń. Podobnie i dodatkowo struktury bez parametrów wymagają niebezpiecznych konstruktorów nie spełniają struct ograniczeń.

unsafe element członkowski nie jest stosowany do żadnych zagnieżdżonych funkcji anonimowych ani lokalnych wewnątrz elementu członkowskiego. To samo dotyczy funkcji anonimowych i lokalnych zadeklarowanych wewnątrz unsafe bloku (są one nadal w unsafe kontekście tak jak zawsze, ale nie stają się niebezpieczne). Aby oznaczyć funkcję lokalną jako wymaga niebezpieczną, należy ją ręcznie oznaczyć jako unsafe. Nie można oznaczyć wyrażeń lambda wymaga niebezpiecznego ( unsafe słowo kluczowe jest niedozwolone).

Gdy element członkowski to partial, obie części muszą uzgodnić unsafe modyfikator bez zmian z reguł języka 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
}

W przypadku właściwości get i set/init metody dostępu można zadeklarować niezależnie jako unsafe; oznaczanie całej właściwości jako unsafe oznacza, że zarówno get metody i set/init metody dostępu są niebezpieczne. Obecnie nie można umieścić żadnych modyfikatorów na akcesorach zdarzeń, a ta propozycja nie zmienia tego, tj addremove . metody dostępu zdarzeń nie mogą być niezależnie zadeklarowane jako unsafe. Tylko wtedy, gdy całe zdarzenie jest oznaczone jako unsafe, oznacza to, że akcesoria są niebezpieczne; w przeciwnym razie są bezpieczne.

Pola formularza

unsafe w polu oznacza go również jako wymaganie niebezpieczne i nie wprowadza unsafe kontekstu w inicjatorze. Tryb zgodności dotyczy również pól.

Oznaczanie właściwości lub zdarzenia, ponieważ unsafe nie powoduje, że jego pole zapasowe wymaga niebezpiecznego.

W typie z [StructLayout(LayoutKind.Explicit)] lub [ExtendedLayout]wszystkie pola wystąpienia muszą być oznaczone albo safe .unsafe Jeśli pole jest "ukryte" za zdarzeniem automatycznym lub przypominającym pole, safe/unsafe zamiast tego wymaganie zostanie przeniesione do zdarzenia automatycznego lub podobnego do pola.

Metadane

Gdy zestaw zostanie skompilowany przy użyciu nowych reguł bezpieczeństwa pamięci, zostanie oznaczony jako MemorySafetyRulesAttribute wersja językowa (szczegółowo poniżej).15 Jest to sygnał dla wszystkich odbiorców podrzędnych, że wszystkie elementy członkowskie zdefiniowane w zestawie zostaną prawidłowo przypisane RequiresUnsafeAttribute (szczegółowo poniżej), jeśli unsafe kontekst jest wymagany do ich wywołania. Każdy element członkowski w takim zestawie, który nie jest oznaczony RequiresUnsafeAttribute , nie wymaga wywoływanego unsafe kontekstu, niezależnie od typów w podpisie elementu członkowskiego.

Jest to błąd podczas stosowania symbolu lub MemorySafetyRulesAttribute do dowolnego symbolu RequiresUnsafeAttribute jawnie w źródle.

Kompilator ignoruje RequiresUnsafeAttribute-oznaczone elementy członkowskie z zestawów korzystających ze starszych reguł bezpieczeństwa pamięci (zamiast tego używany jest tryb zgodności ).

Gdy element członkowski inny niż typ jest oznaczony jako unsafe, kompilator zsyntetyzuje aplikację RequiresUnsafeAttribute na elemencie członkowskim w metadanych. Gdy element członkowski wymagający zagrożenia dla użytkownika generuje ukryte elementy członkowskie, takie jak metody pobierania/ustawiania właściwości automatycznej, zarówno członek użytkownika, jak i wszystkie ukryte elementy członkowskie generowane przez ten element członkowski dostępny dla użytkownika są niebezpieczne i RequiresUnsafeAttribute są stosowane do wszystkich z nich.

Definicja MemorySafetyRulesAttribute i RequiresUnsafeAttribute jest syntetyzowana przez kompilator, jeśli jest to konieczne dla standardowych dobrze znanych reguł składowych.

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
    {
    }
}

Tryb zgodności

W celach związanych z zgodnością i zmniejszenie liczby wyników fałszywie ujemnych występujących podczas włączania nowych reguł mamy regułę rezerwową dla modułów, które nie zostały zaktualizowane do nowych reguł. W przypadku takich modułów element członkowski jest uznawany za niebezpieczny , jeśli zawiera wskaźnik lub wskaźnik funkcji typu gdzieś między typami parametrów lub zwracanym typem (może być zagnieżdżony w typie innym niż wskaźnik, np. int*[]). Należy pamiętać, że nie ma to zastosowania do wskaźników w typach ograniczeń (np where T : I<int*[]>. ), ponieważ nie będą one potrzebne niebezpiecznego kontekstu w lokacjach wywołań wcześniej.

Nie obejmuje to zastąpionych parametrów ogólnych (np. metody I<T>.M(T) w przypadku zastąpienia Tint*[]wartości ) ponieważ nie ma bezpiecznego sposobu, aby element docelowy elementu członkowskiego używał tego typu wskaźnika w każdym razie.

Taki tryb zgodności wymaga, aby niebezpieczne elementy członkowskie wymagały unsafe użycia kontekstu nawet od osób wywołujących, które nie zdecydowały się na zaktualizowane reguły bezpieczeństwa pamięci. Powinno to uniknąć "dip", w którym wystarczy zaktualizować wersję LangVersion (ale nie aktualizować wersji reguł bezpieczeństwa pamięci) sprawia, że większość operacji wskaźnika jest bezpieczna (w tym wywoływanie funkcji ze wskaźnikami w podpisie, które prawdopodobnie będą oznaczone jako niebezpieczne w przypadku wybrania zaktualizowanych reguł), a tym samym uczynienie kodu mniej chronionym w tym oknie migracji.

VB

Nie musimy dodawać obsługi do Visual Basic dla elementów członkowskich wymagających, ponieważ obecnie nie unsafe ma kontekstów w języku VB i nie ma możliwości pracy ze wskaźnikami.

unsafe Wyrażenia

Wyrażenie unsafe wprowadza minimalny unsafe kontekst do obliczania pojedynczego wyrażenia. Jest to przydatne w sytuacjach, gdy unsafe blok niepotrzebnie poszerzyłby unsafe zakres lub nie unsafe można używać bloków składniowo (takich jak w catch filtrach, inicjatorach pól, inicjatorach konstruktorów i pozycji operandu await).

Syntax

Element unsafe_expression jest dodawany jako primary_no_array_creation_expression:

unsafe_expression
    : 'unsafe' '(' expression ')'
    ;

Podlega to takiemu wymaganiu AllowUnsafeBlocks , jak unsafe słowo kluczowe gdzie indziej.

Semantyka

Element unsafe_expression ustanawia unsafe kontekst do obliczania wyrażenia. Oznacza to, że wyłuszenia wskaźnika, wywołania wskaźnika funkcji i wywołania elementów członkowskich wymagających niebezpiecznych są dozwolone w wyrażeniu zamkniętym. Typ i wartość unsafe_expression elementu są typem i wartością ujętego wyrażenia.

Kontekst unsafe ustanowiony przez element unsafe_expression nie wykracza poza nawias zamykający.

Przykłady motywacji i migracji

Kilka pozycji składniowych nie przyznaje unsafe bloków w ogóle, ale może zawierać podrażenia, które wywołuje wymaga niebezpiecznych członków. Bez unsafe wyrażeń migracja takiego kodu wymaga wyodrębnienia niebezpiecznego wyrażenia podrzędnego do funkcji lokalnej pomocnika lub zmiennej tymczasowej, która zasłania intencję i zwiększa szczegółowość.

await w metodzie wymaga-niebezpiecznej . Wyrażenie await nie może pojawić się wewnątrz unsafe bloku. Gdy oczekiwana metoda staje się niebezpieczna, zmienna tymczasowa jest wymagana do przechowywania zadania, zanim będzie można go oczekiwać:

// 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());

Przechwyć filtry. Filtr when klauzuli catch jest wyrażeniem, a nie treścią instrukcji. Blok unsafe może otaczać tylko instrukcje, więc nie ma miejsca, aby umieścić je wokół tylko wyrażenia filtru. Ponadto jeśli try treść zawiera await wyrażenie, unsafe blok nie może ująć całejtry/catch instrukcji —await nie jest dozwolony wewnątrz unsafe bloku. Gdy metoda używana w filtrze staje się niebezpieczna, jedyną alternatywą bez unsafe wyrażeń jest pomocnik:

// 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)))
{
}

Inicjatory pól. Zgodnie ze zaktualizowanymi regułami unsafe pole nie wprowadza unsafe kontekstu w inicjatorze. Gdy inicjator pola wywołuje element członkowski wymaga niebezpiecznego , unsafe wyrażenie udostępnia kontekst bez konieczności używania metody pomocniczej:

// 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());

Inicjatory konstruktorów. this(...) listy base(...) argumentów inicjatora są wyrażeniami, a nie treściami instrukcji. Jeśli jeden z tych argumentów wywołuje element członkowski wymaga niebezpiecznego , nie ma ponownie miejsca do wstawienia unsafe bloku, więc wywołanie musi zostać przeniesione do pomocnika:

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()))
    {
    }
}

Wywołania wbudowane. Ogólniej mówiąc, zawijanie tylko niebezpiecznego podwyrażenia wymaga ścisłego zakresu inspekcji i pozwala uniknąć ściągania otaczającego bezpiecznego kodu (takiego jak ocena argumentu lub wywołanie metody zawierającej unsafe ) w kontekście:

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);

Pytania

(odpowiedź) Służy RequiresUnsafeAttribute do oznaczania elementów członkowskich wymagających niebezpiecznych

Zamiast używać unsafe słowa kluczowego na elemencie członkowskim w celu określenia elementów członkowskich wymagających niebezpiecznych , możemy użyć atrybutu () zastosowanego do elementu członkowskiego (RequiresUnsafeAttributea nie zmienić znaczenia unsafe modyfikatora w elementach członkowskich).

Zalety programu unsafe:

  • podobne do innych języków, a tym samym łatwiejsze do zrozumienia,
  • bardziej wykrywalny niż atrybut.

Zalety atrybutu (lub innego słowa kluczowego):

  • unika łamania istniejących elementów członkowskich oznaczonych jako unsafe,
  • możliwe wdrożenie przyrostowe (członek po elemencie członkowskim),
  • nie wymusza oznaczania całej treści jako unsafe (nawet przy unsafe użyciu słowa kluczowego możemy zmienić,unsafe aby nie miało to wpływu na ciała).
  • umożliwia pomijanie wszystkich błędów wymagających niebezpiecznych bez konieczności oznaczania samego elementu członkowskiego jako wymaganego niebezpiecznego (przykłady).

Dyskusje:

Odpowiedź: użyj słowa kluczowego unsafe , aby oznaczyć niebezpieczne elementy członkowskie.

Funkcje lokalne/konteksty bezpieczne lambda

W tej chwili unsafe w treści metody jest zakres leksykalnie. Wszystkie zagnieżdżone funkcje lokalne lub lambdy dziedziczą to, a ich elementy znajdują się w niebezpiecznym kontekście pamięci. Czy to zachowanie chcemy zachować w języku? Należy pamiętać, że jeśli modyfikator unsafe jest używany do ujawnienia, że obiekt wywołujący musi być niebezpieczny, może to mieć wpływ na podpis metody. Obecnie zagnieżdżone funkcje anonimowe i lokalne nie zachowują niebezpiecznego kontekstu zawierającego element członkowski.

Typ unsafedelegata ty

Możemy zezwolić na oznaczanie typów delegatów i lambd (i typów funkcji) zgodnie z wymaganiami niebezpiecznymi. Wymagałoby to kilku dodatkowych reguł (kontekstu zewnętrznego unsafe ):

  • nie zezwalaj na używanie delegatów wymagających niebezpiecznych jako argumentów typu,
  • nie zezwalaj na konwertowanie tych delegatów na coś, co nie jest niebezpieczne (Delegate,Expression, i object), bez tego istnieje ryzyko wymuszenia unsafe adnotacji w niewłaściwym miejscu i posiadania obszaru, w którym rzeczywisty obszar unsafety nie jest prawidłowo wywoływany.

Konwersja grupy lambda/metody na bezpieczne typy delegatów

Jeśli zezwalamy na unsafe lambda i delegaty, czy konwersja wymaganej grupy lambda lub metody na typ delegata nie wymaga niebezpiecznego dozwolonego bez ostrzeżenia lub błędu w unsafe kontekście? Jeśli tego nie zrobimy, może to być dość bolesne dla różnych części ekosystemu, szczególnie wyliczeń przekazywanych przez zapytania LINQ.

Typy naturalne grupy lambda/metody

Obecnie jedynym realnym wpływem na semantyka i kodowanie (oprócz dodatkowych metadanych) jest zmiana function_type grupy lambda lub metody, gdy unsafe znajduje się w podpisie. Gdybyśmy tego unikali, to nie byłoby żadnego rzeczywistego wpływu na albo, co mogłoby dać adopterom większą pewność, że zachowanie nie zmieniło się subtelnie pod maską.

Note

Jeśli zdecydujemy się zachować możliwość posiadania unsafe wyrażeń lambda, musimy zaktualizować tę propozycję, aby uwzględnić zmianę składni, aby umożliwić deklarowanie unsafe w pierwszej kolejności wyrażeń lambda.

Wskaźniki do typów zarządzanych

Język C# 11 umożliwia używanie wskaźników do typów zarządzanych z ostrzeżeniem. Czy należy złagodzić to ostrzeżenie dotyczące operacji adresowych? Uważamy, że problem występuje tylko wtedy, gdy użytkownik wyłuszcza takie wskaźniki, które są objęte normalnymi niebezpiecznymi regułami ewolucji. Ale co z sizeof?

stackalloc jako zainicjowane

Obecnie specyfikacja zawsze traktuje stackalloc pamięć jako niezainicjowaną i mówi, że zawartość jest niezdefiniowana, chyba że ręcznie wyczyszczone lub przypisane. Czy uważamy to za usterkę specyfikacji lub czy musimy zmienić to, co uważamy za unsafe cel stackalloc ?

stackalloc Reguły

LDM powinien potwierdzić regułę zdefiniowanąstackalloc powyżej i czy powinna być stosowana niezależnie od zgody, podobnie jak inne zmiany związane ze wskaźnikiem.

AllowUnsafeBlocks

AllowUnsafeBlocks Znaczenie wartości jest obecnie niezmienione — musi być ustawiona na true , aby móc używać słowa kluczowego unsafe lub SkipLocalsInitAttribute. Czy nie należy tego wymagać w SkipLocalsInitAttribute ramach zaktualizowanych reguł, ponieważ lista BCL może oznaczyć ten atrybut jako wymagany-niebezpieczny? Czy też powinniśmy tego wymagać dla słowa kluczowego safe ? Czy należy go wymagać zarówno unsafe dla bloków, jak i unsafe deklaracji składowych, czy też innych kombinacji tych?

(odpowiedź) unsafe Wyrażenia

Inne języki z bardziej kompleksowymi unsafe funkcjami zostały dodane unsafe jako wyrażenie, które poprawia precyzję użytkownika i pozwala autorom bardziej precyzyjnie ograniczyć miejsce, w którym unsafe jest używany. Czy jest to coś, co chcemy mieć w języku C#? Rozważ bezpośrednie wywołanie unsafe elementu członkowskiego obsługującego bezpieczeństwo: w tej chwili autor musiałby opakować całą instrukcję w unsafe bloku, rozszerzając zakres unsafe kontekstu, lub musieliby rozbić wywołanie funkcji wewnętrznej do zmiennej pośredniej.

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);

Odpowiedź: tak. Zapoznaj się ze szczegółową sekcją projektowania.

Więcej unsafe kontekstów i relaksu

Czy powinniśmy złagodzić więcej ograniczeń dotyczących unsafe parametrów i wskaźników w iteratorach i metodach asynchronicznych? Szczególnie umożliwienie await UnsafeMethod() byłoby przydatne, ponieważ teraz użytkownicy muszą przepisać to do Task t; unsafe { t = UnsafeMethod(); } await t;. Aby uzyskać więcej informacji, zobacz ref/niebezpieczne w iteratorach/asynchronicznych .

Czy powinniśmy również zezwolić &UnsafeMethod w bezpiecznym kontekście? Obecnie, jak stoi wniosek, wymaga unsafe to kontekstu, jeśli metoda jest oznaczona jako unsafe. Ale ponieważ po prostu otrzymujemy jego adres, który będzie potrzebny unsafe kontekst po wyłuszczeniu/wywołaniu, możemy zezwolić na sam adres w bezpiecznym kontekście.

Niebezpieczne relaksy bramowane na LangVersion

Czy powinniśmy sprawić, aby niebezpieczne relaksy kontekstowe bezwarunkowe na LangVersion?

  • LDM 2026-04-06: nie są one zależne od wersji reguł bezpieczeństwa pamięci
  • TODO: co z LangVersion?

(odpowiedź) unsafe w typach

Możemy rozważyć, że nie możemy automatycznie zmienić całego zakresu unsafe leksykalnych typu unsafe na kontekst i ostrzec o unsafe typie, ponieważ nie miałoby to znaczenia.

  • LDM 2025-11-12: unsafe na typie nie będzie miało znaczenia
  • LDM 2026-05-13: będzie to błąd (można go ponownie przejrzeć na podstawie opinii)

Odpowiedź: unsafe w przypadku typu jest błąd (można go ponownie przejrzeć na podstawie opinii) zgodnie ze zaktualizowanymi regułami.

unsafe na akcesoriach

Nowo zezwalamy unsafe na metody dostępu do właściwości, ale nie na akcesorach zdarzeń, zgodnie z innymi wcześniej istniejącymi modyfikatorami. Oznacza to również, że unsafe na właściwości jest tylko skrótem dla unsafe jego akcesoriów. Jednak w tym samym czasie partialmodyfikatory wymagają unsafe dopasowania:

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`
}

Może powinien zachowywać się podobnie do readonly, tj. nie zezwalać unsafe zarówno na właściwość, jak i jej akcesorium w tym samym czasie:

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 { } }
}

(odpowiedź) Zezwalaj na pomijanie błędów wymagających niebezpiecznych w scenariuszach przypadków brzegowych

Jak należy zezwolić na pomijanie błędów wymagających niebezpiecznych w następujących scenariuszach?

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`
}

Aby pominąć błędy wymagające niebezpieczne , musimy w jakiś sposób wprowadzić unsafe kontekst w podpisie tych członków. unsafe Słowo kluczowe nie wprowadza unsafejuż kontekstu. Moglibyśmy wprowadzić unsafeunsafe kontekst w podpisie, ale wymuszanie tworzenia konstruktora wymaga niebezpiecznego , gdy chce wywołać bazę wymaga niebezpiecznego konstruktora wydaje się niefortunne. Możemy wprowadzić niebezpieczne użycie w ostrzeżeniach dotyczących podpisów, które użytkownicy mogą pominąć w miejscu, jeśli trafią do tych rzadkich przypadków brzegowych.

Istnieje podobny problem z typami, ponieważ unsafe na nich nie wprowadzono unsafe kontekstu:

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:
    • kod nie wykonywalny, taki jak aplikacja atrybutu, powinien pozostać niezachwytalnym błędem (dopóki nie usłyszymy opinii)
    • inne przypadki brzegowe, takie jak new() mogą również pozostać błędem, dopóki nie usłyszymy opinii
    • nie zezwalaj na deklarowanie konstruktorów bez parametrów wymaga niebezpiecznych konstruktorów
    • unsafe tylko inicjator konstruktora może wywołać element wymaga-niebezpiecznybase lub this konstruktor

kolekcje params

class C
{
    unsafe public C() { } // declaring requires-unsafe constructor
}

class B
{
    public void M(params C c) { }
}

Ta deklaracja może być po prostu dozwolona, ponieważ wywoływanie takiej metody wymaga unsafe mimo to kontekstu, ponieważ niebezpieczna params kolekcja jest konstruowana w lokacji wywołania. Chociaż to sprawia, że deklaracja skutecznie wymaga niebezpiecznego, więc możemy po prostu wymagać unsafe adnotacji lub przynajmniej ostrzegać o tym fakcie. Zwróć uwagę, że przypadek params kolekcji jest obecnie błędem zgodnym z zachowaniem innych podobnych funkcji (Obsolete, UnmanagedCallersOnly), ale może to być usterka implementacji.

Dobrze znani członkowie

Dla uproszczenia i rozsądku implementacji proponujemy, aby kompilator mógł założyć, że wszystkie dobrze znane elementy członkowskie (takie jak Array.Length) mogą być bezpieczne (tj. nie wymaga- niebezpieczne).

Dokumentacja xml

Uchwalenie obowiązku rozmówców wiąże się z obowiązkiem, aby wyjaśnić, czym jest ten obowiązek. Czy należy to sformalizować poza tym, co można już przedstawić w dokumentacji xml?

Oznaczone unsafe elementy członkowskie powinny mieć komentarze wskazujące, co jest niezbędne do wykonania przez obiekt wywołujący, aby upewnić się, że kod jest poprawny. Aby ułatwić wyświetlanie i łatwiejsze rozróżnienie w dokumentacji, przydatny byłby nowy tag dokumentacji XML: <safety />. Oczekuje się, że wszystkie warunki wstępne/po niej zostaną umieszczone w <safety> bloku.

Aby udokumentować przyczyny każdego unsafe bloku, zalecamy użycie // SAFETY komentarzy, podobnie jak Rust. Czy powinniśmy również sprawdzić je przez kompilator (np. mieć jakieś ostrzeżenie wyłączone domyślnie), czy pozostawić je analizatorowi?

Bardziej bezsensowne unsafe ostrzeżenia

Czy więcej deklaracji powinno spowodować bezsensowne unsafe ostrzeżenie lub błąd? Na przykład metody z pustymi ciałami (lub extern), itp. Mamy już jednak analizator IDE dla niepotrzebnego unsafe .

Czy powinien [ModuleInitializer] unsafe void M() { } być błąd podobny do konstruktora statycznego?

(odpowiedź) unsafe pola

Dziś żadna propozycja nie jest przedstawiana unsafe na boisku. Może być jednak konieczne dodanie go, tak aby każdy odczyt lub zapis w polu oznaczonym unsafe jako unsafe musi być w kontekście. Umożliwiłoby to lepsze dodawanie adnotacji dotyczących kodu, takich jak:

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];
}

Czy należy również oznaczyć pole tworzenia kopii zapasowej właściwości automatycznej jako unsafe?

Aby zachować spójność z naszą decyzją dla członków, dobrze byłoby podjąć w unsafe polu również nie wprowadzać unsafe kontekstu. Jeśli wymagane-niebezpieczne operacje są używane w inicjatorze pola, użytkownik zawsze może hermetyzować je do metody lub możemy wprowadzić unsafe wyrażenia.

  • LDM 2026-05-13: pola można oznaczyć jako niebezpieczne za pośrednictwem ; unsafeinicjatory nie są w unsafe kontekście.

(odpowiedź) Układ jawny

Czy pola w strukturach oznaczonych jako [StructLayout(Explicit)] lub [ExtendedLayout] muszą być oznaczone jako unsafe?

Zalecenie: tak.

  • LDM 2026-05-13: tak, wymagaj lub unsafesafe, podobnie jak w przypadku externs.

Jawny układ i pola zapasowe

Jeśli kompilator syntetyzuje pole zapasowe dla zdarzenia automatycznego lub podobnego do pola w typie Explicit/Extended , czy zamiast tego należy wymagać safe/unsafe dla właściwości/zdarzenia? W przeciwnym razie użytkownik zostanie zmuszony do rozwinięcia tych automatycznych deklaracji do pól ręcznych oraz deklaracji składowych otoki. Co z podstawowym parametrem konstruktora, który pobiera pole zapasowe? Zarówno, jak safe i unsafe modyfikator jest obecnie niedozwolony w deklaracji parametru.

[Out] i [SkipLocalsInit]

Ponieważ na przykład vb nie gwarantuje, że [Out] parametry są inicjowane, w połączeniu z [SkipLocalsInit]wywołaniem takich parametrów można rozważyć unsafe w języku C#. Z drugiej strony, czuje się jak problem wywoływany, że nie podtrzymuje umowy [Out] (podobnie może być niebezpieczny na wiele innych sposobów).

Jeśli zdecydujemy, że te przypadki powinny mieć unsafewartość , możemy wykluczyć metody , które wiemy, że zostały wybrane (obecnie są to z języka C#, które gwarantują poprawne użycie [Out] , ale jeśli inne języki implementują nowe reguły, powinny one również zagwarantować to).

Pobieranie adresu niezainicjowanej zmiennej

Obecnie biorąc adres nieprzypisanej zmiennej może rozważyć, że zmienna zdecydowanie przypisana, uwidaczniając niezainicjowany element członkowski. Mamy kilka opcji rozwiązywania tego problemu:

  1. Wymagaj, aby zmienne były zdecydowanie przypisane przed zezwoleniem operatorowi adres-of na ich stosowanie.
  2. Wprowadź adres niezainicjowanej zmiennej niebezpiecznej.

Przykłady:

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++;

Wartość MemorySafetyRulesAttribute

Jaka powinna być wersja reguł bezpieczeństwa pamięci "enabled"/"updated"? 2? 15? 11? Zobacz też Zestaw SDK Niebezpieczny wdrażanie i Bardziej stopniowe włączanie?

(odpowiedź) Bardziej stopniowa zgoda?

Obecnie wyrażenie zgody daje dwie rzeczy naraz: wymuszanie niebezpiecznych reguł we własnym kodzie i sygnał opublikowany dla użytkowników (za pośrednictwem atrybutu na poziomie zestawu), że adnotacje są zamierzone. Mogą istnieć użytkownicy, którzy chcą rozpocząć pobieranie diagnostyki wymuszania w miarę dodawania adnotacji, bez gotowości do opublikowania, że w pełni oznaczyli swój zestaw adnotacjami. Czy powinniśmy mieć "środkowy" poziom zgody, który spowoduje, że niebezpieczna diagnostyka zostanie wyświetlona jako ostrzeżenia, a pełna zgoda będzie promować je do błędów?

(odpowiedź) Bardziej szczegółowe wyrażenie zgody

Zapewnij precyzyjny, oparty na regionie mechanizm opt-in analogiczny do tego, który utworzyliśmy dla typów referencyjnych dopuszczanych wartości null, gdzie użytkownicy mogą używać dyrektyw, aby włączyć funkcję dla określonych regionów kodu źródłowego? Zobacz również Rezygnacja/wyraźnie dla regionów kodu.

(odpowiedź) extern niejawnie niebezpieczne

Jest to obecnie jedyne miejsce, w którym RequiresUnsafeAttribute jest syntetyzowany bez jawnego unsafe słowa kluczowego. Czy jesteśmy w porządku z tym odstąpić?

Ponadto biblioteka CoreLib uwidacznia wiele metod eksternu (FCalls) jako bezpieczne dzisiaj. Traktowanie metod extern jako niejawnie niebezpieczne będzie wymagać zawijania niejawnie niebezpiecznych metod extern z bezpiecznym otoką. Możemy napotkać sytuacje, w których dodanie dodatkowej otoki jest trudne ze względu na szczegóły implementacji środowiska uruchomieniowego.

  • LDM 2026-04-01: extern składowe powinny być jawnie oznaczone jako bezpieczne lub niebezpieczne
  • LDM 2026-04-06: ta sama decyzja powtórzyła
  • LDM 2026-04-13: tymczasowa decyzja o użyciu safe słowa kluczowego
  • LDM 2026-05-13: użyj safe słowa kluczowego (nadal otwarte, aby ponownie wrócić).

Odpowiedź: extern członkowie muszą być oznaczeni unsafe albo safe (dodajemy dla niego nowe słowo kluczowe, ale można je ponownie przejrzeć przed udostępnieniem funkcji na podstawie opinii).

(odpowiedź) unsafe ustawienia domyślne kontekstu w elementach członkowskich

Nie można rozważyć automatycznego tworzenia całej unsafe treści metody unsafe jako kontekstu. Rust zrobił to w RFC 2585, z motywacją, że pomaga zmniejszyć zakres bloków unsafe do lokalizacji, w których unsafe jest rzeczywiście używany. Możemy zrobić to samo w języku C#, jako ostrzeżenie lub błąd, z podobnymi motywacjami.

  • LDM 2026-04-22: Tak, w podpisie nie sprawia, unsafe że treść unsafe

(odpowiedź) new() ograniczenie

Czy chcemy obsługiwać elementy new() wymagające niebezpieczne (obecnie nie jest obsługiwane w kompilatorze dla innych funkcji, takich jak 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: typy z konstruktorami bez parametrów wymaga nie powinny spełniać new() ograniczeń

new() ograniczenie i usings

Jak powinien zachowywać się w aliasach i statycznych zastosowaniach?

  • Jeśli jest to błąd w deklaracji using , pomijany za pośrednictwem słowa kluczowego unsafe , które już tam obsługujemy, lub
  • czy powinien to być błąd zwykle w witrynie użycia, tak jak gdyby był używany bezpośrednio bez aliasu lub statycznego przy użyciu?

Note

W drugim przypadku należy dodać "bezsensowne unsafe" ostrzeżenie dotyczące używania aliasów i metod statycznych.

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();

Pamiętaj, że inne ograniczenia zachowują się jak poprzednia opcja dzisiaj:

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();

Czy więcej konstrukcji powinno być unsafe?

  • dynamic (prawdopodobnie powinno być zgodne z tym, co BCL decyduje o interfejsach API odbicia)

(odpowiedź) Jak łamiące chcemy wypaczyć

Tekst pytania

Wstępna propozycja jest maksymalnie przełomowym podejściem, głównie jako test lakmus dla tego, jak agresywni chcemy być. Proponuje brak możliwości rezygnacji z sekcji kodu, zmiany znaczenia unsafe metod, zakazuje użycia unsafe typów, używa błędów zamiast ostrzeżeń, a na ogół wymusza migrację naraz, podczas uaktualniania kompilatora (a następnie potencjalnie wielokrotnie w miarę aktualizowania zależności i dodawania unsafe do elementów członkowskich, które były już używane). Mamy jednak bogate doświadczenie w podejmowaniu takich zmian, że możemy skorzystać z zakresu wielkości podziałów i umożliwić wdrażanie przyrostowe. Poniżej opisano te opcje.

Wyradź/wyrań się dla regionów kodu

Nie jest to pierwszy raz, gdy język C# ponownie zdefiniował przypadek "base" nieoznaczonego kodu. Język C# 8.0 wprowadził funkcję typu odwołania dopuszczalnego do wartości null, która na wiele sposobów może być postrzegana jako strategia unsafe kształtowania funkcji. Miał podobne cele (zapobiegaj błędom, które kosztują miliardy dolarów, definiując sposób, w jaki domyślny język C# jest interpretowany) i podobnym ogólnym zestawem funkcji (dodaj nowe informacje do typów, aby propagować stany i unikać błędów). Było to również mocno łamiące i wymagało silnego zestawu opt-in i rezygnacji z funkcji, aby umożliwić korzystanie z funkcji w czasie przez bazy kodu. Ta funkcja jest "kontekstem typu odwołania dopuszczanego do wartości null". Jest to zakres leksykalny, który informuje kompilator dla danego regionu w kodzie, zarówno jak interpretować odwołania typu nieoznaczonego oraz typy ostrzeżeń, które mają być przekazywane użytkownikowi. Możemy również użyć go jako modelu unsafe , dodając "kontekst reguł bezpieczeństwa" lub podobny do zezwolenia na kontrolowanie, czy te nowe reguły są stosowane, czy nie.

Jedną z zalet, które mamy z nowymi unsafe funkcjami, jest to, że są one znacznie mniej powszechne. Chociaż istnieje przyzwoita liczba unsafe wywołań w najlepszych bibliotekach, nasze odgadywanie procent najlepszych bibliotek, które używają unsafe , jest znacznie niższe niż "każdy wiersz kodu języka C# kiedykolwiek napisany". Mam nadzieję, że oznacza to, że chociaż pewna możliwość rezygnacji/wyjścia jest prawdopodobnie potrzebna, nie potrzebujemy tak skomplikowanego mechanizmu, jak ma wartość null, z dedykowanymi przełącznikami preprocesorowymi i takimi jak.

Ostrzeżenia a błędy

Obecnie wniosek stwierdza, że wymagania dotyczące bezpieczeństwa pamięci są obecnie wymuszane za pośrednictwem ostrzeżenia, a nie błędu. Jest to rysunek z naszego doświadczenia w pracy z funkcją dopuszczaną do wartości null, gdzie ostrzeżenia zezwalały bazom kodu na przyrostowe wdrażanie nowej funkcji i nie trzeba konwertować dużych połaci kodu jednocześnie. Oczekujemy, że podobny proces będzie potrzebny w przypadku niebezpiecznych ostrzeżeń: wiele baz kodu będzie po prostu w stanie włączyć nowe reguły globalnie i przejść z ich życiem. Jednak oczekujemy, że bazy kodu najbardziej zależy nam na przyjęciu nowych reguł, będą miały duże ilości kodu do dodawania adnotacji i chcemy, aby mogły przejść do przodu z funkcją, zamiast widzieć ścianę błędów i natychmiast zrezygnować. Wprowadzając ostrzeżenia dotyczące wymagań, zezwalamy tym bazom kodu na naprawianie ostrzeżeń typu file-by-file lub method-by-method zgodnie z potrzebami, wyłączając ostrzeżenia wszędzie indziej.

Podziały sygnatur metod

W tej chwili proponujemy, aby unsafe jako słowo kluczowe metody przechodziło z czegoś, co ma zakres leksykalnie bez wpływu semantycznego na coś, co ma wpływ semantyczny i nie jest zakresem leksykalnym. Możemy ograniczyć ten podział, wprowadzając nowe słowo kluczowe , gdy obiekt wywołujący metody lub elementu członkowskiego musi być w unsafe kontekście, na przykład callerunsafe jako modyfikator.

Wartości domyślne dla generatorów źródłowych

W przypadku dopuszczania wartości null wymusimy, aby autorzy generatorów jawnie wyrazili zgodę na wartość null niezależnie od tego, czy cały projekt domyślnie zdecydował się na tę funkcję, aby dane wyjściowe generatora nie zostały przerwane przez użytkownika włączającego wartość null i ostrzegając o błędzie. Czy powinniśmy zrobić to samo w przypadku generatorów źródłowych?

Conclusion

Odpowiedź w LDM 2025-11-05. Zgłaszamy błędy problemów z bezpieczeństwem pamięci po włączeniu nowych reguł i nie zostaną wprowadzone żadne wyjątki dla generatorów źródłowych.

(odpowiedź) Błędy lub ostrzeżenia?

(odpowiedź) Dostępność generatora źródła

(odpowiedź) Wymagaj safe twórcy dla członków z blokami unsafe lub wskaźnikami?

(odpowiedź) Tryb zgodności dla osób wywołujących bez zgody?

Odpowiedź: elementy członkowskie, które nie są opted-in z wskaźnikami w podpisie, są uznawane za niebezpieczne , aby wyrazić zgodę na rozmówców.

(odpowiedź) Czy rozszerzyć tryb zgodności?

Czy powinniśmy również rozważyć nint i System.IntPtr jako wskaźniki? Czy powinniśmy rozważyć użycie extern/DllImport osób wywołujących bez zgody, ponieważ wymaga to również niebezpieczne ? Czy powinniśmy mieć ogólne ostrzeżenie, gdy zestaw z wyrażeniem zgody odwołuje się do zestawu, który nie jest opted-in?

  • LDM 2026-04-29: brak rozszerzeń (analizatory mogą obejmować mniej pewne niebezpieczne sygnały)