IndexOf Check Wrong Bound
ID |
csharp.indexof_check_wrong_bound |
Severity |
high |
Remediation Complexity |
trivial |
Remediation Risk |
low |
Remediation Effort |
low |
Resource |
Reliability |
Language |
CSharp |
Tags |
off-by-one, reliability, suspicious-comparison |
Description
Reports an IndexOf, IndexOfAny, LastIndexOf or LastIndexOfAny result tested against > 0,
in either operand order. Position 0 is a valid match, so the test silently misses an occurrence at
the start.
Rationale
All four searches return the zero-based position of the match, and -1 when there is none. > 0
therefore rejects both "not found" and "found at the very beginning" — and the beginning is
precisely the input that tends to matter: the leading separator of a path, the flag at the head of a
list, the quote that opens a token. The code reads as a containment test and behaves as one for every
input except the interesting one, which is why the defect usually reaches production.
The dependable spellings are >= 0 (or != -1) for "found" and < 0 (or == -1) for "not found".
A bound at another position, such as > 1, states a deliberate "after the first character" intent
and is not reported; neither is the correct > -1.
public bool ContainsSeparator(string path)
{
return path.IndexOf('/') > 0; // FLAW — "/etc" answers false
}
public bool StartsWithVowel(string word, char[] vowels)
{
return 0 < word.IndexOfAny(vowels); // FLAW — the mirror image of the same test
}
public bool ContainsSeparatorOk(string path)
{
return path.IndexOf('/') >= 0; // OK — position 0 counts as found
}
public bool MissingOk(string path)
{
return path.IndexOf('/') == -1; // OK — the canonical not-found test
}
public bool AfterFirstOk(string path)
{
return path.IndexOf('/') > 1; // OK — a deliberate position bound
}
Remediation
Replace > 0 with >= 0 when the question is whether the value was found, or with < 0 / == -1
when the question is whether it was absent. For a plain containment test on a string, Contains
states the intent directly and cannot be got wrong; Enumerable.Contains does the same for
sequences. Where the position itself is used afterwards, keep the index but test it with >= 0
before indexing.