Skip to content

Commit 67d4099

Browse files
authored
Merge pull request #593 from hvitved/csharp/nullness
C#: Rewrite nullness queries
2 parents 9157825 + fce8058 commit 67d4099

39 files changed

+4609
-789
lines changed

change-notes/1.20/analysis-csharp.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,11 @@
99

1010
## Changes to existing queries
1111

12-
| *@name of query (Query ID)*| *Impact on results* | *How/why the query has changed* |
12+
| *@name of query (Query ID)* | *Impact on results* | *How/why the query has changed* |
13+
|------------------------------|------------------------|-----------------------------------|
1314
| Off-by-one comparison against container length (cs/index-out-of-bounds) | Fewer false positives | Results have been removed when there are additional guards on the index. |
15+
| Dereferenced variable is always null (cs/dereferenced-value-is-always-null) | Improved results | The query has been rewritten from scratch, and the analysis is now based on static single assignment (SSA) forms. The query is now enabled by default in LGTM. |
16+
| Dereferenced variable may be null (cs/dereferenced-value-may-be-null) | Improved results | The query has been rewritten from scratch, and the analysis is now based on static single assignment (SSA) forms. The query is now enabled by default in LGTM. |
1417

1518
## Changes to code extraction
1619

csharp/ql/src/CSI/NullAlways.qhelp

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,39 @@
22
"-//Semmle//qhelp//EN"
33
"qhelp.dtd">
44
<qhelp>
5+
6+
57
<overview>
6-
<p>If a variable is dereferenced, and the variable has a null value on all possible execution paths
7-
leading to the dereferencing, it is guaranteed to result in a <code>NullReferenceException</code>.
8+
<p>If a variable is dereferenced, for example as the qualifier in a method call, and the
9+
variable has a <code>null</code> value on all possible execution paths leading to the
10+
dereferencing, the dereferencing is guaranteed to result in a <code>NullReferenceException</code>.
811
</p>
912

1013
</overview>
1114
<recommendation>
12-
<p>Examine the code to check for possible errors.</p>
15+
16+
<p>Ensure that the variable does not have a <code>null</code> value when it is dereferenced.
17+
</p>
18+
1319
</recommendation>
20+
<example>
21+
<p>
22+
In the following examples, the condition <code>s.Length > 0</code> is only
23+
executed if <code>s</code> is <code>null</code>.
24+
</p>
25+
26+
<sample src="NullAlwaysBad.cs" />
27+
28+
<p>
29+
In the revised example, the condition is guarded correctly by using <code>&amp;&amp;</code> instead of
30+
<code>||</code>.
31+
</p>
32+
33+
<sample src="NullAlwaysGood.cs" />
34+
</example>
35+
<references>
36+
37+
<li>Microsoft, <a href="https://docs.microsoft.com/en-us/dotnet/api/system.nullreferenceexception">NullReferenceException Class</a>.</li>
38+
39+
</references>
1440
</qhelp>

