From 54c3b8c0290609bf7c37a907c3928f761d7e79b1 Mon Sep 17 00:00:00 2001 From: Antonio Aversa Date: Fri, 31 Mar 2023 11:58:37 +0200 Subject: [PATCH] Honor null forgiving operator --- .../EmptyNullableValueAccessBase.cs | 9 ++++-- ...mptyNullableValueAccess.NullableContext.cs | 30 +++++++++---------- 2 files changed, 22 insertions(+), 17 deletions(-) diff --git a/analyzers/src/SonarAnalyzer.Common/SymbolicExecution/Roslyn/RuleChecks/EmptyNullableValueAccessBase.cs b/analyzers/src/SonarAnalyzer.Common/SymbolicExecution/Roslyn/RuleChecks/EmptyNullableValueAccessBase.cs index a718bee5ca3..df48fb78bb6 100644 --- a/analyzers/src/SonarAnalyzer.Common/SymbolicExecution/Roslyn/RuleChecks/EmptyNullableValueAccessBase.cs +++ b/analyzers/src/SonarAnalyzer.Common/SymbolicExecution/Roslyn/RuleChecks/EmptyNullableValueAccessBase.cs @@ -34,7 +34,8 @@ protected override ProgramState PreProcessSimple(SymbolicContext context) && reference.Property.Name == nameof(Nullable.Value) && reference.Instance is { } instance && instance.Type.IsNullableValueType() - && context.HasConstraint(instance, ObjectConstraint.Null)) + && context.HasConstraint(instance, ObjectConstraint.Null) + && NoNullForgiving(reference.Instance)) { ReportIssue(instance, instance.Syntax.ToString()); } @@ -42,11 +43,15 @@ protected override ProgramState PreProcessSimple(SymbolicContext context) && operationInstance.ToConversion() is var conversion && conversion.Operand.Type.IsNullableValueType() && conversion.Type.IsNonNullableValueType() - && context.HasConstraint(conversion.Operand, ObjectConstraint.Null)) + && context.HasConstraint(conversion.Operand, ObjectConstraint.Null) + && NoNullForgiving(conversion.Operand)) { ReportIssue(conversion.Operand, conversion.Operand.Syntax.ToString()); } return context.State; + + bool NoNullForgiving(IOperation reference) => + SemanticModel.GetTypeInfo(reference.Syntax).Nullability().FlowState != NullableFlowState.NotNull; } } diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/SymbolicExecution/Roslyn/EmptyNullableValueAccess.NullableContext.cs b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/SymbolicExecution/Roslyn/EmptyNullableValueAccess.NullableContext.cs index e14353f904e..d70ad0fbd7c 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/SymbolicExecution/Roslyn/EmptyNullableValueAccess.NullableContext.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/SymbolicExecution/Roslyn/EmptyNullableValueAccess.NullableContext.cs @@ -7,42 +7,42 @@ class NullForgivingOperator { void Basics(int? i) { - _ = i!.Value; // Compliant, unknown + _ = i!.Value; // Compliant, user-asserted non-empty via bang i = SomeMethod(); - _ = i!.Value; // Compliant, unknown + _ = i!.Value; // Compliant i = null; - _ = i!.Value; // Noncompliant, empty + _ = i!.Value; // Compliant i = new int?(); - _ = i!.Value; // Noncompliant, empty + _ = i!.Value; // Compliant i = new Nullable(); - _ = i!.Value; // Noncompliant, empty + _ = i!.Value; // Compliant i = 42; - _ = i!.Value; // Compliant, non-empty + _ = i!.Value; // Compliant } void CastToValueType(int? i) { - _ = (int)i!; // Compliant, unknown + _ = (int)i!; // Compliant, user-asserted non-empty via bang i = SomeMethod(); - _ = (int)i!; // Compliant, unknown + _ = (int)i!; // Compliant i = null; - _ = (int)i!; // Noncompliant, empty + _ = (int)i!; // Compliant i = new int?(); - _ = (int)i!; // Noncompliant, empty + _ = (int)i!; // Compliant i = new Nullable(); - _ = (int)i!; // Noncompliant, empty + _ = (int)i!; // Compliant } void CastToNullableType(int? i) { - _ = ((int?)i)!.Value; // Compliant, unknown - _ = (i as int?)!.Value; // Compliant, unknown + _ = ((int?)i)!.Value; // Compliant, user-asserted non-empty via bang + _ = (i as int?)!.Value; // Compliant - _ = ((int?)null)!.Value; // Noncompliant - _ = (null as int?)!.Value; // Noncompliant + _ = ((int?)null)!.Value; // Compliant + _ = (null as int?)!.Value; // Compliant } static int? SomeMethod() => null;