Skip to content

Commit 6c6d7e4

Browse files
committed
C#: Fix false-positives in cs/index-out-of-bounds.
1 parent c403bb1 commit 6c6d7e4

File tree

2 files changed

+105
-11
lines changed

2 files changed

+105
-11
lines changed

csharp/ql/src/Likely Bugs/Collections/ContainerLengthCmpOffByOne.ql

Lines changed: 49 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,55 @@
1414

1515
import csharp
1616
import semmle.code.csharp.controlflow.Guards
17+
import semmle.code.csharp.commons.ComparisonTest
1718

18-
from RelationalOperation cmp, Variable array, Variable index, ElementAccess ea, VariableAccess indexAccess
19-
// Look for `index <= array.Length` or `array.Length >= index`
20-
where (cmp instanceof GEExpr or cmp instanceof LEExpr)
21-
and cmp.getGreaterOperand() = any(PropertyAccess pa | pa.getQualifier() = array.getAnAccess() and pa.getTarget().hasName("Length"))
22-
and cmp.getLesserOperand() = index.getAnAccess()
19+
/** A comparison of an index variable with the length of an array. */
20+
class IndexGuard extends Expr {
21+
ComparisonTest ct;
22+
VariableAccess indexAccess;
23+
Variable array;
24+
25+
IndexGuard() {
26+
ct.getExpr() = this and
27+
ct.getFirstArgument() = indexAccess and
28+
ct.getSecondArgument() = any(PropertyAccess lengthAccess |
29+
lengthAccess.getQualifier() = array.getAnAccess() and
30+
lengthAccess.getTarget().hasName("Length")
31+
)
32+
}
33+
34+
/** This comparison applies to array `arr` and index `ind`. */
35+
predicate controls(Variable arr, Variable ind) {
36+
arr = array and
37+
ind.getAnAccess() = indexAccess
38+
}
39+
40+
/** This comparison guards `expr`. */
41+
predicate guards(GuardedExpr expr, boolean condition) {
42+
expr.isGuardedBy(this, indexAccess, condition)
43+
}
44+
45+
/** This comparison is an incorrect `<=` or equivalent. */
46+
predicate isIncorrect() {
47+
ct.getComparisonKind().isLessThanEquals()
48+
}
49+
}
50+
51+
from IndexGuard incorrectGuard, Variable array, Variable index, ElementAccess ea, GuardedExpr indexAccess
52+
where
53+
// Look for `index <= array.Length` or `array.Length >= index`
54+
incorrectGuard.controls(array, index) and
55+
incorrectGuard.isIncorrect() and
2356
// Look for `array[index]`
24-
and ea.getQualifier() = array.getAnAccess()
25-
and ea.getIndex(0) = indexAccess
26-
and indexAccess = index.getAnAccess()
57+
ea.getQualifier() = array.getAnAccess() and
58+
ea.getIndex(0) = indexAccess and
59+
indexAccess = index.getAnAccess() and
2760
// Where the index access is guarded by the comparison
28-
and indexAccess.(GuardedExpr).isGuardedBy(cmp, index.getAnAccess(), true)
29-
select cmp, "Off-by-one index comparison against length leads to possible out of bounds $@.", ea, ea.toString()
61+
incorrectGuard.guards(indexAccess, true) and
62+
// And there are no other guards
63+
not exists(IndexGuard validGuard |
64+
not validGuard.isIncorrect() and
65+
validGuard.controls(array, index) and
66+
validGuard.guards(indexAccess, _)
67+
)
68+
select incorrectGuard, "Off-by-one index comparison against length leads to possible out of bounds $@.", ea, ea.toString()

csharp/ql/test/query-tests/Likely Bugs/Collections/ContainerLengthCmpOffByOne/ContainerLengthCmpOffByOne.cs

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
class Test
44
{
5-
static void Main(string[] args)
5+
void Test1(string[] args)
66
{
77
// BAD: Loop upper bound is off-by-one
88
for (int i = 0; i <= args.Length; i++)
@@ -34,11 +34,66 @@ static void Main(string[] args)
3434
{
3535
Console.WriteLine(args[j]);
3636
}
37+
}
38+
39+
void Test2(string[] args)
40+
{
41+
int j = 0;
3742

3843
// GOOD: Correct terminating value
3944
if (args.Length > j)
4045
{
4146
Console.WriteLine(args[j]);
4247
}
4348
}
49+
50+
void Test3(string[] args)
51+
{
52+
// GOOD: Guarded by ternary operator.
53+
for (int i = 0; i <= args.Length; i++)
54+
{
55+
string s = i < args.Length ? args[i] : "";
56+
}
57+
}
58+
59+
void Test4(string[] args)
60+
{
61+
int j = 0;
62+
63+
// GOOD: Guarded by ternary operator.
64+
if( j <= args.Length )
65+
{
66+
string s = j < args.Length ? args[j] : "";
67+
}
68+
}
69+
70+
void Test5(string[] args)
71+
{
72+
// GOOD: A valid test of Length.
73+
for (int i = 0; i != args.Length; i++)
74+
{
75+
Console.WriteLine(args[i]);
76+
}
77+
}
78+
79+
void Test6(string[] args)
80+
{
81+
int j = 0;
82+
83+
// GOOD: There is another correct test.
84+
if( j <= args.Length )
85+
{
86+
if (j == args.Length) return;
87+
Console.WriteLine(args[j]);
88+
}
89+
}
90+
91+
void Test7(string[] args)
92+
{
93+
// GOOD: Guarded by ||.
94+
for (int i = 0; i <= args.Length; i++)
95+
{
96+
bool b = i == args.Length || args[i] == "x";
97+
}
98+
}
4499
}

0 commit comments

Comments
 (0)