From ec6c0d38242aea386c3de38647da6bc89c969070 Mon Sep 17 00:00:00 2001 From: Tarek Mahmoud Sayed Date: Tue, 15 Jun 2021 15:02:03 -0700 Subject: [PATCH 01/15] Allow restricting cultures creation with any arbitrary names in Globalization Invariant Mode --- .../tests/CultureInfo/GetCultureInfo.cs | 56 +++++++++++++++++++ .../src/System/Globalization/CultureData.cs | 5 ++ .../System/Globalization/GlobalizationMode.cs | 2 + .../ManifestBasedResourceGroveler.cs | 2 +- 4 files changed, 64 insertions(+), 1 deletion(-) diff --git a/src/libraries/System.Globalization/tests/CultureInfo/GetCultureInfo.cs b/src/libraries/System.Globalization/tests/CultureInfo/GetCultureInfo.cs index 0b4bdd5708992..d030b81aa7444 100644 --- a/src/libraries/System.Globalization/tests/CultureInfo/GetCultureInfo.cs +++ b/src/libraries/System.Globalization/tests/CultureInfo/GetCultureInfo.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using Microsoft.DotNet.RemoteExecutor; using System.Diagnostics; +using System.Collections.Concurrent; using Xunit; namespace System.Globalization.Tests @@ -139,5 +140,60 @@ public void PredefinedCulturesOnlyEnvVarTest(string predefinedCulturesOnlyEnvVar } }, cultureName, predefinedCulturesOnlyEnvVar, new RemoteInvokeOptions { StartInfo = psi }).Dispose(); } + + [ConditionalTheory(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] + [InlineData(true, true)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(false, false)] + public void TestAllowInvariantCultureOnly(bool enableInvariant, bool enableInvariantOnly) + { + var psi = new ProcessStartInfo(); + psi.Environment.Clear(); + + if (enableInvariant) { psi.Environment.Add("DOTNET_SYSTEM_GLOBALIZATION_INVARIANT", "true"); } + if (enableInvariantOnly) { psi.Environment.Add("DOTNET_SYSTEM_GLOBALIZATION_INVARIANT_CULTURE_ONLY", "true"); } + + RemoteExecutor.Invoke((invarintEnabled, invariantOnlyEnabled) => + { + bool restrictedMode = bool.Parse(invarintEnabled) && bool.Parse(invariantOnlyEnabled); + + // First ensure we can create the current cultures regaldless of the mode we are in + Assert.NotNull(CultureInfo.CurrentCulture); + Assert.NotNull(CultureInfo.CurrentUICulture); + + // Invariant culture should be valid all the time + Assert.Equal("", new CultureInfo("").Name); + Assert.Equal("", CultureInfo.InvariantCulture.Name); + + if (restrictedMode) + { + Assert.Equal("", CultureInfo.CurrentCulture.Name); + Assert.Equal("", CultureInfo.CurrentUICulture.Name); + + // Throwing exception is testing accessing the resources in this restricted mode. + // We should retrieve the resources from the neutral resources in the main assemblies. + AssertExtensions.Throws(() => new CultureInfo("en-US")); + AssertExtensions.Throws(() => new CultureInfo("en")); + + AssertExtensions.Throws(() => new CultureInfo("ja-JP")); + AssertExtensions.Throws(() => new CultureInfo("es")); + + // Test throwing exceptions from non-core assemblies. + Exception exception = Record.Exception(() => new ConcurrentBag(null)); + Assert.NotNull(exception); + Assert.IsType(exception); + Assert.Equal("collection", (exception as ArgumentNullException).ParamName); + Assert.Equal("The collection argument is null. (Parameter 'collection')", exception.Message); + } + else + { + Assert.Equal("en-US", new CultureInfo("en-US").Name); + Assert.Equal("ja-JP", new CultureInfo("ja-JP").Name); + Assert.Equal("en", new CultureInfo("en").Name); + Assert.Equal("es", new CultureInfo("es").Name); + } + }, enableInvariant.ToString(), enableInvariantOnly.ToString(), new RemoteInvokeOptions { StartInfo = psi }).Dispose(); + } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.cs index 55e12c1148330..9f770b6adffd3 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.cs @@ -669,6 +669,11 @@ private static CultureData CreateCultureWithInvariantData() return CultureData.Invariant; } + if (GlobalizationMode.AllowInvariantCultureOnly) + { + return null; + } + if (GlobalizationMode.PredefinedCulturesOnly) { Debug.Assert(!GlobalizationMode.Invariant); diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/GlobalizationMode.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/GlobalizationMode.cs index 5afa2a4e2b979..112a32debe2f0 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/GlobalizationMode.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/GlobalizationMode.cs @@ -13,6 +13,7 @@ private static partial class Settings { internal static readonly bool PredefinedCulturesOnly = AppContextConfigHelper.GetBooleanConfig("System.Globalization.PredefinedCulturesOnly", "DOTNET_SYSTEM_GLOBALIZATION_PREDEFINED_CULTURES_ONLY"); internal static bool Invariant { get; } = GetInvariantSwitchValue(); + internal static readonly bool AllowInvariantCultureOnly = AppContextConfigHelper.GetBooleanConfig("System.Globalization.AllowInvariantCultureOnly", "DOTNET_SYSTEM_GLOBALIZATION_INVARIANT_CULTURE_ONLY"); } // Note: Invariant=true and Invariant=false are substituted at different levels in the ILLink.Substitutions file. @@ -21,6 +22,7 @@ private static partial class Settings internal static bool Invariant => Settings.Invariant; internal static bool PredefinedCulturesOnly => !Invariant && Settings.PredefinedCulturesOnly; + internal static bool AllowInvariantCultureOnly => Invariant && Settings.AllowInvariantCultureOnly; private static bool GetInvariantSwitchValue() => AppContextConfigHelper.GetBooleanConfig("System.Globalization.Invariant", "DOTNET_SYSTEM_GLOBALIZATION_INVARIANT"); diff --git a/src/libraries/System.Private.CoreLib/src/System/Resources/ManifestBasedResourceGroveler.cs b/src/libraries/System.Private.CoreLib/src/System/Resources/ManifestBasedResourceGroveler.cs index 18b714a42ce38..2eb860a16df33 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Resources/ManifestBasedResourceGroveler.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Resources/ManifestBasedResourceGroveler.cs @@ -139,7 +139,7 @@ internal static CultureInfo GetNeutralResourcesLanguage(Assembly a, out Ultimate Debug.Assert(a != null, "assembly != null"); NeutralResourcesLanguageAttribute? attr = a.GetCustomAttribute(); - if (attr == null) + if (attr == null || GlobalizationMode.AllowInvariantCultureOnly) { fallbackLocation = UltimateResourceFallbackLocation.MainAssembly; return CultureInfo.InvariantCulture; From 20760d0877414a37ac6e2b2f672a4456c979d605 Mon Sep 17 00:00:00 2001 From: Tarek Mahmoud Sayed Date: Wed, 16 Jun 2021 14:20:06 -0700 Subject: [PATCH 02/15] Address the feedback --- .../tests/CultureInfo/GetCultureInfo.cs | 6 +++--- .../src/System/Globalization/GlobalizationMode.cs | 3 +-- .../tests/TrimmingTests/InvariantGlobalizationTrue.cs | 2 +- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/libraries/System.Globalization/tests/CultureInfo/GetCultureInfo.cs b/src/libraries/System.Globalization/tests/CultureInfo/GetCultureInfo.cs index d030b81aa7444..6ac3b8da7ae8a 100644 --- a/src/libraries/System.Globalization/tests/CultureInfo/GetCultureInfo.cs +++ b/src/libraries/System.Globalization/tests/CultureInfo/GetCultureInfo.cs @@ -154,11 +154,11 @@ public void TestAllowInvariantCultureOnly(bool enableInvariant, bool enableInvar if (enableInvariant) { psi.Environment.Add("DOTNET_SYSTEM_GLOBALIZATION_INVARIANT", "true"); } if (enableInvariantOnly) { psi.Environment.Add("DOTNET_SYSTEM_GLOBALIZATION_INVARIANT_CULTURE_ONLY", "true"); } - RemoteExecutor.Invoke((invarintEnabled, invariantOnlyEnabled) => + RemoteExecutor.Invoke((invariantEnabled, invariantOnlyEnabled) => { - bool restrictedMode = bool.Parse(invarintEnabled) && bool.Parse(invariantOnlyEnabled); + bool restrictedMode = bool.Parse(invariantEnabled) && bool.Parse(invariantOnlyEnabled); - // First ensure we can create the current cultures regaldless of the mode we are in + // First ensure we can create the current cultures regardless of the mode we are in Assert.NotNull(CultureInfo.CurrentCulture); Assert.NotNull(CultureInfo.CurrentUICulture); diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/GlobalizationMode.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/GlobalizationMode.cs index 112a32debe2f0..ccf912944a65c 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/GlobalizationMode.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/GlobalizationMode.cs @@ -13,7 +13,6 @@ private static partial class Settings { internal static readonly bool PredefinedCulturesOnly = AppContextConfigHelper.GetBooleanConfig("System.Globalization.PredefinedCulturesOnly", "DOTNET_SYSTEM_GLOBALIZATION_PREDEFINED_CULTURES_ONLY"); internal static bool Invariant { get; } = GetInvariantSwitchValue(); - internal static readonly bool AllowInvariantCultureOnly = AppContextConfigHelper.GetBooleanConfig("System.Globalization.AllowInvariantCultureOnly", "DOTNET_SYSTEM_GLOBALIZATION_INVARIANT_CULTURE_ONLY"); } // Note: Invariant=true and Invariant=false are substituted at different levels in the ILLink.Substitutions file. @@ -22,7 +21,7 @@ private static partial class Settings internal static bool Invariant => Settings.Invariant; internal static bool PredefinedCulturesOnly => !Invariant && Settings.PredefinedCulturesOnly; - internal static bool AllowInvariantCultureOnly => Invariant && Settings.AllowInvariantCultureOnly; + internal static bool AllowInvariantCultureOnly { get; } = Invariant && AppContextConfigHelper.GetBooleanConfig("System.Globalization.AllowInvariantCultureOnly", "DOTNET_SYSTEM_GLOBALIZATION_INVARIANT_CULTURE_ONLY"); private static bool GetInvariantSwitchValue() => AppContextConfigHelper.GetBooleanConfig("System.Globalization.Invariant", "DOTNET_SYSTEM_GLOBALIZATION_INVARIANT"); diff --git a/src/libraries/System.Runtime/tests/TrimmingTests/InvariantGlobalizationTrue.cs b/src/libraries/System.Runtime/tests/TrimmingTests/InvariantGlobalizationTrue.cs index 7daa557239f3d..1735a50fcc580 100644 --- a/src/libraries/System.Runtime/tests/TrimmingTests/InvariantGlobalizationTrue.cs +++ b/src/libraries/System.Runtime/tests/TrimmingTests/InvariantGlobalizationTrue.cs @@ -28,7 +28,7 @@ static int Main(string[] args) foreach (MemberInfo member in globalizationMode.GetMembers(allStatics)) { // properties and their backing getter methods are OK - if (member is PropertyInfo || member.Name.StartsWith("get_")) + if ((member is PropertyInfo || member.Name.StartsWith("get_")) || (member is FieldInfo && member.Name.Contains("AllowInvariantCultureOnly"))) { continue; } From 50e2c06d2ea48ad678fc64c8de29ad17cb77e474 Mon Sep 17 00:00:00 2001 From: Tarek Mahmoud Sayed Date: Wed, 16 Jun 2021 14:33:44 -0700 Subject: [PATCH 03/15] Add more Invariant test --- .../tests/CultureInfo/GetCultureInfo.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/libraries/System.Globalization/tests/CultureInfo/GetCultureInfo.cs b/src/libraries/System.Globalization/tests/CultureInfo/GetCultureInfo.cs index 6ac3b8da7ae8a..8cb579eb580ba 100644 --- a/src/libraries/System.Globalization/tests/CultureInfo/GetCultureInfo.cs +++ b/src/libraries/System.Globalization/tests/CultureInfo/GetCultureInfo.cs @@ -193,6 +193,15 @@ public void TestAllowInvariantCultureOnly(bool enableInvariant, bool enableInvar Assert.Equal("en", new CultureInfo("en").Name); Assert.Equal("es", new CultureInfo("es").Name); } + + // Ensure the Invariant Mode functionality still work + if (bool.Parse(invariantEnabled)) + { + Assert.True(CultureInfo.CurrentCulture.Calendar is GregorianCalendar); + Assert.True("abcd".Equals("ABCD", StringComparison.CurrentCultureIgnoreCase)); + Assert.Equal("Invariant Language (Invariant Country)", CultureInfo.CurrentCulture.NativeName); + } + }, enableInvariant.ToString(), enableInvariantOnly.ToString(), new RemoteInvokeOptions { StartInfo = psi }).Dispose(); } } From b56aa677c5d4528744957fb584ac96cc23e8d508 Mon Sep 17 00:00:00 2001 From: Tarek Mahmoud Sayed Date: Thu, 17 Jun 2021 16:09:21 -0700 Subject: [PATCH 04/15] Fix test failing on Linux --- .../tests/TrimmingTests/InvariantGlobalizationTrue.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/libraries/System.Runtime/tests/TrimmingTests/InvariantGlobalizationTrue.cs b/src/libraries/System.Runtime/tests/TrimmingTests/InvariantGlobalizationTrue.cs index 1735a50fcc580..5fcf78e48e2a7 100644 --- a/src/libraries/System.Runtime/tests/TrimmingTests/InvariantGlobalizationTrue.cs +++ b/src/libraries/System.Runtime/tests/TrimmingTests/InvariantGlobalizationTrue.cs @@ -28,7 +28,9 @@ static int Main(string[] args) foreach (MemberInfo member in globalizationMode.GetMembers(allStatics)) { // properties and their backing getter methods are OK - if ((member is PropertyInfo || member.Name.StartsWith("get_")) || (member is FieldInfo && member.Name.Contains("AllowInvariantCultureOnly"))) + if ((member is PropertyInfo || member.Name.StartsWith("get_")) || + (member is FieldInfo && member.Name.Contains("AllowInvariantCultureOnly")) || + (member is ConstructorInfo && member.Name.Contains(".cctor"))) { continue; } From 21dfbbaef4882b97c64fcc1c33b690d7a73ae673 Mon Sep 17 00:00:00 2001 From: Tarek Mahmoud Sayed Date: Thu, 24 Jun 2021 10:28:10 -0700 Subject: [PATCH 05/15] Implement throw by default on culture creation with Invariant Mode --- .../tests/CultureInfo/GetCultureInfo.cs | 24 +++++++++++-------- .../Invariant/runtimeconfig.template.json | 3 ++- .../ILLink/ILLink.Substitutions.Shared.xml | 1 + .../src/Resources/Strings.resx | 3 +++ .../src/System/AppContextConfigHelper.cs | 7 ++---- .../src/System/Globalization/CultureData.cs | 4 ++-- .../src/System/Globalization/CultureInfo.cs | 8 +++---- .../System/Globalization/GlobalizationMode.cs | 5 ++-- .../ManifestBasedResourceGroveler.cs | 2 +- .../InvariantGlobalizationTrue.cs | 16 ++----------- .../System.Runtime.TrimmingTests.proj | 2 +- 11 files changed, 34 insertions(+), 41 deletions(-) diff --git a/src/libraries/System.Globalization/tests/CultureInfo/GetCultureInfo.cs b/src/libraries/System.Globalization/tests/CultureInfo/GetCultureInfo.cs index 8cb579eb580ba..f87383e351c1e 100644 --- a/src/libraries/System.Globalization/tests/CultureInfo/GetCultureInfo.cs +++ b/src/libraries/System.Globalization/tests/CultureInfo/GetCultureInfo.cs @@ -142,21 +142,25 @@ public void PredefinedCulturesOnlyEnvVarTest(string predefinedCulturesOnlyEnvVar } [ConditionalTheory(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] - [InlineData(true, true)] - [InlineData(true, false)] - [InlineData(false, true)] - [InlineData(false, false)] - public void TestAllowInvariantCultureOnly(bool enableInvariant, bool enableInvariantOnly) + [InlineData(true, true, false)] + [InlineData(true, true, true)] + [InlineData(true, false, true)] + [InlineData(false, true, true)] + [InlineData(false, true, false)] + [InlineData(false, false, true)] + public void TestAllowInvariantCultureOnly(bool enableInvariant, bool predefinedCulturesOnly, bool declarePredefinedCulturesOnly) { var psi = new ProcessStartInfo(); psi.Environment.Clear(); - if (enableInvariant) { psi.Environment.Add("DOTNET_SYSTEM_GLOBALIZATION_INVARIANT", "true"); } - if (enableInvariantOnly) { psi.Environment.Add("DOTNET_SYSTEM_GLOBALIZATION_INVARIANT_CULTURE_ONLY", "true"); } + if (enableInvariant) { psi.Environment.Add("DOTNET_SYSTEM_GLOBALIZATION_INVARIANT", "true"); } + if (declarePredefinedCulturesOnly) { psi.Environment.Add("DOTNET_SYSTEM_GLOBALIZATION_PREDEFINED_CULTURES_ONLY", predefinedCulturesOnly ? "true" : "false"); } - RemoteExecutor.Invoke((invariantEnabled, invariantOnlyEnabled) => + bool restricted = enableInvariant && (declarePredefinedCulturesOnly ? predefinedCulturesOnly : true); + + RemoteExecutor.Invoke((invariantEnabled, isRestricted) => { - bool restrictedMode = bool.Parse(invariantEnabled) && bool.Parse(invariantOnlyEnabled); + bool restrictedMode = bool.Parse(isRestricted); // First ensure we can create the current cultures regardless of the mode we are in Assert.NotNull(CultureInfo.CurrentCulture); @@ -202,7 +206,7 @@ public void TestAllowInvariantCultureOnly(bool enableInvariant, bool enableInvar Assert.Equal("Invariant Language (Invariant Country)", CultureInfo.CurrentCulture.NativeName); } - }, enableInvariant.ToString(), enableInvariantOnly.ToString(), new RemoteInvokeOptions { StartInfo = psi }).Dispose(); + }, enableInvariant.ToString(), restricted.ToString(), new RemoteInvokeOptions { StartInfo = psi }).Dispose(); } } } diff --git a/src/libraries/System.Globalization/tests/Invariant/runtimeconfig.template.json b/src/libraries/System.Globalization/tests/Invariant/runtimeconfig.template.json index dd404e151b8d6..076d849845574 100644 --- a/src/libraries/System.Globalization/tests/Invariant/runtimeconfig.template.json +++ b/src/libraries/System.Globalization/tests/Invariant/runtimeconfig.template.json @@ -1,5 +1,6 @@ { "configProperties": { - "System.Globalization.Invariant": true + "System.Globalization.Invariant": true, + "System.Globalization.PredefinedCulturesOnly": false } } \ No newline at end of file diff --git a/src/libraries/System.Private.CoreLib/src/ILLink/ILLink.Substitutions.Shared.xml b/src/libraries/System.Private.CoreLib/src/ILLink/ILLink.Substitutions.Shared.xml index ba2db281981b8..f7ca754bb6a92 100644 --- a/src/libraries/System.Private.CoreLib/src/ILLink/ILLink.Substitutions.Shared.xml +++ b/src/libraries/System.Private.CoreLib/src/ILLink/ILLink.Substitutions.Shared.xml @@ -10,6 +10,7 @@ to be trimmed when Invariant=true, and allows for the Settings static cctor (on Unix) to be preserved when Invariant=false. --> + diff --git a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx index 7c80c23f63241..b9c59908cb024 100644 --- a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx +++ b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx @@ -957,6 +957,9 @@ Culture is not supported. + + Globalization Invariant mode is enabled which limit the cultures that can be created. Either turn off the Invariant mode or use only the Invariant culture. Consult the link https://aka.ms/GlobalizationInvariantMode for more information. + Resolved assembly's simple name should be the same as of the requested assembly. diff --git a/src/libraries/System.Private.CoreLib/src/System/AppContextConfigHelper.cs b/src/libraries/System.Private.CoreLib/src/System/AppContextConfigHelper.cs index 331ef281bb32a..712f8973ca697 100644 --- a/src/libraries/System.Private.CoreLib/src/System/AppContextConfigHelper.cs +++ b/src/libraries/System.Private.CoreLib/src/System/AppContextConfigHelper.cs @@ -10,15 +10,12 @@ internal static class AppContextConfigHelper internal static bool GetBooleanConfig(string configName, bool defaultValue) => AppContext.TryGetSwitch(configName, out bool value) ? value : defaultValue; - internal static bool GetBooleanConfig(string switchName, string envVariable) + internal static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) { if (!AppContext.TryGetSwitch(switchName, out bool ret)) { string? switchValue = Environment.GetEnvironmentVariable(envVariable); - if (switchValue != null) - { - ret = bool.IsTrueStringIgnoreCase(switchValue) || switchValue.Equals("1"); - } + ret = switchValue != null ? (bool.IsTrueStringIgnoreCase(switchValue) || switchValue.Equals("1")) : defaultValue; } return ret; diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.cs index 9f770b6adffd3..78f9800fe9bb4 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.cs @@ -669,7 +669,7 @@ private static CultureData CreateCultureWithInvariantData() return CultureData.Invariant; } - if (GlobalizationMode.AllowInvariantCultureOnly) + if (GlobalizationMode.Invariant && GlobalizationMode.PredefinedCulturesOnly) { return null; } @@ -870,7 +870,7 @@ internal static CultureData GetCultureData(int culture, bool bUseUserOverride) if (GlobalizationMode.Invariant) { // LCID is not supported in the InvariantMode - throw new CultureNotFoundException(nameof(culture), culture, SR.Argument_CultureNotSupported); + throw new CultureNotFoundException(nameof(culture), culture, SR.Argument_CultureNotSupportedInInvariantMode); } // Convert the lcid to a name, then use that diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureInfo.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureInfo.cs index 729119c8c163a..ae507cd13efde 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureInfo.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureInfo.cs @@ -174,7 +174,7 @@ public CultureInfo(string name, bool useUserOverride) if (cultureData == null) { - throw new CultureNotFoundException(nameof(name), name, SR.Argument_CultureNotSupported); + throw new CultureNotFoundException(nameof(name), name, GlobalizationMode.Invariant ? SR.Argument_CultureNotSupportedInInvariantMode : SR.Argument_CultureNotSupported); } _cultureData = cultureData; @@ -249,7 +249,7 @@ internal CultureInfo(string cultureName, string textAndCompareCultureName) } CultureData? cultureData = CultureData.GetCultureData(cultureName, false) ?? - throw new CultureNotFoundException(nameof(cultureName), cultureName, SR.Argument_CultureNotSupported); + throw new CultureNotFoundException(nameof(cultureName), cultureName, GlobalizationMode.Invariant ? SR.Argument_CultureNotSupportedInInvariantMode : SR.Argument_CultureNotSupported); _cultureData = cultureData; @@ -1060,7 +1060,7 @@ public static CultureInfo GetCultureInfo(int culture) } catch (ArgumentException) { - throw new CultureNotFoundException(nameof(culture), culture, SR.Argument_CultureNotSupported); + throw new CultureNotFoundException(nameof(culture), culture, GlobalizationMode.Invariant ? SR.Argument_CultureNotSupportedInInvariantMode : SR.Argument_CultureNotSupported); } lock (lcidTable) @@ -1096,7 +1096,7 @@ public static CultureInfo GetCultureInfo(string name) } result = CreateCultureInfoNoThrow(name, useUserOverride: false) ?? - throw new CultureNotFoundException(nameof(name), name, SR.Argument_CultureNotSupported); + throw new CultureNotFoundException(nameof(name), name, GlobalizationMode.Invariant ? SR.Argument_CultureNotSupportedInInvariantMode : SR.Argument_CultureNotSupported); result._isReadOnly = true; // Remember our name as constructed. Do NOT use alternate sort name versions because diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/GlobalizationMode.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/GlobalizationMode.cs index ccf912944a65c..3536ed7d534e7 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/GlobalizationMode.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/GlobalizationMode.cs @@ -11,8 +11,8 @@ internal static partial class GlobalizationMode // split from GlobalizationMode so the whole class can be trimmed when Invariant=true. private static partial class Settings { - internal static readonly bool PredefinedCulturesOnly = AppContextConfigHelper.GetBooleanConfig("System.Globalization.PredefinedCulturesOnly", "DOTNET_SYSTEM_GLOBALIZATION_PREDEFINED_CULTURES_ONLY"); internal static bool Invariant { get; } = GetInvariantSwitchValue(); + internal static readonly bool PredefinedCulturesOnly = AppContextConfigHelper.GetBooleanConfig("System.Globalization.PredefinedCulturesOnly", "DOTNET_SYSTEM_GLOBALIZATION_PREDEFINED_CULTURES_ONLY", Invariant); } // Note: Invariant=true and Invariant=false are substituted at different levels in the ILLink.Substitutions file. @@ -20,8 +20,7 @@ private static partial class Settings // static cctor (on Unix) to be preserved when Invariant=false. internal static bool Invariant => Settings.Invariant; - internal static bool PredefinedCulturesOnly => !Invariant && Settings.PredefinedCulturesOnly; - internal static bool AllowInvariantCultureOnly { get; } = Invariant && AppContextConfigHelper.GetBooleanConfig("System.Globalization.AllowInvariantCultureOnly", "DOTNET_SYSTEM_GLOBALIZATION_INVARIANT_CULTURE_ONLY"); + internal static bool PredefinedCulturesOnly => Settings.PredefinedCulturesOnly; private static bool GetInvariantSwitchValue() => AppContextConfigHelper.GetBooleanConfig("System.Globalization.Invariant", "DOTNET_SYSTEM_GLOBALIZATION_INVARIANT"); diff --git a/src/libraries/System.Private.CoreLib/src/System/Resources/ManifestBasedResourceGroveler.cs b/src/libraries/System.Private.CoreLib/src/System/Resources/ManifestBasedResourceGroveler.cs index 2eb860a16df33..c940600ee8c25 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Resources/ManifestBasedResourceGroveler.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Resources/ManifestBasedResourceGroveler.cs @@ -139,7 +139,7 @@ internal static CultureInfo GetNeutralResourcesLanguage(Assembly a, out Ultimate Debug.Assert(a != null, "assembly != null"); NeutralResourcesLanguageAttribute? attr = a.GetCustomAttribute(); - if (attr == null || GlobalizationMode.AllowInvariantCultureOnly) + if (attr == null || (GlobalizationMode.Invariant && GlobalizationMode.PredefinedCulturesOnly)) { fallbackLocation = UltimateResourceFallbackLocation.MainAssembly; return CultureInfo.InvariantCulture; diff --git a/src/libraries/System.Runtime/tests/TrimmingTests/InvariantGlobalizationTrue.cs b/src/libraries/System.Runtime/tests/TrimmingTests/InvariantGlobalizationTrue.cs index 5fcf78e48e2a7..d3d1e18099de2 100644 --- a/src/libraries/System.Runtime/tests/TrimmingTests/InvariantGlobalizationTrue.cs +++ b/src/libraries/System.Runtime/tests/TrimmingTests/InvariantGlobalizationTrue.cs @@ -4,7 +4,6 @@ using System; using System.Globalization; using System.Reflection; -using System.Threading; /// /// Ensures setting InvariantGlobalization = true still works in a trimmed app. @@ -13,24 +12,13 @@ class Program { static int Main(string[] args) { - // since we are using Invariant GlobalizationMode = true, setting the culture doesn't matter. - // The app will always use Invariant mode, so even in the Turkish culture, 'i' ToUpper will be "I" - Thread.CurrentThread.CurrentCulture = new CultureInfo("tr-TR"); - if ("i".ToUpper() != "I") - { - // 'i' ToUpper was not "I", so fail - return -1; - } - // Ensure the internal GlobalizationMode class is trimmed correctly Type globalizationMode = GetCoreLibType("System.Globalization.GlobalizationMode"); const BindingFlags allStatics = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static; foreach (MemberInfo member in globalizationMode.GetMembers(allStatics)) { // properties and their backing getter methods are OK - if ((member is PropertyInfo || member.Name.StartsWith("get_")) || - (member is FieldInfo && member.Name.Contains("AllowInvariantCultureOnly")) || - (member is ConstructorInfo && member.Name.Contains(".cctor"))) + if (member is PropertyInfo || member.Name.StartsWith("get_")) { continue; } @@ -46,7 +34,7 @@ static int Main(string[] args) // Some unexpected member was left on GlobalizationMode, fail Console.WriteLine($"Member '{member.Name}' was not trimmed from GlobalizationMode, but should have been."); - return -2; + return -1; } return 100; diff --git a/src/libraries/System.Runtime/tests/TrimmingTests/System.Runtime.TrimmingTests.proj b/src/libraries/System.Runtime/tests/TrimmingTests/System.Runtime.TrimmingTests.proj index 02b4882782e96..2f2da7a4e1205 100644 --- a/src/libraries/System.Runtime/tests/TrimmingTests/System.Runtime.TrimmingTests.proj +++ b/src/libraries/System.Runtime/tests/TrimmingTests/System.Runtime.TrimmingTests.proj @@ -15,7 +15,7 @@ --feature System.Globalization.Invariant false - --feature System.Globalization.Invariant true + --feature System.Globalization.Invariant true --feature System.Globalization.PredefinedCulturesOnly true