diff --git a/analyzers/src/SonarAnalyzer.Common/SymbolicExecution/Roslyn/OperationProcessors/ObjectCreation.cs b/analyzers/src/SonarAnalyzer.Common/SymbolicExecution/Roslyn/OperationProcessors/ObjectCreation.cs index 3e057b1f266..eeecba170ef 100644 --- a/analyzers/src/SonarAnalyzer.Common/SymbolicExecution/Roslyn/OperationProcessors/ObjectCreation.cs +++ b/analyzers/src/SonarAnalyzer.Common/SymbolicExecution/Roslyn/OperationProcessors/ObjectCreation.cs @@ -27,8 +27,26 @@ internal sealed class ObjectCreation : SimpleProcessor IObjectCreationOperationWrapper.FromOperation(operation); - protected override ProgramState Process(SymbolicContext context, IObjectCreationOperationWrapper operation) => - operation.Type.IsNullableValueType() && operation.Arguments.IsEmpty - ? context.SetOperationConstraint(ObjectConstraint.Null) - : context.SetOperationConstraint(ObjectConstraint.NotNull); + protected override ProgramState Process(SymbolicContext context, IObjectCreationOperationWrapper operation) + { + if (operation.Type.IsNullableValueType()) + { + if (operation.Arguments.IsEmpty) + { + return context.SetOperationConstraint(ObjectConstraint.Null); + } + else if (context.State[operation.Arguments.First().ToArgument().Value] is { } value) + { + return context.State.SetOperationValue(context.Operation, value.WithConstraint(ObjectConstraint.NotNull)); + } + else + { + return context.SetOperationConstraint(ObjectConstraint.NotNull); + } + } + else + { + return context.SetOperationConstraint(ObjectConstraint.NotNull); + } + } } diff --git a/analyzers/src/SonarAnalyzer.Common/SymbolicExecution/Roslyn/SymbolicValue.cs b/analyzers/src/SonarAnalyzer.Common/SymbolicExecution/Roslyn/SymbolicValue.cs index 00f12ac0592..c615b961720 100644 --- a/analyzers/src/SonarAnalyzer.Common/SymbolicExecution/Roslyn/SymbolicValue.cs +++ b/analyzers/src/SonarAnalyzer.Common/SymbolicExecution/Roslyn/SymbolicValue.cs @@ -41,7 +41,9 @@ public override string ToString() => SerializeConstraints(); public SymbolicValue WithConstraint(SymbolicConstraint constraint) => - this with { Constraints = Constraints.SetItem(constraint.GetType(), constraint) }; + HasConstraint(constraint) + ? this + : this with { Constraints = Constraints.SetItem(constraint.GetType(), constraint) }; public SymbolicValue WithoutConstraint(SymbolicConstraint constraint) => HasConstraint(constraint) diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/SymbolicExecution/Roslyn/RoslynSymbolicExecutionTest.Nullable.cs b/analyzers/tests/SonarAnalyzer.UnitTest/SymbolicExecution/Roslyn/RoslynSymbolicExecutionTest.Nullable.cs index eac3678bb89..512c3fc938c 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/SymbolicExecution/Roslyn/RoslynSymbolicExecutionTest.Nullable.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/SymbolicExecution/Roslyn/RoslynSymbolicExecutionTest.Nullable.cs @@ -108,4 +108,24 @@ public void Main() where T: struct validator.ValidateTag("GenericValue", x => x.HasConstraint(ObjectConstraint.NotNull).Should().BeTrue("new() of T produces value T")); validator.ValidateTag("GenericNull", x => x.HasConstraint(ObjectConstraint.Null).Should().BeTrue()); } + } + + [TestMethod] + public void Nullable_Ctor_Argument_PropagateConstraints() + { + const string code = """ + var falseValue = false; // This will set additional constraint TestConstraint.First + bool? isTrue = new Nullable(true); + bool? isFalse = new Nullable(falseValue); + int? isInt = new Nullable(42); + Tag("IsTrue", isTrue); + Tag("IsFalse", isFalse); + Tag("IsInt", isInt); + """; + 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, setter).Validator; + validator.ValidateTag("IsTrue", x => x.AllConstraints.Select(x => x.ToString()).OrderBy(x => x).JoinStr(", ").Should().Be("NotNull, True")); + validator.ValidateTag("IsFalse", x => x.AllConstraints.Select(x => x.ToString()).OrderBy(x => x).JoinStr(", ").Should().Be("False, First, NotNull")); + validator.ValidateTag("IsInt", x => x.AllConstraints.Select(x => x.ToString()).OrderBy(x => x).JoinStr(", ").Should().Be("NotNull")); + } }