Add ILLink annotations to S.D.Common related to DbConnectionStringBuilder (#54280)
authorKrzysztof Wicher <mordotymoja@gmail.com>
Wed, 23 Jun 2021 12:02:37 +0000 (14:02 +0200)
committerGitHub <noreply@github.com>
Wed, 23 Jun 2021 12:02:37 +0000 (14:02 +0200)
* Add ILLink annotations to S.D.Common related to DbConnectionStringBuilder

* address some feedback

* Make GetEvents() safe

* make GetProperties safe

* Mark GetProperties with RUC

src/libraries/System.Data.Common/ref/System.Data.Common.cs
src/libraries/System.Data.Common/src/ILLink/ILLink.Suppressions.xml
src/libraries/System.Data.Common/src/System/Data/Common/DbConnectionStringBuilder.cs
src/libraries/System.Data.Common/tests/TrimmingTests/DbConnectionStringBuilder.cs [new file with mode: 0644]
src/libraries/System.Data.Common/tests/TrimmingTests/System.Data.Common.TrimmingTests.proj

index a4bc1f6..ce7de38 100644 (file)
@@ -2095,6 +2095,7 @@ namespace System.Data.Common
         System.Data.IDbTransaction System.Data.IDbConnection.BeginTransaction(System.Data.IsolationLevel isolationLevel) { throw null; }
         System.Data.IDbCommand System.Data.IDbConnection.CreateCommand() { throw null; }
     }
