diff --git a/docs/project/list-of-diagnostics.md b/docs/project/list-of-diagnostics.md
index 0aafc61e5cff8b..71c2c19c35f624 100644
--- a/docs/project/list-of-diagnostics.md
+++ b/docs/project/list-of-diagnostics.md
@@ -191,8 +191,8 @@ The diagnostic id values reserved for .NET Libraries analyzer warnings are `SYSL
| __`SYSLIB1060`__ | Specified marshaller type is invalid |
| __`SYSLIB1061`__ | Marshaller type has incompatible method signatures |
| __`SYSLIB1062`__ | Project must be updated with '\true\' |
-| __`SYSLIB1063`__ | _`SYSLIB1063`-`SYSLIB1069` reserved for Microsoft.Interop.LibraryImportGenerator._ |
-| __`SYSLIB1064`__ | _`SYSLIB1063`-`SYSLIB1069` reserved for Microsoft.Interop.LibraryImportGenerator._ |
+| __`SYSLIB1063`__ | Unnecessary marshalling info |
+| __`SYSLIB1064`__ | Method with 'LibraryImportAttribute' must be marked 'safe' or 'unsafe' |
| __`SYSLIB1065`__ | _`SYSLIB1063`-`SYSLIB1069` reserved for Microsoft.Interop.LibraryImportGenerator._ |
| __`SYSLIB1066`__ | _`SYSLIB1063`-`SYSLIB1069` reserved for Microsoft.Interop.LibraryImportGenerator._ |
| __`SYSLIB1067`__ | _`SYSLIB1063`-`SYSLIB1069` reserved for Microsoft.Interop.LibraryImportGenerator._ |
diff --git a/src/libraries/System.Runtime.InteropServices/gen/Common/MemorySafetyRules.cs b/src/libraries/System.Runtime.InteropServices/gen/Common/MemorySafetyRules.cs
new file mode 100644
index 00000000000000..ad1498204c53c6
--- /dev/null
+++ b/src/libraries/System.Runtime.InteropServices/gen/Common/MemorySafetyRules.cs
@@ -0,0 +1,52 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+
+namespace Microsoft.Interop
+{
+ ///
+ /// Helpers for reasoning about the updated memory safety rules (unsafe evolution) in the interop generators.
+ ///
+ internal static class MemorySafetyRules
+ {
+ ///
+ /// The compiler feature flag that opts an assembly into the updated memory safety rules.
+ ///
+ ///
+ /// Roslyn does not expose the memory safety rules version through a public API yet
+ /// (https://github.com/dotnet/roslyn/issues/82546), so we look at the same feature flag the compiler
+ /// itself uses to determine whether the updated rules are in effect.
+ ///
+ private const string UpdatedMemorySafetyRulesFeature = "updated-memory-safety-rules";
+
+ ///
+ /// The safe contextual keyword introduced by the unsafe evolution feature.
+ ///
+ ///
+ /// The generators compile against an older Roslyn than the one they run on, so the kind is resolved at
+ /// run time instead of referencing SyntaxKind.SafeKeyword directly. It is
+ /// when the hosting compiler does not know the keyword.
+ ///
+ private static readonly SyntaxKind s_safeKeyword = SyntaxFacts.GetContextualKeywordKind("safe");
+
+ ///
+ /// Determines whether the compilation the provided tree belongs to uses the updated memory safety rules.
+ ///
+ public static bool UsesUpdatedMemorySafetyRules(SyntaxTree tree)
+ => tree.Options.Features.ContainsKey(UpdatedMemorySafetyRulesFeature);
+
+ ///
+ /// Determines whether a declaration explicitly states its safety contract with safe or unsafe.
+ ///
+ public static bool HasExplicitSafetyModifier(SyntaxTokenList modifiers)
+ => modifiers.Any(SyntaxKind.UnsafeKeyword) || HasSafeModifier(modifiers);
+
+ ///
+ /// Determines whether a declaration is explicitly marked safe.
+ ///
+ public static bool HasSafeModifier(SyntaxTokenList modifiers)
+ => s_safeKeyword != SyntaxKind.None && modifiers.Any(s_safeKeyword);
+ }
+}
diff --git a/src/libraries/System.Runtime.InteropServices/gen/Common/Resources/Strings.resx b/src/libraries/System.Runtime.InteropServices/gen/Common/Resources/Strings.resx
index 9f63be478045cf..9bd0cc1738e855 100644
--- a/src/libraries/System.Runtime.InteropServices/gen/Common/Resources/Strings.resx
+++ b/src/libraries/System.Runtime.InteropServices/gen/Common/Resources/Strings.resx
@@ -700,6 +700,15 @@
LibraryImportAttribute requires unsafe code.
+
+ Method with 'LibraryImportAttribute' must be marked 'safe' or 'unsafe'
+
+
+ Method '{0}' with 'LibraryImportAttribute' must be marked 'safe' or 'unsafe'
+
+
+ Under the updated memory safety rules, calling into native code is a safety contract that the compiler cannot verify. Mark the method 'unsafe' to require callers to audit the call, or 'safe' after auditing the interop boundary. The generated implementation may or may not be 'extern' depending on the marshalling that is required, so the modifier is required for every method with 'LibraryImportAttribute'.
+
'GeneratedComInterfaceAttribute' and 'GeneratedComClassAttribute' require unsafe code.
diff --git a/src/libraries/System.Runtime.InteropServices/gen/Common/Resources/xlf/Strings.cs.xlf b/src/libraries/System.Runtime.InteropServices/gen/Common/Resources/xlf/Strings.cs.xlf
index 80868ddb5db05e..8c5605efbf526d 100644
--- a/src/libraries/System.Runtime.InteropServices/gen/Common/Resources/xlf/Strings.cs.xlf
+++ b/src/libraries/System.Runtime.InteropServices/gen/Common/Resources/xlf/Strings.cs.xlf
@@ -1057,6 +1057,21 @@
LibraryImportAttribute vyžaduje nebezpečný kód.
+
+ Under the updated memory safety rules, calling into native code is a safety contract that the compiler cannot verify. Mark the method 'unsafe' to require callers to audit the call, or 'safe' after auditing the interop boundary. The generated implementation may or may not be 'extern' depending on the marshalling that is required, so the modifier is required for every method with 'LibraryImportAttribute'.
+ Under the updated memory safety rules, calling into native code is a safety contract that the compiler cannot verify. Mark the method 'unsafe' to require callers to audit the call, or 'safe' after auditing the interop boundary. The generated implementation may or may not be 'extern' depending on the marshalling that is required, so the modifier is required for every method with 'LibraryImportAttribute'.
+
+
+
+ Method '{0}' with 'LibraryImportAttribute' must be marked 'safe' or 'unsafe'
+ Method '{0}' with 'LibraryImportAttribute' must be marked 'safe' or 'unsafe'
+
+
+
+ Method with 'LibraryImportAttribute' must be marked 'safe' or 'unsafe'
+ Method with 'LibraryImportAttribute' must be marked 'safe' or 'unsafe'
+
+ The return type the two method must be the expected type.Návratový typ, který obě metody musí být očekávaného typu.
diff --git a/src/libraries/System.Runtime.InteropServices/gen/Common/Resources/xlf/Strings.de.xlf b/src/libraries/System.Runtime.InteropServices/gen/Common/Resources/xlf/Strings.de.xlf
index 34be0338905f6a..4ea9f46f8a52cc 100644
--- a/src/libraries/System.Runtime.InteropServices/gen/Common/Resources/xlf/Strings.de.xlf
+++ b/src/libraries/System.Runtime.InteropServices/gen/Common/Resources/xlf/Strings.de.xlf
@@ -1057,6 +1057,21 @@
LibraryImportAttribute erfordert unsicheren Code.
+
+ Under the updated memory safety rules, calling into native code is a safety contract that the compiler cannot verify. Mark the method 'unsafe' to require callers to audit the call, or 'safe' after auditing the interop boundary. The generated implementation may or may not be 'extern' depending on the marshalling that is required, so the modifier is required for every method with 'LibraryImportAttribute'.
+ Under the updated memory safety rules, calling into native code is a safety contract that the compiler cannot verify. Mark the method 'unsafe' to require callers to audit the call, or 'safe' after auditing the interop boundary. The generated implementation may or may not be 'extern' depending on the marshalling that is required, so the modifier is required for every method with 'LibraryImportAttribute'.
+
+
+
+ Method '{0}' with 'LibraryImportAttribute' must be marked 'safe' or 'unsafe'
+ Method '{0}' with 'LibraryImportAttribute' must be marked 'safe' or 'unsafe'
+
+
+
+ Method with 'LibraryImportAttribute' must be marked 'safe' or 'unsafe'
+ Method with 'LibraryImportAttribute' must be marked 'safe' or 'unsafe'
+
+ The return type the two method must be the expected type.Der Rückgabetyp, den die beiden Methoden aufweisen müssen, muss der erwartete Typ sein.
diff --git a/src/libraries/System.Runtime.InteropServices/gen/Common/Resources/xlf/Strings.es.xlf b/src/libraries/System.Runtime.InteropServices/gen/Common/Resources/xlf/Strings.es.xlf
index e5d24ed16d3542..8f7cb16b0c62a7 100644
--- a/src/libraries/System.Runtime.InteropServices/gen/Common/Resources/xlf/Strings.es.xlf
+++ b/src/libraries/System.Runtime.InteropServices/gen/Common/Resources/xlf/Strings.es.xlf
@@ -1057,6 +1057,21 @@
LibraryImportAttribute requiere código no seguro.
+
+ Under the updated memory safety rules, calling into native code is a safety contract that the compiler cannot verify. Mark the method 'unsafe' to require callers to audit the call, or 'safe' after auditing the interop boundary. The generated implementation may or may not be 'extern' depending on the marshalling that is required, so the modifier is required for every method with 'LibraryImportAttribute'.
+ Under the updated memory safety rules, calling into native code is a safety contract that the compiler cannot verify. Mark the method 'unsafe' to require callers to audit the call, or 'safe' after auditing the interop boundary. The generated implementation may or may not be 'extern' depending on the marshalling that is required, so the modifier is required for every method with 'LibraryImportAttribute'.
+
+
+
+ Method '{0}' with 'LibraryImportAttribute' must be marked 'safe' or 'unsafe'
+ Method '{0}' with 'LibraryImportAttribute' must be marked 'safe' or 'unsafe'
+
+
+
+ Method with 'LibraryImportAttribute' must be marked 'safe' or 'unsafe'
+ Method with 'LibraryImportAttribute' must be marked 'safe' or 'unsafe'
+
+ The return type the two method must be the expected type.El tipo de valor devuelto por el que ambos métodos deben ser del tipo esperado.
diff --git a/src/libraries/System.Runtime.InteropServices/gen/Common/Resources/xlf/Strings.fr.xlf b/src/libraries/System.Runtime.InteropServices/gen/Common/Resources/xlf/Strings.fr.xlf
index 9031bc0eac1aa8..34f9c16f748591 100644
--- a/src/libraries/System.Runtime.InteropServices/gen/Common/Resources/xlf/Strings.fr.xlf
+++ b/src/libraries/System.Runtime.InteropServices/gen/Common/Resources/xlf/Strings.fr.xlf
@@ -1057,6 +1057,21 @@
LibraryImportAttribute requiert du code non sécurisé.
+
+ Under the updated memory safety rules, calling into native code is a safety contract that the compiler cannot verify. Mark the method 'unsafe' to require callers to audit the call, or 'safe' after auditing the interop boundary. The generated implementation may or may not be 'extern' depending on the marshalling that is required, so the modifier is required for every method with 'LibraryImportAttribute'.
+ Under the updated memory safety rules, calling into native code is a safety contract that the compiler cannot verify. Mark the method 'unsafe' to require callers to audit the call, or 'safe' after auditing the interop boundary. The generated implementation may or may not be 'extern' depending on the marshalling that is required, so the modifier is required for every method with 'LibraryImportAttribute'.
+
+
+
+ Method '{0}' with 'LibraryImportAttribute' must be marked 'safe' or 'unsafe'
+ Method '{0}' with 'LibraryImportAttribute' must be marked 'safe' or 'unsafe'
+
+
+
+ Method with 'LibraryImportAttribute' must be marked 'safe' or 'unsafe'
+ Method with 'LibraryImportAttribute' must be marked 'safe' or 'unsafe'
+
+ The return type the two method must be the expected type.Le type de retour de la méthode deux doit être le type attendu.
diff --git a/src/libraries/System.Runtime.InteropServices/gen/Common/Resources/xlf/Strings.it.xlf b/src/libraries/System.Runtime.InteropServices/gen/Common/Resources/xlf/Strings.it.xlf
index 819990f2268529..7900f791eef1ad 100644
--- a/src/libraries/System.Runtime.InteropServices/gen/Common/Resources/xlf/Strings.it.xlf
+++ b/src/libraries/System.Runtime.InteropServices/gen/Common/Resources/xlf/Strings.it.xlf
@@ -1057,6 +1057,21 @@
LibraryImportAttribute richiede un codice non sicuro.
+
+ Under the updated memory safety rules, calling into native code is a safety contract that the compiler cannot verify. Mark the method 'unsafe' to require callers to audit the call, or 'safe' after auditing the interop boundary. The generated implementation may or may not be 'extern' depending on the marshalling that is required, so the modifier is required for every method with 'LibraryImportAttribute'.
+ Under the updated memory safety rules, calling into native code is a safety contract that the compiler cannot verify. Mark the method 'unsafe' to require callers to audit the call, or 'safe' after auditing the interop boundary. The generated implementation may or may not be 'extern' depending on the marshalling that is required, so the modifier is required for every method with 'LibraryImportAttribute'.
+
+
+
+ Method '{0}' with 'LibraryImportAttribute' must be marked 'safe' or 'unsafe'
+ Method '{0}' with 'LibraryImportAttribute' must be marked 'safe' or 'unsafe'
+
+
+
+ Method with 'LibraryImportAttribute' must be marked 'safe' or 'unsafe'
+ Method with 'LibraryImportAttribute' must be marked 'safe' or 'unsafe'
+
+ The return type the two method must be the expected type.Tipo restituito il due metodo deve essere il tipo previsto.
diff --git a/src/libraries/System.Runtime.InteropServices/gen/Common/Resources/xlf/Strings.ja.xlf b/src/libraries/System.Runtime.InteropServices/gen/Common/Resources/xlf/Strings.ja.xlf
index cd5a062a510fa2..d1104c9c30a20c 100644
--- a/src/libraries/System.Runtime.InteropServices/gen/Common/Resources/xlf/Strings.ja.xlf
+++ b/src/libraries/System.Runtime.InteropServices/gen/Common/Resources/xlf/Strings.ja.xlf
@@ -1057,6 +1057,21 @@
LibraryImportAttribute には安全でないコードが必要です。
+
+ Under the updated memory safety rules, calling into native code is a safety contract that the compiler cannot verify. Mark the method 'unsafe' to require callers to audit the call, or 'safe' after auditing the interop boundary. The generated implementation may or may not be 'extern' depending on the marshalling that is required, so the modifier is required for every method with 'LibraryImportAttribute'.
+ Under the updated memory safety rules, calling into native code is a safety contract that the compiler cannot verify. Mark the method 'unsafe' to require callers to audit the call, or 'safe' after auditing the interop boundary. The generated implementation may or may not be 'extern' depending on the marshalling that is required, so the modifier is required for every method with 'LibraryImportAttribute'.
+
+
+
+ Method '{0}' with 'LibraryImportAttribute' must be marked 'safe' or 'unsafe'
+ Method '{0}' with 'LibraryImportAttribute' must be marked 'safe' or 'unsafe'
+
+
+
+ Method with 'LibraryImportAttribute' must be marked 'safe' or 'unsafe'
+ Method with 'LibraryImportAttribute' must be marked 'safe' or 'unsafe'
+
+ The return type the two method must be the expected type.戻り値の型は、2 つのメソッドが期待する型である必要があります。
diff --git a/src/libraries/System.Runtime.InteropServices/gen/Common/Resources/xlf/Strings.ko.xlf b/src/libraries/System.Runtime.InteropServices/gen/Common/Resources/xlf/Strings.ko.xlf
index 225d0e444546ab..6020e6313572ea 100644
--- a/src/libraries/System.Runtime.InteropServices/gen/Common/Resources/xlf/Strings.ko.xlf
+++ b/src/libraries/System.Runtime.InteropServices/gen/Common/Resources/xlf/Strings.ko.xlf
@@ -1057,6 +1057,21 @@
LibraryImportAttribute에는 안전하지 않은 코드가 필요합니다.
+
+ Under the updated memory safety rules, calling into native code is a safety contract that the compiler cannot verify. Mark the method 'unsafe' to require callers to audit the call, or 'safe' after auditing the interop boundary. The generated implementation may or may not be 'extern' depending on the marshalling that is required, so the modifier is required for every method with 'LibraryImportAttribute'.
+ Under the updated memory safety rules, calling into native code is a safety contract that the compiler cannot verify. Mark the method 'unsafe' to require callers to audit the call, or 'safe' after auditing the interop boundary. The generated implementation may or may not be 'extern' depending on the marshalling that is required, so the modifier is required for every method with 'LibraryImportAttribute'.
+
+
+
+ Method '{0}' with 'LibraryImportAttribute' must be marked 'safe' or 'unsafe'
+ Method '{0}' with 'LibraryImportAttribute' must be marked 'safe' or 'unsafe'
+
+
+
+ Method with 'LibraryImportAttribute' must be marked 'safe' or 'unsafe'
+ Method with 'LibraryImportAttribute' must be marked 'safe' or 'unsafe'
+
+ The return type the two method must be the expected type.두 메서드가 필요한 형식이어야 하는 반환 형식입니다.
diff --git a/src/libraries/System.Runtime.InteropServices/gen/Common/Resources/xlf/Strings.pl.xlf b/src/libraries/System.Runtime.InteropServices/gen/Common/Resources/xlf/Strings.pl.xlf
index daac9d6a6625d0..457fe011a7ab53 100644
--- a/src/libraries/System.Runtime.InteropServices/gen/Common/Resources/xlf/Strings.pl.xlf
+++ b/src/libraries/System.Runtime.InteropServices/gen/Common/Resources/xlf/Strings.pl.xlf
@@ -1057,6 +1057,21 @@
Element LibraryImportAttribute wymaga niebezpiecznego kodu.
+
+ Under the updated memory safety rules, calling into native code is a safety contract that the compiler cannot verify. Mark the method 'unsafe' to require callers to audit the call, or 'safe' after auditing the interop boundary. The generated implementation may or may not be 'extern' depending on the marshalling that is required, so the modifier is required for every method with 'LibraryImportAttribute'.
+ Under the updated memory safety rules, calling into native code is a safety contract that the compiler cannot verify. Mark the method 'unsafe' to require callers to audit the call, or 'safe' after auditing the interop boundary. The generated implementation may or may not be 'extern' depending on the marshalling that is required, so the modifier is required for every method with 'LibraryImportAttribute'.
+
+
+
+ Method '{0}' with 'LibraryImportAttribute' must be marked 'safe' or 'unsafe'
+ Method '{0}' with 'LibraryImportAttribute' must be marked 'safe' or 'unsafe'
+
+
+
+ Method with 'LibraryImportAttribute' must be marked 'safe' or 'unsafe'
+ Method with 'LibraryImportAttribute' must be marked 'safe' or 'unsafe'
+
+ The return type the two method must be the expected type.Zwracany typ obu tych metod musi być oczekiwanym typem.
diff --git a/src/libraries/System.Runtime.InteropServices/gen/Common/Resources/xlf/Strings.pt-BR.xlf b/src/libraries/System.Runtime.InteropServices/gen/Common/Resources/xlf/Strings.pt-BR.xlf
index d5eb03c0744bc4..10b1f5c7d9170e 100644
--- a/src/libraries/System.Runtime.InteropServices/gen/Common/Resources/xlf/Strings.pt-BR.xlf
+++ b/src/libraries/System.Runtime.InteropServices/gen/Common/Resources/xlf/Strings.pt-BR.xlf
@@ -1057,6 +1057,21 @@
LibraryImportAttribute requer código não seguro.
+
+ Under the updated memory safety rules, calling into native code is a safety contract that the compiler cannot verify. Mark the method 'unsafe' to require callers to audit the call, or 'safe' after auditing the interop boundary. The generated implementation may or may not be 'extern' depending on the marshalling that is required, so the modifier is required for every method with 'LibraryImportAttribute'.
+ Under the updated memory safety rules, calling into native code is a safety contract that the compiler cannot verify. Mark the method 'unsafe' to require callers to audit the call, or 'safe' after auditing the interop boundary. The generated implementation may or may not be 'extern' depending on the marshalling that is required, so the modifier is required for every method with 'LibraryImportAttribute'.
+
+
+
+ Method '{0}' with 'LibraryImportAttribute' must be marked 'safe' or 'unsafe'
+ Method '{0}' with 'LibraryImportAttribute' must be marked 'safe' or 'unsafe'
+
+
+
+ Method with 'LibraryImportAttribute' must be marked 'safe' or 'unsafe'
+ Method with 'LibraryImportAttribute' must be marked 'safe' or 'unsafe'
+
+ The return type the two method must be the expected type.O tipo de retorno do método dois deve ser o tipo esperado.
diff --git a/src/libraries/System.Runtime.InteropServices/gen/Common/Resources/xlf/Strings.ru.xlf b/src/libraries/System.Runtime.InteropServices/gen/Common/Resources/xlf/Strings.ru.xlf
index d151ae881df3df..9feed7c77e9dfd 100644
--- a/src/libraries/System.Runtime.InteropServices/gen/Common/Resources/xlf/Strings.ru.xlf
+++ b/src/libraries/System.Runtime.InteropServices/gen/Common/Resources/xlf/Strings.ru.xlf
@@ -1057,6 +1057,21 @@
Для LibraryImportAttribute требуется небезопасный код.
+
+ Under the updated memory safety rules, calling into native code is a safety contract that the compiler cannot verify. Mark the method 'unsafe' to require callers to audit the call, or 'safe' after auditing the interop boundary. The generated implementation may or may not be 'extern' depending on the marshalling that is required, so the modifier is required for every method with 'LibraryImportAttribute'.
+ Under the updated memory safety rules, calling into native code is a safety contract that the compiler cannot verify. Mark the method 'unsafe' to require callers to audit the call, or 'safe' after auditing the interop boundary. The generated implementation may or may not be 'extern' depending on the marshalling that is required, so the modifier is required for every method with 'LibraryImportAttribute'.
+
+
+
+ Method '{0}' with 'LibraryImportAttribute' must be marked 'safe' or 'unsafe'
+ Method '{0}' with 'LibraryImportAttribute' must be marked 'safe' or 'unsafe'
+
+
+
+ Method with 'LibraryImportAttribute' must be marked 'safe' or 'unsafe'
+ Method with 'LibraryImportAttribute' must be marked 'safe' or 'unsafe'
+
+ The return type the two method must be the expected type.Тип возвращаемого значения метода two должен быть ожидаемым.
diff --git a/src/libraries/System.Runtime.InteropServices/gen/Common/Resources/xlf/Strings.tr.xlf b/src/libraries/System.Runtime.InteropServices/gen/Common/Resources/xlf/Strings.tr.xlf
index bef2894aa7962f..ebacf437c10761 100644
--- a/src/libraries/System.Runtime.InteropServices/gen/Common/Resources/xlf/Strings.tr.xlf
+++ b/src/libraries/System.Runtime.InteropServices/gen/Common/Resources/xlf/Strings.tr.xlf
@@ -1057,6 +1057,21 @@
LibraryImportAttribute, güvenli olmayan kod gerektiriyor.
+
+ Under the updated memory safety rules, calling into native code is a safety contract that the compiler cannot verify. Mark the method 'unsafe' to require callers to audit the call, or 'safe' after auditing the interop boundary. The generated implementation may or may not be 'extern' depending on the marshalling that is required, so the modifier is required for every method with 'LibraryImportAttribute'.
+ Under the updated memory safety rules, calling into native code is a safety contract that the compiler cannot verify. Mark the method 'unsafe' to require callers to audit the call, or 'safe' after auditing the interop boundary. The generated implementation may or may not be 'extern' depending on the marshalling that is required, so the modifier is required for every method with 'LibraryImportAttribute'.
+
+
+
+ Method '{0}' with 'LibraryImportAttribute' must be marked 'safe' or 'unsafe'
+ Method '{0}' with 'LibraryImportAttribute' must be marked 'safe' or 'unsafe'
+
+
+
+ Method with 'LibraryImportAttribute' must be marked 'safe' or 'unsafe'
+ Method with 'LibraryImportAttribute' must be marked 'safe' or 'unsafe'
+
+ The return type the two method must be the expected type.İki metodun dönüş türü beklenen tür olmalıdır.
diff --git a/src/libraries/System.Runtime.InteropServices/gen/Common/Resources/xlf/Strings.zh-Hans.xlf b/src/libraries/System.Runtime.InteropServices/gen/Common/Resources/xlf/Strings.zh-Hans.xlf
index 5223e9d8c92e9c..c1a1cfd9464af9 100644
--- a/src/libraries/System.Runtime.InteropServices/gen/Common/Resources/xlf/Strings.zh-Hans.xlf
+++ b/src/libraries/System.Runtime.InteropServices/gen/Common/Resources/xlf/Strings.zh-Hans.xlf
@@ -1057,6 +1057,21 @@
LibraryImportAttribute 需要不安全的代码。
+
+ Under the updated memory safety rules, calling into native code is a safety contract that the compiler cannot verify. Mark the method 'unsafe' to require callers to audit the call, or 'safe' after auditing the interop boundary. The generated implementation may or may not be 'extern' depending on the marshalling that is required, so the modifier is required for every method with 'LibraryImportAttribute'.
+ Under the updated memory safety rules, calling into native code is a safety contract that the compiler cannot verify. Mark the method 'unsafe' to require callers to audit the call, or 'safe' after auditing the interop boundary. The generated implementation may or may not be 'extern' depending on the marshalling that is required, so the modifier is required for every method with 'LibraryImportAttribute'.
+
+
+
+ Method '{0}' with 'LibraryImportAttribute' must be marked 'safe' or 'unsafe'
+ Method '{0}' with 'LibraryImportAttribute' must be marked 'safe' or 'unsafe'
+
+
+
+ Method with 'LibraryImportAttribute' must be marked 'safe' or 'unsafe'
+ Method with 'LibraryImportAttribute' must be marked 'safe' or 'unsafe'
+
+ The return type the two method must be the expected type.这两种方法的返回类型必须为预期类型。
diff --git a/src/libraries/System.Runtime.InteropServices/gen/Common/Resources/xlf/Strings.zh-Hant.xlf b/src/libraries/System.Runtime.InteropServices/gen/Common/Resources/xlf/Strings.zh-Hant.xlf
index da3860843d8b10..fd631f93f54a1d 100644
--- a/src/libraries/System.Runtime.InteropServices/gen/Common/Resources/xlf/Strings.zh-Hant.xlf
+++ b/src/libraries/System.Runtime.InteropServices/gen/Common/Resources/xlf/Strings.zh-Hant.xlf
@@ -1057,6 +1057,21 @@
LibraryImportAttribute 需要不安全的程式碼。
+
+ Under the updated memory safety rules, calling into native code is a safety contract that the compiler cannot verify. Mark the method 'unsafe' to require callers to audit the call, or 'safe' after auditing the interop boundary. The generated implementation may or may not be 'extern' depending on the marshalling that is required, so the modifier is required for every method with 'LibraryImportAttribute'.
+ Under the updated memory safety rules, calling into native code is a safety contract that the compiler cannot verify. Mark the method 'unsafe' to require callers to audit the call, or 'safe' after auditing the interop boundary. The generated implementation may or may not be 'extern' depending on the marshalling that is required, so the modifier is required for every method with 'LibraryImportAttribute'.
+
+
+
+ Method '{0}' with 'LibraryImportAttribute' must be marked 'safe' or 'unsafe'
+ Method '{0}' with 'LibraryImportAttribute' must be marked 'safe' or 'unsafe'
+
+
+
+ Method with 'LibraryImportAttribute' must be marked 'safe' or 'unsafe'
+ Method with 'LibraryImportAttribute' must be marked 'safe' or 'unsafe'
+
+ The return type the two method must be the expected type.兩種方法的傳回類型必須是預期的類型。
diff --git a/src/libraries/System.Runtime.InteropServices/gen/DownlevelLibraryImportGenerator/DownlevelLibraryImportDiagnosticsAnalyzer.cs b/src/libraries/System.Runtime.InteropServices/gen/DownlevelLibraryImportGenerator/DownlevelLibraryImportDiagnosticsAnalyzer.cs
index ff85fad5fcf43b..dd8fc82aa2ed85 100644
--- a/src/libraries/System.Runtime.InteropServices/gen/DownlevelLibraryImportGenerator/DownlevelLibraryImportDiagnosticsAnalyzer.cs
+++ b/src/libraries/System.Runtime.InteropServices/gen/DownlevelLibraryImportGenerator/DownlevelLibraryImportDiagnosticsAnalyzer.cs
@@ -37,7 +37,8 @@ public sealed class DownlevelLibraryImportDiagnosticsAnalyzer : DiagnosticAnalyz
GeneratorDiagnostics.ConfigurationValueNotSupported,
GeneratorDiagnostics.MarshallingAttributeConfigurationNotSupported,
GeneratorDiagnostics.CannotForwardToDllImport,
- GeneratorDiagnostics.RequiresAllowUnsafeBlocks);
+ GeneratorDiagnostics.RequiresAllowUnsafeBlocks,
+ GeneratorDiagnostics.RequiresExplicitSafetyModifier);
public override void Initialize(AnalysisContext context)
{
@@ -174,6 +175,8 @@ private static void AnalyzeMethodSyntax(
}
}
+ ReportMissingExplicitSafetyModifier(context, method, libraryImportAttr);
+
// Calculate stub information and collect diagnostics
var diagnostics = CalculateDiagnostics(methodSyntax, method, libraryImportAttr, env, context.CancellationToken);
@@ -192,6 +195,39 @@ private static void AnalyzeMethodSyntax(
}
}
+ ///
+ /// Requires an explicit safe or unsafe modifier on every method with
+ /// LibraryImportAttribute when the updated memory safety rules are enabled.
+ ///
+ ///
+ /// The compiler only requires the modifier when the generated implementing part is extern, which
+ /// depends on whether the signature needs marshalling. That is an implementation detail of the generator,
+ /// so the requirement is enforced for every shape to keep the contract stable.
+ ///
+ private static void ReportMissingExplicitSafetyModifier(SymbolAnalysisContext context, IMethodSymbol method, AttributeData libraryImportAttr)
+ {
+ // The generator never copies LibraryImportAttribute onto the implementing part, so the attribute
+ // application always points at the declaration the user authored.
+ if (libraryImportAttr.ApplicationSyntaxReference is not { } attributeReference
+ || !MemorySafetyRules.UsesUpdatedMemorySafetyRules(attributeReference.SyntaxTree))
+ {
+ return;
+ }
+
+ if (attributeReference.GetSyntax(context.CancellationToken).FirstAncestorOrSelf() is not { } declaration
+ || MemorySafetyRules.HasExplicitSafetyModifier(declaration.Modifiers))
+ {
+ return;
+ }
+
+ context.ReportDiagnostic(
+ DiagnosticInfo.Create(
+ GeneratorDiagnostics.RequiresExplicitSafetyModifier,
+ declaration.Identifier.GetLocation(),
+ method.Name)
+ .ToDiagnostic());
+ }
+
private static ImmutableArray CalculateDiagnostics(
MethodDeclarationSyntax originalSyntax,
IMethodSymbol symbol,
diff --git a/src/libraries/System.Runtime.InteropServices/gen/DownlevelLibraryImportGenerator/DownlevelLibraryImportGenerator.csproj b/src/libraries/System.Runtime.InteropServices/gen/DownlevelLibraryImportGenerator/DownlevelLibraryImportGenerator.csproj
index 892d285d0e3dc1..54c5bfbf67985e 100644
--- a/src/libraries/System.Runtime.InteropServices/gen/DownlevelLibraryImportGenerator/DownlevelLibraryImportGenerator.csproj
+++ b/src/libraries/System.Runtime.InteropServices/gen/DownlevelLibraryImportGenerator/DownlevelLibraryImportGenerator.csproj
@@ -27,6 +27,7 @@
+
diff --git a/src/libraries/System.Runtime.InteropServices/gen/DownlevelLibraryImportGenerator/GeneratorDiagnostics.cs b/src/libraries/System.Runtime.InteropServices/gen/DownlevelLibraryImportGenerator/GeneratorDiagnostics.cs
index 2e8223fa86bbcb..265469d2494308 100644
--- a/src/libraries/System.Runtime.InteropServices/gen/DownlevelLibraryImportGenerator/GeneratorDiagnostics.cs
+++ b/src/libraries/System.Runtime.InteropServices/gen/DownlevelLibraryImportGenerator/GeneratorDiagnostics.cs
@@ -23,6 +23,7 @@ public static class Ids
public const string RequiresAllowUnsafeBlocks = Prefix + "1062";
public const string UnnecessaryMarshallingInfo = Prefix + "1063";
+ public const string RequiresExplicitSafetyModifier = Prefix + "1064";
public const string NotRecommendedGeneratedComInterfaceUsage = Prefix + "1092";
}
@@ -204,6 +205,17 @@ public static class Ids
isEnabledByDefault: true,
description: GetResourceString(nameof(SR.RequiresAllowUnsafeBlocksDescriptionLibraryImport)));
+ ///
+ public static readonly DiagnosticDescriptor RequiresExplicitSafetyModifier =
+ DiagnosticDescriptorHelper.Create(
+ Ids.RequiresExplicitSafetyModifier,
+ GetResourceString(nameof(SR.RequiresExplicitSafetyModifierTitleLibraryImport)),
+ GetResourceString(nameof(SR.RequiresExplicitSafetyModifierMessageLibraryImport)),
+ Category,
+ DiagnosticSeverity.Error,
+ isEnabledByDefault: true,
+ description: GetResourceString(nameof(SR.RequiresExplicitSafetyModifierDescriptionLibraryImport)));
+
///
public static readonly DiagnosticDescriptor UnnecessaryParameterMarshallingInfo =
DiagnosticDescriptorHelper.Create(
diff --git a/src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/Analyzers/LibraryImportDiagnosticsAnalyzer.cs b/src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/Analyzers/LibraryImportDiagnosticsAnalyzer.cs
index b959f7145e12db..5909c6d22cbc71 100644
--- a/src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/Analyzers/LibraryImportDiagnosticsAnalyzer.cs
+++ b/src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/Analyzers/LibraryImportDiagnosticsAnalyzer.cs
@@ -39,6 +39,7 @@ public class LibraryImportDiagnosticsAnalyzer : DiagnosticAnalyzer
GeneratorDiagnostics.MarshallingAttributeConfigurationNotSupported,
GeneratorDiagnostics.CannotForwardToDllImport,
GeneratorDiagnostics.RequiresAllowUnsafeBlocks,
+ GeneratorDiagnostics.RequiresExplicitSafetyModifier,
GeneratorDiagnostics.UnnecessaryParameterMarshallingInfo,
GeneratorDiagnostics.UnnecessaryReturnMarshallingInfo,
GeneratorDiagnostics.SizeOfInCollectionMustBeDefinedAtCallOutParam,
@@ -175,6 +176,8 @@ private static void AnalyzeMethodSyntax(
// Note: RequiresAllowUnsafeBlocks is reported once per compilation in Initialize method
+ ReportMissingExplicitSafetyModifier(context, method, libraryImportAttr);
+
// Calculate stub information and collect diagnostics
var diagnostics = CalculateDiagnostics(methodSyntax, method, libraryImportAttr, env, options, context.CancellationToken);
@@ -193,6 +196,39 @@ private static void AnalyzeMethodSyntax(
}
}
+ ///
+ /// Requires an explicit safe or unsafe modifier on every method with
+ /// LibraryImportAttribute when the updated memory safety rules are enabled.
+ ///
+ ///
+ /// The compiler only requires the modifier when the generated implementing part is extern, which
+ /// depends on whether the signature needs marshalling. That is an implementation detail of the generator,
+ /// so the requirement is enforced for every shape to keep the contract stable.
+ ///
+ private static void ReportMissingExplicitSafetyModifier(SymbolAnalysisContext context, IMethodSymbol method, AttributeData libraryImportAttr)
+ {
+ // The generator never copies LibraryImportAttribute onto the implementing part, so the attribute
+ // application always points at the declaration the user authored.
+ if (libraryImportAttr.ApplicationSyntaxReference is not { } attributeReference
+ || !MemorySafetyRules.UsesUpdatedMemorySafetyRules(attributeReference.SyntaxTree))
+ {
+ return;
+ }
+
+ if (attributeReference.GetSyntax(context.CancellationToken).FirstAncestorOrSelf() is not { } declaration
+ || MemorySafetyRules.HasExplicitSafetyModifier(declaration.Modifiers))
+ {
+ return;
+ }
+
+ context.ReportDiagnostic(
+ DiagnosticInfo.Create(
+ GeneratorDiagnostics.RequiresExplicitSafetyModifier,
+ declaration.Identifier.GetLocation(),
+ method.Name)
+ .ToDiagnostic());
+ }
+
private static ImmutableArray CalculateDiagnostics(
MethodDeclarationSyntax originalSyntax,
IMethodSymbol symbol,
diff --git a/src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/GeneratorDiagnostics.cs b/src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/GeneratorDiagnostics.cs
index f5fd78d48e587c..69b00243117596 100644
--- a/src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/GeneratorDiagnostics.cs
+++ b/src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/GeneratorDiagnostics.cs
@@ -23,6 +23,7 @@ public class Ids
public const string RequiresAllowUnsafeBlocks = Prefix + "1062";
public const string UnnecessaryMarshallingInfo = Prefix + "1063";
+ public const string RequiresExplicitSafetyModifier = Prefix + "1064";
public const string NotRecommendedGeneratedComInterfaceUsage = Prefix + "1092";
}
@@ -204,6 +205,17 @@ public class Ids
isEnabledByDefault: true,
description: GetResourceString(nameof(SR.RequiresAllowUnsafeBlocksDescriptionLibraryImport)));
+ ///
+ public static readonly DiagnosticDescriptor RequiresExplicitSafetyModifier =
+ DiagnosticDescriptorHelper.Create(
+ Ids.RequiresExplicitSafetyModifier,
+ GetResourceString(nameof(SR.RequiresExplicitSafetyModifierTitleLibraryImport)),
+ GetResourceString(nameof(SR.RequiresExplicitSafetyModifierMessageLibraryImport)),
+ Category,
+ DiagnosticSeverity.Error,
+ isEnabledByDefault: true,
+ description: GetResourceString(nameof(SR.RequiresExplicitSafetyModifierDescriptionLibraryImport)));
+
///
public static readonly DiagnosticDescriptor UnnecessaryParameterMarshallingInfo =
DiagnosticDescriptorHelper.Create(
diff --git a/src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/LibraryImportGenerator.csproj b/src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/LibraryImportGenerator.csproj
index 40dd9ab310c931..4ee8f2619f2a4f 100644
--- a/src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/LibraryImportGenerator.csproj
+++ b/src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/LibraryImportGenerator.csproj
@@ -29,6 +29,7 @@
+
diff --git a/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/ConvertToLibraryImportFixerTests.cs b/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/ConvertToLibraryImportFixerTests.cs
index 51e72181ee7db9..4549c95537c143 100644
--- a/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/ConvertToLibraryImportFixerTests.cs
+++ b/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/ConvertToLibraryImportFixerTests.cs
@@ -1229,6 +1229,32 @@ partial class Test
await VerifyCodeFixAsync(source, fixedSource);
}
+ [Fact]
+ public async Task UnsafeModifierIsPreserved()
+ {
+ string source = """
+ using System.Runtime.InteropServices;
+ partial class Test
+ {
+ [DllImport("DoesNotExist")]
+ public static unsafe extern void [|Method|](int* i);
+ }
+
+ """;
+
+ string fixedSource = """
+ using System.Runtime.InteropServices;
+ partial class Test
+ {
+ [LibraryImport("DoesNotExist")]
+ public static unsafe partial void {|CS8795:Method|}(int* i);
+ }
+
+ """;
+
+ await VerifyCodeFixAsync(source, fixedSource);
+ }
+
private static async Task VerifyCodeFixAsync(string source, string fixedSource, LanguageVersion languageVersion = LanguageVersion.Default, string? editorconfig = null)
{
var test = new TestWithLanguageVersion
diff --git a/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/UnsafeCodeGeneration.cs b/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/UnsafeCodeGeneration.cs
index 2760931454e043..3e1d51df62ebd5 100644
--- a/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/UnsafeCodeGeneration.cs
+++ b/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/UnsafeCodeGeneration.cs
@@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
using System;
+using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
@@ -151,6 +152,165 @@ partial class C
}.RunAsync();
}
+ [Fact]
+ public async Task GeneratedLocalPInvokeIsAlwaysUnsafe()
+ {
+ string source = """
+ using System.Runtime.InteropServices;
+ partial class C
+ {
+ [LibraryImport("DoesNotExist", StringMarshalling = StringMarshalling.Utf16)]
+ public static partial void Method(string s);
+ }
+ """;
+ await new UnsafeShapeTest(compilation =>
+ {
+ MethodDeclarationSyntax stub = GetGeneratedStubSyntax(compilation, "C", "Method");
+ LocalFunctionStatementSyntax localPInvoke = GetLocalPInvoke(stub);
+ // The local P/Invoke is the interop boundary, so it is always caller-unsafe regardless of the
+ // contract the user declared on the method the generator implements.
+ Assert.True(localPInvoke.Modifiers.Any(SyntaxKind.ExternKeyword));
+ Assert.True(localPInvoke.Modifiers.Any(SyntaxKind.UnsafeKeyword));
+ })
+ {
+ TestCode = source,
+ TestBehaviors = TestBehaviors.SkipGeneratedSourcesCheck
+ }.RunAsync();
+ }
+
+ [Fact]
+ public async Task GeneratedLocalPInvokeIsUnsafeWhenUserMethodIsUnsafe()
+ {
+ string source = """
+ using System.Runtime.InteropServices;
+ partial class C
+ {
+ [LibraryImport("DoesNotExist", StringMarshalling = StringMarshalling.Utf16)]
+ public static unsafe partial void Method(string s);
+ }
+ """;
+ await new UnsafeShapeTest(compilation =>
+ {
+ MethodDeclarationSyntax stub = GetGeneratedStubSyntax(compilation, "C", "Method");
+ LocalFunctionStatementSyntax localPInvoke = GetLocalPInvoke(stub);
+ Assert.True(localPInvoke.Modifiers.Any(SyntaxKind.ExternKeyword));
+ Assert.True(localPInvoke.Modifiers.Any(SyntaxKind.UnsafeKeyword));
+ })
+ {
+ TestCode = source,
+ TestBehaviors = TestBehaviors.SkipGeneratedSourcesCheck
+ }.RunAsync();
+ }
+
+ // Under the updated memory safety rules ("unsafe evolution"), a call into native code is a safety contract
+ // the compiler cannot verify. The compiler itself only requires an explicit `safe`/`unsafe` modifier when
+ // the generated implementing part is `extern`, which depends on whether the signature needs marshalling.
+ // That is an implementation detail of the generator, so the analyzer requires the modifier for every
+ // method with `[LibraryImport]`.
+
+ [Fact]
+ public async Task UpdatedRulesWrapperStubWithoutSafetyModifierReportsDiagnostic()
+ {
+ string source = """
+ using System.Runtime.InteropServices;
+ partial class C
+ {
+ [LibraryImport("DoesNotExist", StringMarshalling = StringMarshalling.Utf16)]
+ public static partial void {|SYSLIB1064:Method|}(string s);
+ }
+ """;
+ await new UpdatedMemorySafetyRulesTest
+ {
+ TestCode = source,
+ TestBehaviors = TestBehaviors.SkipGeneratedSourcesCheck
+ }.RunAsync();
+ }
+
+ [Fact]
+ public async Task UpdatedRulesForwarderStubWithoutSafetyModifierReportsDiagnostic()
+ {
+ // The generated forwarder is `extern`, so the compiler reports CS9389 for the same declaration.
+ string source = """
+ using System.Runtime.InteropServices;
+ partial class C
+ {
+ [LibraryImport("DoesNotExist")]
+ public static partial int {|CS9389:{|SYSLIB1064:Method|}|}(int i);
+ }
+ """;
+ await new UpdatedMemorySafetyRulesTest
+ {
+ TestCode = source,
+ TestBehaviors = TestBehaviors.SkipGeneratedSourcesCheck
+ }.RunAsync();
+ }
+
+ [Theory]
+ [InlineData("[LibraryImport(\"DoesNotExist\", StringMarshalling = StringMarshalling.Utf16)]", "unsafe", "void Method(string s)")]
+ // Utf8 marshalling uses a caller-allocated `stackalloc` buffer which, combined with the `[SkipLocalsInit]`
+ // the generator emits, requires an unsafe context under the updated rules.
+ [InlineData("[LibraryImport(\"DoesNotExist\", StringMarshalling = StringMarshalling.Utf8)]", "unsafe", "void Method(string s)")]
+ [InlineData("[LibraryImport(\"DoesNotExist\")]", "unsafe", "int Method(int i)")]
+ [InlineData("[LibraryImport(\"DoesNotExist\")]", "safe", "int Method(int i)")]
+ public async Task UpdatedRulesExplicitSafetyModifierSatisfiesRequirement(string attribute, string safetyModifier, string signature)
+ {
+ string source = $$"""
+ using System.Runtime.InteropServices;
+ partial class C
+ {
+ {{attribute}}
+ public static {{safetyModifier}} partial {{signature}};
+ }
+ """;
+ await new UpdatedMemorySafetyRulesTest
+ {
+ TestCode = source,
+ TestBehaviors = TestBehaviors.SkipGeneratedSourcesCheck
+ }.RunAsync();
+ }
+
+ [Fact]
+ public async Task UpdatedRulesSafeModifierIsForwardedToGeneratedExternStub()
+ {
+ string source = """
+ using System.Runtime.InteropServices;
+ partial class C
+ {
+ [LibraryImport("DoesNotExist")]
+ public static safe partial int Method(int i);
+ }
+ """;
+ await new UpdatedMemorySafetyRulesTest(compilation =>
+ {
+ MethodDeclarationSyntax stub = GetGeneratedStubSyntax(compilation, "C", "Method");
+ // Both parts of a partial member must agree on their safety modifier, so the user's `safe` is
+ // copied onto the generated `extern` forwarder.
+ Assert.True(stub.Modifiers.Any(SyntaxKind.ExternKeyword));
+ Assert.Contains(stub.Modifiers, modifier => modifier.IsKind(SyntaxFacts.GetContextualKeywordKind("safe")));
+ })
+ {
+ TestCode = source,
+ TestBehaviors = TestBehaviors.SkipGeneratedSourcesCheck
+ }.RunAsync();
+ }
+
+ [Fact]
+ public async Task LegacyRulesDoNotRequireSafetyModifier()
+ {
+ string source = """
+ using System.Runtime.InteropServices;
+ partial class C
+ {
+ [LibraryImport("DoesNotExist", StringMarshalling = StringMarshalling.Utf16)]
+ public static partial void Method(string s);
+
+ [LibraryImport("DoesNotExist")]
+ public static partial int Forwarded(int i);
+ }
+ """;
+ await VerifyCS.VerifySourceGeneratorAsync(source);
+ }
+
private static MethodDeclarationSyntax GetGeneratedStubSyntax(Compilation compilation, string typeName, string methodName)
{
INamedTypeSymbol type = compilation.GetTypeByMetadataName(typeName)!;
@@ -168,6 +328,9 @@ private static void AssertNoUnsafeModifierOnContainingTypes(MethodDeclarationSyn
}
}
+ private static LocalFunctionStatementSyntax GetLocalPInvoke(MethodDeclarationSyntax stub) =>
+ Assert.Single(stub.Body!.DescendantNodes().OfType());
+
private sealed class UnsafeShapeTest : VerifyCS.Test
{
private readonly Action _verifyCompilation;
@@ -180,5 +343,30 @@ public UnsafeShapeTest(Action verifyCompilation)
protected override void VerifyFinalCompilation(Compilation compilation) => _verifyCompilation(compilation);
}
+
+ ///
+ /// Runs the generator with the updated memory safety rules ("unsafe evolution") enabled.
+ ///
+ private sealed class UpdatedMemorySafetyRulesTest : VerifyCS.Test
+ {
+ private readonly Action? _verifyCompilation;
+
+ public UpdatedMemorySafetyRulesTest(Action? verifyCompilation = null)
+ : base(referenceAncillaryInterop: false)
+ {
+ _verifyCompilation = verifyCompilation;
+ }
+
+ protected override ParseOptions CreateParseOptions()
+ {
+ // Roslyn does not expose the memory safety rules version through a public API yet, so opt in
+ // through the same feature flag the compiler uses.
+ var parseOptions = (CSharpParseOptions)base.CreateParseOptions();
+ return parseOptions.WithFeatures(
+ [.. parseOptions.Features, new KeyValuePair("updated-memory-safety-rules", "")]);
+ }
+
+ protected override void VerifyFinalCompilation(Compilation compilation) => _verifyCompilation?.Invoke(compilation);
+ }
}
}
diff --git a/src/tools/illink/src/ILLink.CodeFix/AddUnsafeToLibraryImportCodeFixProvider.cs b/src/tools/illink/src/ILLink.CodeFix/AddUnsafeToLibraryImportCodeFixProvider.cs
new file mode 100644
index 00000000000000..dbebb67f62daf2
--- /dev/null
+++ b/src/tools/illink/src/ILLink.CodeFix/AddUnsafeToLibraryImportCodeFixProvider.cs
@@ -0,0 +1,42 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+#if DEBUG
+using System.Collections.Immutable;
+using System.Composition;
+using System.Threading.Tasks;
+using ILLink.CodeFixProvider;
+using ILLink.Shared;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CodeFixes;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+
+namespace ILLink.CodeFix
+{
+ ///
+ /// Fixes analyzer diagnostic IL5007 by marking a LibraryImportAttribute method unsafe.
+ /// The generated contract is intentionally conservative so developers can replace it with safe after
+ /// auditing the interop boundary.
+ ///
+ [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(AddUnsafeToLibraryImportCodeFixProvider)), Shared]
+ public sealed class AddUnsafeToLibraryImportCodeFixProvider : Microsoft.CodeAnalysis.CodeFixes.CodeFixProvider
+ {
+ private static LocalizableString CodeFixTitle =>
+ new LocalizableResourceString(
+ nameof(Resources.AddUnsafeToLibraryImportCodeFixTitle),
+ Resources.ResourceManager,
+ typeof(Resources));
+
+ public override ImmutableArray FixableDiagnosticIds =>
+ [DiagnosticId.LibraryImportRequiresExplicitSafety.AsString()];
+
+ public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer;
+
+ public override Task RegisterCodeFixesAsync(CodeFixContext context) =>
+ UnsafeModifierCodeFixHelpers.RegisterAddUnsafeCodeFixAsync(
+ context,
+ CodeFixTitle,
+ static declaration => declaration is MethodDeclarationSyntax);
+ }
+}
+#endif
diff --git a/src/tools/illink/src/ILLink.CodeFix/Resources.resx b/src/tools/illink/src/ILLink.CodeFix/Resources.resx
index 99b54822b57a9f..3757fbe40f1573 100644
--- a/src/tools/illink/src/ILLink.CodeFix/Resources.resx
+++ b/src/tools/illink/src/ILLink.CodeFix/Resources.resx
@@ -144,6 +144,9 @@
Mark field-like member 'unsafe'
+
+ Mark 'LibraryImport' method 'unsafe'
+
Add UnconditionalSuppressMessage attribute to parent method
diff --git a/src/tools/illink/src/ILLink.CodeFix/UnsafeModifierCodeFixHelpers.cs b/src/tools/illink/src/ILLink.CodeFix/UnsafeModifierCodeFixHelpers.cs
index 2813ff19d1402d..54a26551b379a6 100644
--- a/src/tools/illink/src/ILLink.CodeFix/UnsafeModifierCodeFixHelpers.cs
+++ b/src/tools/illink/src/ILLink.CodeFix/UnsafeModifierCodeFixHelpers.cs
@@ -168,8 +168,8 @@ private static AccessorDeclarationSyntax RemoveUnsafeModifier(AccessorDeclaratio
private static SyntaxTokenList AddUnsafeModifier(SyntaxTokenList modifiers)
{
- // Place unsafe before extern while preserving the existing modifier order.
- int insertionIndex = modifiers.IndexOf(SyntaxKind.ExternKeyword);
+ // Place unsafe before extern and partial while preserving the existing modifier order.
+ int insertionIndex = GetFirstModifierIndex(modifiers, SyntaxKind.ExternKeyword, SyntaxKind.PartialKeyword);
if (insertionIndex < 0)
insertionIndex = modifiers.Count;
@@ -186,6 +186,17 @@ private static SyntaxTokenList AddUnsafeModifier(SyntaxTokenList modifiers)
return modifiers.Insert(insertionIndex, unsafeModifier);
}
+ private static int GetFirstModifierIndex(SyntaxTokenList modifiers, SyntaxKind first, SyntaxKind second)
+ {
+ for (int i = 0; i < modifiers.Count; i++)
+ {
+ if (modifiers[i].IsKind(first) || modifiers[i].IsKind(second))
+ return i;
+ }
+
+ return -1;
+ }
+
private static SyntaxTokenList RemoveUnsafeModifier(SyntaxTokenList modifiers)
{
int unsafeIndex = GetUnsafeModifierIndex(modifiers);
diff --git a/src/tools/illink/src/ILLink.RoslynAnalyzer/LibraryImportRequiresExplicitSafetyAnalyzer.cs b/src/tools/illink/src/ILLink.RoslynAnalyzer/LibraryImportRequiresExplicitSafetyAnalyzer.cs
new file mode 100644
index 00000000000000..151d27abd40b10
--- /dev/null
+++ b/src/tools/illink/src/ILLink.RoslynAnalyzer/LibraryImportRequiresExplicitSafetyAnalyzer.cs
@@ -0,0 +1,82 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+#if DEBUG
+using System.Collections.Immutable;
+using ILLink.Shared;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Microsoft.CodeAnalysis.Diagnostics;
+
+namespace ILLink.RoslynAnalyzer
+{
+ ///
+ /// Reports IL5007 for methods with LibraryImportAttribute that do not declare an explicit
+ /// unsafe or safe contract.
+ ///
+ ///
+ /// Under the updated memory safety rules the compiler only requires the modifier when the generated
+ /// implementing part is extern, which depends on whether the signature needs marshalling. Because that
+ /// is an implementation detail of the source generator, the requirement is reported for every shape. The
+ /// diagnostic is reported regardless of whether the assembly has opted into the updated rules so that it can
+ /// be used to annotate a code base before the opt-in is flipped. It is disabled by default while this
+ /// migration tooling remains experimental.
+ ///
+ [DiagnosticAnalyzer(LanguageNames.CSharp)]
+ public sealed class LibraryImportRequiresExplicitSafetyAnalyzer : DiagnosticAnalyzer
+ {
+ private const string LibraryImportAttributeNamespace = "System.Runtime.InteropServices";
+ private const string LibraryImportAttributeName = "LibraryImportAttribute";
+
+ private static readonly DiagnosticDescriptor s_rule =
+ DiagnosticDescriptors.GetDiagnosticDescriptor(
+ DiagnosticId.LibraryImportRequiresExplicitSafety,
+ isEnabledByDefault: false);
+
+ public override ImmutableArray SupportedDiagnostics => [s_rule];
+
+ public override void Initialize(AnalysisContext context)
+ {
+ context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
+
+ if (!System.Diagnostics.Debugger.IsAttached)
+ context.EnableConcurrentExecution();
+
+ context.RegisterSymbolAction(AnalyzeMethod, SymbolKind.Method);
+ }
+
+ private static void AnalyzeMethod(SymbolAnalysisContext context)
+ {
+ var method = (IMethodSymbol)context.Symbol;
+
+ // Both parts of a partial member are analyzed as separate symbols, so report only from the part the
+ // user authored.
+ if (method.PartialDefinitionPart is not null)
+ return;
+
+ foreach (AttributeData attribute in method.GetAttributes())
+ {
+ if (attribute.AttributeClass is not { } attributeClass
+ || !attributeClass.IsTypeOf(LibraryImportAttributeNamespace, LibraryImportAttributeName))
+ {
+ continue;
+ }
+
+ // The source generator never copies LibraryImportAttribute onto the implementing part, so the
+ // attribute application always points at the declaration the user authored.
+ if (attribute.ApplicationSyntaxReference is not { } attributeReference
+ || attributeReference.GetSyntax(context.CancellationToken).FirstAncestorOrSelf() is not { } declaration
+ || UnsafeMigrationSyntaxHelpers.HasModifier(declaration, SyntaxKind.UnsafeKeyword)
+ || UnsafeMigrationSyntaxHelpers.HasSafeModifier(declaration))
+ {
+ return;
+ }
+
+ context.ReportDiagnostic(Diagnostic.Create(s_rule, declaration.Identifier.GetLocation()));
+ return;
+ }
+ }
+ }
+}
+#endif
diff --git a/src/tools/illink/src/ILLink.Shared/DiagnosticId.cs b/src/tools/illink/src/ILLink.Shared/DiagnosticId.cs
index 116954140fcd65..ba578b0da4a2a5 100644
--- a/src/tools/illink/src/ILLink.Shared/DiagnosticId.cs
+++ b/src/tools/illink/src/ILLink.Shared/DiagnosticId.cs
@@ -223,6 +223,7 @@ public enum DiagnosticId
// Memory safety migration diagnostic ids.
UnsafeMemberMissingSafetyDocumentation = 5005,
PointerSignatureRequiresUnsafe = 5006,
+ LibraryImportRequiresExplicitSafety = 5007,
}
public static class DiagnosticIdExtensions
@@ -255,7 +256,7 @@ public static string GetDiagnosticCategory(this DiagnosticId diagnosticId) =>
{
> 2000 and < 3000 => DiagnosticCategory.Trimming,
>= 3000 and < 3050 => DiagnosticCategory.SingleFile,
- 5005 or 5006 => DiagnosticCategory.Safety,
+ 5005 or 5006 or 5007 => DiagnosticCategory.Safety,
>= 3050 and <= 6000 => DiagnosticCategory.AOT,
_ => throw new ArgumentException($"The provided diagnostic id '{diagnosticId}' does not fall into the range of supported warning codes 2001 to 6000 (inclusive).")
};
diff --git a/src/tools/illink/src/ILLink.Shared/SharedStrings.resx b/src/tools/illink/src/ILLink.Shared/SharedStrings.resx
index dcb7b1bdc6cbab..7f46e89eea556c 100644
--- a/src/tools/illink/src/ILLink.Shared/SharedStrings.resx
+++ b/src/tools/illink/src/ILLink.Shared/SharedStrings.resx
@@ -699,6 +699,12 @@
A member with a pointer or function-pointer signature must be marked 'unsafe' or include a '<safety>' XML documentation comment explaining why it is safe.
+
+ Methods with 'LibraryImportAttribute' should declare their safety contract
+
+
+ A method with 'LibraryImportAttribute' must be marked 'unsafe' or 'safe' because the generated implementation calls into native code.
+
An attribute element has property but this could not be found.
diff --git a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/AddUnsafeToLibraryImportCodeFixTests.cs b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/AddUnsafeToLibraryImportCodeFixTests.cs
new file mode 100644
index 00000000000000..834e66dc60ca90
--- /dev/null
+++ b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/AddUnsafeToLibraryImportCodeFixTests.cs
@@ -0,0 +1,122 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+#if DEBUG
+using System.Threading.Tasks;
+using ILLink.CodeFix;
+using Microsoft.CodeAnalysis.Testing;
+using Xunit;
+
+namespace ILLink.RoslynAnalyzer.Tests
+{
+ ///
+ /// Verifies the IL5007 code fix, which conservatively marks methods with
+ /// LibraryImportAttribute as unsafe so a code base can be migrated to the updated memory safety
+ /// rules before the opt-in is flipped.
+ ///
+ public class AddUnsafeToLibraryImportCodeFixTests
+ {
+ public static TheoryData Declarations => new()
+ {
+ {
+ """
+ using System.Runtime.InteropServices;
+
+ partial class C
+ {
+ [LibraryImport("nativelib")]
+ static partial void {|IL5007:Method|}(int i);
+ }
+ """,
+ """
+ using System.Runtime.InteropServices;
+
+ partial class C
+ {
+ [LibraryImport("nativelib")]
+ static unsafe partial void Method(int i);
+ }
+ """
+ },
+ {
+ """
+ using System.Runtime.InteropServices;
+
+ partial class C
+ {
+ [LibraryImport("nativelib", StringMarshalling = StringMarshalling.Utf8)]
+ static partial void {|IL5007:Method|}(string s);
+ }
+ """,
+ """
+ using System.Runtime.InteropServices;
+
+ partial class C
+ {
+ [LibraryImport("nativelib", StringMarshalling = StringMarshalling.Utf8)]
+ static unsafe partial void Method(string s);
+ }
+ """
+ },
+ {
+ // The fix keeps documentation and attribute trivia attached to the declaration.
+ """
+ using System.Runtime.InteropServices;
+
+ partial class C
+ {
+ /// Invokes native code.
+ [LibraryImport("nativelib")]
+ static partial void {|IL5007:Method|}(int i);
+ }
+ """,
+ """
+ using System.Runtime.InteropServices;
+
+ partial class C
+ {
+ /// Invokes native code.
+ [LibraryImport("nativelib")]
+ static unsafe partial void Method(int i);
+ }
+ """
+ },
+ };
+
+ [Theory]
+ [MemberData(nameof(Declarations))]
+ public async Task AddsUnsafeToLibraryImportMethod(string source, string fixedSource)
+ {
+ var test = UnsafeMigrationTestHelpers
+ .CreateCodeFixTest(
+ source,
+ fixedSource);
+ // LibraryImportAttribute only exists in the live reference pack.
+ test.ReferenceAssemblies = new ReferenceAssemblies(string.Empty);
+ test.TestState.AdditionalReferences.AddRange(SourceGenerators.Tests.LiveReferencePack.GetMetadataReferences());
+ await test.RunAsync();
+ }
+
+ [Fact]
+ public async Task IsIdempotentForAlreadyAnnotatedMethods()
+ {
+ string source = """
+ using System.Runtime.InteropServices;
+
+ partial class C
+ {
+ [LibraryImport("nativelib")]
+ static unsafe partial void Method(int i);
+ }
+ """;
+
+ var test = UnsafeMigrationTestHelpers
+ .CreateCodeFixTest(
+ source);
+ test.ReferenceAssemblies = new ReferenceAssemblies(string.Empty);
+ test.TestState.AdditionalReferences.AddRange(SourceGenerators.Tests.LiveReferencePack.GetMetadataReferences());
+ await test.RunAsync();
+ }
+ }
+}
+#endif
diff --git a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/LibraryImportRequiresExplicitSafetyAnalyzerTests.cs b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/LibraryImportRequiresExplicitSafetyAnalyzerTests.cs
new file mode 100644
index 00000000000000..ec6b30e00f833a
--- /dev/null
+++ b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/LibraryImportRequiresExplicitSafetyAnalyzerTests.cs
@@ -0,0 +1,134 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+#if DEBUG
+using System.Threading.Tasks;
+using Xunit;
+
+namespace ILLink.RoslynAnalyzer.Tests
+{
+ ///
+ /// Verifies that IL5007 reports methods with LibraryImportAttribute that do not declare an
+ /// explicit safety contract, independently of the shape the source generator would emit for them.
+ ///
+ public class LibraryImportRequiresExplicitSafetyAnalyzerTests
+ {
+ public static TheoryData UnannotatedDeclarations => new()
+ {
+ // A blittable signature is implemented by an `extern` forwarder.
+ "static partial void {|IL5007:Method|}(int i);",
+ // A signature that needs marshalling is implemented by a wrapper around an `extern` local function.
+ "static partial void {|IL5007:Method|}(string s);",
+ "static partial void {|IL5007:Method|}();",
+ };
+
+ [Theory]
+ [MemberData(nameof(UnannotatedDeclarations))]
+ public async Task ReportsMethodWithoutSafetyModifier(string declaration)
+ {
+ string source = $$"""
+ using System.Runtime.InteropServices;
+
+ partial class C
+ {
+ [LibraryImport("nativelib")]
+ {{declaration}}
+ }
+ """;
+
+ await UnsafeMigrationTestHelpers
+ .CreateAnalyzerTest(source)
+ .RunAsync();
+ }
+
+ [Fact]
+ public async Task DoesNotReportMethodMarkedUnsafe()
+ {
+ string source = """
+ using System.Runtime.InteropServices;
+
+ partial class C
+ {
+ [LibraryImport("nativelib")]
+ static unsafe partial void Method(int i);
+ }
+ """;
+
+ await UnsafeMigrationTestHelpers
+ .CreateAnalyzerTest(source)
+ .RunAsync();
+ }
+
+ [Fact]
+ public async Task DoesNotReportMethodMarkedSafe()
+ {
+ // 'safe' is only allowed on 'extern' members today, so this uses the shape the generator emits for a
+ // blittable signature: an 'extern' implementing part that agrees with the user's declaration.
+ string source = """
+ using System.Runtime.InteropServices;
+
+ partial class C
+ {
+ [LibraryImport("nativelib")]
+ public static safe partial int Method(int i);
+ }
+
+ partial class C
+ {
+ [DllImport("nativelib", EntryPoint = "Method", ExactSpelling = true)]
+ public static safe extern partial int Method(int i);
+ }
+ """;
+
+ await UnsafeMigrationTestHelpers
+ .CreateAnalyzerTest(source)
+ .RunAsync();
+ }
+
+ [Fact]
+ public async Task DoesNotReportMethodWithoutLibraryImportAttribute()
+ {
+ string source = """
+ using System.Runtime.InteropServices;
+
+ partial class C
+ {
+ [DllImport("nativelib")]
+ static extern unsafe void Method(int i);
+
+ static partial void Other(int i);
+ }
+ """;
+
+ await UnsafeMigrationTestHelpers
+ .CreateAnalyzerTest(source)
+ .RunAsync();
+ }
+
+ [Fact]
+ public async Task ReportsOnceForPartialMemberWithImplementation()
+ {
+ // The wrapper shape the generator emits for a signature that needs marshalling: the implementing part
+ // is not 'extern', so neither part carries a safety modifier and only the user's part is reported.
+ string source = """
+ using System.Runtime.InteropServices;
+
+ partial class C
+ {
+ [LibraryImport("nativelib")]
+ public static partial int {|IL5007:Method|}(int i);
+ }
+
+ partial class C
+ {
+ public static partial int Method(int i) => 0;
+ }
+ """;
+
+ await UnsafeMigrationTestHelpers
+ .CreateAnalyzerTest(source)
+ .RunAsync();
+ }
+ }
+}
+#endif
diff --git a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/UnsafeMigrationTestHelpers.cs b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/UnsafeMigrationTestHelpers.cs
index 63ac6c37f9fca7..ec5f6eb7bcb578 100644
--- a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/UnsafeMigrationTestHelpers.cs
+++ b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/UnsafeMigrationTestHelpers.cs
@@ -70,7 +70,8 @@ internal static Solution SetOptions(Solution solution, ProjectId projectId)
var diagnosticOptions = compilationOptions.SpecificDiagnosticOptions
.SetItems(CSharpVerifierHelper.NullableWarnings)
.SetItem(DiagnosticId.UnsafeMemberMissingSafetyDocumentation.AsString(), ReportDiagnostic.Warn)
- .SetItem(DiagnosticId.PointerSignatureRequiresUnsafe.AsString(), ReportDiagnostic.Warn);
+ .SetItem(DiagnosticId.PointerSignatureRequiresUnsafe.AsString(), ReportDiagnostic.Warn)
+ .SetItem(DiagnosticId.LibraryImportRequiresExplicitSafety.AsString(), ReportDiagnostic.Warn);
compilationOptions = compilationOptions
.WithAllowUnsafe(true)
.WithWarningLevel(999)