From 29c423ffe073b203f4884b6d3aa3b27102b99f9f Mon Sep 17 00:00:00 2001 From: Sven Boemer Date: Thu, 15 Jun 2023 18:35:24 +0000 Subject: [PATCH 1/2] Limit number of tracked values in trim dataflow analysis --- .../src/ILLink.Shared/DataFlow/ValueSet.cs | 6 + .../DataFlowTests.g.cs | 6 + .../DataFlow/ExponentialDataFlow.cs | 168 ++++++++++++++++++ 3 files changed, 180 insertions(+) create mode 100644 src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/ExponentialDataFlow.cs diff --git a/src/tools/illink/src/ILLink.Shared/DataFlow/ValueSet.cs b/src/tools/illink/src/ILLink.Shared/DataFlow/ValueSet.cs index 199a2761f95f2a..e870d3de8b7a57 100644 --- a/src/tools/illink/src/ILLink.Shared/DataFlow/ValueSet.cs +++ b/src/tools/illink/src/ILLink.Shared/DataFlow/ValueSet.cs @@ -15,6 +15,8 @@ namespace ILLink.Shared.DataFlow public readonly struct ValueSet : IEquatable>, IEnumerable, IDeepCopyValue> where TValue : notnull { + const int MaxValuesInSet = 1024; + // Since we're going to do lot of type checks for this class a lot, it is much more efficient // if the class is sealed (as then the runtime can do a simple method table pointer comparison) private sealed class EnumerableValues : HashSet @@ -194,6 +196,10 @@ internal static ValueSet Meet (ValueSet left, ValueSet r var values = new EnumerableValues (left.DeepCopy ()); values.UnionWith (right.DeepCopy ()); + // Limit the number of values we track, to prevent hangs in case of patterns that + // create exponentially many possible values. This will result in analysis holes. + if (values.Count > MaxValuesInSet) + return default; return new ValueSet (values); } diff --git a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/DataFlowTests.g.cs b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/DataFlowTests.g.cs index a9d51cb1fa073e..7963bf379491bc 100644 --- a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/DataFlowTests.g.cs +++ b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/DataFlowTests.g.cs @@ -7,6 +7,12 @@ namespace ILLink.RoslynAnalyzer.Tests public sealed partial class DataFlowTests : LinkerTestBase { + [Fact] + public Task ExponentialDataFlow () + { + return RunTest (allowMissingWarnings: true); + } + [Fact] public Task GenericParameterDataFlowMarking () { diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/ExponentialDataFlow.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/ExponentialDataFlow.cs new file mode 100644 index 00000000000000..240a0e9ab08928 --- /dev/null +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/ExponentialDataFlow.cs @@ -0,0 +1,168 @@ +// 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. + +using System; +using System.Diagnostics.CodeAnalysis; +using Mono.Linker.Tests.Cases.Expectations.Assertions; +using Mono.Linker.Tests.Cases.Expectations.Helpers; + +namespace Mono.Linker.Tests.Cases.DataFlow +{ + [ExpectedNoWarnings] + [SkipKeptItemsValidation] + public class ExponentialDataFlow + { + public static void Main () + { + ExponentialArrayStates.Test (); + ExponentialArrayStatesDataFlow.Test (); + ArrayStatesDataFlow.Test (); + } + + class ExponentialArrayStates + { + public static void Test () + { + object[] data = new object[20]; + if (true) data[0] = new object (); + if (true) data[1] = new object (); + if (true) data[2] = new object (); + if (true) data[3] = new object (); + if (true) data[4] = new object (); + if (true) data[5] = new object (); + if (true) data[6] = new object (); + if (true) data[7] = new object (); + if (true) data[8] = new object (); + if (true) data[9] = new object (); + if (true) data[10] = new object (); + if (true) data[11] = new object (); + if (true) data[12] = new object (); + if (true) data[13] = new object (); + if (true) data[14] = new object (); + if (true) data[15] = new object (); + if (true) data[16] = new object (); + if (true) data[17] = new object (); + if (true) data[18] = new object (); + if (true) data[19] = new object (); + } + } + + class ArrayStatesDataFlow + { + class GenericTypeWithRequires<[DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)] T> + { + } + + [ExpectedWarning ("IL3050", ProducedBy = Tool.Analyzer | Tool.NativeAot)] + [ExpectedWarning ("IL2090", "'T'")] + public static void Test () + { + Type[] types = new Type[1] { typeof (int) }; + if (true) types[0] = typeof (T); + typeof (GenericTypeWithRequires<>).MakeGenericType (types); + } + } + + class ExponentialArrayStatesDataFlow + { + class GenericTypeWithRequires< + [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)] T0, + [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)] T1, + [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)] T2, + [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)] T3, + [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)] T4, + [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)] T5, + [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)] T6, + [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)] T7, + [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)] T8, + [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)] T9, + [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)] T10, + [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)] T11, + [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)] T12, + [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)] T13, + [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)] T14, + [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)] T15, + [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)] T16, + [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)] T17, + [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)] T18, + [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)] T19> + { + } + + [ExpectedWarning ("IL3050", ProducedBy = Tool.Analyzer | Tool.NativeAot)] + // The way we track arrays causes the analyzer to track exponentially many + // ArrayValues in the ValueSet for the pattern in this method, hitting the limit. + // When this happens, we replace the ValueSet wit a TopValue, which doesn't + // produce a warning in this case. + [ExpectedWarning ("IL2090", "'T'", ProducedBy = Tool.Trimmer | Tool.NativeAot)] + [ExpectedWarning ("IL2090", "'T'", ProducedBy = Tool.Trimmer | Tool.NativeAot)] + [ExpectedWarning ("IL2090", "'T'", ProducedBy = Tool.Trimmer | Tool.NativeAot)] + [ExpectedWarning ("IL2090", "'T'", ProducedBy = Tool.Trimmer | Tool.NativeAot)] + [ExpectedWarning ("IL2090", "'T'", ProducedBy = Tool.Trimmer | Tool.NativeAot)] + [ExpectedWarning ("IL2090", "'T'", ProducedBy = Tool.Trimmer | Tool.NativeAot)] + [ExpectedWarning ("IL2090", "'T'", ProducedBy = Tool.Trimmer | Tool.NativeAot)] + [ExpectedWarning ("IL2090", "'T'", ProducedBy = Tool.Trimmer | Tool.NativeAot)] + [ExpectedWarning ("IL2090", "'T'", ProducedBy = Tool.Trimmer | Tool.NativeAot)] + [ExpectedWarning ("IL2090", "'T'", ProducedBy = Tool.Trimmer | Tool.NativeAot)] + [ExpectedWarning ("IL2090", "'T'", ProducedBy = Tool.Trimmer | Tool.NativeAot)] + [ExpectedWarning ("IL2090", "'T'", ProducedBy = Tool.Trimmer | Tool.NativeAot)] + [ExpectedWarning ("IL2090", "'T'", ProducedBy = Tool.Trimmer | Tool.NativeAot)] + [ExpectedWarning ("IL2090", "'T'", ProducedBy = Tool.Trimmer | Tool.NativeAot)] + [ExpectedWarning ("IL2090", "'T'", ProducedBy = Tool.Trimmer | Tool.NativeAot)] + [ExpectedWarning ("IL2090", "'T'", ProducedBy = Tool.Trimmer | Tool.NativeAot)] + [ExpectedWarning ("IL2090", "'T'", ProducedBy = Tool.Trimmer | Tool.NativeAot)] + [ExpectedWarning ("IL2090", "'T'", ProducedBy = Tool.Trimmer | Tool.NativeAot)] + [ExpectedWarning ("IL2090", "'T'", ProducedBy = Tool.Trimmer | Tool.NativeAot)] + [ExpectedWarning ("IL2090", "'T'", ProducedBy = Tool.Trimmer | Tool.NativeAot)] + public static void Test () + { + Type[] types = new Type[20] { + typeof (int), + typeof (int), + typeof (int), + typeof (int), + typeof (int), + typeof (int), + typeof (int), + typeof (int), + typeof (int), + typeof (int), + typeof (int), + typeof (int), + typeof (int), + typeof (int), + typeof (int), + typeof (int), + typeof (int), + typeof (int), + typeof (int), + typeof (int) + }; + if (Condition) types[0] = typeof (T); + if (Condition) types[1] = typeof (T); + if (Condition) types[2] = typeof (T); + if (Condition) types[3] = typeof (T); + if (Condition) types[4] = typeof (T); + if (Condition) types[5] = typeof (T); + if (Condition) types[6] = typeof (T); + if (Condition) types[7] = typeof (T); + if (Condition) types[8] = typeof (T); + if (Condition) types[9] = typeof (T); + if (Condition) types[10] = typeof (T); + if (Condition) types[11] = typeof (T); + if (Condition) types[12] = typeof (T); + if (Condition) types[13] = typeof (T); + if (Condition) types[14] = typeof (T); + if (Condition) types[15] = typeof (T); + if (Condition) types[16] = typeof (T); + if (Condition) types[17] = typeof (T); + if (Condition) types[18] = typeof (T); + if (Condition) types[19] = typeof (T); + + typeof (GenericTypeWithRequires<,,,,,,,,,,,,,,,,,,,>).MakeGenericType (types); + } + + static bool Condition => Random.Shared.Next (2) == 0; + } + } +} From 9de6420e9e0625a082a641b0de618db9c420bc91 Mon Sep 17 00:00:00 2001 From: Sven Boemer Date: Fri, 16 Jun 2023 17:04:09 +0000 Subject: [PATCH 2/2] Adjust limit to 256 --- src/tools/illink/src/ILLink.Shared/DataFlow/ValueSet.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/illink/src/ILLink.Shared/DataFlow/ValueSet.cs b/src/tools/illink/src/ILLink.Shared/DataFlow/ValueSet.cs index e870d3de8b7a57..2fb1fc3f21746e 100644 --- a/src/tools/illink/src/ILLink.Shared/DataFlow/ValueSet.cs +++ b/src/tools/illink/src/ILLink.Shared/DataFlow/ValueSet.cs @@ -15,7 +15,7 @@ namespace ILLink.Shared.DataFlow public readonly struct ValueSet : IEquatable>, IEnumerable, IDeepCopyValue> where TValue : notnull { - const int MaxValuesInSet = 1024; + const int MaxValuesInSet = 256; // Since we're going to do lot of type checks for this class a lot, it is much more efficient // if the class is sealed (as then the runtime can do a simple method table pointer comparison)