+    [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)]
     public partial class DbConnectionStringBuilder : System.Collections.ICollection, System.Collections.IDictionary, System.Collections.IEnumerable, System.ComponentModel.ICustomTypeDescriptor
     {
         public DbConnectionStringBuilder() { }
@@ -2130,6 +2131,7 @@ namespace System.Data.Common
         protected internal void ClearPropertyDescriptors() { }
         public virtual bool ContainsKey(string keyword) { throw null; }
         public virtual bool EquivalentTo(System.Data.Common.DbConnectionStringBuilder connectionStringBuilder) { throw null; }
+        [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("PropertyDescriptor's PropertyType cannot be statically discovered.")]
         protected virtual void GetProperties(System.Collections.Hashtable propertyDescriptors) { }
         public virtual bool Remove(string keyword) { throw null; }
         public virtual bool ShouldSerialize(string keyword) { throw null; }
index ffcb7d7..3e47be7 100644 (file)
       <property name="Scope">member</property>
       <property name="Target">M:System.Data.ProviderBase.SchemaMapping.SetupSchemaWithoutKeyInfo(System.Data.MissingMappingAction,System.Data.MissingSchemaAction,System.Boolean,System.Data.DataColumn,System.Object)</property>
     </attribute>
-    <attribute fullname="System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessageAttribute">
-      <argument>ILLink</argument>
-      <argument>IL2026</argument>
-      <property name="Scope">member</property>
-      <property name="Target">M:System.Data.Common.DbConnectionStringBuilder.GetProperties(System.Collections.Hashtable)</property>
-    </attribute>
-    <attribute fullname="System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessageAttribute">
-      <argument>ILLink</argument>
-      <argument>IL2026</argument>
-      <property name="Scope">member</property>
-      <property name="Target">M:System.Data.Common.DbConnectionStringBuilder.System#ComponentModel#ICustomTypeDescriptor#GetAttributes</property>
-    </attribute>
-    <attribute fullname="System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessageAttribute">
-      <argument>ILLink</argument>
-      <argument>IL2026</argument>
-      <property name="Scope">member</property>
-      <property name="Target">M:System.Data.Common.DbConnectionStringBuilder.System#ComponentModel#ICustomTypeDescriptor#GetClassName</property>
-    </attribute>
-    <attribute fullname="System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessageAttribute">
-      <argument>ILLink</argument>
-      <argument>IL2026</argument>
-      <property name="Scope">member</property>
-      <property name="Target">M:System.Data.Common.DbConnectionStringBuilder.System#ComponentModel#ICustomTypeDescriptor#GetComponentName</property>
-    </attribute>
-    <attribute fullname="System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessageAttribute">
-      <argument>ILLink</argument>
-      <argument>IL2026</argument>
-      <property name="Scope">member</property>
-      <property name="Target">M:System.Data.Common.DbConnectionStringBuilder.System#ComponentModel#ICustomTypeDescriptor#GetConverter</property>
-    </attribute>
-    <attribute fullname="System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessageAttribute">
-      <argument>ILLink</argument>
-      <argument>IL2026</argument>
-      <property name="Scope">member</property>
-      <property name="Target">M:System.Data.Common.DbConnectionStringBuilder.System#ComponentModel#ICustomTypeDescriptor#GetDefaultEvent</property>
-    </attribute>
-    <attribute fullname="System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessageAttribute">
-      <argument>ILLink</argument>
-      <argument>IL2026</argument>
-      <property name="Scope">member</property>
-      <property name="Target">M:System.Data.Common.DbConnectionStringBuilder.System#ComponentModel#ICustomTypeDescriptor#GetDefaultProperty</property>
-    </attribute>
-    <attribute fullname="System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessageAttribute">
-      <argument>ILLink</argument>
-      <argument>IL2026</argument>
-      <property name="Scope">member</property>
-      <property name="Target">M:System.Data.Common.DbConnectionStringBuilder.System#ComponentModel#ICustomTypeDescriptor#GetEditor(System.Type)</property>
-    </attribute>
-    <attribute fullname="System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessageAttribute">
-      <argument>ILLink</argument>
-      <argument>IL2026</argument>
-      <property name="Scope">member</property>
-      <property name="Target">M:System.Data.Common.DbConnectionStringBuilder.System#ComponentModel#ICustomTypeDescriptor#GetEvents</property>
-    </attribute>
   </assembly>
 </linker>
index 3c3ac63..027fe42 100644 (file)
@@ -12,6 +12,7 @@ using System.Diagnostics.CodeAnalysis;
 
 namespace System.Data.Common
 {
+    [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)]
     public class DbConnectionStringBuilder : IDictionary, ICustomTypeDescriptor
     {
         // keyword->value currently listed in the connection string
@@ -387,6 +388,7 @@ namespace System.Data.Common
             return attributes;
         }
 
+        [RequiresUnreferencedCode("PropertyDescriptor's PropertyType cannot be statically discovered.")]
         private PropertyDescriptorCollection GetProperties()
         {
             PropertyDescriptorCollection? propertyDescriptors = _propertyDescriptors;
@@ -412,11 +414,16 @@ namespace System.Data.Common
             return propertyDescriptors;
         }
 
+        [RequiresUnreferencedCode("PropertyDescriptor's PropertyType cannot be statically discovered.")]
         protected virtual void GetProperties(Hashtable propertyDescriptors)
         {
             long logScopeId = DataCommonEventSource.Log.EnterScope("<comm.DbConnectionStringBuilder.GetProperties|API> {0}", ObjectID);
             try
             {
+                // Below call is necessary to tell the trimmer that it should mark derived types appropriately.
+                // We cannot use overload which takes type because the result might differ if derived class implements ICustomTypeDescriptor.
+                Type thisType = GetType();
+
                 // show all strongly typed properties (not already added)
                 // except ConnectionString iff BrowsableConnectionString
                 Attribute[]? attributes;
@@ -562,16 +569,28 @@ namespace System.Data.Common
             return new PropertyDescriptorCollection(filteredPropertiesArray);
         }
 
-// TODO: Enable after System.ComponentModel.TypeConverter is annotated
+        // TODO-NULLABLE: Enable after System.ComponentModel.TypeConverter is annotated
 #nullable disable
+        [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode",
+            Justification = "The type of component is statically known. This class is marked with [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)]")]
         string ICustomTypeDescriptor.GetClassName()
         {
+            // Below call is necessary to tell the trimmer that it should mark derived types appropriately.
+            // We cannot use overload which takes type because the result might differ if derived class implements ICustomTypeDescriptor.
+            Type thisType = GetType();
             return TypeDescriptor.GetClassName(this, true);
         }
+        [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode",
+            Justification = "The type of component is statically known. This class is marked with [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)]")]
         string ICustomTypeDescriptor.GetComponentName()
         {
+            // Below call is necessary to tell the trimmer that it should mark derived types appropriately.
+            // We cannot use overload which takes type because the result might differ if derived class implements ICustomTypeDescriptor.
+            Type thisType = GetType();
             return TypeDescriptor.GetComponentName(this, true);
         }
+        [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode",
+            Justification = "The type of component is statically known. This class is marked with [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)]")]
         AttributeCollection ICustomTypeDescriptor.GetAttributes()
         {
             return TypeDescriptor.GetAttributes(this, true);
@@ -606,8 +625,13 @@ namespace System.Data.Common
         {
             return TypeDescriptor.GetDefaultEvent(this, true);
         }
+        [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode",
+            Justification = "The type of component is statically known. This class is marked with [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)]")]
         EventDescriptorCollection ICustomTypeDescriptor.GetEvents()
         {
+            // Below call is necessary to tell the trimmer that it should mark derived types appropriately.
+            // We cannot use overload which takes type because the result might differ if derived class implements ICustomTypeDescriptor.
+            Type thisType = GetType();
             return TypeDescriptor.GetEvents(this, true);
         }
         [RequiresUnreferencedCode("The public parameterless constructor or the 'Default' static field may be trimmed from the Attribute's Type.")]
