diff --git a/analyzers/src/SonarAnalyzer.Common/SymbolicExecution/Roslyn/OperationProcessors/References.cs b/analyzers/src/SonarAnalyzer.Common/SymbolicExecution/Roslyn/OperationProcessors/References.cs index df6d0163a86..cc2c9fe04ae 100644 --- a/analyzers/src/SonarAnalyzer.Common/SymbolicExecution/Roslyn/OperationProcessors/References.cs +++ b/analyzers/src/SonarAnalyzer.Common/SymbolicExecution/Roslyn/OperationProcessors/References.cs @@ -71,10 +71,20 @@ internal sealed class PropertyReference : SimpleProcessor IPropertyReferenceOperationWrapper.FromOperation(operation); - protected override ProgramState Process(SymbolicContext context, IPropertyReferenceOperationWrapper propertyReference) => - propertyReference.Instance.TrackedSymbol() is { } symbol - ? context.SetSymbolConstraint(symbol, ObjectConstraint.NotNull) - : context.State; + protected override ProgramState Process(SymbolicContext context, IPropertyReferenceOperationWrapper propertyReference) + { + if (propertyReference.Instance.TrackedSymbol() is { } symbol) + { + var state = context.State.SetSymbolConstraint(symbol, ObjectConstraint.NotNull); + return propertyReference.Property.Name == "Value" && propertyReference.Instance.Type.IsNullableValueType() && context.State[symbol] is { } value + ? state.SetOperationValue(context.Operation, value) + : state; + } + else + { + return context.State; + } + } } internal sealed class ArrayElementReference : SimpleProcessor diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/SymbolicExecution/Roslyn/RoslynSymbolicExecutionTest.Nullable.cs b/analyzers/tests/SonarAnalyzer.UnitTest/SymbolicExecution/Roslyn/RoslynSymbolicExecutionTest.Nullable.cs new file mode 100644 index 00000000000..ba8e0050877 --- /dev/null +++ b/analyzers/tests/SonarAnalyzer.UnitTest/SymbolicExecution/Roslyn/RoslynSymbolicExecutionTest.Nullable.cs @@ -0,0 +1,62 @@ +/* + * 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. + */ + +using SonarAnalyzer.SymbolicExecution.Constraints; +using SonarAnalyzer.UnitTest.TestFramework.SymbolicExecution; + +namespace SonarAnalyzer.UnitTest.SymbolicExecution.Roslyn; + +public partial class RoslynSymbolicExecutionTest +{ + [TestMethod] + public void Nullable_Assignment_PropagatesConstrainsToValue() + { + const string code = """ + bool? value = null; + Tag("Null", value); + value = true; + Tag("True", value); + """; + var validator = SETestContext.CreateCS(code).Validator; + validator.ValidateTag("Null", x => x.HasConstraint(ObjectConstraint.Null).Should().BeTrue()); + validator.ValidateTag("True", x => x.HasConstraint(BoolConstraint.True).Should().BeTrue()); + } + + [TestMethod] + public void Nullable_Value_ReadsConstraintsFromInstance() + { + const string code = """ + var value = arg.Value; + Tag("Unknown", value); + arg = true; + value = arg.Value; + Tag("True", value); + arg = false; // This will set additional constraint TestConstraint.First + value = arg.Value; + Tag("FalseFirst", value); + """; + var setter = new PreProcessTestCheck(OperationKind.Literal, x => x.Operation.Instance.ConstantValue.Value is false ? x.SetOperationConstraint(TestConstraint.First) : x.State); + var validator = SETestContext.CreateCS(code, ", bool? arg", setter).Validator; + validator.ValidateTag("Unknown", x => x.Should().BeNull()); + validator.ValidateTag("True", x => x.HasConstraint(BoolConstraint.True).Should().BeTrue()); + validator.ValidateTag("FalseFirst", x => x.HasConstraint(BoolConstraint.False).Should().BeTrue()); + validator.ValidateTag("FalseFirst", x => x.HasConstraint(TestConstraint.First).Should().BeTrue()); + } +}