String comparison performed without an explicit StringComparison
ID |
vbnet.correctness.stringcomparison |
Severity |
low |
Remediation Complexity |
trivial |
Remediation Risk |
low |
Remediation Effort |
low |
Resource |
String Handling |
Language |
VB.NET |
Description
Reports string comparisons that do not state which comparison rules to apply: the
single-argument StartsWith and EndsWith overloads, the two-argument
String.Compare(left, right) overload, and Equals called with a string literal and no
StringComparison. Each of these has an overload that takes a StringComparison, and that
overload is what the rule asks for. Calls that already pass a StringComparison are accepted,
and Contains is not reported because it is ordinal by definition. Two forms are excluded
because no StringComparison overload exists for them: a character-literal argument such as
StartsWith("-"c), and Equals("") - an empty-string test, whose proper fix is
String.IsNullOrEmpty rather than a comparison mode.
Rationale
StartsWith, EndsWith and String.Compare default to a culture-sensitive comparison driven
by the current thread culture, so their result depends on where the process happens to be
running. Culture-sensitive rules are not simple character matching: under many cultures
certain characters are ignored for comparison purposes, and comparison order differs between
cultures - in Swedish z sorts before the last letters of the alphabet, in Hungarian dzs
collates as one unit, and Turkish treats dotted and dotless i as separate letters, so a
prefix test that passes on a developer machine can fail on a customer’s. When the strings
being compared are not human-readable text at all - a file extension, a URL scheme, a
registry key, a role name, an account prefix - culture is the wrong rule in every locale, and
the resulting mismatch shows up as a permission check that silently returns the wrong answer
or a lookup that finds nothing. Equals is already ordinal, but with no StringComparison
argument the reader cannot tell whether ordinal was chosen or merely inherited, and a later
edit to a culture-sensitive method is invisible in review.
The following code illustrates the pattern detected by this rule:
Public Function IsAdministrator(ByVal roleName As String) As Boolean
' FLAGGED: String comparison performed without an explicit StringComparison
Return roleName.Equals("administrator")
End Function
Remediation
Pass an explicit StringComparison. For identifiers, protocol tokens, file paths, keys and
any other value not shown to a user, use StringComparison.Ordinal (or
StringComparison.OrdinalIgnoreCase when case should be ignored) - it is both correct and the
fastest option. Reserve StringComparison.CurrentCulture for text that is genuinely being
compared or ordered on behalf of the user, and use StringComparison.InvariantCulture only
for a stable, culture-independent linguistic ordering.
' Before: the current culture decides the result
Return roleName.Equals("administrator")
Return emailAddress.EndsWith("@example.com")
Return String.Compare(left, right)
' After: the rules are stated at the call site
Return roleName.Equals("administrator", StringComparison.OrdinalIgnoreCase)
Return emailAddress.EndsWith("@example.com", StringComparison.OrdinalIgnoreCase)
Return String.Compare(left, right, StringComparison.Ordinal)