public void checkForUnreachableTokens(Grammar g) {
		if (g.isLexer()) {
			LexerGrammar lexerGrammar = (LexerGrammar)g;
			for (List<Rule> rules : lexerGrammar.modes.values()) {
				// Collect string literal lexer rules for each mode
				List<Rule> stringLiteralRules = new ArrayList<>();
				List<List<String>> stringLiteralValues = new ArrayList<>();
				for (int i = 0; i < rules.size(); i++) {
					Rule rule = rules.get(i);

					List<String> ruleStringAlts = getSingleTokenValues(rule);
					if (ruleStringAlts != null && ruleStringAlts.size() > 0) {
						stringLiteralRules.add(rule);
						stringLiteralValues.add(ruleStringAlts);
					}
				}

				// Check string sets intersection
				for (int i = 0; i < stringLiteralRules.size(); i++) {
					List<String> firstTokenStringValues = stringLiteralValues.get(i);
					Rule rule1 =  stringLiteralRules.get(i);
					checkForOverlap(g, rule1, rule1, firstTokenStringValues, stringLiteralValues.get(i));

					// Check fragment rules only with themself
					if (!rule1.isFragment()) {
						for (int j = i + 1; j < stringLiteralRules.size(); j++) {
							Rule rule2 = stringLiteralRules.get(j);
							if (!rule2.isFragment()) {
								checkForOverlap(g, rule1, stringLiteralRules.get(j), firstTokenStringValues, stringLiteralValues.get(j));
							}
						}
					}
				}
			}
		}
	}
Algorithm steps:
1. Collect all simple string literals (i.e. 'asdf', 'as' 'df', but not [a-z]+, 'a'..'z')
   for all lexer rules in each mode except of autogenerated tokens ({@link #getSingleTokenValues(Rule) getSingleTokenValues})
2. Compare every string literal with each other ({@link #checkForOverlap(Grammar, Rule, Rule, List<String>, List<String>) checkForOverlap})
   and throw TOKEN_UNREACHABLE warning if the same string found.
Complexity: O(m * n^2 / 2), approximately equals to O(n^2)
where m - number of modes, n - average number of lexer rules per mode.
See also testUnreachableTokens unit test for details.
tool/src/org/antlr/v4/semantics/SymbolChecks.java