Reputation: 4262
The code at the end of this question has a GetHashCode() on Source. The same holds for anonymous types: the compiler generates similar code.
Using Crypto Obfuscator, the GetHashCode public method is renamed when you specify a renaming method. Result is that the GroupBy returns the wrong elements: elements that should be equal are considered different since they refer to different objects and the default GetHashCode implementation is used.
Is there a sensible way to disable renaming of Crypto Obfuscator on essential methods such as GetHashCode(), especially when they are auto-generated by the compiler?
Or do we have to completely stop using anonymous types, replace them by explicit types and make sure all GetHashCode and Equals methods are decorated correctly with the Obfuscation attribute?
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
namespace Invantive.Basics
{
/// <summary>
/// Check obfuscation not too aggressive.
/// </summary>
[Obfuscation(ApplyToMembers=false, Exclude = true)]
public static class ObfuscationAsserter
{
private class Source
{
public string S1;
public string S2;
public Source(string s1, string s2)
{
this.S1 = s1;
this.S2 = s2;
}
public override int GetHashCode()
{
return 23 * this.S1.GetHashCode() + this.S2.GetHashCode();
}
public override bool Equals(object o)
{
if (o is Source s)
{
return s.S1 == this.S1 && s.S2 == this.S2;
}
else
{
return false;
}
}
}
/// <summary>
/// Check anonymous types etc. are correctly obfuscated.
/// </summary>
public static void Check()
{
List<Source> strings = new List<Source>();
strings.Add(new Source("a", "a"));
strings.Add(new Source("a", "b"));
strings.Add(new Source("a", "b"));
strings.Add(new Source("c", "c"));
int expected = 3;
int t1cnt = strings.GroupBy(x => new { x.S1, x.S2 }).ToList().Count();
int t2cnt = strings.GroupBy(x => new { x.S1, x.S2 }.GetHashCode()).ToList().Count();
int t3cnt = strings.GroupBy(x => x.S1 + x.S2).ToList().Count();
int t4cnt = strings.GroupBy(x => (x.S1 + x.S2).GetHashCode()).ToList().Count();
int t5cnt = strings.GroupBy(x => x).ToList().Count();
int t6cnt = strings.GroupBy(x => x.GetHashCode()).ToList().Count();
int t7cnt = strings.GroupBy(x => (x.S1, x.S2)).ToList().Count();
int t8cnt = strings.GroupBy(x => (x.S1, x.S2).GetHashCode()).ToList().Count();
InvantiveTrace.WriteLine($"t1: {t1cnt}, t2: {t2cnt}, t3: {t3cnt}, t4: {t4cnt}, t5: {t5cnt}, t6: {t6cnt}, t7: {t7cnt}, t8: {t8cnt}.");
if (t1cnt != expected)
{
throw new Exception($"t1 is {t1cnt:N0} versus expected {expected:N0}.");
}
if (t2cnt != expected)
{
throw new Exception($"t2 is {t2cnt:N0} versus expected {expected:N0}.");
}
if (t3cnt != expected)
{
throw new Exception($"t3 is {t3cnt:N0} versus expected {expected:N0}.");
}
if (t4cnt != expected)
{
throw new Exception($"t4 is {t4cnt:N0} versus expected {expected:N0}.");
}
if (t5cnt != expected)
{
throw new Exception($"t5 is {t5cnt:N0} versus expected {expected:N0}.");
}
if (t6cnt != expected)
{
throw new Exception($"t6 is {t6cnt:N0} versus expected {expected:N0}.");
}
if (t7cnt != expected)
{
throw new Exception($"t7 is {t7cnt:N0} versus expected {expected:N0}.");
}
if (t8cnt != expected)
{
throw new Exception($"t8 is {t8cnt:N0} versus expected {expected:N0}.");
}
}
}
}
Runs fine: Check() raises no exception.
The methods W
and Z
should not have been introduced since they are used by GroupBy
:
private class D
{
public string J;
public string V;
public D(string text1, string text2)
{
this.J = text1;
this.V = text2;
}
public override int W() =>
((0x17 * this.J.GetHashCode()) + this.V.GetHashCode());
public override bool Z(object obj1)
{
ObfuscationAsserter.D d = obj1 as ObfuscationAsserter.D;
if (d == null)
{
return false;
}
return ((d.J == this.J) && (d.V == this.V));
}
}
This one still works fine:
private class Source
{
public string S1;
public string S2;
public Source(string s1, string s2)
{
this.S1 = s1;
this.S2 = s2;
}
public override bool Equals(object o)
{
ObfuscationAsserter.Source source = o as ObfuscationAsserter.Source;
if (source == null)
{
return false;
}
return ((source.S1 == this.S1) && (source.S2 == this.S2));
}
public override int GetHashCode() =>
((0x17 * this.S1.GetHashCode()) + this.S2.GetHashCode());
}
The compiler-generated class has the same issue, since GetHashCode() no longer exists:
[Serializable, CompilerGenerated]
private sealed class <>c
{
public static Func<ObfuscationAsserter.D, int> E;
public static Func<ObfuscationAsserter.D, int> F;
public static Func<ObfuscationAsserter.D, ObfuscationAsserter.D> I;
public static readonly ObfuscationAsserter.<>c J = new ObfuscationAsserter.<>c();
public static Func<ObfuscationAsserter.D, J<string, string>> V;
public static Func<ObfuscationAsserter.D, int> W;
[TupleElementNames(new string[] { "S1", "S2" })]
public static Func<ObfuscationAsserter.D, (string S1, string S2)> X;
public static Func<ObfuscationAsserter.D, int> Y;
public static Func<ObfuscationAsserter.D, string> Z;
internal int B(ObfuscationAsserter.D d1)
{
(string, string) tuple = (d1.J, d1.V);
return tuple.GetHashCode();
}
[return: TupleElementNames(new string[] { "S1", "S2" })]
internal (string S1, string S2) L(ObfuscationAsserter.D d1) =>
(d1.J, d1.V);
internal int N(ObfuscationAsserter.D d1) =>
d1.GetHashCode();
internal string P(ObfuscationAsserter.D d1) =>
(d1.J + d1.V);
internal J<string, string> R(ObfuscationAsserter.D d1) =>
new J<string, string>(d1.J, d1.V);
internal int S(ObfuscationAsserter.D d1) =>
(d1.J + d1.V).GetHashCode();
internal int T(ObfuscationAsserter.D d1) =>
new J<string, string>(d1.J, d1.V).GetHashCode();
internal ObfuscationAsserter.D U(ObfuscationAsserter.D d1) =>
d1;
}
Upvotes: 1
Views: 92