diff --git a/analyzers/src/SonarAnalyzer.Common/SymbolicExecution/Roslyn/OperationDispatcher.cs b/analyzers/src/SonarAnalyzer.Common/SymbolicExecution/Roslyn/OperationDispatcher.cs index aff6abf1f44..8bfbeb47c9c 100644 --- a/analyzers/src/SonarAnalyzer.Common/SymbolicExecution/Roslyn/OperationDispatcher.cs +++ b/analyzers/src/SonarAnalyzer.Common/SymbolicExecution/Roslyn/OperationDispatcher.cs @@ -52,6 +52,7 @@ internal static class OperationDispatcher { OperationKindEx.EventReference, new EventReference() }, { OperationKindEx.FieldReference, new FieldReference() }, { OperationKindEx.FlowCapture, new FlowCapture() }, + { OperationKindEx.FlowCaptureReference, new FlowCaptureReference() }, { OperationKindEx.Increment, new IncrementOrDecrement() }, { OperationKindEx.InstanceReference, new InstanceReference() }, { OperationKindEx.LocalReference, new LocalReference() }, diff --git a/analyzers/src/SonarAnalyzer.Common/SymbolicExecution/Roslyn/OperationProcessors/FlowCapture.cs b/analyzers/src/SonarAnalyzer.Common/SymbolicExecution/Roslyn/OperationProcessors/FlowCapture.cs index ca705625f86..f448b94d0bc 100644 --- a/analyzers/src/SonarAnalyzer.Common/SymbolicExecution/Roslyn/OperationProcessors/FlowCapture.cs +++ b/analyzers/src/SonarAnalyzer.Common/SymbolicExecution/Roslyn/OperationProcessors/FlowCapture.cs @@ -28,3 +28,12 @@ protected override IFlowCaptureOperationWrapper Convert(IOperation operation) => protected override ProgramState Process(SymbolicContext context, IFlowCaptureOperationWrapper capture) => context.State.SetCapture(capture.Id, context.State.ResolveCapture(capture.Value)); // Capture can transitively reference another IFlowCaptureReference } + +internal sealed class FlowCaptureReference : SimpleProcessor +{ + protected override IFlowCaptureReferenceOperationWrapper Convert(IOperation operation) => + IFlowCaptureReferenceOperationWrapper.FromOperation(operation); + + protected override ProgramState Process(SymbolicContext context, IFlowCaptureReferenceOperationWrapper capture) => + context.State.SetOperationValue(capture, context.State[context.State.ResolveCapture(capture.WrappedOperation)]); +} diff --git a/analyzers/src/SonarAnalyzer.Common/SymbolicExecution/Roslyn/ProgramState.cs b/analyzers/src/SonarAnalyzer.Common/SymbolicExecution/Roslyn/ProgramState.cs index 8e121c94a4b..34bfff4db97 100644 --- a/analyzers/src/SonarAnalyzer.Common/SymbolicExecution/Roslyn/ProgramState.cs +++ b/analyzers/src/SonarAnalyzer.Common/SymbolicExecution/Roslyn/ProgramState.cs @@ -39,7 +39,7 @@ public sealed record ProgramState : IEquatable public ExceptionState Exception => Exceptions.IsEmpty ? null : Exceptions.Peek(); public SymbolicValue this[IOperationWrapperSonar operation] => this[operation.Instance]; public SymbolicValue this[IOperationWrapper operation] => this[operation.WrappedOperation]; - public SymbolicValue this[IOperation operation] => OperationValue.TryGetValue(ResolveCapture(operation), out var value) ? value : null; + public SymbolicValue this[IOperation operation] => OperationValue.TryGetValue(operation, out var value) ? value : null; public SymbolicValue this[ISymbol symbol] => SymbolValue.TryGetValue(symbol, out var value) ? value : null; public IOperation this[CaptureId capture] => CaptureOperation.TryGetValue(capture, out var value) ? value : null; @@ -74,10 +74,13 @@ operation is null : SetOperationValue(operation.Instance, value); public ProgramState SetOperationValue(IOperation operation, SymbolicValue value) => - (operation ?? throw new ArgumentNullException(nameof(operation))) is var _ - && value is null - ? this with { OperationValue = OperationValue.Remove(ResolveCapture(operation)) } - : this with { OperationValue = OperationValue.SetItem(ResolveCapture(operation), value) }; + SetOperationValueCore(ResolveCapture(operation), value); + + /// + /// Sets state directly to the FlowCaptureReferenceOperation directly, without resolving the capture itself. + /// + public ProgramState SetOperationValue(IFlowCaptureReferenceOperationWrapper capture, SymbolicValue value) => + SetOperationValueCore(capture.WrappedOperation, value); public ProgramState SetOperationConstraint(IOperationWrapper operation, SymbolicConstraint constraint) => SetOperationConstraint(operation.WrappedOperation, constraint); @@ -168,6 +171,12 @@ public ProgramState[] ToArray() => public override string ToString() => Equals(Empty) ? "Empty" + Environment.NewLine : SerializeExceptions() + SerializeSymbols() + SerializeOperations() + SerializeCaptures(); + private ProgramState SetOperationValueCore(IOperation operation, SymbolicValue value) => + (operation ?? throw new ArgumentNullException(nameof(operation))) is var _ + && value is null + ? this with { OperationValue = OperationValue.Remove(operation) } + : this with { OperationValue = OperationValue.SetItem(operation, value) }; + private string SerializeExceptions() => Exceptions.IsEmpty ? null : Exceptions.JoinStr(string.Empty, x => $"Exception: {x}{Environment.NewLine}"); diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/SymbolicExecution/Roslyn/ProgramStateTest.OperationValue.cs b/analyzers/tests/SonarAnalyzer.UnitTest/SymbolicExecution/Roslyn/ProgramStateTest.OperationValue.cs index eca0349da57..405536562b0 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/SymbolicExecution/Roslyn/ProgramStateTest.OperationValue.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/SymbolicExecution/Roslyn/ProgramStateTest.OperationValue.cs @@ -206,6 +206,21 @@ public void SetOperationValue_OnCaptureReference_SetsValueToCapturedOperation() sut[capture.Value].Should().Be(value); } + [TestMethod] + public void SetOperationValue_OnFlowCaptureReferenceOperationWrapper_SetsValueToOperation() + { + var value = SymbolicValue.Empty; + var cfg = TestHelper.CompileCfgBodyCS("a ??= b;", "object a, object b"); + var capture = IFlowCaptureOperationWrapper.FromOperation(cfg.Blocks[1].Operations[0]); + var captureReference = IFlowCaptureReferenceOperationWrapper.FromOperation(cfg.Blocks[3].Operations[0].Children.First()); + captureReference.Id.Should().Be(capture.Id); + var sut = ProgramState.Empty + .SetCapture(capture.Id, capture.Value) + .SetOperationValue(captureReference, value); + sut[capture.Value].Should().BeNull(); + sut[captureReference].Should().Be(value); + } + [TestMethod] public void ResetOperations_IsImmutable() {