diff --git a/analyzers/src/SonarAnalyzer.Common/SymbolicExecution/Roslyn/SymbolicValue.cs b/analyzers/src/SonarAnalyzer.Common/SymbolicExecution/Roslyn/SymbolicValue.cs index ce541f7509a..de7097dffa1 100644 --- a/analyzers/src/SonarAnalyzer.Common/SymbolicExecution/Roslyn/SymbolicValue.cs +++ b/analyzers/src/SonarAnalyzer.Common/SymbolicExecution/Roslyn/SymbolicValue.cs @@ -34,8 +34,19 @@ public sealed record SymbolicValue public static readonly SymbolicValue True = NotNull.WithConstraint(BoolConstraint.True); public static readonly SymbolicValue False = NotNull.WithConstraint(BoolConstraint.False); + private readonly ImmutableDictionary constraints = ImmutableDictionary.Empty; + private int? hashCode; + // SymbolicValue can have only one constraint instance of specific type at a time - private ImmutableDictionary Constraints { get; init; } = ImmutableDictionary.Empty; + private ImmutableDictionary Constraints + { + get => constraints; + init + { + constraints = value; + hashCode = null; + } + } public IEnumerable AllConstraints => Constraints.Values; @@ -84,7 +95,7 @@ public T Constraint() where T : SymbolicConstraint => Constraints.TryGetValue(typeof(T), out var value) ? (T)value : null; public override int GetHashCode() => - HashCode.DictionaryContentHash(Constraints); + hashCode ??= HashCode.DictionaryContentHash(constraints); public bool Equals(SymbolicValue other) => other is not null && other.Constraints.DictionaryEquals(Constraints); diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/SymbolicExecution/Roslyn/SymbolicValueTest.cs b/analyzers/tests/SonarAnalyzer.UnitTest/SymbolicExecution/Roslyn/SymbolicValueTest.cs index e644642a1ae..f9d98c623cd 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/SymbolicExecution/Roslyn/SymbolicValueTest.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/SymbolicExecution/Roslyn/SymbolicValueTest.cs @@ -335,5 +335,46 @@ public void RemoveEntry_Miss_Returns_Instance_Type() var sut = SymbolicValue.Null.WithConstraint(TestConstraint.First); sut.WithoutConstraint().Should().BeSameAs(sut).And.HaveOnlyConstraints(ObjectConstraint.Null, TestConstraint.First); } + + [TestMethod] + public void GetHashCode_ReturnsDifferentValuesForPredefinedValues() + { + SymbolicValue.Empty.GetHashCode().Should().Be(SymbolicValue.Empty.GetHashCode()); + SymbolicValue.Empty.GetHashCode().Should().NotBe(SymbolicValue.Null.GetHashCode()) + .And.NotBe(SymbolicValue.NotNull.GetHashCode()) + .And.NotBe(SymbolicValue.True.GetHashCode()) + .And.NotBe(SymbolicValue.False.GetHashCode()); + } + + [TestMethod] + public void GetHashCode_DifferentValuesAreUnique() + { + new[] + { + SymbolicValue.Empty.GetHashCode(), + SymbolicValue.NotNull.GetHashCode(), + SymbolicValue.Null.GetHashCode(), + SymbolicValue.True.GetHashCode(), + SymbolicValue.False.GetHashCode(), + SymbolicValue.Empty.WithConstraint(DummyConstraint.Dummy).GetHashCode(), + SymbolicValue.Empty.WithConstraint(DummyConstraint.Dummy).WithConstraint(TestConstraint.First).WithConstraint(ObjectConstraint.Null).GetHashCode(), + SymbolicValue.Empty.WithConstraint(DummyConstraint.Dummy).WithConstraint(TestConstraint.First).WithConstraint(ObjectConstraint.NotNull).GetHashCode(), + }.Should().OnlyHaveUniqueItems(); + } + + [TestMethod] + public void GetHashCode_UncachedValues() + { + var baseConstraint = SymbolicValue.Empty + .WithConstraint(DummyConstraint.Dummy) + .WithConstraint(TestConstraint.First) + .WithConstraint(ObjectConstraint.Null) + .WithConstraint(BoolConstraint.True); + baseConstraint.GetHashCode().Should().Be(baseConstraint.GetHashCode()); + var similar = baseConstraint.WithoutConstraint(DummyConstraint.Dummy).WithConstraint(DummyConstraint.Dummy); + similar.Should().NotBeSameAs(baseConstraint); + similar.GetHashCode().Should().Be(baseConstraint.GetHashCode()); + similar.Equals(baseConstraint).Should().BeTrue(); + } } }