diff --git a/.gitignore b/.gitignore index 608cb86294e..6ab337b2e4c 100644 --- a/.gitignore +++ b/.gitignore @@ -137,7 +137,7 @@ php # ------------------------------------------------------------------------------ /ext/json/json_scanner.c /ext/json/php_json_scanner_defs.h -/ext/pdo/pdo_sql_parser.c +/ext/pdo*/*_sql_parser.c /ext/phar/phar_path_check.c /ext/standard/url_scanner_ex.c /ext/standard/var_unserializer.c diff --git a/NEWS b/NEWS index 2a409508b38..292a447e19a 100644 --- a/NEWS +++ b/NEWS @@ -171,6 +171,7 @@ PHP NEWS - PDO: . Fixed setAttribute and getAttribute. (SakiTakamachi) . Implemented PDO driver-specific subclasses RFC. (danack, kocsismate) + . Added support for PDO driver-specific SQL parsers. (Matteo Beccati) - PDO_DBLIB: . Fixed setAttribute and getAttribute. (SakiTakamachi) @@ -185,6 +186,7 @@ PHP NEWS - PDO_MYSQL: . Fixed setAttribute and getAttribute. (SakiTakamachi) . Added class Pdo\Mysql. (danack, kocsismate) + . Added custom SQL parser. (Matteo Beccati) - PDO_ODBC: . Added class Pdo\Odbc. (danack, kocsismate) @@ -197,11 +199,13 @@ PHP NEWS . Retrieve the memory usage of the query result resource. (KentarouTakeda) . Added Pdo\Pgsql::setNoticeCallBack method to receive DB notices. (outtersg) + . Added custom SQL parser. (Matteo Beccati) - PDO_SQLITE: . Added class Pdo\Sqlite. (danack, kocsismate) . Fixed bug #81227 (PDO::inTransaction reports false when in transaction). (nielsdos) + . Added custom SQL parser. (Matteo Beccati) - PGSQL: . Added the possibility to have no conditions for pg_select. (OmarEmaraDev) diff --git a/UPGRADING b/UPGRADING index 0df93a766aa..791f51bf7a0 100644 --- a/UPGRADING +++ b/UPGRADING @@ -240,6 +240,32 @@ PHP 8.4 UPGRADE NOTES openssl_pkey_get_details as well as openssl_sign and openssl_verify were extended to support those keys. +- PDO: + . Added support for driver specific SQL parsers. The default parser supports: + - single and double quoted literals, with doubling as escaping mechanism. + - two-dashes and non-nested C-style comments. + +- PDO_MYSQL: + . Added custom parser supporting: + - single and double-quoted literals, with doubling and backslash as escaping + mechanism + - backtick literal identifiers and with doubling as escaping mechanism + - two dashes followed by at least 1 whitespace, non-nested C-style comments, + and hash-comments + +- PDO_PGSQL: + . Added custom parser supporting: + - single and double quoted literals, with doubling as escaping mechanism + - C-style "escape" string literals (E'string') + - dollar-quoted string literals + - two-dashes and C-style comments (non-nested) + - support for "??" as escape sequence for the "?" operator + +- PDO_SQLITE: + . Added custom parser supporting: + - single, double quoted, and backtick literals, with doubling as escaping mechanism + - square brackets quoting for identifiers + - two-dashes and C-style comments (non-nested) - Phar: . Added support for the unix timestamp extension for zip archives. @@ -355,6 +381,11 @@ PHP 8.4 UPGRADE NOTES . Calling ldap_exop() with more than 4 arguments is deprecated. Use ldap_exop_sync() instead. +- PDO_PGSQL: + . Using escaped question marks (??) inside dollar-quoted strings is deprecated. + Since PDO_PGSQL has its own SQL parser with dollar-quoted strings support, it + is no longer necessary to escape question marks inside them. + - PgSQL: . Calling pgsql_fetch_result() with 2 arguments is deprecated. Use the 3-parameter signature with a null $row parameter instead. diff --git a/ext/pdo/pdo_sql_parser.h b/ext/pdo/pdo_sql_parser.h new file mode 100644 index 00000000000..060f5a8e050 --- /dev/null +++ b/ext/pdo/pdo_sql_parser.h @@ -0,0 +1,33 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: George Schlossnagle | + +----------------------------------------------------------------------+ +*/ + +#define PDO_PARSER_TEXT 1 +#define PDO_PARSER_BIND 2 +#define PDO_PARSER_BIND_POS 3 +#define PDO_PARSER_ESCAPED_QUESTION 4 +#define PDO_PARSER_CUSTOM_QUOTE 5 +#define PDO_PARSER_EOI 6 + +#define PDO_PARSER_BINDNO_ESCAPED_CHAR -1 + +#define RET(i) {s->cur = cursor; return i; } +#define SKIP_ONE(i) {s->cur = s->tok + 1; return i; } + +#define YYCTYPE unsigned char +#define YYCURSOR cursor +#define YYLIMIT s->end +#define YYMARKER s->ptr +#define YYFILL(n) { if (YYLIMIT - 1 <= YYCURSOR) RET(PDO_PARSER_EOI); } diff --git a/ext/pdo/pdo_sql_parser.re b/ext/pdo/pdo_sql_parser.re index 6bb0837fb31..1de8a618549 100644 --- a/ext/pdo/pdo_sql_parser.re +++ b/ext/pdo/pdo_sql_parser.re @@ -17,29 +17,9 @@ #include "php.h" #include "php_pdo_driver.h" #include "php_pdo_int.h" +#include "pdo_sql_parser.h" -#define PDO_PARSER_TEXT 1 -#define PDO_PARSER_BIND 2 -#define PDO_PARSER_BIND_POS 3 -#define PDO_PARSER_ESCAPED_QUESTION 4 -#define PDO_PARSER_EOI 5 - -#define PDO_PARSER_BINDNO_ESCAPED_CHAR -1 - -#define RET(i) {s->cur = cursor; return i; } -#define SKIP_ONE(i) {s->cur = s->tok + 1; return i; } - -#define YYCTYPE unsigned char -#define YYCURSOR cursor -#define YYLIMIT s->end -#define YYMARKER s->ptr -#define YYFILL(n) { RET(PDO_PARSER_EOI); } - -typedef struct Scanner { - const char *ptr, *cur, *tok, *end; -} Scanner; - -static int scan(Scanner *s) +static int default_scanner(pdo_scanner_t *s) { const char *cursor = s->cur; @@ -47,18 +27,16 @@ static int scan(Scanner *s) /*!re2c BINDCHR = [:][a-zA-Z0-9_]+; QUESTION = [?]; - ESCQUESTION = [?][?]; - COMMENTS = ("/*"([^*]+|[*]+[^/*])*[*]*"*/"|"--"[^\r\n]*); - SPECIALS = [:?"'-/]; - MULTICHAR = [:]{2,}; + COMMENTS = ("/*"([^*]+|[*]+[^/*])*[*]*"*/"|"--".*); + SPECIALS = [:?"'/-]; + MULTICHAR = ([:]{2,}|[?]{2,}); ANYNOEOF = [\001-\377]; */ /*!re2c - (["](([\\]ANYNOEOF)|ANYNOEOF\["\\])*["]) { RET(PDO_PARSER_TEXT); } - (['](([\\]ANYNOEOF)|ANYNOEOF\['\\])*[']) { RET(PDO_PARSER_TEXT); } + (["]((["]["])|ANYNOEOF\["])*["]) { RET(PDO_PARSER_TEXT); } + (['](([']['])|ANYNOEOF\['])*[']) { RET(PDO_PARSER_TEXT); } MULTICHAR { RET(PDO_PARSER_TEXT); } - ESCQUESTION { RET(PDO_PARSER_ESCAPED_QUESTION); } BINDCHR { RET(PDO_PARSER_BIND); } QUESTION { RET(PDO_PARSER_BIND_POS); } SPECIALS { SKIP_ONE(PDO_PARSER_TEXT); } @@ -75,13 +53,18 @@ struct placeholder { struct placeholder *next; }; +struct custom_quote { + const char *pos; + size_t len; +}; + static void free_param_name(zval *el) { zend_string_release(Z_PTR_P(el)); } PDO_API int pdo_parse_params(pdo_stmt_t *stmt, zend_string *inquery, zend_string **outquery) { - Scanner s; + pdo_scanner_t s; char *newbuffer; ptrdiff_t t; uint32_t bindno = 0; @@ -91,12 +74,42 @@ PDO_API int pdo_parse_params(pdo_stmt_t *stmt, zend_string *inquery, zend_string struct pdo_bound_param_data *param; int query_type = PDO_PLACEHOLDER_NONE; struct placeholder *placeholders = NULL, *placetail = NULL, *plc = NULL; + int (*scan)(pdo_scanner_t *s); + struct custom_quote custom_quote = {NULL, 0}; + + scan = stmt->dbh->methods->scanner ? stmt->dbh->methods->scanner : default_scanner; s.cur = ZSTR_VAL(inquery); s.end = s.cur + ZSTR_LEN(inquery) + 1; /* phase 1: look for args */ while((t = scan(&s)) != PDO_PARSER_EOI) { + if (custom_quote.pos) { + /* Inside a custom quote */ + if (t == PDO_PARSER_CUSTOM_QUOTE && custom_quote.len == s.cur - s.tok && !strncmp(s.tok, custom_quote.pos, custom_quote.len)) { + /* Matching closing quote found, end custom quoting */ + custom_quote.pos = NULL; + custom_quote.len = 0; + } else if (t == PDO_PARSER_ESCAPED_QUESTION) { + /* An escaped question mark has been used inside a dollar quoted string, most likely as a workaround + * as a single "?" would have been parsed as placeholder, due to the lack of support for dollar quoted + * strings. For now, we emit a deprecation notice, but still process it */ + php_error_docref(NULL, E_DEPRECATED, "Escaping question marks inside dollar quoted strings is not required anymore and is deprecated"); + + goto placeholder; + } + + continue; + } + + if (t == PDO_PARSER_CUSTOM_QUOTE) { + /* Start of a custom quote, keep a reference to search for the matching closing quote */ + custom_quote.pos = s.tok; + custom_quote.len = s.cur - s.tok; + + continue; + } + if (t == PDO_PARSER_BIND || t == PDO_PARSER_BIND_POS || t == PDO_PARSER_ESCAPED_QUESTION) { if (t == PDO_PARSER_ESCAPED_QUESTION && stmt->supports_placeholders == PDO_PLACEHOLDER_POSITIONAL) { /* escaped question marks unsupported, treat as text */ @@ -113,6 +126,7 @@ PDO_API int pdo_parse_params(pdo_stmt_t *stmt, zend_string *inquery, zend_string query_type |= PDO_PLACEHOLDER_POSITIONAL; } +placeholder: plc = emalloc(sizeof(*plc)); memset(plc, 0, sizeof(*plc)); plc->next = NULL; diff --git a/ext/pdo/php_pdo_driver.h b/ext/pdo/php_pdo_driver.h index 262a908f976..0f700c6818d 100644 --- a/ext/pdo/php_pdo_driver.h +++ b/ext/pdo/php_pdo_driver.h @@ -24,6 +24,7 @@ typedef struct _pdo_dbh_t pdo_dbh_t; typedef struct _pdo_dbh_object_t pdo_dbh_object_t; typedef struct _pdo_stmt_t pdo_stmt_t; typedef struct _pdo_row_t pdo_row_t; +typedef struct _pdo_scanner_t pdo_scanner_t; struct pdo_bound_param_data; #ifndef TRUE @@ -33,7 +34,7 @@ struct pdo_bound_param_data; # define FALSE 0 #endif -#define PDO_DRIVER_API 20170320 +#define PDO_DRIVER_API 20240423 /* Doctrine hardcodes these constants, avoid changing their values. */ enum pdo_param_type { @@ -275,6 +276,9 @@ typedef void (*pdo_dbh_request_shutdown)(pdo_dbh_t *dbh); * with any zvals in the driver_data that would be freed if the handle is destroyed. */ typedef void (*pdo_dbh_get_gc_func)(pdo_dbh_t *dbh, zend_get_gc_buffer *buffer); +/* driver specific re2s sql parser, overrides the default one if present */ +typedef int (*pdo_dbh_sql_scanner)(pdo_scanner_t *s); + /* for adding methods to the dbh or stmt objects pointer to a list of driver specific functions. The convention is to prefix the function names using the PDO driver name; this will @@ -307,6 +311,7 @@ struct pdo_dbh_methods { /* if defined to NULL, PDO will use its internal transaction tracking state */ pdo_dbh_txn_func in_transaction; pdo_dbh_get_gc_func get_gc; + pdo_dbh_sql_scanner scanner; }; /* }}} */ @@ -647,6 +652,10 @@ struct _pdo_row_t { pdo_stmt_t *stmt; }; +struct _pdo_scanner_t { + const char *ptr, *cur, *tok, *end; +}; + /* Call this in MINIT to register the PDO driver. * Registering the driver might fail and should be reported accordingly in MINIT. */ PDO_API zend_result php_pdo_register_driver(const pdo_driver_t *driver); diff --git a/ext/pdo_dblib/dblib_driver.c b/ext/pdo_dblib/dblib_driver.c index cfce8c50461..17036d2b002 100644 --- a/ext/pdo_dblib/dblib_driver.c +++ b/ext/pdo_dblib/dblib_driver.c @@ -436,7 +436,8 @@ static const struct pdo_dbh_methods dblib_methods = { NULL, /* get driver methods */ NULL, /* request shutdown */ NULL, /* in transaction, use PDO's internal tracking mechanism */ - NULL /* get gc */ + NULL, /* get gc */ + NULL /* scanner */ }; static int pdo_dblib_handle_factory(pdo_dbh_t *dbh, zval *driver_options) diff --git a/ext/pdo_firebird/firebird_driver.c b/ext/pdo_firebird/firebird_driver.c index 8d62936bb37..82df4ae40ff 100644 --- a/ext/pdo_firebird/firebird_driver.c +++ b/ext/pdo_firebird/firebird_driver.c @@ -1274,7 +1274,8 @@ static const struct pdo_dbh_methods firebird_methods = { /* {{{ */ NULL, /* get driver methods */ NULL, /* request shutdown */ pdo_firebird_in_manually_transaction, - NULL /* get gc */ + NULL, /* get gc */ + NULL /* scanner */ }; /* }}} */ diff --git a/ext/pdo_mysql/Makefile.frag b/ext/pdo_mysql/Makefile.frag new file mode 100644 index 00000000000..8349f47a669 --- /dev/null +++ b/ext/pdo_mysql/Makefile.frag @@ -0,0 +1,7 @@ +$(srcdir)/mysql_sql_parser.c: $(srcdir)/mysql_sql_parser.re + @(cd $(top_srcdir); \ + if test -f ./mysql_sql_parser.re; then \ + $(RE2C) $(RE2C_FLAGS) --no-generation-date -o mysql_sql_parser.c mysql_sql_parser.re; \ + else \ + $(RE2C) $(RE2C_FLAGS) --no-generation-date -o ext/pdo_mysql/mysql_sql_parser.c ext/pdo_mysql/mysql_sql_parser.re; \ + fi) diff --git a/ext/pdo_mysql/Makefile.frag.w32 b/ext/pdo_mysql/Makefile.frag.w32 new file mode 100644 index 00000000000..02da654de36 --- /dev/null +++ b/ext/pdo_mysql/Makefile.frag.w32 @@ -0,0 +1,3 @@ +ext\pdo_mysql\mysql_sql_parser.c: ext\pdo_mysql\mysql_sql_parser.re + cd $(PHP_SRC_DIR) + $(RE2C) $(RE2C_FLAGS) --no-generation-date -o ext/pdo_mysql/mysql_sql_parser.c ext/pdo_mysql/mysql_sql_parser.re diff --git a/ext/pdo_mysql/config.m4 b/ext/pdo_mysql/config.m4 index 122376bdc02..9465d61496b 100644 --- a/ext/pdo_mysql/config.m4 +++ b/ext/pdo_mysql/config.m4 @@ -85,9 +85,10 @@ if test "$PHP_PDO_MYSQL" != "no"; then AC_DEFINE_UNQUOTED(PDO_MYSQL_UNIX_ADDR, "$PDO_MYSQL_SOCKET", [ ]) fi - PHP_NEW_EXTENSION(pdo_mysql, pdo_mysql.c mysql_driver.c mysql_statement.c, $ext_shared,, -DZEND_ENABLE_STATIC_TSRMLS_CACHE=1) + PHP_NEW_EXTENSION(pdo_mysql, pdo_mysql.c mysql_driver.c mysql_statement.c mysql_sql_parser.c, $ext_shared,, -DZEND_ENABLE_STATIC_TSRMLS_CACHE=1) PHP_ADD_EXTENSION_DEP(pdo_mysql, pdo) + PHP_ADD_MAKEFILE_FRAGMENT if test "$PHP_PDO_MYSQL" = "yes" || test "$PHP_PDO_MYSQL" = "mysqlnd"; then PHP_ADD_EXTENSION_DEP(pdo_mysql, mysqlnd) diff --git a/ext/pdo_mysql/config.w32 b/ext/pdo_mysql/config.w32 index 48e47f78718..ce5584386e3 100644 --- a/ext/pdo_mysql/config.w32 +++ b/ext/pdo_mysql/config.w32 @@ -6,15 +6,17 @@ if (PHP_PDO_MYSQL != "no") { if (PHP_PDO_MYSQL == "yes" || PHP_PDO_MYSQL == "mysqlnd") { AC_DEFINE('PDO_USE_MYSQLND', 1, 'Using MySQL native driver'); STDOUT.WriteLine("INFO: mysqlnd build"); - EXTENSION("pdo_mysql", "pdo_mysql.c mysql_driver.c mysql_statement.c"); + EXTENSION("pdo_mysql", "pdo_mysql.c mysql_driver.c mysql_statement.c mysql_sql_parser.c"); ADD_EXTENSION_DEP('pdo_mysql', 'pdo'); + ADD_MAKEFILE_FRAGMENT(); } else { if (CHECK_LIB("libmysql.lib", "pdo_mysql", PHP_PDO_MYSQL) && CHECK_HEADER_ADD_INCLUDE("mysql.h", "CFLAGS_PDO_MYSQL", PHP_PDO_MYSQL + "\\include;" + PHP_PHP_BUILD + "\\include\\mysql;" + PHP_PDO_MYSQL)) { - EXTENSION("pdo_mysql", "pdo_mysql.c mysql_driver.c mysql_statement.c", null, "/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1"); + EXTENSION("pdo_mysql", "pdo_mysql.c mysql_driver.c mysql_statement.c mysql_sql_parser.c", null, "/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1"); + ADD_MAKEFILE_FRAGMENT(); } else { WARNING("pdo_mysql not enabled; libraries and headers not found"); } diff --git a/ext/pdo_mysql/mysql_driver.c b/ext/pdo_mysql/mysql_driver.c index 95d54500467..16e3620bb80 100644 --- a/ext/pdo_mysql/mysql_driver.c +++ b/ext/pdo_mysql/mysql_driver.c @@ -650,7 +650,8 @@ static const struct pdo_dbh_methods mysql_methods = { NULL, pdo_mysql_request_shutdown, pdo_mysql_in_transaction, - NULL /* get_gc */ + NULL, /* get_gc */ + pdo_mysql_scanner }; /* }}} */ diff --git a/ext/pdo_mysql/mysql_sql_parser.re b/ext/pdo_mysql/mysql_sql_parser.re new file mode 100644 index 00000000000..7dc7feeb704 --- /dev/null +++ b/ext/pdo_mysql/mysql_sql_parser.re @@ -0,0 +1,48 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Matteo Beccati | + +----------------------------------------------------------------------+ +*/ + + +#include "php.h" +#include "ext/pdo/php_pdo_driver.h" +#include "ext/pdo/php_pdo_int.h" +#include "ext/pdo/pdo_sql_parser.h" + +int pdo_mysql_scanner(pdo_scanner_t *s) +{ + const char *cursor = s->cur; + + s->tok = cursor; + /*!re2c + BINDCHR = [:][a-zA-Z0-9_]+; + QUESTION = [?]; + COMMENTS = ("/*"([^*]+|[*]+[^/*])*[*]*"*/"|(("--"[ \t\v\f\r])|[#]).*); + SPECIALS = [:?"'`/#-]; + MULTICHAR = ([:]{2,}|[?]{2,}); + ANYNOEOF = [\001-\377]; + */ + + /*!re2c + (["]((["]["])|([\\]ANYNOEOF)|ANYNOEOF\["\\])*["]) { RET(PDO_PARSER_TEXT); } + (['](([']['])|([\\]ANYNOEOF)|ANYNOEOF\['\\])*[']) { RET(PDO_PARSER_TEXT); } + ([`]([`][`]|ANYNOEOF\[`])*[`]) { RET(PDO_PARSER_TEXT); } + MULTICHAR { RET(PDO_PARSER_TEXT); } + BINDCHR { RET(PDO_PARSER_BIND); } + QUESTION { RET(PDO_PARSER_BIND_POS); } + SPECIALS { SKIP_ONE(PDO_PARSER_TEXT); } + COMMENTS { RET(PDO_PARSER_TEXT); } + (ANYNOEOF\SPECIALS)+ { RET(PDO_PARSER_TEXT); } + */ +} diff --git a/ext/pdo_mysql/php_pdo_mysql_int.h b/ext/pdo_mysql/php_pdo_mysql_int.h index adde9a4ebc0..b2e15e3080d 100644 --- a/ext/pdo_mysql/php_pdo_mysql_int.h +++ b/ext/pdo_mysql/php_pdo_mysql_int.h @@ -147,6 +147,8 @@ typedef struct { extern const pdo_driver_t pdo_mysql_driver; +extern int pdo_mysql_scanner(pdo_scanner_t *s); + extern int _pdo_mysql_error(pdo_dbh_t *dbh, pdo_stmt_t *stmt, const char *file, int line); #define pdo_mysql_error(s) _pdo_mysql_error(s, NULL, __FILE__, __LINE__) #define pdo_mysql_error_stmt(s) _pdo_mysql_error(stmt->dbh, stmt, __FILE__, __LINE__) diff --git a/ext/pdo_mysql/tests/pdo_mysql_parser.phpt b/ext/pdo_mysql/tests/pdo_mysql_parser.phpt new file mode 100644 index 00000000000..4c65a6e60a9 --- /dev/null +++ b/ext/pdo_mysql/tests/pdo_mysql_parser.phpt @@ -0,0 +1,68 @@ +--TEST-- +MySQL PDO Parser custom syntax +--EXTENSIONS-- +pdo_mysql +--SKIPIF-- + +--FILE-- +exec("DROP TABLE IF EXISTS {$table}"); +$db->exec("CREATE TABLE {$table} (`a``?` int NOT NULL)"); +$db->exec("INSERT INTO {$table} VALUES (1)"); + +// No parameters +$queries = [ + "SELECT * FROM {$table}", + "SELECT * FROM {$table} -- ?", + "SELECT * FROM {$table} # ?", + "SELECT * FROM {$table} /* ? */", +]; + +foreach ($queries as $k => $query) { + $stmt = $db->prepare($query); + $stmt->execute(); + var_dump($query, $stmt->fetch(PDO::FETCH_NUM) === [0 => 1]); +} + +// One parameter +$queries = [ + "SELECT * FROM {$table} WHERE 1 = ?", + "SELECT * FROM {$table} WHERE 1 = --?", + "SELECT * FROM {$table} WHERE \"?\" IN (?, '?')", + "SELECT * FROM {$table} WHERE `a``?` = ?", +]; + +foreach ($queries as $k => $query) { + $stmt = $db->prepare($query); + $stmt->execute([1]); + var_dump($query, $stmt->fetch(PDO::FETCH_NUM) === [0 => 1]); +} + +$db->exec("DROP TABLE pdo_mysql_parser"); + +?> +--EXPECT-- +string(30) "SELECT * FROM pdo_mysql_parser" +bool(true) +string(35) "SELECT * FROM pdo_mysql_parser -- ?" +bool(true) +string(34) "SELECT * FROM pdo_mysql_parser # ?" +bool(true) +string(38) "SELECT * FROM pdo_mysql_parser /* ? */" +bool(true) +string(42) "SELECT * FROM pdo_mysql_parser WHERE 1 = ?" +bool(true) +string(44) "SELECT * FROM pdo_mysql_parser WHERE 1 = --?" +bool(true) +string(52) "SELECT * FROM pdo_mysql_parser WHERE "?" IN (?, '?')" +bool(true) +string(47) "SELECT * FROM pdo_mysql_parser WHERE `a``?` = ?" +bool(true) diff --git a/ext/pdo_odbc/odbc_driver.c b/ext/pdo_odbc/odbc_driver.c index d991b8dc5fe..0e89e2b21d4 100644 --- a/ext/pdo_odbc/odbc_driver.c +++ b/ext/pdo_odbc/odbc_driver.c @@ -461,7 +461,8 @@ static const struct pdo_dbh_methods odbc_methods = { NULL, /* get_driver_methods */ NULL, /* request_shutdown */ NULL, /* in transaction, use PDO's internal tracking mechanism */ - NULL /* get_gc */ + NULL, /* get_gc */ + NULL /* scanner */ }; static int pdo_odbc_handle_factory(pdo_dbh_t *dbh, zval *driver_options) /* {{{ */ diff --git a/ext/pdo_pgsql/Makefile.frag b/ext/pdo_pgsql/Makefile.frag new file mode 100644 index 00000000000..33abcfc79ef --- /dev/null +++ b/ext/pdo_pgsql/Makefile.frag @@ -0,0 +1,7 @@ +$(srcdir)/pgsql_sql_parser.c: $(srcdir)/pgsql_sql_parser.re + @(cd $(top_srcdir); \ + if test -f ./pgsql_sql_parser.re; then \ + $(RE2C) $(RE2C_FLAGS) --no-generation-date -o pgsql_sql_parser.c pgsql_sql_parser.re; \ + else \ + $(RE2C) $(RE2C_FLAGS) --no-generation-date -o ext/pdo_pgsql/pgsql_sql_parser.c ext/pdo_pgsql/pgsql_sql_parser.re; \ + fi) diff --git a/ext/pdo_pgsql/Makefile.frag.w32 b/ext/pdo_pgsql/Makefile.frag.w32 new file mode 100644 index 00000000000..fb7cab96a44 --- /dev/null +++ b/ext/pdo_pgsql/Makefile.frag.w32 @@ -0,0 +1,3 @@ +ext\pdo_pgsql\pgsql_sql_parser.c: ext\pdo_pgsql\pgsql_sql_parser.re + cd $(PHP_SRC_DIR) + $(RE2C) $(RE2C_FLAGS) --no-generation-date -o ext/pdo_pgsql/pgsql_sql_parser.c ext/pdo_pgsql/pgsql_sql_parser.re diff --git a/ext/pdo_pgsql/config.m4 b/ext/pdo_pgsql/config.m4 index 492a2e8b66a..436841417a6 100644 --- a/ext/pdo_pgsql/config.m4 +++ b/ext/pdo_pgsql/config.m4 @@ -79,6 +79,7 @@ if test "$PHP_PDO_PGSQL" != "no"; then PHP_CHECK_PDO_INCLUDES - PHP_NEW_EXTENSION(pdo_pgsql, pdo_pgsql.c pgsql_driver.c pgsql_statement.c, $ext_shared) + PHP_NEW_EXTENSION(pdo_pgsql, pdo_pgsql.c pgsql_driver.c pgsql_statement.c pgsql_sql_parser.c, $ext_shared) PHP_ADD_EXTENSION_DEP(pdo_pgsql, pdo) + PHP_ADD_MAKEFILE_FRAGMENT fi diff --git a/ext/pdo_pgsql/config.w32 b/ext/pdo_pgsql/config.w32 index cda62a64dba..d6be4487c58 100644 --- a/ext/pdo_pgsql/config.w32 +++ b/ext/pdo_pgsql/config.w32 @@ -5,7 +5,7 @@ ARG_WITH("pdo-pgsql", "PostgreSQL support for PDO", "no"); if (PHP_PDO_PGSQL != "no") { if (CHECK_LIB("libpq.lib", "pdo_pgsql", PHP_PDO_PGSQL) && CHECK_HEADER_ADD_INCLUDE("libpq-fe.h", "CFLAGS_PDO_PGSQL", PHP_PDO_PGSQL + "\\include;" + PHP_PHP_BUILD + "\\include\\pgsql;" + PHP_PHP_BUILD + "\\include\\libpq;")) { - EXTENSION("pdo_pgsql", "pdo_pgsql.c pgsql_driver.c pgsql_statement.c"); + EXTENSION("pdo_pgsql", "pdo_pgsql.c pgsql_driver.c pgsql_statement.c pgsql_sql_parser.c"); if (X64) { ADD_FLAG('CFLAGS_PDO_PGSQL', "/D HAVE_PG_LO64=1"); @@ -14,6 +14,7 @@ if (PHP_PDO_PGSQL != "no") { AC_DEFINE('HAVE_PDO_PGSQL', 1, 'Have PostgreSQL library'); ADD_EXTENSION_DEP('pdo_pgsql', 'pdo'); + ADD_MAKEFILE_FRAGMENT(); } else { WARNING("pdo_pgsql not enabled; libraries and headers not found"); } diff --git a/ext/pdo_pgsql/pgsql_driver.c b/ext/pdo_pgsql/pgsql_driver.c index a8d4f7efbd6..3171653eab0 100644 --- a/ext/pdo_pgsql/pgsql_driver.c +++ b/ext/pdo_pgsql/pgsql_driver.c @@ -1321,7 +1321,8 @@ static const struct pdo_dbh_methods pgsql_methods = { pdo_pgsql_get_driver_methods, /* get_driver_methods */ NULL, pgsql_handle_in_transaction, - NULL /* get_gc */ + NULL, /* get_gc */ + pdo_pgsql_scanner }; static int pdo_pgsql_handle_factory(pdo_dbh_t *dbh, zval *driver_options) /* {{{ */ diff --git a/ext/pdo_pgsql/pgsql_sql_parser.re b/ext/pdo_pgsql/pgsql_sql_parser.re new file mode 100644 index 00000000000..13db8e6ffc8 --- /dev/null +++ b/ext/pdo_pgsql/pgsql_sql_parser.re @@ -0,0 +1,53 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Matteo Beccati | + +----------------------------------------------------------------------+ +*/ + + +#include "php.h" +#include "ext/pdo/php_pdo_driver.h" +#include "ext/pdo/php_pdo_int.h" +#include "ext/pdo/pdo_sql_parser.h" + +int pdo_pgsql_scanner(pdo_scanner_t *s) +{ + const char *cursor = s->cur; + + s->tok = cursor; + /*!re2c + BINDCHR = [:][a-zA-Z0-9_]+; + QUESTION = [?]; + ESCQUESTION = [?][?]; + COMMENTS = ("/*"([^*]+|[*]+[^/*])*[*]*"*/"|"--".*); + DOLQ_START = [A-Za-z\200-\377_]; + DOLQ_CONT = [A-Za-z\200-\377_0-9]; + SPECIALS = [$eE:?"'/-]; + MULTICHAR = [:]{2,}; + ANYNOEOF = [\001-\377]; + */ + + /*!re2c + [eE](['](([']['])|([\\]ANYNOEOF)|ANYNOEOF\['\\])*[']) { RET(PDO_PARSER_TEXT); } + (["]((["]["])|ANYNOEOF\["])*["]) { RET(PDO_PARSER_TEXT); } + (['](([']['])|ANYNOEOF\['])*[']) { RET(PDO_PARSER_TEXT); } + [$](DOLQ_START DOLQ_CONT*)?[$] { RET(PDO_PARSER_CUSTOM_QUOTE); } + MULTICHAR { RET(PDO_PARSER_TEXT); } + ESCQUESTION { RET(PDO_PARSER_ESCAPED_QUESTION); } + BINDCHR { RET(PDO_PARSER_BIND); } + QUESTION { RET(PDO_PARSER_BIND_POS); } + SPECIALS { SKIP_ONE(PDO_PARSER_TEXT); } + COMMENTS { RET(PDO_PARSER_TEXT); } + (ANYNOEOF\SPECIALS)+ { RET(PDO_PARSER_TEXT); } + */ +} diff --git a/ext/pdo_pgsql/php_pdo_pgsql_int.h b/ext/pdo_pgsql/php_pdo_pgsql_int.h index 5b3e194f23d..3cd0b005fcc 100644 --- a/ext/pdo_pgsql/php_pdo_pgsql_int.h +++ b/ext/pdo_pgsql/php_pdo_pgsql_int.h @@ -74,6 +74,8 @@ typedef struct { extern const pdo_driver_t pdo_pgsql_driver; +extern int pdo_pgsql_scanner(pdo_scanner_t *s); + extern int _pdo_pgsql_error(pdo_dbh_t *dbh, pdo_stmt_t *stmt, int errcode, const char *sqlstate, const char *msg, const char *file, int line); #define pdo_pgsql_error(d,e,z) _pdo_pgsql_error(d, NULL, e, z, NULL, __FILE__, __LINE__) #define pdo_pgsql_error_msg(d,e,m) _pdo_pgsql_error(d, NULL, e, NULL, m, __FILE__, __LINE__) diff --git a/ext/pdo_pgsql/tests/bug_14244.phpt b/ext/pdo_pgsql/tests/bug_14244.phpt new file mode 100644 index 00000000000..2268ac9bedf --- /dev/null +++ b/ext/pdo_pgsql/tests/bug_14244.phpt @@ -0,0 +1,47 @@ +--TEST-- +PDO PgSQL Bug #14244 (Postgres sees parameters in a dollar-delimited string) +--EXTENSIONS-- +pdo +pdo_pgsql +--SKIPIF-- + +--FILE-- +setAttribute (\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); +$pdo->setAttribute (\PDO::ATTR_DEFAULT_FETCH_MODE, \PDO::FETCH_ASSOC); + +echo "Already working (see bug64953.phpt):\n"; + +$st = $pdo->prepare("SELECT '?' question"); +$st->execute(); +var_dump($st->fetch()); + +echo "Inside a dollar-quoted string:\n"; + +$st = $pdo->prepare("SELECT \$\$?\$\$ question"); +$st->execute(); +var_dump($st->fetch()); + +?> +Done +--EXPECT-- +Test +Already working (see bug64953.phpt): +array(1) { + ["question"]=> + string(1) "?" +} +Inside a dollar-quoted string: +array(1) { + ["question"]=> + string(1) "?" +} +Done diff --git a/ext/pdo_pgsql/tests/pdo_pgsql_parser.phpt b/ext/pdo_pgsql/tests/pdo_pgsql_parser.phpt new file mode 100644 index 00000000000..803123d1c88 --- /dev/null +++ b/ext/pdo_pgsql/tests/pdo_pgsql_parser.phpt @@ -0,0 +1,67 @@ +--TEST-- +PgSQL PDO Parser custom syntax +--EXTENSIONS-- +pdo +pdo_pgsql +--SKIPIF-- + +--FILE-- +setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + +$query = <<<'EOF' +SELECT e'He''ll\'o? ' || ? AS b -- ' +UNION ALL +SELECT 'He''ll''o?\' || ? AS b -- ' +UNION ALL +SELECT U&'d\0061t\+000061? ' || ? AS b /* :name */ +UNION ALL +SELECT $__$Is this a $$dollar$$ 'escaping'? $__$ || ? AS b -- ? +UNION ALL +SELECT $$Escaped question mark here?? $$ || ? AS b -- ? +EOF; + +$stmt = $db->prepare($query); +$stmt->execute(['World', 'World', 'base', 'Yes', 'Yes']); + +while ($row = $stmt->fetchColumn()) { + var_dump($row); +} + +// Nested comments are incompatible: PDO parses the "?" inside the comment, Postgres doesn't +$query = <<<'EOF' +SELECT 'Hello' || ? /* is this a /* nested */ 'comment' ? */ +EOF; + +$stmt = $db->prepare($query); + +try { + $stmt->execute(['X', 'Y', 'Z']); +} catch (PDOException $e) { + // PDO error: 3 parameters vs 2 expected + var_dump('HY093' === $e->getCode()); +} + +try { + $stmt->execute(['X', 'Y']); +} catch (PDOException $e) { + // PgSQL error: Indeterminate datatype (1 extra parameter) + var_dump('42P18' === $e->getCode()); +} + +?> +--EXPECTF-- +Deprecated: PDO::prepare(): Escaping question marks inside dollar quoted strings is not required anymore and is deprecated in %s on line %d +string(14) "He'll'o? World" +string(14) "He'll'o?\World" +string(10) "data? base" +string(36) "Is this a $$dollar$$ 'escaping'? Yes" +string(31) "Escaped question mark here? Yes" +bool(true) +bool(true) diff --git a/ext/pdo_sqlite/Makefile.frag b/ext/pdo_sqlite/Makefile.frag new file mode 100644 index 00000000000..c439367799d --- /dev/null +++ b/ext/pdo_sqlite/Makefile.frag @@ -0,0 +1,7 @@ +$(srcdir)/sqlite_sql_parser.c: $(srcdir)/sqlite_sql_parser.re + @(cd $(top_srcdir); \ + if test -f ./sqlite_sql_parser.re; then \ + $(RE2C) $(RE2C_FLAGS) --no-generation-date -o sqlite_sql_parser.c sqlite_sql_parser.re; \ + else \ + $(RE2C) $(RE2C_FLAGS) --no-generation-date -o ext/pdo_sqlite/sqlite_sql_parser.c ext/pdo_sqlite/sqlite_sql_parser.re; \ + fi) diff --git a/ext/pdo_sqlite/Makefile.frag.w32 b/ext/pdo_sqlite/Makefile.frag.w32 new file mode 100644 index 00000000000..a7763758bb5 --- /dev/null +++ b/ext/pdo_sqlite/Makefile.frag.w32 @@ -0,0 +1,3 @@ +ext\pdo_sqlite\sqlite_sql_parser.c: ext\pdo_sqlite\sqlite_sql_parser.re + cd $(PHP_SRC_DIR) + $(RE2C) $(RE2C_FLAGS) --no-generation-date -o ext/pdo_sqlite/sqlite_sql_parser.c ext/pdo_sqlite/sqlite_sql_parser.re diff --git a/ext/pdo_sqlite/config.m4 b/ext/pdo_sqlite/config.m4 index 62c738f01e8..17b2fca8a1e 100644 --- a/ext/pdo_sqlite/config.m4 +++ b/ext/pdo_sqlite/config.m4 @@ -29,8 +29,9 @@ if test "$PHP_PDO_SQLITE" != "no"; then ) PHP_SUBST(PDO_SQLITE_SHARED_LIBADD) - PHP_NEW_EXTENSION(pdo_sqlite, pdo_sqlite.c sqlite_driver.c sqlite_statement.c, + PHP_NEW_EXTENSION(pdo_sqlite, pdo_sqlite.c sqlite_driver.c sqlite_statement.c sqlite_sql_parser.c, $ext_shared) PHP_ADD_EXTENSION_DEP(pdo_sqlite, pdo) + PHP_ADD_MAKEFILE_FRAGMENT fi diff --git a/ext/pdo_sqlite/config.w32 b/ext/pdo_sqlite/config.w32 index 1ad3b74e8c3..59496f4ca52 100644 --- a/ext/pdo_sqlite/config.w32 +++ b/ext/pdo_sqlite/config.w32 @@ -4,11 +4,12 @@ ARG_WITH("pdo-sqlite", "for pdo_sqlite support", "no"); if (PHP_PDO_SQLITE != "no") { if (SETUP_SQLITE3("pdo_sqlite", PHP_PDO_SQLITE, PHP_PDO_SQLITE_SHARED)) { - EXTENSION("pdo_sqlite", "pdo_sqlite.c sqlite_driver.c sqlite_statement.c"); + EXTENSION("pdo_sqlite", "pdo_sqlite.c sqlite_driver.c sqlite_statement.c sqlite_sql_parser.c"); ADD_EXTENSION_DEP('pdo_sqlite', 'pdo'); AC_DEFINE("HAVE_SQLITE3_COLUMN_TABLE_NAME", 1, "have sqlite3_column_table_name"); AC_DEFINE("HAVE_SQLITE3_CLOSE_V2", 1, "have sqlite3_close_v2"); + ADD_MAKEFILE_FRAGMENT(); } else { WARNING("pdo_sqlite not enabled; libraries and/or headers not found"); } diff --git a/ext/pdo_sqlite/php_pdo_sqlite_int.h b/ext/pdo_sqlite/php_pdo_sqlite_int.h index 054e1fbae26..43a07345ed6 100644 --- a/ext/pdo_sqlite/php_pdo_sqlite_int.h +++ b/ext/pdo_sqlite/php_pdo_sqlite_int.h @@ -61,6 +61,8 @@ typedef struct { extern const pdo_driver_t pdo_sqlite_driver; +extern int pdo_sqlite_scanner(pdo_scanner_t *s); + extern int _pdo_sqlite_error(pdo_dbh_t *dbh, pdo_stmt_t *stmt, const char *file, int line); #define pdo_sqlite_error(s) _pdo_sqlite_error(s, NULL, __FILE__, __LINE__) #define pdo_sqlite_error_stmt(s) _pdo_sqlite_error(stmt->dbh, stmt, __FILE__, __LINE__) diff --git a/ext/pdo_sqlite/sqlite_driver.c b/ext/pdo_sqlite/sqlite_driver.c index 76e82cc2e61..8d0c034fee4 100644 --- a/ext/pdo_sqlite/sqlite_driver.c +++ b/ext/pdo_sqlite/sqlite_driver.c @@ -743,7 +743,8 @@ static const struct pdo_dbh_methods sqlite_methods = { get_driver_methods, pdo_sqlite_request_shutdown, pdo_sqlite_in_transaction, - pdo_sqlite_get_gc + pdo_sqlite_get_gc, + pdo_sqlite_scanner }; static char *make_filename_safe(const char *filename) diff --git a/ext/pdo_sqlite/sqlite_sql_parser.re b/ext/pdo_sqlite/sqlite_sql_parser.re new file mode 100644 index 00000000000..d1b6f099d73 --- /dev/null +++ b/ext/pdo_sqlite/sqlite_sql_parser.re @@ -0,0 +1,49 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Matteo Beccati | + +----------------------------------------------------------------------+ +*/ + + +#include "php.h" +#include "ext/pdo/php_pdo_driver.h" +#include "ext/pdo/php_pdo_int.h" +#include "ext/pdo/pdo_sql_parser.h" + +int pdo_sqlite_scanner(pdo_scanner_t *s) +{ + const char *cursor = s->cur; + + s->tok = cursor; + /*!re2c + BINDCHR = [:][a-zA-Z0-9_]+; + QUESTION = [?]; + COMMENTS = ("/*"([^*]+|[*]+[^/*])*[*]*"*/"|"--".*); + SPECIALS = [:?"'`/[-]; + MULTICHAR = ([:]{2,}|[?]{2,}); + ANYNOEOF = [\001-\377]; + */ + + /*!re2c + (["]((["]["])|ANYNOEOF)*["]) { RET(PDO_PARSER_TEXT); } + (['](([']['])|ANYNOEOF)*[']) { RET(PDO_PARSER_TEXT); } + ([`](([`][`])|ANYNOEOF)*[`]) { RET(PDO_PARSER_TEXT); } + ("["ANYNOEOF*"]") { RET(PDO_PARSER_TEXT); } + MULTICHAR { RET(PDO_PARSER_TEXT); } + BINDCHR { RET(PDO_PARSER_BIND); } + QUESTION { RET(PDO_PARSER_BIND_POS); } + SPECIALS { SKIP_ONE(PDO_PARSER_TEXT); } + COMMENTS { RET(PDO_PARSER_TEXT); } + (ANYNOEOF\SPECIALS)+ { RET(PDO_PARSER_TEXT); } + */ +} diff --git a/ext/pdo_sqlite/tests/pdo_sqlite_parser.phpt b/ext/pdo_sqlite/tests/pdo_sqlite_parser.phpt new file mode 100644 index 00000000000..d5eda8d9b82 --- /dev/null +++ b/ext/pdo_sqlite/tests/pdo_sqlite_parser.phpt @@ -0,0 +1,62 @@ +--TEST-- +PDO_sqlite: Parser custom syntax +--EXTENSIONS-- +pdo_sqlite +--FILE-- + PDO::ERRMODE_EXCEPTION]); + +$table = 'pdo_sqlite_parser'; + +$db->exec("CREATE TABLE {$table} (`a``?` int NOT NULL)"); +$db->exec("INSERT INTO {$table} VALUES (1)"); + +// No parameters +$queries = [ + "SELECT * FROM {$table}", + "SELECT * FROM {$table} -- ?", + "SELECT * FROM {$table} /* ? */", +]; + +foreach ($queries as $k => $query) { + $stmt = $db->prepare($query); + $stmt->execute(); + var_dump($stmt->fetch(PDO::FETCH_NUM) === [0 => 1]); +} + +// One parameter +$queries = [ + "SELECT * FROM {$table} WHERE '1' = ?", + "SELECT * FROM {$table} WHERE \"?\" IN (?, \"?\")", + "SELECT * FROM {$table} WHERE `a``?` = ?", + "SELECT * FROM {$table} WHERE \"a`?\" = ?", + "SELECT * FROM {$table} WHERE [a`?] = ?", +]; + +foreach ($queries as $k => $query) { + $stmt = $db->prepare($query); + $stmt->execute([1]); + var_dump($stmt->fetch(PDO::FETCH_NUM) === [0 => 1]); +} + +?> +--CLEAN-- + +--EXPECT-- +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) diff --git a/scripts/dev/genfiles b/scripts/dev/genfiles index 2121596fbf1..0e3f8a09489 100755 --- a/scripts/dev/genfiles +++ b/scripts/dev/genfiles @@ -125,6 +125,21 @@ $MAKE RE2C="$RE2C" RE2C_FLAGS="$RE2C_FLAGS" srcdir=ext/pdo builddir=ext/pdo top_ -f ext/pdo/Makefile.frag \ ext/pdo/pdo_sql_parser.c +echo "genfiles: Generating PDO_mysql lexer file" +$MAKE RE2C="$RE2C" RE2C_FLAGS="$RE2C_FLAGS" srcdir=ext/pdo_mysql builddir=ext/pdo_mysql top_srcdir=. \ + -f ext/pdo_mysql/Makefile.frag \ + ext/pdo_mysql/mysql_sql_parser.c + +echo "genfiles: Generating PDO_pgsql lexer file" +$MAKE RE2C="$RE2C" RE2C_FLAGS="$RE2C_FLAGS" srcdir=ext/pdo_pgsql builddir=ext/pdo_pgsql top_srcdir=. \ + -f ext/pdo_pgsql/Makefile.frag \ + ext/pdo_pgsql/pgsql_sql_parser.c + +echo "genfiles: Generating PDO_sqlite lexer file" +$MAKE RE2C="$RE2C" RE2C_FLAGS="$RE2C_FLAGS" srcdir=ext/pdo_sqlite builddir=ext/pdo_sqlite top_srcdir=. \ + -f ext/pdo_sqlite/Makefile.frag \ + ext/pdo_sqlite/sqlite_sql_parser.c + echo "genfiles: Generating standard extension lexer files" $MAKE RE2C="$RE2C" RE2C_FLAGS="$RE2C_FLAGS" srcdir=ext/standard builddir=ext/standard top_srcdir=. \ -f ext/standard/Makefile.frag \