Skip to content
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

Use LVA in the new SE to reduce memory usage and improve performance #5475

Merged
merged 19 commits into from
Mar 23, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions analyzers/src/SonarAnalyzer.Common/Helpers/HashCode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ namespace SonarAnalyzer.Helpers
public static int DictionaryContentHash<TKey, TValue>(IDictionary<TKey, TValue> dictionary) =>
dictionary.Aggregate(0, (seed, kvp) => Combine(seed, kvp.Key, kvp.Value));

public static int EnumerableContentHash<TValue>(IEnumerable<TValue> enumerable) =>
enumerable.Aggregate(0, (seed, x) => Combine(seed, x));

public static int Combine<T1, T2>(T1 a, T2 b) =>
(int)Seed
.AddHash((uint)(a?.GetHashCode() ?? 0))
Expand All @@ -45,6 +48,13 @@ public static int Combine<T1, T2, T3>(T1 a, T2 b, T3 c) =>
.AddHash((uint)(b?.GetHashCode() ?? 0))
.AddHash((uint)(c?.GetHashCode() ?? 0));

public static int Combine<T1, T2, T3, T4>(T1 a, T2 b, T3 c, T4 d) =>
(int)Seed
.AddHash((uint)(a?.GetHashCode() ?? 0))
.AddHash((uint)(b?.GetHashCode() ?? 0))
.AddHash((uint)(c?.GetHashCode() ?? 0))
.AddHash((uint)(d?.GetHashCode() ?? 0));

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static uint AddHash(this uint hash, uint value) =>
RotateLeft(hash + value * PreMultiplier) * PostMultiplier;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ public sealed record ProgramState : IEquatable<ProgramState>
private ImmutableDictionary<ISymbol, SymbolicValue> SymbolValue { get; init; }
private ImmutableDictionary<int, int> VisitCount { get; init; }
private ImmutableDictionary<CaptureId, IOperation> CaptureOperation { get; init; }
private ImmutableHashSet<ISymbol> PreservedSymbols { get; init; }

public SymbolicValue this[IOperationWrapperSonar operation] => this[operation.Instance];
public SymbolicValue this[IOperation operation] => OperationValue.TryGetValue(ResolveCapture(operation), out var value) ? value : null;
Expand All @@ -50,6 +51,7 @@ private ProgramState()
SymbolValue = ImmutableDictionary<ISymbol, SymbolicValue>.Empty;
VisitCount = ImmutableDictionary<int, int>.Empty;
CaptureOperation = ImmutableDictionary<CaptureId, IOperation>.Empty;
PreservedSymbols = ImmutableHashSet<ISymbol>.Empty;
pavel-mikula-sonarsource marked this conversation as resolved.
Show resolved Hide resolved
}

public ProgramState SetOperationValue(IOperationWrapperSonar operation, SymbolicValue value) =>
Expand Down Expand Up @@ -95,9 +97,15 @@ public IOperation ResolveCapture(IOperation operation) =>
? captured
: operation;

public ProgramState RemoveSymbols(Func<ISymbol, bool> remove) =>
this with { SymbolValue = SymbolValue.Where(kv => PreservedSymbols.Contains(kv.Key) || !remove(kv.Key)).ToImmutableDictionary() };

public ProgramState AddVisit(int programPointHash) =>
this with { VisitCount = VisitCount.SetItem(programPointHash, GetVisitCount(programPointHash) + 1) };

public ProgramState Preserve(ISymbol symbol) =>
this with { PreservedSymbols = PreservedSymbols.Add(symbol) };

public int GetVisitCount(int programPointHash) =>
VisitCount.TryGetValue(programPointHash, out var count) ? count : 0;

Expand All @@ -106,14 +114,16 @@ public override int GetHashCode() =>
HashCode.Combine(
HashCode.DictionaryContentHash(OperationValue),
HashCode.DictionaryContentHash(SymbolValue),
HashCode.EnumerableContentHash(PreservedSymbols),
HashCode.DictionaryContentHash(CaptureOperation));

