Skip to content

Commit

Permalink
Add well-known Debug.Assert method
Browse files Browse the repository at this point in the history
  • Loading branch information
jcouv committed May 5, 2018
1 parent 12f6478 commit ad53fbf
Show file tree
Hide file tree
Showing 6 changed files with 271 additions and 6 deletions.
41 changes: 37 additions & 4 deletions src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ internal sealed partial class NullableWalker : DataFlowPassBase<NullableWalker.L
/// </summary>
private PooledDictionary<BoundExpression, ObjectCreationPlaceholderLocal> _placeholderLocals;

private bool _disableDiagnostics = false;

protected override void Free()
{
_variableTypes.Free();
Expand Down Expand Up @@ -470,7 +472,10 @@ private bool IsByRefTarget(int slot)

private void ReportStaticNullCheckingDiagnostics(ErrorCode errorCode, SyntaxNode syntaxNode, params object[] arguments)
{
Diagnostics.Add(errorCode, syntaxNode.GetLocation(), arguments);
if (!_disableDiagnostics)
{
Diagnostics.Add(errorCode, syntaxNode.GetLocation(), arguments);
}
}

private void InheritNullableStateOfTrackableStruct(TypeSymbol targetType, int targetSlot, int valueSlot, bool isByRefTarget, int slotWatermark)
Expand Down Expand Up @@ -1548,16 +1553,17 @@ public override BoundNode VisitCall(BoundCall node)

// PROTOTYPE(NullableReferenceTypes): Can we handle some error cases?
// (Compare with CSharpOperationFactory.CreateBoundCallOperation.)
ImmutableArray<BoundExpression> arguments = node.Arguments;
if (!node.HasErrors)
{
ImmutableArray<RefKind> refKindsOpt = node.ArgumentRefKindsOpt;
ImmutableArray<BoundExpression> arguments = RemoveArgumentConversions(node.Arguments, refKindsOpt);
ImmutableArray<Result> results = VisitArgumentsEvaluate(arguments, refKindsOpt, node.Expanded);
ImmutableArray<BoundExpression> strippedArguments = RemoveArgumentConversions(arguments, refKindsOpt);
ImmutableArray<Result> results = VisitArgumentsEvaluate(strippedArguments, refKindsOpt, node.Expanded);
if (method.IsGenericMethod && HasImplicitTypeArguments(node))
{
method = InferMethod(node, method, results.SelectAsArray(r => r.Type));
}
VisitArgumentsWarn(arguments, refKindsOpt, method.Parameters, node.ArgsToParamsOpt, node.Expanded, results);
VisitArgumentsWarn(strippedArguments, refKindsOpt, method.Parameters, node.ArgsToParamsOpt, node.Expanded, results);
}

UpdateStateForCall(node);
Expand All @@ -1568,6 +1574,11 @@ public override BoundNode VisitCall(BoundCall node)
ReplayReadsAndWrites(localFunc, node.Syntax, writes: true);
}

if (MethodEnsuresTrueWhenExits(method) && arguments.Length > 0)
{
VisitTrueWhenExits(condition: arguments[0]);
}

Debug.Assert(!IsConditionalState);
//if (this.State.Reachable) // PROTOTYPE(NullableReferenceTypes): Consider reachability?
{
Expand All @@ -1577,6 +1588,28 @@ public override BoundNode VisitCall(BoundCall node)
return null;
}

private bool MethodEnsuresTrueWhenExits(MethodSymbol method)
{
return method.Equals(compilation.GetWellKnownTypeMember(WellKnownMember.System_Diagnostics_Debug_Assert1)) ||
method.Equals(compilation.GetWellKnownTypeMember(WellKnownMember.System_Diagnostics_Debug_Assert2)) ||
method.Equals(compilation.GetWellKnownTypeMember(WellKnownMember.System_Diagnostics_Debug_Assert3)) ||
method.Equals(compilation.GetWellKnownTypeMember(WellKnownMember.System_Diagnostics_Debug_Assert4));
}

private void VisitTrueWhenExits(BoundExpression condition)
{
// We only need to update the null-state, so we'll do the flow analysis without diagnostics

bool savedDisableDiagnostics = _disableDiagnostics;
_disableDiagnostics = true;

// if (!condition) throw;
VisitIfStatement(new BoundIfStatement(condition.Syntax, SyntheticBoundNodeFactory.NotCore(condition),
new BoundThrowStatement(condition.Syntax, expressionOpt: null), alternativeOpt: null));

_disableDiagnostics = savedDisableDiagnostics;
}

