[Bug] MySqlLexer.isIdentifierChar throws ArrayIndexOutOfBoundsException when char == 256 (U+0100)
Description
MySqlLexer.isIdentifierChar(char) has an off-by-one bounds check that throws ArrayIndexOutOfBoundsException when the input character has the exact value 256 (U+0100 Ā).
identifierFlags is declared with a fixed size of 256:
private static final boolean[] identifierFlags = new boolean[256];and the method guards the array access with <=:
public static boolean isIdentifierChar(char c) {
if (c <= identifierFlags.length) { // identifierFlags.length == 256
return identifierFlags[c]; // c == 256 -> index out of bounds
}
return c != ' ' && c != ',';
}Valid indices are 0..255, but c <= 256 lets c == 256 through to the array access, throwing:
java.lang.ArrayIndexOutOfBoundsException: Index 256 out of bounds for length 256A single such statement aborts the whole parse (the exception is a RuntimeException, not a ParserException), which is especially harmful in Kafka-listener / streaming SQL-sniffing use cases where one malformed statement crashes the listener thread.
Which positions trigger it
Only U+0100 in an identifier position (table / column / alias) triggers the crash; the same character inside a string literal or comment is fine:
| Position | Example | Result |
|---|---|---|
| String literal | WHERE name = 'Ā' |
OK |
| Double-quoted string | WHERE name = "Ā" |
OK |
| Block comment | /* Ā */ |
OK |
| Line comment | -- Ā |
OK |
| Column | SELECT Ā FROM t |
throws |
| WHERE left-hand column | WHERE Ā = 1 |
throws |
| Table | FROM tĀ |
throws |
| Alias | FROM t AS Ā |
throws |
And only the exact code point 256 is affected (neighboring values are fine):
| Code point | Result |
|---|---|
| U+00FF (255) | OK |
| U+0100 (256) | throws |
| U+0101 (257) | OK |
CJK e.g. 张 (U+5F20) |
OK |
Steps to Reproduce
import com.alibaba.druid.DbType;
import com.alibaba.druid.sql.SQLUtils;
public class Repro {
public static void main(String[] args) {
String sql = "select * from t\u0100 where id = 1"; // \u0100 == U+0100 'Ā'
SQLUtils.parseStatements(sql, DbType.mysql);
}
}Observed (druid 1.2.28):
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 256 out of bounds for length 256
at com.alibaba.druid.sql.dialect.mysql.parser.MySqlLexer.isIdentifierChar(MySqlLexer.java:964)
at com.alibaba.druid.sql.dialect.mysql.parser.MySqlLexer.scanIdentifier(MySqlLexer.java:445)
at com.alibaba.druid.sql.parser.Lexer.nextTokenIdent(Lexer.java:684)
at com.alibaba.druid.sql.dialect.mysql.parser.MySqlSelectParser.parseFrom(MySqlSelectParser.java:68)
at com.alibaba.druid.sql.dialect.mysql.parser.MySqlSelectParser.query(MySqlSelectParser.java:237)
at com.alibaba.druid.sql.parser.SQLSelectParser.select(SQLSelectParser.java:78)
at com.alibaba.druid.sql.dialect.mysql.parser.MySqlStatementParser.parseSelect(MySqlStatementParser.java:120)
at com.alibaba.druid.sql.parser.SQLStatementParser.parseStatementList(SQLStatementParser.java:238)
at com.alibaba.druid.sql.SQLUtils.parseStatements(SQLUtils.java:668)
at com.alibaba.druid.sql.SQLUtils.parseStatements(SQLUtils.java:689)
at Repro.main(Repro.java:7)Expected Behavior
Parsing should not throw; the U+0100 character should fall through to the default branch (return c != ' ' && c != ',';).
Environment
- Druid version:
1.2.27and1.2.28(both affected;isIdentifierCharis unchanged between them) - Java: 21
- DbType: mysql
Suggested Fix
Change the bounds check from <= to <:
public static boolean isIdentifierChar(char c) {
if (c < identifierFlags.length) {
return identifierFlags[c];
}
return c != ' ' && c != ',';
}(or equivalently use c >= identifierFlags.length on the fallback side).
Source: alibaba/druid