public bool Equals(ProgramState other) =>
// VisitCount is not compared, two ProgramState are equal if their current state is equal. No matter was historical path led to it.
other is not null
&& other.OperationValue.DictionaryEquals(OperationValue)
&& other.SymbolValue.DictionaryEquals(SymbolValue)
&& other.CaptureOperation.DictionaryEquals(CaptureOperation);
&& other.CaptureOperation.DictionaryEquals(CaptureOperation)
&& other.PreservedSymbols.SetEquals(PreservedSymbols);

public override string ToString() =>
Equals(Empty) ? "Empty" : SerializeSymbols() + SerializeOperations() + SerializeCaptures();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.CodeAnalysis;
using SonarAnalyzer.CFG.LiveVariableAnalysis;
using SonarAnalyzer.CFG.Roslyn;
using SonarAnalyzer.Helpers;
using SonarAnalyzer.SymbolicExecution.Constraints;
using SonarAnalyzer.SymbolicExecution.Roslyn.Checks;
using SonarAnalyzer.SymbolicExecution.Roslyn.OperationProcessors;
Expand All @@ -39,6 +42,7 @@ internal class RoslynSymbolicExecution
private readonly Queue<ExplodedNode> queue = new();
private readonly SymbolicValueCounter symbolicValueCounter = new();
private readonly HashSet<ExplodedNode> visited = new();
private readonly RoslynLiveVariableAnalysis lva;

public RoslynSymbolicExecution(ControlFlowGraph cfg, SymbolicCheck[] checks)
{
Expand All @@ -48,6 +52,7 @@ public RoslynSymbolicExecution(ControlFlowGraph cfg, SymbolicCheck[] checks)
throw new ArgumentException("At least one check is expected", nameof(checks));
}
this.checks = new(new[] { new ConstantCheck() }.Concat(checks).ToArray());
lva = new RoslynLiveVariableAnalysis(cfg);
}

public void Execute()
Expand Down Expand Up @@ -154,7 +159,9 @@ private ProgramState ProcessBranchState(ControlFlowBranch branch, ProgramState s
{
state = state.RemoveCapture(capture);
}
return state.ResetOperations();
var liveVariables = lva.LiveOut(branch.Source).ToHashSet();
return state.RemoveSymbols(x => lva.IsLocal(x) && (x is ILocalSymbol or IParameterSymbol { RefKind: RefKind.None }) && !liveVariables.Contains(x))
.ResetOperations();
}

private IEnumerable<ExplodedNode> ProcessOperation(ExplodedNode node)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ protected override ProgramState PostProcessSimple(SymbolicContext context)
&& assignment.Target.TrackedSymbol() is { } symbol)
{
lastSymbolLock[symbol] = new IOperationWrapperSonar(objectCreation.WrappedOperation);
return AddLock(context, objectCreation.WrappedOperation);
return AddLock(context, objectCreation.WrappedOperation).Preserve(symbol);
}
}
else if (context.Operation.Instance.AsInvocation() is { } invocation)
Expand Down Expand Up @@ -136,7 +136,7 @@ private ProgramState AddLock(SymbolicContext context, ISymbol symbol)
}

lastSymbolLock[symbol] = context.Operation;
return context.SetSymbolConstraint(symbol, LockConstraint.Held);
return context.SetSymbolConstraint(symbol, LockConstraint.Held).Preserve(symbol);
}

private ProgramState RemoveLock(SymbolicContext context, ISymbol symbol)
Expand All @@ -147,7 +147,7 @@ private ProgramState RemoveLock(SymbolicContext context, ISymbol symbol)
}

releasedSymbols.Add(symbol);
return context.SetSymbolConstraint(symbol, LockConstraint.Released);
return context.SetSymbolConstraint(symbol, LockConstraint.Released).Preserve(symbol);
}

