Can't delete unused method

70 views
Skip to first unread message

Rick Martin

unread,
Apr 14, 2020, 12:41:48 PM4/14/20
to mono-cecil
I have an assembly with a single module.
I'm iterating over all the methods in this module and wish to delete the ones that aren't used. I'm positive that they aren't used, as they're the result of an obfuscator adding junk code.

To determine whether a method is used, I iterate all call instructions from all methods of all types in the main module and cache the distinct full names. Inefficient but so be it.



var assembly = AssemblyDefinition.ReadAssembly(path);
var module = assembly.MainModule;

var calls = module.Types
    .SelectMany(m => m.Methods)
    .Where(m => m.HasBody)
    .SelectMany(m => m.Body.Instructions)
    .Where(m => m.OpCode == OpCodes.Call)
    .Select(i => i.Operand as MethodReference)
    .Select(i => i.FullName)
    .Distinct().ToArray();

I then iterate the methods of all types and see if the method's full name is in this list. If it's not, it's not referenced anywhere and should be removed.

foreach (var t in module.Types)
{
        var toRemove = new List<MethodDefinition>();

    foreach (var m in t.Methods)
    {
        if (calls.All(c => c != m.FullName))
        {
            toRemove.Add(m);
        }
    }

    foreach (var m in toRemove)
    {
        t.Methods.Remove(m);
    }
}

Seems fine, I see the methods that I would have removed by hand. However, when I then go to write the assembly back, I get a vague error.

System.ArgumentException: 'Member 'System.Void Foo(System.Int32)' is declared in another module and needs to be imported'

Have I missed something in finding all the method usages? Am I wrong in thinking that I don't need to import references since I'm not adding anything new?

Thanks in advance

Jb Evain

unread,
Apr 14, 2020, 12:44:42 PM4/14/20
to mono-...@googlegroups.com
Hey Rick,

Yes, it seems you missed some usages :)

In the IL, the method call be called using callvirt for instance, or referenced by a ldtoken.

Unfortunately the error is not very explicit, but you're completely right, you don't need to import a reference here. It just means that Cecil is seeing a reference to a stray method that is without a module.

Jb


--
--
--
mono-cecil
---
You received this message because you are subscribed to the Google Groups "mono-cecil" group.
To unsubscribe from this group and stop receiving emails from it, send an email to mono-cecil+...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/mono-cecil/9764e3d5-21a8-4017-84e4-2eb89f18ec5f%40googlegroups.com.

Rick Martin

unread,
Apr 14, 2020, 11:05:07 PM4/14/20
to mono-cecil
Hiya :)
Thanks for the reply, yes I did some reading and ended up finding that some methods are called with callvirt, or ldftn/ldvirtftn in some cases. I also ran in to a case where a usage was in a nested type, and my original code wasn't searching through the nesting.
Problem solved.

I'm trying to apply the same logic to find fields which are only written to and never read. I'm whitelisting fields by finding all reads, assuming that Ldfld, Ldflda, Ldsfld and Ldsflda are the only Opcodes I should expect for reading.

private static IEnumerable<FieldReference> GetFieldReadsInProperties(IEnumerable<PropertyDefinition> properties)
{
    var getMethods = properties.Select(p => p.GetMethod).Where(m => m != null);
    var setMethods = properties.Select(p => p.SetMethod).Where(m => m != null);

    var allMethods = getMethods.Concat(setMethods).Where(m => m.HasBody);
            
    return allMethods
        .SelectMany(m => m.Body.Instructions)
        .Where(i => ReadOpCodes.Any(o => i.OpCode == o))
        .Select(f => f.Operand as FieldReference)
        .DistinctBy(f => f.FullName);
}

private static IEnumerable<FieldReference> GetFieldReadsInMethods(IEnumerable<MethodDefinition> methods)
{
    return methods.Where(m => m.HasBody)
        .SelectMany(m => m.Body.Instructions)
        .Where(i => ReadOpCodes.Any(o => i.OpCode == o))
        .Select(f => f.Operand as FieldReference)
        .DistinctBy(f => f.FullName);
}

private static OpCode[] ReadOpCodes = new[] { OpCodes.Ldfld, OpCodes.Ldflda, OpCodes.Ldsfld, OpCodes.Ldsflda };

This seems to be ok, but it trips up with enum fields, again with a vague error :D

