提交 8a7cae5d 编写于 作者: M Manish Vasani

Add bunch of exclusions to dead code analysis for special methods

Fixes #30886: Bail oun special serialization constructors and methods with certain special attributes indicating special usage.

Fixes #30887: Bail out on ShouldSerializeXXX and ResetXXX pattern that was also excluded from legacy FxCop implementation and seems to be common in real world code.

Fixes #30377 : Bail out on methods with signature matching event handlers, which are very common in Web apps.
上级 151e59e6
......@@ -1868,6 +1868,106 @@ class Nested
}");
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)]
[WorkItem(30886, "https://github.com/dotnet/roslyn/issues/30886")]
public async Task SerializableConstructor_TypeImplementsISerializable()
{
await TestDiagnosticMissingAsync(
@"using System.Runtime.Serialization;
class C : ISerializable
{
public C()
{
}
private [|C|](SerializationInfo info, StreamingContext context)
{
}
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
}
}");
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)]
[WorkItem(30886, "https://github.com/dotnet/roslyn/issues/30886")]
public async Task SerializableConstructor_BaseTypeImplementsISerializable()
{
await TestDiagnosticMissingAsync(
@"using System;
using System.Runtime.Serialization;
class C : Exception
{
public C()
{
}
private [|C|](SerializationInfo info, StreamingContext context)
: base(info, context)
{
}
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
}
}");
}
[Theory, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)]
[InlineData(@"[System.Runtime.Serialization.OnDeserializingAttribute]")]
[InlineData(@"[System.Runtime.Serialization.OnDeserializedAttribute]")]
[InlineData(@"[System.Runtime.Serialization.OnSerializingAttribute]")]
[InlineData(@"[System.Runtime.Serialization.OnSerializedAttribute]")]
[InlineData(@"[System.Runtime.InteropServices.ComRegisterFunctionAttribute]")]
[InlineData(@"[System.Runtime.InteropServices.ComUnregisterFunctionAttribute]")]
public async Task MethodsWithSpecialAttributes(string attribute)
{
await TestDiagnosticMissingAsync(
$@"class C
{{
{attribute}
private void [|M|]()
{{
}}
}}");
}
[Theory, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)]
[InlineData("ShouldSerialize")]
[InlineData("Reset")]
[WorkItem(30887, "https://github.com/dotnet/roslyn/issues/30887")]
public async Task ShouldSerializeOrResetPropertyMethod(string prefix)
{
await TestDiagnosticMissingAsync(
$@"class C
{{
private bool [|{prefix}Data|]()
{{
return true;
}}
public int Data {{ get; private set; }}
}}");
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)]
[WorkItem(30377, "https://github.com/dotnet/roslyn/issues/30377")]
public async Task EventHandlerMethod()
{
await TestDiagnosticMissingAsync(
$@"using System;
class C
{{
private void [|M|](object o, EventArgs args)
{{
}}
}}");
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)]
public async Task FixAllFields_Document()
{
......
......@@ -56,6 +56,8 @@ private sealed class CompilationAnalyzer
private readonly object _gate;
private readonly Dictionary<ISymbol, ValueUsageInfo> _symbolValueUsageStateMap;
private readonly INamedTypeSymbol _taskType, _genericTaskType, _debuggerDisplayAttributeType, _structLayoutAttributeType;
private readonly INamedTypeSymbol _eventArgsType, _iSerializableType, _serializationInfoType, _streamingContextType;
private readonly ImmutableHashSet<INamedTypeSymbol> _attributeSetForMethodsToIgnore;
private CompilationAnalyzer(Compilation compilation)
{
......@@ -68,6 +70,52 @@ private CompilationAnalyzer(Compilation compilation)
_genericTaskType = compilation.TaskOfTType();
_debuggerDisplayAttributeType = compilation.DebuggerDisplayAttributeType();
_structLayoutAttributeType = compilation.StructLayoutAttributeType();
_eventArgsType = compilation.EventArgsType();
_iSerializableType = compilation.ISerializableType();
_serializationInfoType = compilation.SerializationInfoType();
_streamingContextType = compilation.StreamingContextType();
_attributeSetForMethodsToIgnore = ImmutableHashSet.CreateRange(GetAttributesForMethodsToIgnore(compilation));
}
private static IEnumerable<INamedTypeSymbol> GetAttributesForMethodsToIgnore(Compilation compilation)
{
// Ignore methods with special serialization attributes, which are invoked by the runtime
// for deserialization.
var onDeserializingAttribute = compilation.OnDeserializingAttribute();
if (onDeserializingAttribute != null)
{
yield return onDeserializingAttribute;
}
var onDeserializedAttribute = compilation.OnDeserializedAttribute();
if (onDeserializedAttribute != null)
{
yield return onDeserializedAttribute;
}
var onSerializingAttribute = compilation.OnSerializingAttribute();
if (onSerializingAttribute != null)
{
yield return onSerializingAttribute;
}
var onSerializedAttribute = compilation.OnSerializedAttribute();
if (onSerializedAttribute != null)
{
yield return onSerializedAttribute;
}
var comRegisterFunctionAttribute = compilation.ComRegisterFunctionAttribute();
if (comRegisterFunctionAttribute != null)
{
yield return comRegisterFunctionAttribute;
}
var comUnregisterFunctionAttribute = compilation.ComUnregisterFunctionAttribute();
if (comUnregisterFunctionAttribute != null)
{
yield return comUnregisterFunctionAttribute;
}
}
public static void CreateAndRegisterActions(CompilationStartAnalysisContext compilationStartContext)
......@@ -411,11 +459,8 @@ void AddDebuggerDisplayAttributeArgumentsCore(ISymbol symbol, ArrayBuilder<strin
/// 1. It is marked as "private".
/// 2. It is not an implicitly declared symbol.
/// 3. It is either a method, field, property or an event.
/// 4. If method, then one of the following must be true:
/// a. It is a constructor with non-zero parameters OR
/// b. It is a method with <see cref="MethodKind.Ordinary"/>,
/// such that it is not an accessor, not an entry point method,
/// not an extern method and is not an explicit interface method implementation.
/// 4. If method, then it is a constructor OR a method with <see cref="MethodKind.Ordinary"/>,
/// such that is meets a few criteria (see implementation details below).
/// 5. If field, then it must not be a backing field for an auto property.
/// Backing fields have a non-null <see cref="IFieldSymbol.AssociatedSymbol"/>.
/// 6. If property, then it must not be an explicit interface property implementation.
......@@ -439,14 +484,76 @@ private bool IsCandidateSymbol(ISymbol memberSymbol)
// without parameters.
// This is commonly used for static holder types
// that want to block instantiation of the type.
return methodSymbol.Parameters.Length > 0;
if (methodSymbol.Parameters.Length == 0)
{
return false;
}
// ISerializable constructor is invoked by the runtime for deserialization
// and it is a common pattern to have a private serialization constructor
// that is not explicitly referenced in code.
if (IsISerializableConstructor(methodSymbol))
{
return false;
}
return true;
case MethodKind.Ordinary:
// Do not flag accessors, as we will track the associated symbol.
return methodSymbol.AssociatedSymbol == null &&
!IsEntryPoint(methodSymbol) &&
!methodSymbol.IsExtern &&
methodSymbol.ExplicitInterfaceImplementations.IsEmpty;
// Do not track accessors, as we will track/flag the associated symbol.
if (methodSymbol.AssociatedSymbol != null)
{
return false;
}
// Do not flag unused entry point (Main) method.
if (IsEntryPoint(methodSymbol))
{
return false;
}
// It is fine to have unused virtual/abstract/overrides/extern
// methods as they might be used in another type in the containing
// type's type hierarchy.
if (methodSymbol.IsAbstract ||
methodSymbol.IsVirtual ||
methodSymbol.IsOverride ||
methodSymbol.IsExtern)
{
return false;
}
// Explicit interface implementations are not referenced explicitly,
// but are still used.
if (!methodSymbol.ExplicitInterfaceImplementations.IsEmpty)
{
return false;
}
// Ignore methods with special attributes that indicate special/reflection
// based access.
if (IsMethodWithSpecialAttribute(methodSymbol))
{
return false;
}
// ShouldSerializeXXX and ResetXXX are ok if there is a matching
// property XXX as they are used by the windows designer property grid
if (IsShouldSerializeOrResetPropertyMethod(methodSymbol))
{
return false;
}
// Ignore methods with event handler signature
// as lot of ASP.NET types have many special event handlers
// that are invoked with reflection (e.g. Application_XXX, Page_XXX,
// OnTransactionXXX, etc).
if (methodSymbol.HasEventHandlerSignature(_eventArgsType))
{
return false;
}
return true;
default:
return false;
......@@ -472,6 +579,40 @@ private bool IsEntryPoint(IMethodSymbol methodSymbol)
(methodSymbol.ReturnsVoid ||
methodSymbol.ReturnType.OriginalDefinition == _taskType ||
methodSymbol.ReturnType.OriginalDefinition == _genericTaskType);
private bool IsMethodWithSpecialAttribute(IMethodSymbol methodSymbol)
=> methodSymbol.ContainsAnyAttributeFrom(_attributeSetForMethodsToIgnore);
private bool IsShouldSerializeOrResetPropertyMethod(IMethodSymbol methodSymbol)
{
// ShouldSerializeXXX and ResetXXX are ok if there is a matching
// property XXX as they are used by the windows designer property grid
return methodSymbol.ReturnType.SpecialType == SpecialType.System_Boolean &&
methodSymbol.Parameters.IsEmpty &&
(IsSpecialMethodWithMatchingProperty("ShouldSerialize") ||
IsSpecialMethodWithMatchingProperty("Reset"));
// Local functions.
bool IsSpecialMethodWithMatchingProperty(string prefix)
{
if (methodSymbol.Name.StartsWith(prefix))
{
var suffix = methodSymbol.Name.Substring(prefix.Length);
return suffix.Length > 0 &&
methodSymbol.ContainingType.GetMembers(suffix).Any(m => m is IPropertySymbol);
}
return false;
}
}
private bool IsISerializableConstructor(IMethodSymbol methodSymbol)
=> methodSymbol.Parameters.Length == 2 &&
methodSymbol.Parameters[0].Type == _serializationInfoType &&
methodSymbol.Parameters[1].Type == _streamingContextType &&
_iSerializableType != null &&
methodSymbol.ContainingType.AllInterfaces.Contains(_iSerializableType);
}
}
}
......@@ -8,6 +8,7 @@
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Serialization;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.PooledObjects;
......@@ -107,13 +108,13 @@ public static INamedTypeSymbol IEnumerableOfTType(this Compilation compilation)
=> compilation.GetTypeByMetadataName(typeof(IEnumerable<>).FullName);
public static INamedTypeSymbol SerializableAttributeType(this Compilation compilation)
=> compilation.GetTypeByMetadataName("System.SerializableAttribute");
=> compilation.GetTypeByMetadataName(typeof(SerializableAttribute).FullName);
public static INamedTypeSymbol CoClassType(this Compilation compilation)
=> compilation.GetTypeByMetadataName(typeof(CoClassAttribute).FullName);
public static INamedTypeSymbol ComAliasNameAttributeType(this Compilation compilation)
=> compilation.GetTypeByMetadataName("System.Runtime.InteropServices.ComAliasNameAttribute");
=> compilation.GetTypeByMetadataName(typeof(ComAliasNameAttribute).FullName);
public static INamedTypeSymbol SuppressMessageAttributeType(this Compilation compilation)
=> compilation.GetTypeByMetadataName(typeof(SuppressMessageAttribute).FullName);
......@@ -122,9 +123,36 @@ public static INamedTypeSymbol TupleElementNamesAttributeType(this Compilation c
=> compilation.GetTypeByMetadataName(typeof(TupleElementNamesAttribute).FullName);
public static INamedTypeSymbol DynamicAttributeType(this Compilation compilation)
=> compilation.GetTypeByMetadataName("System.Runtime.CompilerServices.DynamicAttribute");
=> compilation.GetTypeByMetadataName(typeof(DynamicAttribute).FullName);
public static INamedTypeSymbol LazyOfTType(this Compilation compilation)
=> compilation.GetTypeByMetadataName(typeof(Lazy<>).FullName);
public static INamedTypeSymbol ISerializableType(this Compilation compilation)
=> compilation.GetTypeByMetadataName(typeof(ISerializable).FullName);
public static INamedTypeSymbol SerializationInfoType(this Compilation compilation)
=> compilation.GetTypeByMetadataName(typeof(SerializationInfo).FullName);
public static INamedTypeSymbol StreamingContextType(this Compilation compilation)
=> compilation.GetTypeByMetadataName(typeof(StreamingContext).FullName);
public static INamedTypeSymbol OnDeserializingAttribute(this Compilation compilation)
=> compilation.GetTypeByMetadataName(typeof(OnDeserializingAttribute).FullName);
public static INamedTypeSymbol OnDeserializedAttribute(this Compilation compilation)
=> compilation.GetTypeByMetadataName(typeof(OnDeserializedAttribute).FullName);
public static INamedTypeSymbol OnSerializingAttribute(this Compilation compilation)
=> compilation.GetTypeByMetadataName(typeof(OnSerializingAttribute).FullName);
public static INamedTypeSymbol OnSerializedAttribute(this Compilation compilation)
=> compilation.GetTypeByMetadataName(typeof(OnSerializedAttribute).FullName);
public static INamedTypeSymbol ComRegisterFunctionAttribute(this Compilation compilation)
=> compilation.GetTypeByMetadataName(typeof(ComRegisterFunctionAttribute).FullName);
public static INamedTypeSymbol ComUnregisterFunctionAttribute(this Compilation compilation)
=> compilation.GetTypeByMetadataName(typeof(ComUnregisterFunctionAttribute).FullName);
}
}
......@@ -357,5 +357,16 @@ public static PredefinedOperator GetPredefinedOperator(this IMethodSymbol symbol
return PredefinedOperator.None;
}
}
/// <summary>
/// Returns true for void returning methods with two parameters, where
/// the first parameter is of <see cref="object"/> type and the second
/// parameter inherits from or equals <see cref="EventArgs"/> type.
/// </summary>
public static bool HasEventHandlerSignature(this IMethodSymbol method, INamedTypeSymbol eventArgsType)
=> eventArgsType != null &&
method.Parameters.Length == 2 &&
method.Parameters[0].Type.SpecialType == SpecialType.System_Object &&
method.Parameters[1].Type.InheritsFromOrEquals(eventArgsType);
}
}
......@@ -1010,5 +1010,9 @@ private static bool VerifyGetAwaiter(IMethodSymbol getAwaiter)
return symbols.FilterToVisibleAndBrowsableSymbols(hideAdvancedMembers, compilation)
.WhereAsArray(s => !s.IsUnsafe());
}
public static bool ContainsAnyAttributeFrom(this ISymbol symbol, ImmutableHashSet<INamedTypeSymbol> attributes)
=> !attributes.IsEmpty &&
symbol.GetAttributes().Any(a => a.AttributeClass != null && attributes.Contains(a.AttributeClass));
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册