// This method should be removed once the engine has support for `True/False` boolean constraints.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public void GivenSyntaxNodeWithHighDepth_SafeVisit_ReturnsFalse()
var code = $@"
Public Class Sample
Public Function Main(Arg as Boolean) As Boolean
Return Arg {Enumerable.Repeat("AndAlso Arg", 5000).JoinStr(" ")}
Return Arg {Enumerable.Repeat("AndAlso Arg", 7000).JoinStr(" ")}
End Function
End Class";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,11 @@ public void Combine_Null()
{
var two = HashCode.Combine<object, object>(null, null);
var three = HashCode.Combine<object, object, object>(null, null, null);
var four = HashCode.Combine<object, object, object, object>(null, null, null, null);

two.Should().NotBe(0);
three.Should().NotBe(0).And.NotBe(two);
four.Should().NotBe(0).And.NotBe(three).And.NotBe(two);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -973,11 +973,7 @@ private class Context

public Context(string code, AnalyzerLanguage language, string localFunctionName = null)
{
Cfg = TestHelper.CompileCfg(code, language, code.Contains("// Error CS"));
if (localFunctionName != null)
{
Cfg = Cfg.GetLocalFunctionControlFlowGraph(Cfg.LocalFunctions.Single(x => x.Name == localFunctionName));
}
Cfg = TestHelper.CompileCfg(code, language, code.Contains("// Error CS"), localFunctionName);
Console.WriteLine(CfgSerializer.Serialize(Cfg));
Lva = new RoslynLiveVariableAnalysis(Cfg);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
using System;
using System.Linq;
using FluentAssertions;
using Microsoft.CodeAnalysis;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using SonarAnalyzer.SymbolicExecution.Roslyn;
using SonarAnalyzer.UnitTest.TestFramework.SymbolicExecution;
Expand Down Expand Up @@ -119,6 +120,29 @@ public void SymbolsWith_IgnoreNullValue()
sut.SymbolsWith(DummyConstraint.Dummy).Should().BeEmpty();
}

[TestMethod]
public void Preserve_PreservedSymbolCannotBeRemoved()
{
var symbolicValue = new SymbolicValue(new()).WithConstraint(DummyConstraint.Dummy);
var symbol = CreateSymbols().First();
var sut = ProgramState.Empty.SetSymbolValue(symbol, symbolicValue)
.Preserve(symbol)
.RemoveSymbols(x => true);
sut.SymbolsWith(DummyConstraint.Dummy).Should().Contain(symbol);
}

[TestMethod]
public void RemoveSymbols_RemovesSymbolsMatchingThePredicate()
{
var symbolicValue = new SymbolicValue(new()).WithConstraint(DummyConstraint.Dummy);
var symbols = CreateSymbols().ToArray();
var sut = ProgramState.Empty.SetSymbolValue(symbols[0], symbolicValue)
.SetSymbolValue(symbols[1], symbolicValue)
.SetSymbolValue(symbols[2], symbolicValue)
.RemoveSymbols(x => !SymbolEqualityComparer.Default.Equals(x, symbols[1]));
sut.SymbolsWith(DummyConstraint.Dummy).Should().Contain(symbols[1]).And.HaveCount(1);
}

[TestMethod]
public void SetSymbolConstraint_NoValue_CreatesNewValue()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,21 +39,24 @@ public void Equals_ReturnsTrueForEquivalent()
var counter = new SymbolicValueCounter();
var reusedValue = new SymbolicValue(counter).WithConstraint(TestConstraint.First);
var anotherValue = new SymbolicValue(counter).WithConstraint(TestConstraint.Second);
var operations = TestHelper.CompileCfgBodyCS("var x = 42;").Blocks[1].Operations.ToExecutionOrder().ToArray();
var symbol = operations.Select(x => x.Instance.TrackedSymbol()).First(x => x is not null);
var operations = TestHelper.CompileCfgBodyCS("var x = 42; var y = 42;").Blocks[1].Operations.ToExecutionOrder().ToArray();
var symbols = operations.Select(x => x.Instance.TrackedSymbol()).Where(x => x is not null).ToArray();
var empty = ProgramState.Empty;
var withOperationOrig = empty.SetOperationValue(operations[0], reusedValue);
var withOperationSame = empty.SetOperationValue(operations[0], reusedValue);
var withOperationDiff = empty.SetOperationValue(operations[0], anotherValue);
var withSymbolOrig = empty.SetSymbolValue(symbol, reusedValue);
var withSymbolSame = empty.SetSymbolValue(symbol, reusedValue);
var withSymbolDiff = empty.SetSymbolValue(symbol, anotherValue);
var withSymbolOrig = empty.SetSymbolValue(symbols[0], reusedValue);
var withSymbolSame = empty.SetSymbolValue(symbols[0], reusedValue);
var withSymbolDiff = empty.SetSymbolValue(symbols[0], anotherValue);
var withCaptureOrig = empty.SetCapture(new CaptureId(0), operations[0].Instance);
var withCaptureSame = empty.SetCapture(new CaptureId(0), operations[0].Instance);
var withCaptureDiff = empty.SetCapture(new CaptureId(0), operations[1].Instance);
var mixedOrig = withOperationOrig.SetSymbolValue(symbol, reusedValue);
var mixedSame = withOperationSame.SetSymbolValue(symbol, reusedValue);
var mixedDiff = withOperationDiff.SetSymbolValue(symbol, anotherValue);
var withPreservedSymbolOrig = empty.Preserve(symbols[0]);
var withPreservedSymbolSame = empty.Preserve(symbols[0]);
var withPreservedSymbolDiff = empty.Preserve(symbols[1]);
var mixedOrig = withOperationOrig.SetSymbolValue(symbols[0], reusedValue);
var mixedSame = withOperationSame.SetSymbolValue(symbols[0], reusedValue);
var mixedDiff = withOperationDiff.SetSymbolValue(symbols[0], anotherValue);

empty.Equals((object)empty).Should().BeTrue();
empty.Equals((object)withOperationOrig).Should().BeFalse();
Expand All @@ -67,20 +70,28 @@ public void Equals_ReturnsTrueForEquivalent()
withOperationOrig.Equals(empty).Should().BeFalse();
withOperationOrig.Equals(withSymbolDiff).Should().BeFalse();
withOperationOrig.Equals(withCaptureDiff).Should().BeFalse();
withOperationOrig.Equals(withPreservedSymbolDiff).Should().BeFalse();

withSymbolOrig.Equals(withSymbolOrig).Should().BeTrue();
withSymbolOrig.Equals(withSymbolSame).Should().BeTrue();
withSymbolOrig.Equals(withSymbolDiff).Should().BeFalse();
withSymbolOrig.Equals(empty).Should().BeFalse();
withSymbolOrig.Equals(withOperationDiff).Should().BeFalse();
withSymbolOrig.Equals(withCaptureDiff).Should().BeFalse();
withSymbolOrig.Equals(withPreservedSymbolDiff).Should().BeFalse();

withCaptureOrig.Equals(withCaptureOrig).Should().BeTrue();
withCaptureOrig.Equals(withCaptureSame).Should().BeTrue();
withCaptureOrig.Equals(withCaptureDiff).Should().BeFalse();
withCaptureOrig.Equals(empty).Should().BeFalse();
withCaptureOrig.Equals(withOperationDiff).Should().BeFalse();
withCaptureOrig.Equals(withSymbolDiff).Should().BeFalse();
withPreservedSymbolOrig.Equals(withPreservedSymbolOrig).Should().BeTrue();
withPreservedSymbolOrig.Equals(withPreservedSymbolSame).Should().BeTrue();
withPreservedSymbolOrig.Equals(withPreservedSymbolDiff).Should().BeFalse();
withPreservedSymbolOrig.Equals(empty).Should().BeFalse();
withPreservedSymbolOrig.Equals(withOperationDiff).Should().BeFalse();
withPreservedSymbolOrig.Equals(withSymbolDiff).Should().BeFalse();

mixedOrig.Equals(mixedOrig).Should().BeTrue();
mixedOrig.Equals(mixedSame).Should().BeTrue();
Expand All @@ -94,21 +105,24 @@ public void GetHashCode_ReturnsSameForEquivalent()
var counter = new SymbolicValueCounter();
var reusedValue = new SymbolicValue(counter).WithConstraint(TestConstraint.First);
var anotherValue = new SymbolicValue(counter).WithConstraint(TestConstraint.Second);
var operations = TestHelper.CompileCfgBodyCS("var x = 42;").Blocks[1].Operations.ToExecutionOrder().ToArray();
var symbol = operations.Select(x => x.Instance.TrackedSymbol()).First(x => x is not null);
var operations = TestHelper.CompileCfgBodyCS("var x = 42; var y = 42;").Blocks[1].Operations.ToExecutionOrder().ToArray();
var symbols = operations.Select(x => x.Instance.TrackedSymbol()).Where(x => x is not null).ToArray();
var empty = ProgramState.Empty;
var withOperationOrig = empty.SetOperationValue(operations[0], reusedValue);
var withOperationSame = empty.SetOperationValue(operations[0], reusedValue);
var withOperationDiff = empty.SetOperationValue(operations[0], anotherValue);
var withSymbolOrig = empty.SetSymbolValue(symbol, reusedValue);
var withSymbolSame = empty.SetSymbolValue(symbol, reusedValue);
var withSymbolDiff = empty.SetSymbolValue(symbol, anotherValue);
var withSymbolOrig = empty.SetSymbolValue(symbols[0], reusedValue);
var withSymbolSame = empty.SetSymbolValue(symbols[0], reusedValue);
var withSymbolDiff = empty.SetSymbolValue(symbols[0], anotherValue);
var withCaptureOrig = empty.SetCapture(new CaptureId(0), operations[0].Instance);
var withCaptureSame = empty.SetCapture(new CaptureId(0), operations[0].Instance);
var withCaptureDiff = empty.SetCapture(new CaptureId(0), operations[1].Instance);
var mixedOrig = withOperationOrig.SetSymbolValue(symbol, reusedValue);
var mixedSame = withOperationSame.SetSymbolValue(symbol, reusedValue);
var mixedDiff = withOperationDiff.SetSymbolValue(symbol, anotherValue);
var withPreservedSymbolOrig = empty.Preserve(symbols[0]);
var withPreservedSymbolSame = empty.Preserve(symbols[0]);
var withPreservedSymbolDiff = empty.Preserve(symbols[1]);
var mixedOrig = withOperationOrig.SetSymbolValue(symbols[0], reusedValue);
var mixedSame = withOperationSame.SetSymbolValue(symbols[0], reusedValue);
var mixedDiff = withOperationDiff.SetSymbolValue(symbols[0], anotherValue);

empty.GetHashCode().Should().Be(empty.GetHashCode());

Expand All @@ -124,6 +138,10 @@ public void GetHashCode_ReturnsSameForEquivalent()
withCaptureOrig.GetHashCode().Should().Be(withCaptureSame.GetHashCode());
withCaptureOrig.GetHashCode().Should().NotBe(withCaptureDiff.GetHashCode());

withPreservedSymbolOrig.GetHashCode().Should().Be(withPreservedSymbolSame.GetHashCode());
withPreservedSymbolOrig.GetHashCode().Should().NotBe(withPreservedSymbolDiff.GetHashCode());
withPreservedSymbolOrig.GetHashCode().Should().NotBe(mixedSame.GetHashCode());

mixedOrig.GetHashCode().Should().Be(mixedSame.GetHashCode());
mixedOrig.GetHashCode().Should().NotBe(mixedDiff.GetHashCode());
}
Expand Down Expand Up @@ -205,7 +223,7 @@ public void ToString_WithAll()
.SetSymbolValue(variableSymbol, new SymbolicValue(counter))
.SetSymbolValue(variableSymbol.ContainingSymbol, valueWithConstraint)
.SetOperationValue(assignment, new SymbolicValue(counter))
.SetOperationValue(assignment.Children.First(), valueWithConstraint)
.SetOperationValue(assignment.Children.First(), valueWithConstraint).Preserve(variableSymbol)
.SetCapture(new CaptureId(0), assignment)
.SetCapture(new CaptureId(1), assignment.Children.First());

Expand Down
Loading