-
Notifications
You must be signed in to change notification settings - Fork 229
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
SE: Caching: Cache "GetHashCode" for SymbolicValue #6968
Changes from 9 commits
65d1021
49b5c30
59cf2fb
80c001e
cbd6fae
83d3aae
389b423
82134d2
ee16d48
0a5e74f
bd2aa1a
f3ef93d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -35,8 +35,22 @@ 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<Type, SymbolicConstraint> constraints = ImmutableDictionary<Type, SymbolicConstraint>.Empty; | ||||||||||||||
private int? constraintsHashCode; | ||||||||||||||
|
||||||||||||||
// SymbolicValue can have only one constraint instance of specific type at a time | ||||||||||||||
private ImmutableDictionary<Type, SymbolicConstraint> Constraints { get; init; } = ImmutableDictionary<Type, SymbolicConstraint>.Empty; | ||||||||||||||
private ImmutableDictionary<Type, SymbolicConstraint> Constraints | ||||||||||||||
{ | ||||||||||||||
get => constraints; | ||||||||||||||
init | ||||||||||||||
{ | ||||||||||||||
if (constraints != value) | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This condition is not covered when values are the same. We don't use it that way. And we actually shouldn't, as we should attempt no-operation when possible. Do we really need the condition then? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It doesn't hurt to keep it, IMO. It allows doing something like, e.g. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would remove the condition. It's one more additional And so far, we didn't need it. And it's against the intended usage of the property. We can always add it back if we need to. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's fine with me. |
||||||||||||||
{ | ||||||||||||||
constraints = value; | ||||||||||||||
constraintsHashCode = null; | ||||||||||||||
} | ||||||||||||||
} | ||||||||||||||
} | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is also sonar-dotnet/analyzers/src/SonarAnalyzer.Common/SymbolicExecution/Roslyn/ProgramState.cs Lines 138 to 143 in f3c2ea1
and I want to use the same pattern there as well. There it is more important to reset the cached hashcode in the init setter. There we can do this, to make the declaration more compact: private readonly ImmutableDictionary<IOperation, SymbolicValue> operationValue;
private int? operationValueHashCode;
private ImmutableDictionary<IOperation, SymbolicValue> OperationValue
{
get => operationValue;
init => SetHashCodeCachedField(value, ref operationValue, ref operationValueHashCode);
}
private static void SetHashCodeCachedField<T>(T value, ref T backingField, ref int? backingFieldHashCode) where T: class
{
if (value != backingField)
{
backingField = value;
backingFieldHashCode = null;
}
} There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's also my understanding that it should not be here, because it's not needed (yet). |
||||||||||||||
|
||||||||||||||
public IEnumerable<SymbolicConstraint> AllConstraints => | ||||||||||||||
Constraints.Values; | ||||||||||||||
|
@@ -83,7 +97,7 @@ public T Constraint<T>() where T : SymbolicConstraint => | |||||||||||||
Constraints.TryGetValue(typeof(T), out var value) ? (T)value : null; | ||||||||||||||
|
||||||||||||||
public override int GetHashCode() => | ||||||||||||||
HashCode.DictionaryContentHash(Constraints); | ||||||||||||||
constraintsHashCode ??= HashCode.DictionaryContentHash(constraints); | ||||||||||||||
|
||||||||||||||
public bool Equals(SymbolicValue other) => | ||||||||||||||
other is not null && other.Constraints.DictionaryEquals(Constraints); | ||||||||||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -335,5 +335,47 @@ public void RemoveEntry_Miss_Returns_Instance_Type() | |
var sut = SymbolicValue.Null.WithConstraint(TestConstraint.First); | ||
sut.WithoutConstraint<DummyConstraint>().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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's put the |
||
.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(); | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is and will be hash code for the entire class. No matter how much mess we add.