|
// 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.Collections.Generic;
using System.Linq;
using Mono.Cecil;
using Mono.Cecil.Cil;
namespace TLens.Analyzers
{
sealed class UnnecessaryFieldsAssignmentAnalyzer : Analyzer
{
[Flags]
enum Access
{
None = 0,
Read = 1 << 1,
Write = 1 << 2,
ValuePropagation = 1 << 3,
}
readonly Dictionary<FieldDefinition, Access> fields = new Dictionary<FieldDefinition, Access>();
readonly Dictionary<FieldDefinition, List<MethodDefinition>> writes = new Dictionary<FieldDefinition, List<MethodDefinition>>();
protected override void ProcessMethod(MethodDefinition method)
{
if (!method.HasBody)
return;
foreach (var instr in method.Body.Instructions)
{
Access access;
switch (instr.OpCode.Code)
{
case Code.Ldsfld:
case Code.Ldfld:
access = Access.Read;
break;
case Code.Ldsflda:
case Code.Ldflda:
access = Access.Read | Access.Write;
break;
case Code.Stsfld:
case Code.Stfld:
access = Access.Write;
switch (instr.Previous.OpCode.Code)
{
case Code.Ldarg_0 when method.IsStatic:
case Code.Ldarg:
case Code.Ldarg_1:
case Code.Ldarg_2:
case Code.Ldarg_3:
case Code.Ldarg_S:
access |= Access.ValuePropagation;
break;
}
break;
default:
continue;
}
var reference = (FieldReference)instr.Operand;
FieldDefinition field = reference.Resolve();
if (field == null)
continue;
if (access == Access.Write)
{
if (!writes.TryGetValue(field, out var methods))
{
methods = new List<MethodDefinition>();
writes.Add(field, methods);
}
methods.Add(method);
}
if (!fields.TryAdd(field, access))
fields[field] |= access;
}
}
public override void PrintResults(int maxCount)
{
var static_entries = fields.Where(l => l.Value == Access.Write && l.Key.IsStatic).
OrderBy(l => l.Key, default(FieldTypeSizeComparer)).
ThenBy(l => l.Key.DeclaringType.FullName).
Take(maxCount);
if (static_entries.Any())
{
PrintHeader("Unnecessary static fields assignments");
foreach (var entry in static_entries)
{
Console.WriteLine($"Field '{entry.Key.FullName}' is never read but has value assigned");
foreach (var loc in writes[entry.Key])
{
Console.WriteLine($"\t{loc.ToDisplay()}");
}
Console.WriteLine();
}
}
var instance_entries = fields.Where(l => l.Value == Access.Write && !l.Key.IsStatic).
OrderBy(l => l.Key, default(FieldTypeSizeComparer)).
ThenBy(l => l.Key.DeclaringType.FullName).
Take(maxCount);
if (instance_entries.Any())
{
PrintHeader("Unnecessary instance fields assignments");
foreach (var entry in instance_entries)
{
Console.WriteLine($"Field '{entry.Key.FullName}' is never read but has value assigned");
foreach (var loc in writes[entry.Key])
{
Console.WriteLine($"\t{loc.ToDisplay()}");
}
Console.WriteLine();
}
}
}
struct FieldTypeSizeComparer : IComparer<FieldDefinition>
{
public int Compare(FieldDefinition x, FieldDefinition y)
{
var x_type = x.FieldType;
var y_type = y.FieldType;
return GetSize(y_type).CompareTo(GetSize(x_type));
}
static int GetSize(TypeReference type)
{
if (type.IsArray)
return 100;
if (type.MetadataType == MetadataType.String)
return 50;
if (type.IsPrimitive || type.IsPointer)
return 1;
return 10;
}
}
}
}
|