csharp/ql/src/CSI/NullAlways.ql

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
/**
22
* @name Dereferenced variable is always null
3-
* @description Finds uses of a variable that may cause a NullPointerException
3+
* @description Dereferencing a variable whose value is 'null' causes a 'NullReferenceException'.
44
* @kind problem
55
* @problem.severity error
6-
* @precision medium
6+
* @precision very-high
77
* @id cs/dereferenced-value-is-always-null
88
* @tags reliability
99
* correctness
@@ -14,6 +14,6 @@
1414
import csharp
1515
import semmle.code.csharp.dataflow.Nullness
1616

17-
from VariableAccess access, LocalVariable var
18-
where access = unguardedNullDereference(var)
19-
select access, "Variable $@ is always null here.", var, var.getName()
17+
from Dereference d, Ssa::SourceVariable v
18+
where d.isFirstAlwaysNull(v)
19+
select d, "Variable $@ is always null here.", v, v.toString()

csharp/ql/src/CSI/NullAlwaysBad.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
using System;
2+
3+
namespace NullAlways
4+
{
5+
class Bad
6+
{
7+
void DoPrint(string s)
8+
{
9+
if (s != null || s.Length > 0)
10+
Console.WriteLine(s);
11+
}
12+
}
13+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
using System;
2+
3+
namespace NullAlways
4+
{
5+
class Good
6+
{
7+
void DoPrint(string s)
8+
{
9+
if (s != null && s.Length > 0)
10+
Console.WriteLine(s);
11+
}
12+
}
13+
}

csharp/ql/src/CSI/NullMaybe.qhelp

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,41 @@
22
"-//Semmle//qhelp//EN"
33
"qhelp.dtd">
44
<qhelp>
5+
6+
57
<overview>
6-
<p>If a variable is dereferenced, and the variable may have a null value on some execution paths
7-
leading to the dereferencing, the dereferencing may result in a <code>NullReferenceException</code>.
8+
<p>If a variable is dereferenced, for example as the qualifier in a method call, and the
9+
variable may have a <code>null</code> value on some execution paths leading to the
10+
dereferencing, the dereferencing may result in a <code>NullReferenceException</code>.
811
</p>
912

1013
</overview>
1114
<recommendation>
12-
<p>Examine the code to check for possible errors.</p>
15+
16+
<p>Ensure that the variable does not have a <code>null</code> value when it is dereferenced.
17+
</p>
18+
1319
</recommendation>
20+
<example>
21+
<p>
22+
In the following example, the method <code>DoPrint()</code> dereferences its parameter
23+
<code>o</code> unconditionally, resulting in a <code>NullReferenceException</code> via
24+
the call <code>DoPrint(null)</code>.
25+
</p>
26+
27+
<sample src="NullMaybeBad.cs" />
28+
29+
<p>
30+
In the revised example, the method <code>DoPrint()</code> guards the dereferencing with
31+
a <code>null</code> check.
32+
</p>
33+
34+
<sample src="NullMaybeGood.cs" />
35+
36+
</example>
37+
<references>
38+
39+
<li>Microsoft, <a href="https://docs.microsoft.com/en-us/dotnet/api/system.nullreferenceexception">NullReferenceException Class</a>.</li>
40+
41+
</references>
1442
</qhelp>

csharp/ql/src/CSI/NullMaybe.ql

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
/**
22
* @name Dereferenced variable may be null
3-
* @description Finds uses of a variable that may cause a NullPointerException
3+
* @description Dereferencing a variable whose value may be 'null' may cause a
4+
* 'NullReferenceException'.
45
* @kind problem
56
* @problem.severity warning
6-
* @precision medium
7+
* @precision high
78
* @id cs/dereferenced-value-may-be-null
89
* @tags reliability
910
* correctness
@@ -14,8 +15,6 @@
1415
import csharp
1516
import semmle.code.csharp.dataflow.Nullness
1617

17-
from VariableAccess access, LocalVariable var
18-
where access = unguardedMaybeNullDereference(var)
19-
// do not flag definite nulls here; these are already flagged by NullAlways.ql
20-
and not access = unguardedNullDereference(var)
21-
select access, "Variable $@ may be null here.", var, var.getName()
18+
from Dereference d, Ssa::SourceVariable v, string msg, Element reason
19+
where d.isFirstMaybeNull(v.getAnSsaDefinition(), msg, reason)
20+
select d, "Variable $@ may be null here " + msg + ".", v, v.toString(), reason, "this"

csharp/ql/src/CSI/NullMaybeBad.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
using System;
2+
3+
class Bad
4+
{
5+
void DoPrint(object o)
6+
{
7+
Console.WriteLine(o.ToString());
8+
}
9+
10+
void M()
11+
{
12+
DoPrint("Hello");
13+
DoPrint(null);
14+
}
15+
}

csharp/ql/src/CSI/NullMaybeGood.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
using System;
2+
3+
class Good
4+
{
5+
void DoPrint(object o)
6+
{
7+
if (o != null)
8+
Console.WriteLine(o.ToString());
9+
}
10+
11+
void M()
12+
{
13+
DoPrint("Hello");
14+
DoPrint(null);
15+
}
16+
}

csharp/ql/src/semmle/code/csharp/Stmt.qll

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -966,12 +966,17 @@ class TryStmt extends Stmt, @try_stmt {
966966
exists(ControlFlowElement mid |
967967
mid = getATriedElement() and
968968
not mid instanceof TryStmt and
969-
result = mid.getAChild() and
970-
mid.getEnclosingCallable() = result.getEnclosingCallable()
969+
result = getAChild(mid, mid.getEnclosingCallable())
971970
)
972971
}
973972
}
974973

974+
pragma[noinline]
975+
private ControlFlowElement getAChild(ControlFlowElement cfe, Callable c) {
976+
result = cfe.getAChild() and
977+
c = result.getEnclosingCallable()
978+
}
979+
975980
/**
976981
* A `catch` clause within a `try` statement.
977982
*

0 commit comments

Comments
 (0)