Misleading Regex Argument In String Methods
ID |
java.string_regex_literal_dot |
Severity |
low |
Remediation Complexity |
trivial |
Remediation Risk |
low |
Remediation Effort |
low |
Resource |
Reliability |
Language |
Java |
Tags |
code-style |
Description
Reports calls to String.split, String.replaceAll, String.replaceFirst and String.matches whose first argument is one of three values that read as a regular expression but are almost always meant as plain text: the literal ".", the literal "|", or File.separator.
Rationale
The first argument of these four methods is a pattern, not a plain string, and three values are routinely passed as though it were one.
. matches any character, so it eats the whole input:
"a.b.c".split(".") // returns String[0] — every char is eaten
"abc".replaceAll(".", "X") // returns "XXX", not "abc"
| is an alternation between two empty branches, so it matches the zero-length string in front of every character:
"north|south".split("|") // ["n","o","r","t","h","|","s","o","u","t","h"]
"north|south".replaceAll("|", "-") // "-n-o-r-t-h-|-s-o-u-t-h-"
File.separator carries whatever the running platform uses. Where that is a backslash the value is the regex escape character, so the pattern is malformed and the call throws PatternSyntaxException — on that platform only, which is how it survives testing elsewhere:
path.split(File.separator); // throws on a backslash platform
The bug usually hides behind the assumption that the argument is the separator character rather than a pattern describing it.
Remediation
Escape the metacharacter, quote a value that was never meant to be a pattern, or call a method that takes plain text — String.replace(CharSequence, CharSequence) never interprets its argument.
// Escape, when a pattern really is wanted
String[] parts = hostname.split("\\.");
String[] legs = route.split("\\|");
// Quote, when the separator is computed at runtime
String[] steps = path.split(Pattern.quote(File.separator));
// Or sidestep patterns altogether
String cleaned = text.replace(".", "_");
For filesystem paths, iterating the elements of a NIO Path avoids splitting on a separator in the first place.
What is not reported
matches(".") is a legitimate "exactly one character" test, so the dot is only reported for split, replaceAll and replaceFirst. An argument built at runtime — a concatenation, or a nested call such as Pattern.quote(File.separator) — is not reported either, because the value reaching the matcher is no longer the bare metacharacter.