diff --git a/src/libraries/System.Data.Common/tests/TrimmingTests/DbConnectionStringBuilder.cs b/src/libraries/System.Data.Common/tests/TrimmingTests/DbConnectionStringBuilder.cs
new file mode 100644 (file)
index 0000000..78ac56e
--- /dev/null
@@ -0,0 +1,159 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Data.Common;
+using System.ComponentModel;
+using System.Collections;
+
+namespace DbConnectionStringBuilderTrimmingTests
+{
+    class Program
+    {
+        static int Main(string[] args)
+        {
+            DbConnectionStringBuilder2 dcsb2 = new();
+            ICustomTypeDescriptor td = dcsb2;
+
+            if (td.GetClassName() != "DbConnectionStringBuilderTrimmingTests.DbConnectionStringBuilder2")
+            {
+                throw new Exception("Class name got trimmed");
+            }
+
+            if (td.GetComponentName() != "Test Component Name")
+            {
+                throw new Exception("Component name got trimmed");
+            }
+
+            bool foundAttr = false;
+
+            foreach (Attribute attr in td.GetAttributes())
+            {
+                if (attr.GetType().Name == "TestAttribute")
+                {
+                    if (attr.ToString() != "Test Attribute Value")
+                    {
+                        throw new Exception("Test attribute value differs");
+                    }
+
+                    if (foundAttr)
+                    {
+                        throw new Exception("More than one attribute found");
+                    }
+
+                    foundAttr = true;
+                }
+            }
+
+            if (!foundAttr)
+            {
+                throw new Exception("Attribute not found");
+            }
+
+            bool foundEvent = false;
+            bool foundEventWithDisplayName = false;
+
+            foreach (EventDescriptor ev in td.GetEvents())
+            {
+                if (ev.DisplayName == "TestEvent")
+                {
+                    if (foundEvent)
+                    {
+                        throw new Exception("More than one event TestEvent found.");
+                    }
+
+                    foundEvent = true;
+                }
+
+                if (ev.DisplayName == "Event With DisplayName")
+                {
+                    if (foundEventWithDisplayName)
+                    {
+                        throw new Exception("More than one event with display name found.");
+                    }
+
+                    foundEventWithDisplayName = true;
+                }
+            }
+
+            if (!foundEvent)
+            {
+                throw new Exception("Event not found");
+            }
+
+            if (!foundEventWithDisplayName)
+            {
+                throw new Exception("Event with DisplayName not found");
+            }
+
+            bool propertyFound = false;
+            foreach (DictionaryEntry kv in dcsb2.GetProperties2())
+            {
+                PropertyDescriptor val = (PropertyDescriptor)kv.Value;
+                if (val.Name == "TestProperty")
+                {
+                    if (propertyFound)
+                    {
+                        throw new Exception("More than one property TestProperty found.");
+                    }
+
+                    propertyFound = true;
+                }
+            }
+
+            if (!propertyFound)
+            {
+                throw new Exception("Property not found");
+            }
+
+            return 100;
+        }
+    }
+
+    [Test("Test Attribute Value")]
+    class DbConnectionStringBuilder2 : DbConnectionStringBuilder, IComponent
+    {
+#pragma warning disable CS0067 // The event is never used
+        public event EventHandler Disposed;
+        public event Action TestEvent;
+        [DisplayName("Event With DisplayName")]
+        public event Action TestEvent2;
+#pragma warning restore CS0067
+
+        public string TestProperty { get; set; }
+        public ISite Site { get => new TestSite(); set => throw new NotImplementedException(); }
+        public void Dispose() { }
+
+        public Hashtable GetProperties2()
+        {
+            Hashtable propertyDescriptors = new Hashtable();
+            GetProperties(propertyDescriptors);
+            return propertyDescriptors;
+        }
+    }
+
+    class TestSite : INestedSite
+    {
+        public string FullName => null;
+        public IComponent Component => throw new NotImplementedException();
+        public IContainer Container => throw new NotImplementedException();
+        public bool DesignMode => throw new NotImplementedException();
+        public string Name { get => "Test Component Name"; set => throw new NotImplementedException(); }
+        public object GetService(Type serviceType) => null;
+    }
+
+    class TestAttribute : Attribute
+    {
+        public string Test { get; private set; }
+
+        public TestAttribute(string test)
+        {
+            Test = test;
+        }
+
+        public override string ToString()
+        {
+            return Test;
+        }
+    }
+}
index da4a46f..7254c33 100644 (file)
@@ -1,5 +1,10 @@
 <Project DefaultTargets="Build">
   <Import Project="$([MSBuild]::GetPathOfFileAbove(Directory.Build.props))" />
 
+  <ItemGroup>
+    <TestConsoleAppSourceFiles Include="CreateSqlXmlReader.cs" />
+    <TestConsoleAppSourceFiles Include="DbConnectionStringBuilder.cs" />
+  </ItemGroup>
+
   <Import Project="$([MSBuild]::GetPathOfFileAbove(Directory.Build.targets))" />
 </Project>