From c2a0b7c65e9cb16cc575a51abfaf9ac7cd5688a2 Mon Sep 17 00:00:00 2001 From: Zsolt Kolbay Date: Mon, 11 Dec 2023 22:24:25 +0100 Subject: [PATCH] Add Processor for deconstruction operations --- .../Roslyn/Extensions/IOperationExtensions.cs | 9 +++ .../Roslyn/OperationDispatcher.cs | 1 + .../DeconstructionAssignment.cs | 70 +++++++++++++++++++ .../SymbolicExecution/Roslyn/ProgramState.cs | 10 +++ 4 files changed, 90 insertions(+) create mode 100644 analyzers/src/SonarAnalyzer.Common/SymbolicExecution/Roslyn/OperationProcessors/DeconstructionAssignment.cs diff --git a/analyzers/src/SonarAnalyzer.Common/SymbolicExecution/Roslyn/Extensions/IOperationExtensions.cs b/analyzers/src/SonarAnalyzer.Common/SymbolicExecution/Roslyn/Extensions/IOperationExtensions.cs index 1394b758ad3..61c338b71f3 100644 --- a/analyzers/src/SonarAnalyzer.Common/SymbolicExecution/Roslyn/Extensions/IOperationExtensions.cs +++ b/analyzers/src/SonarAnalyzer.Common/SymbolicExecution/Roslyn/Extensions/IOperationExtensions.cs @@ -51,6 +51,9 @@ OperationKindEx.PropertyReference when operation.ToPropertyReference() is { Prop internal static IConversionOperationWrapper? AsConversion(this IOperation operation) => operation.As(OperationKindEx.Conversion, IConversionOperationWrapper.FromOperation); + internal static IDeclarationExpressionOperationWrapper? AsDeclarationExpression(this IOperation operation) => + operation.As(OperationKindEx.DeclarationExpression, IDeclarationExpressionOperationWrapper.FromOperation); + internal static IInvocationOperationWrapper? AsInvocation(this IOperation operation) => operation.As(OperationKindEx.Invocation, IInvocationOperationWrapper.FromOperation); @@ -66,6 +69,9 @@ OperationKindEx.PropertyReference when operation.ToPropertyReference() is { Prop internal static IPropertyReferenceOperationWrapper? AsPropertyReference(this IOperation operation) => operation.As(OperationKindEx.PropertyReference, IPropertyReferenceOperationWrapper.FromOperation); + internal static ITupleOperationWrapper? AsTuple(this IOperation operation) => + operation.As(OperationKindEx.Tuple, ITupleOperationWrapper.FromOperation); + internal static IAwaitOperationWrapper ToAwait(this IOperation operation) => IAwaitOperationWrapper.FromOperation(operation); @@ -120,6 +126,9 @@ internal static IParameterReferenceOperationWrapper ToParameterReference(this IO internal static IEventReferenceOperationWrapper ToEventReference(this IOperation operation) => IEventReferenceOperationWrapper.FromOperation(operation); + internal static ITupleOperationWrapper ToTuple(this IOperation operation) => + ITupleOperationWrapper.FromOperation(operation); + internal static IUnaryOperationWrapper ToUnary(this IOperation operation) => IUnaryOperationWrapper.FromOperation(operation); diff --git a/analyzers/src/SonarAnalyzer.Common/SymbolicExecution/Roslyn/OperationDispatcher.cs b/analyzers/src/SonarAnalyzer.Common/SymbolicExecution/Roslyn/OperationDispatcher.cs index 8bfbeb47c9c..e20c87f3d23 100644 --- a/analyzers/src/SonarAnalyzer.Common/SymbolicExecution/Roslyn/OperationDispatcher.cs +++ b/analyzers/src/SonarAnalyzer.Common/SymbolicExecution/Roslyn/OperationDispatcher.cs @@ -45,6 +45,7 @@ internal static class OperationDispatcher { OperationKindEx.CompoundAssignment, new CompoundAssignment() }, { OperationKindEx.Conversion, new Conversion() }, { OperationKindEx.DeclarationPattern, new DeclarationPattern() }, + { OperationKindEx.DeconstructionAssignment, new DeconstructionAssignment() }, { OperationKindEx.Decrement, new IncrementOrDecrement() }, { OperationKindEx.DefaultValue, new DefaultValue() }, { OperationKindEx.DelegateCreation, new NotNullOperation() }, diff --git a/analyzers/src/SonarAnalyzer.Common/SymbolicExecution/Roslyn/OperationProcessors/DeconstructionAssignment.cs b/analyzers/src/SonarAnalyzer.Common/SymbolicExecution/Roslyn/OperationProcessors/DeconstructionAssignment.cs new file mode 100644 index 00000000000..de0b849c869 --- /dev/null +++ b/analyzers/src/SonarAnalyzer.Common/SymbolicExecution/Roslyn/OperationProcessors/DeconstructionAssignment.cs @@ -0,0 +1,70 @@ +/* + * SonarAnalyzer for .NET + * Copyright (C) 2015-2023 SonarSource SA + * mailto: contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +namespace SonarAnalyzer.SymbolicExecution.Roslyn.OperationProcessors; + +internal sealed class DeconstructionAssignment : SimpleProcessor +{ + protected override IDeconstructionAssignmentOperationWrapper Convert(IOperation operation) => + IDeconstructionAssignmentOperationWrapper.FromOperation(operation); + + protected override ProgramState Process(SymbolicContext context, IDeconstructionAssignmentOperationWrapper assignment) => + Process(context.State, assignment.Target, assignment.Value); + + private static ProgramState Process(ProgramState state, IOperation target, IOperation value) + { + var leftTupleElements = UnwrapDeclaration(target).ToTuple().Elements; + + // If the right side is a tuple, then every symbol/constraint is copied to the left side. + if (value.AsTuple() is { } rightSideTuple) + { + for (var i = 0; i < leftTupleElements.Length; i++) + { + var leftTupleMember = UnwrapDeclaration(leftTupleElements[i]); + var rightTupleMember = rightSideTuple.Elements[i]; + if (leftTupleMember.Kind == OperationKindEx.Discard) + { + continue; + } + if (leftTupleMember.AsTuple() is { } nestedTuple) + { + var rightSideMember = rightTupleMember.AsTuple()?.WrappedOperation ?? rightTupleMember; + state = Process(state, nestedTuple.WrappedOperation, rightSideMember); + } + else + { + state = state.SetOperationAndSymbolValue(leftTupleMember, state[rightTupleMember]); + } + } + } + // If the right side is not a tuple, then every member of the left side tuple is set to empty. + else + { + foreach (var tupleMember in leftTupleElements.Where(x => x.Kind != OperationKindEx.Discard)) + { + state = state.SetOperationAndSymbolValue(tupleMember, SymbolicValue.Empty); + } + } + return state; + } + + private static IOperation UnwrapDeclaration(IOperation operation) => + operation.AsDeclarationExpression()?.Expression ?? operation; +} diff --git a/analyzers/src/SonarAnalyzer.Common/SymbolicExecution/Roslyn/ProgramState.cs b/analyzers/src/SonarAnalyzer.Common/SymbolicExecution/Roslyn/ProgramState.cs index 1931f74c1d8..29be2be48b8 100644 --- a/analyzers/src/SonarAnalyzer.Common/SymbolicExecution/Roslyn/ProgramState.cs +++ b/analyzers/src/SonarAnalyzer.Common/SymbolicExecution/Roslyn/ProgramState.cs @@ -63,6 +63,16 @@ protected ProgramState(ProgramState original) // Custom record override constr Exceptions = original.Exceptions; } + public ProgramState SetOperationAndSymbolValue(IOperation operation, SymbolicValue value) + { + var newState = SetOperationValue(operation, value); + if (operation.TrackedSymbol(this) is { } symbol) + { + newState = newState.SetSymbolValue(symbol, value); + } + return newState; + } + public ProgramState SetOperationValue(IOperationWrapper operation, SymbolicValue value) => operation is null ? throw new ArgumentNullException(nameof(operation))