// PROTOTYPE(NullableReferenceTypes): Record in the node whether type
// arguments were implicit, to allow for cases where the syntax is not an
// invocation (such as a synthesized call from a query interpretation).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1252,8 +1252,12 @@ internal static BoundExpression NullOrDefault(TypeSymbol typeSymbol, SyntaxNode
return typeSymbol.IsReferenceType ? Null(typeSymbol, syntax) : Default(typeSymbol, syntax);
}

internal BoundExpression Not(
BoundExpression expression)
internal BoundExpression Not(BoundExpression expression)
{
return NotCore(expression);
}

internal static BoundUnaryOperator NotCore(BoundExpression expression)
{
return new BoundUnaryOperator(expression.Syntax, UnaryOperatorKind.BoolLogicalNegation, expression, null, null, LookupResultKind.Viable, expression.Type);
}
Expand Down
178 changes: 178 additions & 0 deletions src/Compilers/CSharp/Test/Semantic/Semantics/StaticNullChecking.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5437,6 +5437,184 @@ class CL1<T>
);
}

[Fact]
public void Wellknown_Debug_Assert1_NotNull()
{
CSharpCompilation c = CreateCompilation(@"
class C
{
void Main(C? c)
{
System.Diagnostics.Debug.Assert(c != null);
c.ToString();
}
}
", parseOptions: TestOptions.Regular8);

c.VerifyDiagnostics();
}

[Fact]
public void Wellknown_Debug_Assert1_NotNullAndNotEmpty()
{
CSharpCompilation c = CreateCompilation(@"
class C
{
void Main(string? c)
{
System.Diagnostics.Debug.Assert(c != null && c != """");
c.ToString();
}
}
", parseOptions: TestOptions.Regular8);

c.VerifyDiagnostics();
}

[Fact]
public void Wellknown_Debug_Assert1_NotNullOrUnknown()
{
CSharpCompilation c = CreateCompilation(@"
class C
{
void Main(string? c, bool b)
{
System.Diagnostics.Debug.Assert(c != null || b);
c.ToString();
}
}
", parseOptions: TestOptions.Regular8);

c.VerifyDiagnostics(
// (7,9): warning CS8602: Possible dereference of a null reference.
// c.ToString();
Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "c").WithLocation(7, 9)
);
}

[Fact]
public void Wellknown_Debug_Assert2_NotNull()
{
CSharpCompilation c = CreateCompilation(@"
class C
{
void Main(C? c)
{
System.Diagnostics.Debug.Assert(c != null, ""hello"");
c.ToString();
}
}
", parseOptions: TestOptions.Regular8);

c.VerifyDiagnostics();
}

[Fact]
public void Wellknown_Debug_Assert3_NotNull()
{
CSharpCompilation c = CreateCompilation(@"
class C
{
void Main(C? c)
{
System.Diagnostics.Debug.Assert(c != null, ""hello"", ""world"");
c.ToString();
}
}
", parseOptions: TestOptions.Regular8);

c.VerifyDiagnostics();
}

[Fact]
public void Wellknown_Debug_Assert4_NotNull()
{
CSharpCompilation c = CreateCompilation(@"
class C
{
void Main(C? c)
{
System.Diagnostics.Debug.Assert(c != null, ""hello"", ""world"", new object[] { });
c.ToString();
}
}
", parseOptions: TestOptions.Regular8);

c.VerifyDiagnostics();
}

[Fact]
public void Wellknown_Debug_Assert_IsNull()
{
CSharpCompilation c = CreateCompilation(@"
class C
{
void Main(C c)
{
System.Diagnostics.Debug.Assert(c == null, ""hello"");
c.ToString();
}
}
", parseOptions: TestOptions.Regular8);

c.VerifyDiagnostics(
// (6,41): hidden CS8606: Result of the comparison is possibly always false.
// System.Diagnostics.Debug.Assert(c == null, "hello");
Diagnostic(ErrorCode.HDN_NullCheckIsProbablyAlwaysFalse, "c == null").WithLocation(6, 41)
);
}

[Fact]
public void Wellknown_Debug_Assert_NoDuplicateDiagnostics()
{
CSharpCompilation c = CreateCompilation(@"
class C
{
void Main(C? c)
{
System.Diagnostics.Debug.Assert(Method(null), ""hello"");
c.ToString();
}
bool Method(string x) => throw null;
}
", parseOptions: TestOptions.Regular8);

c.VerifyDiagnostics(
// (6,48): warning CS8625: Cannot convert null literal to non-nullable reference or unconstrained type parameter.
// System.Diagnostics.Debug.Assert(Method(null), "hello");
Diagnostic(ErrorCode.WRN_NullAsNonNullable, "null").WithLocation(6, 48),
// (7,9): warning CS8602: Possible dereference of a null reference.
// c.ToString();
Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "c").WithLocation(7, 9)
);
}

[Fact]
public void Wellknown_Debug_Assert_InTry()
{
CSharpCompilation c = CreateCompilation(@"
class C
{
void Main(C? c)
{
try
{
System.Diagnostics.Debug.Assert(c != null, ""hello"");
}
catch { }

c.ToString();
}
}
", parseOptions: TestOptions.Regular8);

c.VerifyDiagnostics(
// (12,9): warning CS8602: Possible dereference of a null reference.
// c.ToString();
Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "c").WithLocation(12, 9)
);
}

[Fact]
public void ConditionalBranching_01()
{
Expand Down
5 changes: 5 additions & 0 deletions src/Compilers/Core/Portable/WellKnownMember.cs
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,11 @@ internal enum WellKnownMember

System_Runtime_CompilerServices_IsUnmanagedAttribute__ctor,

System_Diagnostics_Debug_Assert1,
System_Diagnostics_Debug_Assert2,
System_Diagnostics_Debug_Assert3,
System_Diagnostics_Debug_Assert4,

Count
}
}
41 changes: 41 additions & 0 deletions src/Compilers/Core/Portable/WellKnownMembers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2991,6 +2991,43 @@ static WellKnownMembers()
0, // Method Signature
(byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Void,

// System_Diagnostics_Debug_Assert
(byte)(MemberFlags.Method | MemberFlags.Static), // Flags
(byte)WellKnownType.ExtSentinel, (byte)(WellKnownType.System_Diagnostics_Debug - WellKnownType.ExtSentinel), // DeclaringTypeId
0, // Arity
1, // Method Signature
(byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Void,
(byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Boolean,

// System_Diagnostics_Debug_Assert
(byte)(MemberFlags.Method | MemberFlags.Static), // Flags
(byte)WellKnownType.ExtSentinel, (byte)(WellKnownType.System_Diagnostics_Debug - WellKnownType.ExtSentinel), // DeclaringTypeId
0, // Arity
2, // Method Signature
(byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Void,
(byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Boolean,
(byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_String,

// System_Diagnostics_Debug_Assert
(byte)(MemberFlags.Method | MemberFlags.Static), // Flags
(byte)WellKnownType.ExtSentinel, (byte)(WellKnownType.System_Diagnostics_Debug - WellKnownType.ExtSentinel), // DeclaringTypeId
0, // Arity
3, // Method Signature
(byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Void,
(byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Boolean,
(byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_String,
(byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_String,

// System_Diagnostics_Debug_Assert
(byte)(MemberFlags.Method | MemberFlags.Static), // Flags
(byte)WellKnownType.ExtSentinel, (byte)(WellKnownType.System_Diagnostics_Debug - WellKnownType.ExtSentinel), // DeclaringTypeId
0, // Arity
4, // Method Signature
(byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Void,
(byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Boolean,
(byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_String,
(byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_String,
(byte)SignatureTypeCode.SZArray, (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Object,
};

string[] allNames = new string[(int)WellKnownMember.Count]
Expand Down Expand Up @@ -3364,6 +3401,10 @@ static WellKnownMembers()
"get_Item", // System_ReadOnlySpan__get_Item
"get_Length", // System_ReadOnlySpan__get_Length
".ctor", // System_Runtime_CompilerServices_IsUnmanagedAttribute__ctor
"Assert", // System_Diagnostics_Debug_Assert1
"Assert", // System_Diagnostics_Debug_Assert2
"Assert", // System_Diagnostics_Debug_Assert3
"Assert", // System_Diagnostics_Debug_Assert4
};

s_descriptors = MemberDescriptor.InitializeFromStream(new System.IO.MemoryStream(initializationBytes, writable: false), allNames);
Expand Down
4 changes: 4 additions & 0 deletions src/Compilers/Core/Portable/WellKnownTypes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,8 @@ internal enum WellKnownType
System_Runtime_InteropServices_UnmanagedType,
System_Runtime_CompilerServices_IsUnmanagedAttribute,

System_Diagnostics_Debug,

NextAvailable,
}

Expand Down Expand Up @@ -543,6 +545,8 @@ internal static class WellKnownTypes
"System.ReadOnlySpan`1",
"System.Runtime.InteropServices.UnmanagedType",
"System.Runtime.CompilerServices.IsUnmanagedAttribute",

"System.Diagnostics.Debug",
};

private readonly static Dictionary<string, WellKnownType> s_nameToTypeIdMap = new Dictionary<string, WellKnownType>((int)Count);
Expand Down

0 comments on commit ad53fbf

Please sign in to comment.