From 0e56dbf2806c74480beb99f24d329caae28b0767 Mon Sep 17 00:00:00 2001 From: v-reye Date: Fri, 28 Jun 2019 14:18:38 -0700 Subject: [PATCH] Changes after Fuzz Testing useFmtOnly (#1094) Fixes some unexpected exceptions being thrown. --- .../sqlserver/jdbc/ParameterUtils.java | 2 +- .../sqlserver/jdbc/SQLServerFMTQuery.java | 30 +- .../sqlserver/jdbc/SQLServerParser.java | 263 ++++++++++-------- .../sqlserver/jdbc/SQLServerResource.java | 6 +- 4 files changed, 180 insertions(+), 121 deletions(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/ParameterUtils.java b/src/main/java/com/microsoft/sqlserver/jdbc/ParameterUtils.java index e51f97dd1..4be648d37 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/ParameterUtils.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/ParameterUtils.java @@ -87,7 +87,7 @@ static int scanSQLForChar(char ch, String sql, int offset) { // Fall through - will fail next if and end up in default case case '-': - if (sql.charAt(offset) == '-') { // If '-- ... \n' comment + if (offset >= 0 && offset < sql.length() && sql.charAt(offset) == '-') { // If '-- ... \n' comment while (++offset < len) { // Go thru comment. if (sql.charAt(offset) == '\n' || sql.charAt(offset) == '\r') { // If end of comment diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerFMTQuery.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerFMTQuery.java index 93f67b32d..a89d95252 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerFMTQuery.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerFMTQuery.java @@ -13,7 +13,10 @@ import java.util.List; import java.util.stream.Collectors; +import org.antlr.v4.runtime.BaseErrorListener; import org.antlr.v4.runtime.CharStreams; +import org.antlr.v4.runtime.RecognitionException; +import org.antlr.v4.runtime.Recognizer; import org.antlr.v4.runtime.Token; @@ -89,25 +92,42 @@ String getFMTQuery() { private SQLServerFMTQuery() {}; SQLServerFMTQuery(String userSql) throws SQLServerException { - if (null == userSql || userSql.length() == 0) { + if (null == userSql || 0 == userSql.length()) { SQLServerException.makeFromDriverError(null, this, - SQLServerResource.getResource("R_noTokensFoundInUserQuery"), "", false); + SQLServerResource.getResource("R_noTokensFoundInUserQuery"), null, false); } InputStream stream = new ByteArrayInputStream(userSql.getBytes(StandardCharsets.UTF_8)); SQLServerLexer lexer = null; try { lexer = new SQLServerLexer(CharStreams.fromStream(stream)); } catch (IOException e) { - SQLServerException.makeFromDriverError(null, userSql, e.getLocalizedMessage(), "", false); + SQLServerException.makeFromDriverError(null, userSql, e.getLocalizedMessage(), null, false); } - + lexer.removeErrorListeners(); + lexer.addErrorListener(new SQLServerErrorListener()); this.tokenList = (ArrayList) lexer.getAllTokens(); if (tokenList.size() <= 0) { SQLServerException.makeFromDriverError(null, this, - SQLServerResource.getResource("R_noTokensFoundInUserQuery"), "", false); + SQLServerResource.getResource("R_noTokensFoundInUserQuery"), null, false); } SQLServerTokenIterator iter = new SQLServerTokenIterator(tokenList); this.prefix = SQLServerParser.getCTE(iter); SQLServerParser.parseQuery(iter, this); } } + + +class SQLServerErrorListener extends BaseErrorListener { + static final private java.util.logging.Logger logger = java.util.logging.Logger + .getLogger("com.microsoft.sqlserver.jdbc.internals.SQLServerFMTQuery"); + + @Override + public void syntaxError(Recognizer recognizer, Object offendingSymbol, int line, int charPositionInLine, + String msg, RecognitionException e) { + if (logger.isLoggable(java.util.logging.Level.FINE)) { + logger.fine("Error occured during token parsing: " + msg); + logger.fine("line " + line + ":" + charPositionInLine + " token recognition error at: " + + offendingSymbol.toString()); + } + } +} diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerParser.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerParser.java index c6b7e1181..c17af811b 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerParser.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerParser.java @@ -66,31 +66,43 @@ static void parseQuery(SQLServerTokenIterator iter, SQLServerFMTQuery query) thr } query.getTableTarget().add(getTableTargetChunk(iter, query.getAliases(), INSERT_DELIMITING_WORDS)); - List tableValues = getValuesList(iter); - // VALUES case - boolean valuesFound = false; - int valuesMarker = iter.nextIndex(); - while (!valuesFound && iter.hasNext()) { - t = iter.next(); - if (t.getType() == SQLServerLexer.VALUES) { - valuesFound = true; - do { - query.getValuesList().add(getValuesList(iter)); - } while (iter.hasNext() && iter.next().getType() == SQLServerLexer.COMMA); - iter.previous(); - } - } - if (!valuesFound) { - resetIteratorIndex(iter, valuesMarker); - } - if (!query.getValuesList().isEmpty()) { - for (List ls : query.getValuesList()) { - if (tableValues.isEmpty()) { - query.getColumns().add("*"); + if (iter.hasNext()) { + List tableValues = getValuesList(iter); + // VALUES case + boolean valuesFound = false; + int valuesMarker = iter.nextIndex(); + while (!valuesFound && iter.hasNext()) { + t = iter.next(); + if (t.getType() == SQLServerLexer.VALUES) { + valuesFound = true; + do { + query.getValuesList().add(getValuesList(iter)); + } while (iter.hasNext() && iter.next().getType() == SQLServerLexer.COMMA); + iter.previous(); } - for (int i = 0; i < ls.size(); i++) { - if (ls.get(i).equalsIgnoreCase("?")) { - query.getColumns().add((tableValues.size() == 0) ? "?" : tableValues.get(i)); + } + if (!valuesFound) { + resetIteratorIndex(iter, valuesMarker); + } + if (!query.getValuesList().isEmpty()) { + for (List ls : query.getValuesList()) { + if (tableValues.isEmpty()) { + query.getColumns().add("*"); + } + for (int i = 0; i < ls.size(); i++) { + if (ls.get(i).equalsIgnoreCase("?")) { + if (0 == tableValues.size()) { + query.getColumns().add("?"); + } else { + if (i < tableValues.size()) { + query.getColumns().add(tableValues.get(i)); + } else { + SQLServerException.makeFromDriverError(null, null, + SQLServerResource.getResource("R_invalidInsertValuesQuery"), + null, false); + } + } + } } } } @@ -138,12 +150,12 @@ static void resetIteratorIndex(SQLServerTokenIterator iter, int index) { } } - private static String getRoundBracketChunk(SQLServerTokenIterator iter, Token t) { + private static String getRoundBracketChunk(SQLServerTokenIterator iter, Token t) throws SQLServerException { StringBuilder sb = new StringBuilder(); sb.append('('); Stack s = new Stack<>(); s.push("("); - while (!s.empty()) { + while (!s.empty() && iter.hasNext()) { t = iter.next(); if (t.getType() == SQLServerLexer.RR_BRACKET) { sb.append(")"); @@ -185,7 +197,7 @@ private static String getRoundBracketChunkBefore(SQLServerTokenIterator iter, To SQLServerLexer.OR_ASSIGN, SQLServerLexer.STAR, SQLServerLexer.DIVIDE, SQLServerLexer.MODULE, SQLServerLexer.PLUS, SQLServerLexer.MINUS, SQLServerLexer.LIKE, SQLServerLexer.IN, SQLServerLexer.BETWEEN); - static String findColumnAroundParameter(SQLServerTokenIterator iter) { + static String findColumnAroundParameter(SQLServerTokenIterator iter) throws SQLServerException { int index = iter.nextIndex(); iter.previous(); String value = findColumnBeforeParameter(iter); @@ -197,11 +209,11 @@ static String findColumnAroundParameter(SQLServerTokenIterator iter) { return value; } - private static String findColumnAfterParameter(SQLServerTokenIterator iter) { + private static String findColumnAfterParameter(SQLServerTokenIterator iter) throws SQLServerException { StringBuilder sb = new StringBuilder(); - while (sb.length() == 0 && iter.hasNext()) { + while (0 == sb.length() && iter.hasNext()) { Token t = iter.next(); - if (t.getType() == SQLServerLexer.NOT) { + if (t.getType() == SQLServerLexer.NOT && iter.hasNext()) { t = iter.next(); // skip NOT } if (OPERATORS.contains(t.getType()) && iter.hasNext()) { @@ -216,8 +228,10 @@ private static String findColumnAfterParameter(SQLServerTokenIterator iter) { t = iter.next(); if (t.getType() == SQLServerLexer.DOT) { sb.append("."); - t = iter.next(); - sb.append(t.getText()); + if (iter.hasNext()) { + t = iter.next(); + sb.append(t.getText()); + } } } } @@ -230,22 +244,20 @@ private static String findColumnAfterParameter(SQLServerTokenIterator iter) { private static String findColumnBeforeParameter(SQLServerTokenIterator iter) { StringBuilder sb = new StringBuilder(); - while (sb.length() == 0 && iter.hasPrevious()) { + while (0 == sb.length() && iter.hasPrevious()) { Token t = iter.previous(); - if (t.getType() == SQLServerLexer.DOLLAR) { + if (t.getType() == SQLServerLexer.DOLLAR && iter.hasPrevious()) { t = iter.previous(); // skip if it's a $ sign } - if (t.getType() == SQLServerLexer.AND) { + if (t.getType() == SQLServerLexer.AND && iter.hasPrevious()) { + t = iter.previous(); if (iter.hasPrevious()) { - t = iter.previous(); - if (iter.hasPrevious()) { - t = iter.previous(); // try to find BETWEEN - if (t.getType() == SQLServerLexer.BETWEEN) { - iter.next(); - continue; - } else { - return ""; - } + t = iter.previous(); // try to find BETWEEN + if (t.getType() == SQLServerLexer.BETWEEN && iter.hasNext()) { + iter.next(); + continue; + } else { + return ""; } } } @@ -262,12 +274,14 @@ private static String findColumnBeforeParameter(SQLServerTokenIterator iter) { d.push(t.getText()); } // Linked-servers can have a maximum of 4 parts - for (int i = 0; i < 3; i++) { + for (int i = 0; i < 3 && iter.hasPrevious(); i++) { t = iter.previous(); if (t.getType() == SQLServerLexer.DOT) { d.push("."); - t = iter.previous(); - d.push(t.getText()); + if (iter.hasPrevious()) { + t = iter.previous(); + d.push(t.getText()); + } } } d.stream().forEach(sb::append); @@ -279,7 +293,7 @@ private static String findColumnBeforeParameter(SQLServerTokenIterator iter) { return sb.toString(); } - static List getValuesList(SQLServerTokenIterator iter) { + static List getValuesList(SQLServerTokenIterator iter) throws SQLServerException { Token t = iter.next(); if (t.getType() == SQLServerLexer.LR_BRACKET) { ArrayList parameterColumns = new ArrayList<>(); @@ -317,6 +331,9 @@ static List getValuesList(SQLServerTokenIterator iter) { } if (iter.hasNext() && !d.isEmpty()) { t = iter.next(); + } else if (!iter.hasNext() && !d.isEmpty()) { + SQLServerException.makeFromDriverError(null, null, + SQLServerResource.getResource("R_invalidValuesList"), null, false); } } while (!d.isEmpty()); return parameterColumns; @@ -331,6 +348,10 @@ static List getValuesList(SQLServerTokenIterator iter) { */ static Token skipTop(SQLServerTokenIterator iter) throws SQLServerException { // Look for the TOP token + if (!iter.hasNext()) { + SQLServerException.makeFromDriverError(null, null, SQLServerResource.getResource("R_invalidUserSQL"), null, + false); + } Token t = iter.next(); if (t.getType() == SQLServerLexer.TOP) { t = iter.next(); @@ -362,88 +383,102 @@ static Token skipTop(SQLServerTokenIterator iter) throws SQLServerException { } static String getCTE(SQLServerTokenIterator iter) throws SQLServerException { - Token t = iter.next(); - if (t.getType() == SQLServerLexer.WITH) { - StringBuilder sb = new StringBuilder("WITH "); - getCTESegment(iter, sb); - return sb.toString(); - } else { - iter.previous(); - return ""; + if (iter.hasNext()) { + Token t = iter.next(); + if (t.getType() == SQLServerLexer.WITH) { + StringBuilder sb = new StringBuilder("WITH "); + getCTESegment(iter, sb); + return sb.toString(); + } else { + iter.previous(); + } } + return ""; } static void getCTESegment(SQLServerTokenIterator iter, StringBuilder sb) throws SQLServerException { - sb.append(getTableTargetChunk(iter, null, Arrays.asList(SQLServerLexer.AS))); - iter.next(); - Token t = iter.next(); - sb.append(" AS "); - if (t.getType() != SQLServerLexer.LR_BRACKET) { - SQLServerException.makeFromDriverError(null, null, SQLServerResource.getResource("R_invalidCTEFormat"), "", - false); - } - int leftRoundBracketCount = 0; - do { - sb.append(t.getText()).append(' '); - if (t.getType() == SQLServerLexer.LR_BRACKET) { - leftRoundBracketCount++; - } else if (t.getType() == SQLServerLexer.RR_BRACKET) { - leftRoundBracketCount--; + try { + sb.append(getTableTargetChunk(iter, null, Arrays.asList(SQLServerLexer.AS))); + iter.next(); + Token t = iter.next(); + sb.append(" AS "); + if (t.getType() != SQLServerLexer.LR_BRACKET) { + SQLServerException.makeFromDriverError(null, null, SQLServerResource.getResource("R_invalidCTEFormat"), + null, false); } - t = iter.next(); - } while (leftRoundBracketCount > 0); + int leftRoundBracketCount = 0; + do { + sb.append(t.getText()).append(' '); + if (t.getType() == SQLServerLexer.LR_BRACKET) { + leftRoundBracketCount++; + } else if (t.getType() == SQLServerLexer.RR_BRACKET) { + leftRoundBracketCount--; + } + t = iter.next(); + } while (leftRoundBracketCount > 0); - if (t.getType() == SQLServerLexer.COMMA) { - sb.append(", "); - getCTESegment(iter, sb); - } else { - iter.previous(); + if (t.getType() == SQLServerLexer.COMMA) { + sb.append(", "); + getCTESegment(iter, sb); + } else { + iter.previous(); + } + } catch (java.util.NoSuchElementException e) { + SQLServerException.makeFromDriverError(null, null, SQLServerResource.getResource("R_invalidCTEFormat"), + null, false); } } private static String getTableTargetChunk(SQLServerTokenIterator iter, List possibleAliases, List delimiters) throws SQLServerException { StringBuilder sb = new StringBuilder(); - Token t = iter.next(); - do { - switch (t.getType()) { - case SQLServerLexer.LR_BRACKET: - sb.append(getRoundBracketChunk(iter, t)); - break; - case SQLServerLexer.OPENDATASOURCE: - case SQLServerLexer.OPENJSON: - case SQLServerLexer.OPENQUERY: - case SQLServerLexer.OPENROWSET: - case SQLServerLexer.OPENXML: - sb.append(t.getText()); + if (iter.hasNext()) { + Token t = iter.next(); + do { + switch (t.getType()) { + case SQLServerLexer.LR_BRACKET: + sb.append(getRoundBracketChunk(iter, t)); + break; + case SQLServerLexer.OPENDATASOURCE: + case SQLServerLexer.OPENJSON: + case SQLServerLexer.OPENQUERY: + case SQLServerLexer.OPENROWSET: + case SQLServerLexer.OPENXML: + sb.append(t.getText()); + t = iter.next(); + if (t.getType() != SQLServerLexer.LR_BRACKET) { + SQLServerException.makeFromDriverError(null, null, + SQLServerResource.getResource("R_invalidOpenqueryCall"), null, false); + } + sb.append(getRoundBracketChunk(iter, t)); + break; + case SQLServerLexer.AS: + sb.append(t.getText()); + if (iter.hasNext()) { + String s = iter.next().getText(); + if (possibleAliases != null) { + possibleAliases.add(s); + } else { + SQLServerException.makeFromDriverError(null, null, + SQLServerResource.getResource("R_invalidCTEFormat"), null, false); + } + sb.append(" ").append(s); + } + break; + default: + sb.append(t.getText()); + break; + } + if (iter.hasNext()) { + sb.append(' '); t = iter.next(); - if (t.getType() != SQLServerLexer.LR_BRACKET) { - SQLServerException.makeFromDriverError(null, null, - SQLServerResource.getResource("R_invalidOpenqueryCall"), "", false); - } - sb.append(getRoundBracketChunk(iter, t)); - break; - case SQLServerLexer.AS: - sb.append(t.getText()); - if (iter.hasNext()) { - String s = iter.next().getText(); - possibleAliases.add(s); - sb.append(" ").append(s); - } - break; - default: - sb.append(t.getText()); + } else { break; - } + } + } while (!delimiters.contains(t.getType()) && t.getType() != SQLServerLexer.SEMI); if (iter.hasNext()) { - sb.append(' '); - t = iter.next(); - } else { - break; + iter.previous(); } - } while (!delimiters.contains(t.getType()) && t.getType() != SQLServerLexer.SEMI); - if (iter.hasNext()) { - iter.previous(); } return sb.toString().trim(); } diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java index fd99f83b3..eb7c7b0c5 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java @@ -590,5 +590,9 @@ protected Object[][] getContents() { "Invalid syntax: OPENQUERY/OPENJSON/OPENDATASOURCE/OPENROWSET/OPENXML must be preceded by round brackets"}, {"R_invalidCTEFormat", "Invalid syntax: AS must be followed by round brackets in Common Table Expressions."}, - {"R_noTokensFoundInUserQuery", "Invalid query: No tokens were parsed from the SQL provided."}}; + {"R_noTokensFoundInUserQuery", "Invalid query: No tokens were parsed from the SQL provided."}, + {"R_invalidUserSQL", "An error occurred when attempting to parse user SQL. Please verify SQL syntax."}, + {"R_invalidInsertValuesQuery", + "An error occurred when matching VALUES list to table columns. Please verify SQL syntax."}, + {"R_invalidValuesList", "An error occurred when reading VALUES list. Please verify SQL syntax."}}; };