Reduce an unnecessary O(N^3) loop in lexer.
authorAndrew Gierth <rhodiumtoad@postgresql.org>
Thu, 23 Aug 2018 18:59:38 +0000 (19:59 +0100)
committerAndrew Gierth <rhodiumtoad@postgresql.org>
Thu, 23 Aug 2018 20:33:38 +0000 (21:33 +0100)
The lexer's handling of operators contained an O(N^3) hazard when
dealing with long strings of + or - characters; it seems hard to
prevent this case from being O(N^2), but the additional N multiplier
was not needed.

Backpatch all the way since this has been there since 7.x, and it
presents at least a mild hazard in that trying to do Bind, PREPARE or
EXPLAIN on a hostile query could take excessive time (without
honouring cancels or timeouts) even if the query was never executed.

src/backend/parser/scan.l
src/bin/psql/psqlscan.l
src/interfaces/ecpg/preproc/pgc.l

index 30242d343d1502859e982cf289468d329d1886d1..4b2444d3e80b0e47194ba76dbbb9bbc28b75d988 100644 (file)
@@ -844,20 +844,33 @@ other         .
                     * to forbid operator names like '?-' that could not be
                     * sequences of SQL operators.
                     */
-                   while (nchars > 1 &&
-                          (yytext[nchars-1] == '+' ||
-                           yytext[nchars-1] == '-'))
+                   if (nchars > 1 &&
+                       (yytext[nchars - 1] == '+' ||
+                        yytext[nchars - 1] == '-'))
                    {
                        int     ic;
 
-                       for (ic = nchars-2; ic >= 0; ic--)
+                       for (ic = nchars - 2; ic >= 0; ic--)
                        {
-                           if (strchr("~!@#^&|`?%", yytext[ic]))
+                           char c = yytext[ic];
+                           if (c == '~' || c == '!' || c == '@' ||
+                               c == '#' || c == '^' || c == '&' ||
+                               c == '|' || c == '`' || c == '?' ||
+                               c == '%')
                                break;
                        }
-                       if (ic >= 0)
-                           break; /* found a char that makes it OK */
-                       nchars--; /* else remove the +/-, and check again */
+                       if (ic < 0)
+                       {
+                           /*
+                            * didn't find a qualifying character, so remove
+                            * all trailing [+-]
+                            */
+                           do {
+                               nchars--;
+                           } while (nchars > 1 &&
+                                (yytext[nchars - 1] == '+' ||
+                                 yytext[nchars - 1] == '-'));
+                       }
                    }
 
                    SET_YYLLOC();
index 5013b933635bbf20cd2374f14f4df0eaff155a9c..4f9b4bfa0f0aa1313f8686aec33b4e0c500f214c 100644 (file)
@@ -811,20 +811,33 @@ other         .
                     * to forbid operator names like '?-' that could not be
                     * sequences of SQL operators.
                     */
-                   while (nchars > 1 &&
-                          (yytext[nchars-1] == '+' ||
-                           yytext[nchars-1] == '-'))
+                   if (nchars > 1 &&
+                       (yytext[nchars - 1] == '+' ||
+                        yytext[nchars - 1] == '-'))
                    {
                        int     ic;
 
-                       for (ic = nchars-2; ic >= 0; ic--)
+                       for (ic = nchars - 2; ic >= 0; ic--)
                        {
-                           if (strchr("~!@#^&|`?%", yytext[ic]))
+                           char c = yytext[ic];
+                           if (c == '~' || c == '!' || c == '@' ||
+                               c == '#' || c == '^' || c == '&' ||
+                               c == '|' || c == '`' || c == '?' ||
+                               c == '%')
                                break;
                        }
-                       if (ic >= 0)
-                           break; /* found a char that makes it OK */
-                       nchars--; /* else remove the +/-, and check again */
+                       if (ic < 0)
+                       {
+                           /*
+                            * didn't find a qualifying character, so remove
+                            * all trailing [+-]
+                            */
+                           do {
+                               nchars--;
+                           } while (nchars > 1 &&
+                                (yytext[nchars - 1] == '+' ||
+                                 yytext[nchars - 1] == '-'));
+                       }
                    }
 
                    if (nchars < yyleng)
index 0dc98f79a85b0470b26b878937ce5a949b28d613..aaaa5885d9074d3a3fcca1749beb6cc3aa19edd1 100644 (file)
@@ -675,20 +675,33 @@ cppline           {space}*#([^i][A-Za-z]*|{if}|{ifdef}|{ifndef}|{import})(.*\\{space})*.
                         * to forbid operator names like '?-' that could not be
                         * sequences of SQL operators.
                         */
-                       while (nchars > 1 &&
-                              (yytext[nchars-1] == '+' ||
-                               yytext[nchars-1] == '-'))
+                       if (nchars > 1 &&
+                           (yytext[nchars - 1] == '+' ||
+                            yytext[nchars - 1] == '-'))
                        {
                            int     ic;
 
-                           for (ic = nchars-2; ic >= 0; ic--)
+                           for (ic = nchars - 2; ic >= 0; ic--)
                            {
-                               if (strchr("~!@#^&|`?%", yytext[ic]))
+                               char c = yytext[ic];
+                               if (c == '~' || c == '!' || c == '@' ||
+                                   c == '#' || c == '^' || c == '&' ||
+                                   c == '|' || c == '`' || c == '?' ||
+                                   c == '%')
                                    break;
                            }
-                           if (ic >= 0)
-                               break; /* found a char that makes it OK */
-                           nchars--; /* else remove the +/-, and check again */
+                           if (ic < 0)
+                           {
+                               /*
+                                * didn't find a qualifying character, so remove
+                                * all trailing [+-]
+                                */
+                               do {
+                                   nchars--;
+                               } while (nchars > 1 &&
+                                    (yytext[nchars - 1] == '+' ||
+                                     yytext[nchars - 1] == '-'));
+                           }
                        }
 
                        if (nchars < yyleng)