System.ArgumentException
  HResult=0x80070057
  Message=Value does not fall within the expected range.
  Source=Mono.Cecil
  StackTrace:
   at Mono.Cecil.Mixin.GetEnumUnderlyingType(TypeDefinition self) in <redacted>\cecil-master\Mono.Cecil\TypeDefinition.cs:line 600
   at Mono.Cecil.MetadataBuilder.GetConstantType(TypeReference constant_type, Object constant) in <redacted>\cecil-master\Mono.Cecil\AssemblyWriter.cs:line 1882
   at Mono.Cecil.MetadataBuilder.AddConstant(IConstantProvider owner, TypeReference type) in <redacted>\cecil-master\Mono.Cecil\AssemblyWriter.cs:line 1864
   at Mono.Cecil.MetadataBuilder.AddField(FieldDefinition field) in <redacted>\cecil-master\Mono.Cecil\AssemblyWriter.cs:line 1624
   at Mono.Cecil.MetadataBuilder.AddFields(TypeDefinition type) in <redacted>\cecil-master\Mono.Cecil\AssemblyWriter.cs:line 1602
   at Mono.Cecil.MetadataBuilder.AddType(TypeDefinition type) in <redacted>\cecil-master\Mono.Cecil\AssemblyWriter.cs:line 1437
   at Mono.Cecil.MetadataBuilder.AddNestedTypes(TypeDefinition type) in <redacted>\cecil-master\Mono.Cecil\AssemblyWriter.cs:line 1592
   at Mono.Cecil.MetadataBuilder.AddType(TypeDefinition type) in <redacted>\cecil-master\Mono.Cecil\AssemblyWriter.cs:line 1455
   at Mono.Cecil.MetadataBuilder.AddTypes() in <redacted>\cecil-master\Mono.Cecil\AssemblyWriter.cs:line 1413
   at Mono.Cecil.MetadataBuilder.BuildTypes() in <redacted>\cecil-master\Mono.Cecil\AssemblyWriter.cs:line 1266
   at Mono.Cecil.MetadataBuilder.BuildModule() in <redacted>\cecil-master\Mono.Cecil\AssemblyWriter.cs:line 1036
   at Mono.Cecil.MetadataBuilder.BuildMetadata() in <redacted>\cecil-master\Mono.Cecil\AssemblyWriter.cs:line 1006
   at Mono.Cecil.ModuleWriter.<>c.<BuildMetadata>b__2_0(MetadataBuilder builder, MetadataReader _) in <redacted>\cecil-master\Mono.Cecil\AssemblyWriter.cs:line 144
   at Mono.Cecil.ModuleDefinition.Read[TItem,TRet](TItem item, Func`3 read) in <redacted>\cecil-master\Mono.Cecil\ModuleDefinition.cs:line 950
   at Mono.Cecil.ModuleWriter.BuildMetadata(ModuleDefinition module, MetadataBuilder metadata) in <redacted>\cecil-master\Mono.Cecil\AssemblyWriter.cs:line 143
   at Mono.Cecil.ModuleWriter.Write(ModuleDefinition module, Disposable`1 stream, WriterParameters parameters) in <redacted>\cecil-master\Mono.Cecil\AssemblyWriter.cs:line 119
   at Mono.Cecil.ModuleWriter.WriteModule(ModuleDefinition module, Disposable`1 stream, WriterParameters parameters) in <redacted>\cecil-master\Mono.Cecil\AssemblyWriter.cs:line 78
   at Mono.Cecil.ModuleDefinition.Write(String fileName, WriterParameters parameters) in <redacted>\cecil-master\Mono.Cecil\ModuleDefinition.cs:line 1139
   at Mono.Cecil.AssemblyDefinition.Write(String fileName, WriterParameters parameters) in <redacted>\cecil-master\Mono.Cecil\AssemblyDefinition.cs:line 161
   at Mono.Cecil.AssemblyDefinition.Write(String fileName) in <redacted>\cecil-master\Mono.Cecil\AssemblyDefinition.cs:line 156
   at <redacted>(String[] args) in <redacted>

Guess I need to do some more reading :D

Jb Evain

unread,
Apr 15, 2020, 12:28:05 PM4/15/20
to mono-...@googlegroups.com
The error originates from:


Which points at you removing the magic instance field of an enum type that Cecil uses to find out what primitive type the enum is. (Don't remove this field :) )

Jb

--
--
--
mono-cecil
---
You received this message because you are subscribed to the Google Groups "mono-cecil" group.
To unsubscribe from this group and stop receiving emails from it, send an email to mono-cecil+...@googlegroups.com.
Reply all
Reply to author
Forward
0 new messages