From 4149edc002fe40901ece2e1d8d949a303c707ae7 Mon Sep 17 00:00:00 2001 From: rjhdby Date: Wed, 13 Mar 2019 13:51:31 +0300 Subject: [PATCH 01/15] deprecation --- Zend/tests/036.phpt | 3 +++ Zend/tests/bug71572.phpt | 8 ++++---- Zend/tests/str_offset_004.phpt | 18 +++++++++--------- Zend/zend_compile.c | 4 ++++ Zend/zend_language_parser.y | 3 ++- tests/strings/offsets_chaining_2.phpt | 2 +- tests/strings/offsets_chaining_4.phpt | 2 +- tests/strings/offsets_general.phpt | 16 +++------------- 8 files changed, 27 insertions(+), 29 deletions(-) diff --git a/Zend/tests/036.phpt b/Zend/tests/036.phpt index 6feb23f679ba8..3ff522b16f06e 100644 --- a/Zend/tests/036.phpt +++ b/Zend/tests/036.phpt @@ -8,6 +8,9 @@ $a{function() { }} = 1; ?> --EXPECTF-- + +Deprecated: Array and string offset access syntax with curly braces is deprecated in %s line %d + Warning: Illegal offset type in %s on line %d Warning: Illegal offset type in %s on line %d diff --git a/Zend/tests/bug71572.phpt b/Zend/tests/bug71572.phpt index 4eb16246a12e5..f4f44449cd603 100644 --- a/Zend/tests/bug71572.phpt +++ b/Zend/tests/bug71572.phpt @@ -4,10 +4,10 @@ Bug #71572: String offset assignment from an empty string inserts null byte ==DONE== diff --git a/Zend/tests/str_offset_004.phpt b/Zend/tests/str_offset_004.phpt index c8ce607535752..435ab235fa61d 100644 --- a/Zend/tests/str_offset_004.phpt +++ b/Zend/tests/str_offset_004.phpt @@ -8,31 +8,31 @@ $str = "abcdefghijklmno"; $i = 3; $j = -4; -$str{2} = 'C'; +$str[2] = 'C'; var_dump($str); -$str{$i} = 'Z'; +$str[$i] = 'Z'; var_dump($str); -$str{-5} = 'P'; +$str[-5] = 'P'; var_dump($str); -$str{$j} = 'Q'; +$str[$j] = 'Q'; var_dump($str); -$str{-20} = 'Y'; +$str[-20] = 'Y'; var_dump($str); -$str{-strlen($str)} = strtoupper($str{0}); /* An exotic ucfirst() ;) */ +$str[-strlen($str)] = strtoupper($str[0]); /* An exotic ucfirst() ;) */ var_dump($str); -$str{20} = 'N'; +$str[20] = 'N'; var_dump($str); -$str{-2} = 'UFO'; +$str[-2] = 'UFO'; var_dump($str); -$str{-$i} = $str{$j*2}; +$str[-$i] = $str[$j*2]; var_dump($str); ?> --EXPECTF-- diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 1e57c7bf095da..debd4e886a22d 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -2371,6 +2371,10 @@ static inline void zend_emit_assign_znode(zend_ast *var_ast, znode *value_node) static zend_op *zend_delayed_compile_dim(znode *result, zend_ast *ast, uint32_t type) /* {{{ */ { + if (ast->attr == T_DEPRECATED) { + zend_error(E_DEPRECATED, "Array and string offset access syntax with curly braces is deprecated"); + } + zend_ast *var_ast = ast->child[0]; zend_ast *dim_ast = ast->child[1]; zend_op *opline; diff --git a/Zend/zend_language_parser.y b/Zend/zend_language_parser.y index 1ecbab0b3d307..fcad1ac9a9140 100644 --- a/Zend/zend_language_parser.y +++ b/Zend/zend_language_parser.y @@ -225,6 +225,7 @@ static YYSIZE_T zend_yytnamerr(char*, const char*); /* Token used to force a parse error from the lexer */ %token T_ERROR +%token T_DEPRECATED %type top_statement namespace_name name statement function_declaration_statement %type class_declaration_statement trait_declaration_statement @@ -1155,7 +1156,7 @@ callable_variable: | constant '[' optional_expr ']' { $$ = zend_ast_create(ZEND_AST_DIM, $1, $3); } | dereferencable '{' expr '}' - { $$ = zend_ast_create(ZEND_AST_DIM, $1, $3); } + { $$ = zend_ast_create_ex(ZEND_AST_DIM, T_DEPRECATED, $1, $3); } | dereferencable T_OBJECT_OPERATOR property_name argument_list { $$ = zend_ast_create(ZEND_AST_METHOD_CALL, $1, $3, $4); } | function_call { $$ = $1; } diff --git a/tests/strings/offsets_chaining_2.phpt b/tests/strings/offsets_chaining_2.phpt index bbc170a6e6621..0c3c0074b138b 100644 --- a/tests/strings/offsets_chaining_2.phpt +++ b/tests/strings/offsets_chaining_2.phpt @@ -5,7 +5,7 @@ error_reporting=E_ALL | E_DEPRECATED --FILE-- --EXPECT-- string(1) "f" diff --git a/tests/strings/offsets_chaining_4.phpt b/tests/strings/offsets_chaining_4.phpt index d1f3de26af4fe..fc11b8d797907 100644 --- a/tests/strings/offsets_chaining_4.phpt +++ b/tests/strings/offsets_chaining_4.phpt @@ -5,7 +5,7 @@ error_reporting=E_ALL | E_DEPRECATED --FILE-- --EXPECT-- bool(true) diff --git a/tests/strings/offsets_general.phpt b/tests/strings/offsets_general.phpt index 4ec6aa5b86b9b..35ef682bb0778 100644 --- a/tests/strings/offsets_general.phpt +++ b/tests/strings/offsets_general.phpt @@ -1,7 +1,7 @@ --TEST-- testing the behavior of string offsets --INI-- -error_reporting=E_ALL | E_DEPRECATED +error_reporting=E_ALL --FILE-- --EXPECTF-- -string(1) "f" -string(1) "o" -bool(true) -bool(true) -Warning: Illegal string offset 'foo' in %s line %d -string(1) "f" -bool(false) +Deprecated: Array and string offset access syntax with curly braces is deprecated in %s line %d string(1) "f" string(1) "o" bool(true) @@ -35,3 +24,4 @@ bool(true) Warning: Illegal string offset 'foo' in %s line %d string(1) "f" bool(false) +string(1) "f" From 8044b7aa5c0daff53560520d0e499c4895226357 Mon Sep 17 00:00:00 2001 From: rjhdby Date: Wed, 13 Mar 2019 14:35:41 +0300 Subject: [PATCH 02/15] update tests --- ext/bz2/tests/005.phpt | 2 +- ext/exif/tests/bug64739.phpt | 4 ++-- ext/opcache/tests/phi_remove_001.phpt | 2 +- ext/zlib/tests/005.phpt | 2 +- ext/zlib/tests/006.phpt | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/ext/bz2/tests/005.phpt b/ext/bz2/tests/005.phpt index da29a6d0b4079..27dffb9c1b7d6 100644 --- a/ext/bz2/tests/005.phpt +++ b/ext/bz2/tests/005.phpt @@ -21,7 +21,7 @@ $data = bzcompress($string); $data2 = bzcompress($string, 1, 10); $data3 = $data2; -$data3{3} = 0; +$data3[3] = 0; var_dump(bzdecompress()); var_dump(bzdecompress(1,1,1)); diff --git a/ext/exif/tests/bug64739.phpt b/ext/exif/tests/bug64739.phpt index d47f8849a2c4f..de97e0db3c0d9 100644 --- a/ext/exif/tests/bug64739.phpt +++ b/ext/exif/tests/bug64739.phpt @@ -16,8 +16,8 @@ if ($headers1 === false) { exit; } -var_dump($headers1['Title']{0} === '?'); -var_dump($headers1['Author']{0} === '?'); +var_dump($headers1['Title'][0] === '?'); +var_dump($headers1['Author'][0] === '?'); ini_set('exif.decode_unicode_motorola', 'UCS-2LE'); diff --git a/ext/opcache/tests/phi_remove_001.phpt b/ext/opcache/tests/phi_remove_001.phpt index 4be8dcfd6ad5c..3a76a9da5c653 100644 --- a/ext/opcache/tests/phi_remove_001.phpt +++ b/ext/opcache/tests/phi_remove_001.phpt @@ -31,7 +31,7 @@ function getOnlyMPEGaudioInfoBruteForce($info) { if ($MPEGaudioHeaderLengthCache[$head4] > 4) { $WhereWeWere = mftell(); $next4 = test(4); - if ($next4{0} == "\xFF") { + if ($next4[0] == "\xFF") { if (!isset($MPEGaudioHeaderDecodeCache[$next4])) { $MPEGaudioHeaderDecodeCache[$next4] = MPEGaudioHeaderDecode($next4); } diff --git a/ext/zlib/tests/005.phpt b/ext/zlib/tests/005.phpt index 6333612183086..daa178ec69afd 100644 --- a/ext/zlib/tests/005.phpt +++ b/ext/zlib/tests/005.phpt @@ -28,7 +28,7 @@ var_dump(gzuncompress("", 9)); var_dump(gzuncompress($data1)); var_dump(gzuncompress($data2)); -$data2{4} = 0; +$data2[4] = 0; var_dump(gzuncompress($data2)); echo "Done\n"; diff --git a/ext/zlib/tests/006.phpt b/ext/zlib/tests/006.phpt index 0d082092ca377..1caebd7a62450 100644 --- a/ext/zlib/tests/006.phpt +++ b/ext/zlib/tests/006.phpt @@ -29,7 +29,7 @@ var_dump(gzinflate("asdf", 9)); var_dump(gzinflate($data1)); var_dump(gzinflate($data2)); -$data2{4} = 0; +$data2[4] = 0; var_dump(gzinflate($data2)); echo "Done\n"; From e1fe34907fef3fc7f7e33af92f4498c5b42bf7fd Mon Sep 17 00:00:00 2001 From: rjhdby Date: Wed, 13 Mar 2019 15:59:08 +0300 Subject: [PATCH 03/15] token replaced by constant --- Zend/zend_compile.c | 2 +- Zend/zend_compile.h | 3 +++ Zend/zend_language_parser.y | 3 +-- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index debd4e886a22d..0f4d6f40d57d6 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -2371,7 +2371,7 @@ static inline void zend_emit_assign_znode(zend_ast *var_ast, znode *value_node) static zend_op *zend_delayed_compile_dim(znode *result, zend_ast *ast, uint32_t type) /* {{{ */ { - if (ast->attr == T_DEPRECATED) { + if (ast->attr == ZEND_ALTERNATIVE_ARRAY_SYNTAX) { zend_error(E_DEPRECATED, "Array and string offset access syntax with curly braces is deprecated"); } diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h index c9b827041373f..299a95ccae209 100644 --- a/Zend/zend_compile.h +++ b/Zend/zend_compile.h @@ -1090,4 +1090,7 @@ END_EXTERN_C() ZEND_API zend_bool zend_binary_op_produces_numeric_string_error(uint32_t opcode, zval *op1, zval *op2); +/* Array/string access syntax with curly braces is used */ +#define ZEND_ALTERNATIVE_ARRAY_SYNTAX 1 + #endif /* ZEND_COMPILE_H */ diff --git a/Zend/zend_language_parser.y b/Zend/zend_language_parser.y index fcad1ac9a9140..975b88a7e3b8c 100644 --- a/Zend/zend_language_parser.y +++ b/Zend/zend_language_parser.y @@ -225,7 +225,6 @@ static YYSIZE_T zend_yytnamerr(char*, const char*); /* Token used to force a parse error from the lexer */ %token T_ERROR -%token T_DEPRECATED %type top_statement namespace_name name statement function_declaration_statement %type class_declaration_statement trait_declaration_statement @@ -1156,7 +1155,7 @@ callable_variable: | constant '[' optional_expr ']' { $$ = zend_ast_create(ZEND_AST_DIM, $1, $3); } | dereferencable '{' expr '}' - { $$ = zend_ast_create_ex(ZEND_AST_DIM, T_DEPRECATED, $1, $3); } + { $$ = zend_ast_create_ex(ZEND_AST_DIM, ZEND_ALTERNATIVE_ARRAY_SYNTAX, $1, $3); } | dereferencable T_OBJECT_OPERATOR property_name argument_list { $$ = zend_ast_create(ZEND_AST_METHOD_CALL, $1, $3, $4); } | function_call { $$ = $1; } From 42cb84a6f22207eb4d62226a454c1a3203fdd172 Mon Sep 17 00:00:00 2001 From: rjhdby Date: Fri, 15 Mar 2019 16:24:52 +0300 Subject: [PATCH 04/15] migration script --- convert_array_access_braces.php | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 convert_array_access_braces.php diff --git a/convert_array_access_braces.php b/convert_array_access_braces.php new file mode 100644 index 0000000000000..e69de29bb2d1d From 41a906cc1b65014303010af04aa3c9848530ca5a Mon Sep 17 00:00:00 2001 From: rjhdby Date: Fri, 15 Mar 2019 16:28:52 +0300 Subject: [PATCH 05/15] migration script --- convert_array_access_braces.php | 324 ++++++++++++++++++++++++++++++++ 1 file changed, 324 insertions(+) diff --git a/convert_array_access_braces.php b/convert_array_access_braces.php index e69de29bb2d1d..2c21ec0725a50 100644 --- a/convert_array_access_braces.php +++ b/convert_array_access_braces.php @@ -0,0 +1,324 @@ + [-b] [--php ] [ --diff []] + php $self -d [-b] [-r] [--php ] [--ext [,...] [ --diff []] + +Where: + -f Convert single file . + -d Convert all ".php"(see "--ext") files inside directory. + -r Walk through directories recursively. Without this flag only concrete directory will be processed. + -b Backup converted files into . + --ext Comma separated list of file extensions for conversion. If set then ".php" will not be added automatically. + --php Path to PHP interpreter to migrate. + --diff[=] Redirect diff-info into . Write to stdout if does not specified. + +Examples: + php $self -f./index.php -b --diff + Convert file ./index.php and make backup to ./index.php_backup and show diff + + php $self -d/srv/http/api -r --ext phpt,php + Convert all ".php" and ".phpt" files inside whole /srv/http/api directory tree + + php $self -d/srv/http/api --php /root/sapi/bin/php --diff=./diff.out + Convert all ".php" files inside /srv/http/api directory and write diff to ./diff.out + using /root/sapi/bin/php for check for deprecation +EOF; + exit(0); +} + +$converter = new Converter($opts); + +if (isset($opts['f'])) { + $converter->convertFile($opts['f']); +} elseif (isset($opts['d'])) { + $converter->convertDirectory($opts['d'], isset($opts['r'])); +} + +class Converter +{ + private $php; + private $backupDir; + private $ext; + private $diff; + private $dir = ''; + private $diffContent = ''; + + public function __construct($opts) { + $this->php = isset($opts['php']) ? $opts['php'] : PHP_BINARY; + $this->backupDir = isset($opts['b']) ? rtrim($opts['b'], "/\\") . DIRECTORY_SEPARATOR : false; + $this->ext = isset($opts['ext']) ? explode(',', $opts['ext']) : ['php']; + $this->diff = isset($opts['diff']) ? ($opts['diff'] !== false ? $opts['diff'] : true) : false; + + if ($this->backupDir && !is_dir($this->backupDir)) { + $this->fatalError("Backup directory $this->backupDir not found"); + } + } + + public function convertDirectory($dir, $recursively) { + if (!is_dir($dir)) { + $this->fatalError("Target directory $dir not found"); + } + $this->dir = rtrim($dir, "/\\") . DIRECTORY_SEPARATOR; + $regex = '/^.+\.(' . implode('|', $this->ext) . ')$/'; + + if ($recursively) { + $dirIterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir)); + $path = ''; + } else { + $dirIterator = new DirectoryIterator($dir); + $path = $this->dir; + } + + $iterator = new RegexIterator($dirIterator, $regex, RegexIterator::GET_MATCH); + + foreach ($iterator as $file) { + $this->convertFile($path . $file[0]); + } + } + + public function convertFile($file) { + if (!is_file($file)) { + $this->fatalError("Target file $file not found"); + } + + $deprecated = $this->getDeprecatedLines($file); + + if (empty($deprecated)) { + return; + } + + $tokens = token_get_all(file_get_contents($file)); + $actualTokens = $this->actualTokens($tokens); + + while ($braces = $this->findPair($actualTokens)) { + if ($braces[2] !== 0) { // Semicolon inside. So function. + unset($actualTokens[ $braces[2] ]); + } else if ($actualTokens[ $braces[0] ] !== '${' // Complex string. + && $this->validPrevious($tokens, $braces[0]) + && $this->validNext($tokens, $braces[0])) { + $tokens[ $braces[0] ] = '['; + $tokens[ $braces[1] ] = ']'; + } + unset($actualTokens[ $braces[0] ], $actualTokens[ $braces[1] ]); + } + + $convertedFile = $this->writeConverted($tokens, $file); + + $this->diff($file, $convertedFile); + + $this->backup($file, $convertedFile); + + $notConverted = $this->getDeprecatedLines($convertedFile); + + if (!empty($notConverted)) { + foreach ($notConverted as $line) { + echo " - Failed to convert line $line in $file" . PHP_EOL; + } + + echo " ? $file is not fully converted." . PHP_EOL; + } else { + echo "+ $file successfully converted." . PHP_EOL; + } + + if (!rename($convertedFile, $file)) { + $this->fatalError("Failed to replace $file with $convertedFile", $convertedFile); + } + } + + private function getDeprecatedLines($file) { + $output = []; + exec("$this->php -d error_reporting=E_ALL -l $file", $output, $ret); + if ($ret !== 0) { + foreach ($output as $string) { + echo '> ' . $string . PHP_EOL; + } + $this->fatalError("Processing of file $file is failed"); + } + $output = array_filter($output, function ($line) { return strpos($line, 'Deprecated: Array and string') !== false; }); + array_walk($output, function (&$line) { $line = (int)trim(strrchr($line, ' ')); }); + + return $output; + } + + private function writeConverted($tokens, $original) { + do { + $outputFileName = $original . mt_rand(0, 1000); + } while (is_file($outputFileName)); + + $outputFile = fopen($outputFileName, 'wb'); + + if (!$outputFile) { + $this->fatalError("Cannot create temp file $outputFileName"); + } + + foreach ($tokens as $token) { + if (is_array($token)) { + fwrite($outputFile, $token[1]); + } else { + fwrite($outputFile, $token); + } + } + + fclose($outputFile); + + return $outputFileName; + } + + private function backup($file, $convertedFile) { + if ($this->backupDir) { + $backupFileName = $this->backupDir . str_replace($this->dir, '', $file); + if (!is_dir(pathinfo($backupFileName, PATHINFO_DIRNAME))) { + if (!mkdir(pathinfo($backupFileName, PATHINFO_DIRNAME), 0777, true)) { + $this->fatalError("Failed to create directory tree for $backupFileName", $convertedFile); + } + } + if (!rename($file, $backupFileName)) { + $this->fatalError("Failed to backup $file into $backupFileName", $convertedFile); + } + } + } + + private function actualTokens($tokens) { + $result = []; + foreach ($tokens as $key => $token) { + if ($token === '{' || $token === '}') { + $result[ $key ] = $token; + } else if (is_array($token) && $token[1] === '${') { + $result[ $key ] = $token[1]; + } else if (!empty($result) && $token === ';' && end($result) !== ';') { + $result[ $key ] = ';'; + } + } + while (end($result) === ';') { + array_pop($result); + } + + return $result; + } + + private function findPair($tokens) { + if (count($tokens) < 2) { + return false; + } + $last = ''; + $lastKey = 0; + $semicolon = 0; + foreach ($tokens as $key => $token) { + if (($last === '{' || $last === '${') && $token === '}') { + return [$lastKey, $key, $semicolon]; + } else if ($token === ';') { + $semicolon = $key; + } else { + $last = $token; + $lastKey = $key; + $semicolon = 0; + } + } + + return false; + } + + private function validPrevious($tokens, $index) { + do { + $index--; + if (!isset($tokens[ $index ])) { + return false; + } else if (is_array($tokens[ $index ]) && $tokens[ $index ][0] === T_WHITESPACE) { + continue; + } else if ($tokens[ $index ] === ']') { // In-place array. + return true; + } else if ($tokens[ $index ] === ')') { // Expression/function returning array/string, function declaration or array(...). + return $this->isNotFunction($tokens, $index); + } else if (is_array($tokens[ $index ])) { // All others possible variants here. + return true; + } else { // Nor string, nor variable, nor constant. + return false; + } + } while (true); + } + + private function validNext($tokens, $index) { + do { + $index++; + if (!isset($tokens[ $index ])) { + return false; + } else if (is_array($tokens[ $index ]) && $tokens[ $index ][0] === T_WHITESPACE) { + continue; + } else if ($tokens[ $index ] === '}') { // Function with empty body. Other functions has semicolons inside. + return false; + } else { + return true; + } + } while (true); + } + + private function isNotFunction($tokens, $index) { + do { + $index--; + if (!isset($tokens[ $index ])) { + return false; + } else if (is_array($tokens[ $index ])) { + if ($tokens[ $index ][0] === T_FUNCTION) { + return false; + } + continue; + } else if (!in_array($tokens[ $index ], ['(', ')', '[', ']', '=', '&'])) { + return true; + } + } while (true); + } + + private function fatalError($text, $file = false) { + if ($file) { + unlink($file); + } + + if ($this->diffContent !== '') { + $this->writeDiff(); + } + + die($text . PHP_EOL); + } + + private function diff($file, $convertedFile) { + if (!$this->diff) { + return; + } + $original = fopen($file, 'rb'); + $converted = fopen($convertedFile, 'rb'); + $i = 1; + $this->diffContent .= "file: $file" . PHP_EOL; + while (($line = fgets($original)) !== false) { + $newLine = fgets($converted); + if ($line !== $newLine) { + $this->diffContent .= $i . 'c' . $i . PHP_EOL; + $this->diffContent .= "< $line"; + $this->diffContent .= '---' . PHP_EOL; + $this->diffContent .= "> $newLine" . PHP_EOL; + } + $i++; + } + + fclose($converted); + fclose($original); + } + + private function writeDiff() { + if ($this->diff === true) { + echo $this->diffContent; + } else if ($this->diff !== false) { + file_put_contents($this->diff, $this->diffContent); + } + } + + public function __destruct() { + $this->writeDiff(); + } + +} From c1163dd1d7cadd37acaa35af54184401e24c27a0 Mon Sep 17 00:00:00 2001 From: Theodore Brown Date: Sun, 14 Jul 2019 11:24:08 -0500 Subject: [PATCH 06/15] Fix migration script and run it on run-tests.php --- convert_array_access_braces.php | 105 ++++++++++++++------------------ run-tests.php | 8 +-- 2 files changed, 51 insertions(+), 62 deletions(-) diff --git a/convert_array_access_braces.php b/convert_array_access_braces.php index 2c21ec0725a50..909e46333a476 100644 --- a/convert_array_access_braces.php +++ b/convert_array_access_braces.php @@ -1,4 +1,5 @@ actualTokens($tokens); while ($braces = $this->findPair($actualTokens)) { - if ($braces[2] !== 0) { // Semicolon inside. So function. - unset($actualTokens[ $braces[2] ]); - } else if ($actualTokens[ $braces[0] ] !== '${' // Complex string. - && $this->validPrevious($tokens, $braces[0]) - && $this->validNext($tokens, $braces[0])) { + if ( + $actualTokens[ $braces[0] ] !== '${' // Complex string. + && $this->validNext($tokens, $braces[0], $braces[1]) + ) { $tokens[ $braces[0] ] = '['; $tokens[ $braces[1] ] = ']'; } @@ -186,17 +186,41 @@ private function backup($file, $convertedFile) { private function actualTokens($tokens) { $result = []; + $inString = false; + $depth = 0; + foreach ($tokens as $key => $token) { + if ($token === '"') { + $inString = !$inString; + } + + $tokenStr = null; + if ($token === '{' || $token === '}') { - $result[ $key ] = $token; - } else if (is_array($token) && $token[1] === '${') { - $result[ $key ] = $token[1]; - } else if (!empty($result) && $token === ';' && end($result) !== ';') { - $result[ $key ] = ';'; + $tokenStr = $token; + } else if (is_array($token) && ($token[1] === '${' || $token[1] === '{')) { + $tokenStr = $token[1]; + } + + if ($tokenStr !== null) { + if ($inString) { + if ($tokenStr === '}') { + $depth--; + + if ($depth === 0) { + continue; // ignore outer closing brace + } + } else { + $depth++; + + if ($depth === 1) { + continue; // ignore outer opening brace + } + } + } + + $result[ $key ] = $tokenStr; } - } - while (end($result) === ';') { - array_pop($result); } return $result; @@ -208,70 +232,35 @@ private function findPair($tokens) { } $last = ''; $lastKey = 0; - $semicolon = 0; foreach ($tokens as $key => $token) { if (($last === '{' || $last === '${') && $token === '}') { - return [$lastKey, $key, $semicolon]; + return [$lastKey, $key]; } else if ($token === ';') { - $semicolon = $key; } else { $last = $token; $lastKey = $key; - $semicolon = 0; } } return false; } - private function validPrevious($tokens, $index) { - do { - $index--; - if (!isset($tokens[ $index ])) { - return false; - } else if (is_array($tokens[ $index ]) && $tokens[ $index ][0] === T_WHITESPACE) { - continue; - } else if ($tokens[ $index ] === ']') { // In-place array. - return true; - } else if ($tokens[ $index ] === ')') { // Expression/function returning array/string, function declaration or array(...). - return $this->isNotFunction($tokens, $index); - } else if (is_array($tokens[ $index ])) { // All others possible variants here. - return true; - } else { // Nor string, nor variable, nor constant. - return false; - } - } while (true); - } + private function validNext($tokens, $index, $endIndex) { + if ($tokens[$index + 1] === '}') { + // Empty block. Other blocks have a semicolon inside. + return false; + } - private function validNext($tokens, $index) { do { $index++; if (!isset($tokens[ $index ])) { return false; - } else if (is_array($tokens[ $index ]) && $tokens[ $index ][0] === T_WHITESPACE) { - continue; - } else if ($tokens[ $index ] === '}') { // Function with empty body. Other functions has semicolons inside. - return false; - } else { - return true; + } else if ($tokens[ $index ] === ';') { + return false; // array/string offset accesses can't contain semicolon } - } while (true); - } + } while ($index < $endIndex); - private function isNotFunction($tokens, $index) { - do { - $index--; - if (!isset($tokens[ $index ])) { - return false; - } else if (is_array($tokens[ $index ])) { - if ($tokens[ $index ][0] === T_FUNCTION) { - return false; - } - continue; - } else if (!in_array($tokens[ $index ], ['(', ')', '[', ']', '=', '&'])) { - return true; - } - } while (true); + return true; } private function fatalError($text, $file = false) { diff --git a/run-tests.php b/run-tests.php index f54dc2d37a6a4..fe5b31dba255a 100755 --- a/run-tests.php +++ b/run-tests.php @@ -2944,12 +2944,12 @@ function settings2params($ini_settings) $settings .= " -d \"$name=$val\""; } } else { - if (substr(PHP_OS, 0, 3) == "WIN" && !empty($value) && $value{0} == '"') { + if (substr(PHP_OS, 0, 3) == "WIN" && !empty($value) && $value[0] == '"') { $len = strlen($value); - if ($value{$len - 1} == '"') { - $value{0} = "'"; - $value{$len - 1} = "'"; + if ($value[$len - 1] == '"') { + $value[0] = "'"; + $value[$len - 1] = "'"; } } else { $value = addslashes($value); From 5d79e94396c1e318a275782684f4a0d9f24ea7c4 Mon Sep 17 00:00:00 2001 From: Theodore Brown Date: Sun, 14 Jul 2019 11:27:47 -0500 Subject: [PATCH 07/15] Move migration script to https://gist.github.com/theodorejb/763b83a43522b0fc1755a537663b1863 This script isn't something that should be maintained long term in php-src. --- convert_array_access_braces.php | 313 -------------------------------- 1 file changed, 313 deletions(-) delete mode 100644 convert_array_access_braces.php diff --git a/convert_array_access_braces.php b/convert_array_access_braces.php deleted file mode 100644 index 909e46333a476..0000000000000 --- a/convert_array_access_braces.php +++ /dev/null @@ -1,313 +0,0 @@ - [-b] [--php ] [ --diff []] - php $self -d [-b] [-r] [--php ] [--ext [,...] [ --diff []] - -Where: - -f Convert single file . - -d Convert all ".php"(see "--ext") files inside directory. - -r Walk through directories recursively. Without this flag only concrete directory will be processed. - -b Backup converted files into . - --ext Comma separated list of file extensions for conversion. If set then ".php" will not be added automatically. - --php Path to PHP interpreter to migrate. - --diff[=] Redirect diff-info into . Write to stdout if does not specified. - -Examples: - php $self -f./index.php -b --diff - Convert file ./index.php and make backup to ./index.php_backup and show diff - - php $self -d/srv/http/api -r --ext phpt,php - Convert all ".php" and ".phpt" files inside whole /srv/http/api directory tree - - php $self -d/srv/http/api --php /root/sapi/bin/php --diff=./diff.out - Convert all ".php" files inside /srv/http/api directory and write diff to ./diff.out - using /root/sapi/bin/php for check for deprecation -EOF; - exit(0); -} - -$converter = new Converter($opts); - -if (isset($opts['f'])) { - $converter->convertFile($opts['f']); -} elseif (isset($opts['d'])) { - $converter->convertDirectory($opts['d'], isset($opts['r'])); -} - -class Converter -{ - private $php; - private $backupDir; - private $ext; - private $diff; - private $dir = ''; - private $diffContent = ''; - - public function __construct($opts) { - $this->php = isset($opts['php']) ? $opts['php'] : PHP_BINARY; - $this->backupDir = isset($opts['b']) ? rtrim($opts['b'], "/\\") . DIRECTORY_SEPARATOR : false; - $this->ext = isset($opts['ext']) ? explode(',', $opts['ext']) : ['php']; - $this->diff = isset($opts['diff']) ? ($opts['diff'] !== false ? $opts['diff'] : true) : false; - - if ($this->backupDir && !is_dir($this->backupDir)) { - $this->fatalError("Backup directory $this->backupDir not found"); - } - } - - public function convertDirectory($dir, $recursively) { - if (!is_dir($dir)) { - $this->fatalError("Target directory $dir not found"); - } - $this->dir = rtrim($dir, "/\\") . DIRECTORY_SEPARATOR; - $regex = '/^.+\.(' . implode('|', $this->ext) . ')$/'; - - if ($recursively) { - $dirIterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir)); - $path = ''; - } else { - $dirIterator = new DirectoryIterator($dir); - $path = $this->dir; - } - - $iterator = new RegexIterator($dirIterator, $regex, RegexIterator::GET_MATCH); - - foreach ($iterator as $file) { - $this->convertFile($path . $file[0]); - } - } - - public function convertFile($file) { - if (!is_file($file)) { - $this->fatalError("Target file $file not found"); - } - - $deprecated = $this->getDeprecatedLines($file); - - if (empty($deprecated)) { - return; - } - - $tokens = token_get_all(file_get_contents($file)); - $actualTokens = $this->actualTokens($tokens); - - while ($braces = $this->findPair($actualTokens)) { - if ( - $actualTokens[ $braces[0] ] !== '${' // Complex string. - && $this->validNext($tokens, $braces[0], $braces[1]) - ) { - $tokens[ $braces[0] ] = '['; - $tokens[ $braces[1] ] = ']'; - } - unset($actualTokens[ $braces[0] ], $actualTokens[ $braces[1] ]); - } - - $convertedFile = $this->writeConverted($tokens, $file); - - $this->diff($file, $convertedFile); - - $this->backup($file, $convertedFile); - - $notConverted = $this->getDeprecatedLines($convertedFile); - - if (!empty($notConverted)) { - foreach ($notConverted as $line) { - echo " - Failed to convert line $line in $file" . PHP_EOL; - } - - echo " ? $file is not fully converted." . PHP_EOL; - } else { - echo "+ $file successfully converted." . PHP_EOL; - } - - if (!rename($convertedFile, $file)) { - $this->fatalError("Failed to replace $file with $convertedFile", $convertedFile); - } - } - - private function getDeprecatedLines($file) { - $output = []; - exec("$this->php -d error_reporting=E_ALL -l $file", $output, $ret); - if ($ret !== 0) { - foreach ($output as $string) { - echo '> ' . $string . PHP_EOL; - } - $this->fatalError("Processing of file $file is failed"); - } - $output = array_filter($output, function ($line) { return strpos($line, 'Deprecated: Array and string') !== false; }); - array_walk($output, function (&$line) { $line = (int)trim(strrchr($line, ' ')); }); - - return $output; - } - - private function writeConverted($tokens, $original) { - do { - $outputFileName = $original . mt_rand(0, 1000); - } while (is_file($outputFileName)); - - $outputFile = fopen($outputFileName, 'wb'); - - if (!$outputFile) { - $this->fatalError("Cannot create temp file $outputFileName"); - } - - foreach ($tokens as $token) { - if (is_array($token)) { - fwrite($outputFile, $token[1]); - } else { - fwrite($outputFile, $token); - } - } - - fclose($outputFile); - - return $outputFileName; - } - - private function backup($file, $convertedFile) { - if ($this->backupDir) { - $backupFileName = $this->backupDir . str_replace($this->dir, '', $file); - if (!is_dir(pathinfo($backupFileName, PATHINFO_DIRNAME))) { - if (!mkdir(pathinfo($backupFileName, PATHINFO_DIRNAME), 0777, true)) { - $this->fatalError("Failed to create directory tree for $backupFileName", $convertedFile); - } - } - if (!rename($file, $backupFileName)) { - $this->fatalError("Failed to backup $file into $backupFileName", $convertedFile); - } - } - } - - private function actualTokens($tokens) { - $result = []; - $inString = false; - $depth = 0; - - foreach ($tokens as $key => $token) { - if ($token === '"') { - $inString = !$inString; - } - - $tokenStr = null; - - if ($token === '{' || $token === '}') { - $tokenStr = $token; - } else if (is_array($token) && ($token[1] === '${' || $token[1] === '{')) { - $tokenStr = $token[1]; - } - - if ($tokenStr !== null) { - if ($inString) { - if ($tokenStr === '}') { - $depth--; - - if ($depth === 0) { - continue; // ignore outer closing brace - } - } else { - $depth++; - - if ($depth === 1) { - continue; // ignore outer opening brace - } - } - } - - $result[ $key ] = $tokenStr; - } - } - - return $result; - } - - private function findPair($tokens) { - if (count($tokens) < 2) { - return false; - } - $last = ''; - $lastKey = 0; - foreach ($tokens as $key => $token) { - if (($last === '{' || $last === '${') && $token === '}') { - return [$lastKey, $key]; - } else if ($token === ';') { - } else { - $last = $token; - $lastKey = $key; - } - } - - return false; - } - - private function validNext($tokens, $index, $endIndex) { - if ($tokens[$index + 1] === '}') { - // Empty block. Other blocks have a semicolon inside. - return false; - } - - do { - $index++; - if (!isset($tokens[ $index ])) { - return false; - } else if ($tokens[ $index ] === ';') { - return false; // array/string offset accesses can't contain semicolon - } - } while ($index < $endIndex); - - return true; - } - - private function fatalError($text, $file = false) { - if ($file) { - unlink($file); - } - - if ($this->diffContent !== '') { - $this->writeDiff(); - } - - die($text . PHP_EOL); - } - - private function diff($file, $convertedFile) { - if (!$this->diff) { - return; - } - $original = fopen($file, 'rb'); - $converted = fopen($convertedFile, 'rb'); - $i = 1; - $this->diffContent .= "file: $file" . PHP_EOL; - while (($line = fgets($original)) !== false) { - $newLine = fgets($converted); - if ($line !== $newLine) { - $this->diffContent .= $i . 'c' . $i . PHP_EOL; - $this->diffContent .= "< $line"; - $this->diffContent .= '---' . PHP_EOL; - $this->diffContent .= "> $newLine" . PHP_EOL; - } - $i++; - } - - fclose($converted); - fclose($original); - } - - private function writeDiff() { - if ($this->diff === true) { - echo $this->diffContent; - } else if ($this->diff !== false) { - file_put_contents($this->diff, $this->diffContent); - } - } - - public function __destruct() { - $this->writeDiff(); - } - -} From f666b7df7a1aa6165ad0d3145c44e82075a609a0 Mon Sep 17 00:00:00 2001 From: Theodore Brown Date: Mon, 15 Jul 2019 13:16:03 -0500 Subject: [PATCH 08/15] Replace deprecated curly brace uses in fcgi.inc --- sapi/fpm/tests/fcgi.inc | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/sapi/fpm/tests/fcgi.inc b/sapi/fpm/tests/fcgi.inc index f31811aef6744..721b94b504500 100644 --- a/sapi/fpm/tests/fcgi.inc +++ b/sapi/fpm/tests/fcgi.inc @@ -362,19 +362,19 @@ class Client while ($p != $length) { - $nlen = ord($data{$p++}); + $nlen = ord($data[$p++]); if ($nlen >= 128) { $nlen = ($nlen & 0x7F << 24); - $nlen |= (ord($data{$p++}) << 16); - $nlen |= (ord($data{$p++}) << 8); - $nlen |= (ord($data{$p++})); + $nlen |= (ord($data[$p++]) << 16); + $nlen |= (ord($data[$p++]) << 8); + $nlen |= (ord($data[$p++])); } - $vlen = ord($data{$p++}); + $vlen = ord($data[$p++]); if ($vlen >= 128) { $vlen = ($nlen & 0x7F << 24); - $vlen |= (ord($data{$p++}) << 16); - $vlen |= (ord($data{$p++}) << 8); - $vlen |= (ord($data{$p++})); + $vlen |= (ord($data[$p++]) << 16); + $vlen |= (ord($data[$p++]) << 8); + $vlen |= (ord($data[$p++])); } $array[substr($data, $p, $nlen)] = substr($data, $p+$nlen, $vlen); $p += ($nlen + $vlen); @@ -392,12 +392,12 @@ class Client private function decodePacketHeader($data) { $ret = array(); - $ret['version'] = ord($data{0}); - $ret['type'] = ord($data{1}); - $ret['requestId'] = (ord($data{2}) << 8) + ord($data{3}); - $ret['contentLength'] = (ord($data{4}) << 8) + ord($data{5}); - $ret['paddingLength'] = ord($data{6}); - $ret['reserved'] = ord($data{7}); + $ret['version'] = ord($data[0]); + $ret['type'] = ord($data[1]); + $ret['requestId'] = (ord($data[2]) << 8) + ord($data[3]); + $ret['contentLength'] = (ord($data[4]) << 8) + ord($data[5]); + $ret['paddingLength'] = ord($data[6]); + $ret['reserved'] = ord($data[7]); return $ret; } @@ -634,7 +634,7 @@ class Client // Reset timeout $this->set_ms_timeout($this->_readWriteTimeout); - switch (ord($resp['content']{4})) { + switch (ord($resp['content'][4])) { case self::CANT_MPX_CONN: throw new \Exception('This app can\'t multiplex [CANT_MPX_CONN]'); break; From e938af5cfa1daf9ccdcf58c77c8b29d96783ebd1 Mon Sep 17 00:00:00 2001 From: Theodore Brown Date: Tue, 16 Jul 2019 10:33:48 -0500 Subject: [PATCH 09/15] Improve test coverage and handle deprecated syntax in const expressions --- Zend/zend_compile.c | 4 ++++ Zend/zend_compile.h | 6 ++--- tests/strings/offsets_general.phpt | 37 ++++++++++++++++++++++++++++-- 3 files changed, 42 insertions(+), 5 deletions(-) diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 0f4d6f40d57d6..a4fbf6b682f04 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -8802,6 +8802,10 @@ void zend_eval_const_expr(zend_ast **ast_ptr) /* {{{ */ zend_error_noreturn(E_COMPILE_ERROR, "Cannot use [] for reading"); } + if (ast->attr == ZEND_ALTERNATIVE_ARRAY_SYNTAX) { + zend_error(E_DEPRECATED, "Array and string offset access syntax with curly braces is deprecated"); + } + /* Set isset fetch indicator here, opcache disallows runtime altering of the AST */ if (ast->attr == ZEND_DIM_IS && ast->child[0]->kind == ZEND_AST_DIM) { ast->child[0]->attr = ZEND_DIM_IS; diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h index 299a95ccae209..34d81711ba0f0 100644 --- a/Zend/zend_compile.h +++ b/Zend/zend_compile.h @@ -991,6 +991,9 @@ static zend_always_inline int zend_check_arg_send_type(const zend_function *zf, #define ZEND_ARRAY_NOT_PACKED (1<<1) #define ZEND_ARRAY_SIZE_SHIFT 2 +/* Array/string access syntax with curly braces is used */ +#define ZEND_ALTERNATIVE_ARRAY_SYNTAX 1 + /* Attribute for ternary inside parentheses */ #define ZEND_PARENTHESIZED_CONDITIONAL 1 @@ -1090,7 +1093,4 @@ END_EXTERN_C() ZEND_API zend_bool zend_binary_op_produces_numeric_string_error(uint32_t opcode, zval *op1, zval *op2); -/* Array/string access syntax with curly braces is used */ -#define ZEND_ALTERNATIVE_ARRAY_SYNTAX 1 - #endif /* ZEND_COMPILE_H */ diff --git a/tests/strings/offsets_general.phpt b/tests/strings/offsets_general.phpt index 35ef682bb0778..335b53489d742 100644 --- a/tests/strings/offsets_general.phpt +++ b/tests/strings/offsets_general.phpt @@ -1,21 +1,46 @@ --TEST-- testing the behavior of string offsets ---INI-- -error_reporting=E_ALL --FILE-- --EXPECTF-- Deprecated: Array and string offset access syntax with curly braces is deprecated in %s line %d + +Deprecated: Array and string offset access syntax with curly braces is deprecated in %s line %d + +Deprecated: Array and string offset access syntax with curly braces is deprecated in %s line %d + +Deprecated: Array and string offset access syntax with curly braces is deprecated in %s line %d + +Deprecated: Array and string offset access syntax with curly braces is deprecated in %s line %d + +Deprecated: Array and string offset access syntax with curly braces is deprecated in %s line %d + +Deprecated: Array and string offset access syntax with curly braces is deprecated in %s line %d + +Deprecated: Array and string offset access syntax with curly braces is deprecated in %s line %d + +Deprecated: Array and string offset access syntax with curly braces is deprecated in %s line %d +string(1) "B" string(1) "f" string(1) "o" bool(true) @@ -24,4 +49,12 @@ bool(true) Warning: Illegal string offset 'foo' in %s line %d string(1) "f" bool(false) +string(1) "B" +string(1) "f" +string(1) "o" +bool(true) +bool(true) + +Warning: Illegal string offset 'foo' in %s line %d string(1) "f" +bool(false) From 41878385bd976fb566f73496dc759a6093f6704f Mon Sep 17 00:00:00 2001 From: Theodore Brown Date: Tue, 16 Jul 2019 11:49:12 -0500 Subject: [PATCH 10/15] Change ZEND_ALTERNATIVE_ARRAY_SYNTAX value so it doesn't conflict with ZEND_DIM_IS This was causing the constant_expressions_coalesce.phpt test to fail. --- Zend/zend_compile.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h index 34d81711ba0f0..55bf0ee8ea49d 100644 --- a/Zend/zend_compile.h +++ b/Zend/zend_compile.h @@ -992,7 +992,7 @@ static zend_always_inline int zend_check_arg_send_type(const zend_function *zf, #define ZEND_ARRAY_SIZE_SHIFT 2 /* Array/string access syntax with curly braces is used */ -#define ZEND_ALTERNATIVE_ARRAY_SYNTAX 1 +#define ZEND_ALTERNATIVE_ARRAY_SYNTAX 2 /* Attribute for ternary inside parentheses */ #define ZEND_PARENTHESIZED_CONDITIONAL 1 From 292c23c1a8290af6ed5085fbd11b780215f0288b Mon Sep 17 00:00:00 2001 From: Theodore Brown Date: Tue, 16 Jul 2019 14:58:06 -0500 Subject: [PATCH 11/15] Output correct notice when deprecated curly braces are used with null coalescing operator --- Zend/tests/constant_expressions_coalesce.phpt | 26 +++++++++++++++++++ Zend/zend_ast.c | 2 +- Zend/zend_compile.c | 8 +++--- 3 files changed, 31 insertions(+), 5 deletions(-) diff --git a/Zend/tests/constant_expressions_coalesce.phpt b/Zend/tests/constant_expressions_coalesce.phpt index 425aba69c403e..27740aa72e704 100644 --- a/Zend/tests/constant_expressions_coalesce.phpt +++ b/Zend/tests/constant_expressions_coalesce.phpt @@ -5,6 +5,12 @@ Constant expressions with null coalescing operator ?? const A = [1 => [[]]]; +// should produce deprecation notices +const D_1 = null ?? A[1]{'undefined'}['index'] ?? 1; +const D_2 = null ?? A['undefined']{'index'} ?? 2; +const D_3 = null ?? A[1]{0}{2} ?? 3; // 2 deprecation notices +const D_4 = A[1]{0} ?? 4; + const T_1 = null ?? A[1]['undefined']['index'] ?? 1; const T_2 = null ?? A['undefined']['index'] ?? 2; const T_3 = null ?? A[1][0][2] ?? 3; @@ -12,6 +18,11 @@ const T_4 = A[1][0][2] ?? 4; const T_5 = null ?? __LINE__; const T_6 = __LINE__ ?? "bar"; +var_dump(D_1); +var_dump(D_2); +var_dump(D_3); +var_dump(D_4); + var_dump(T_1); var_dump(T_2); var_dump(T_3); @@ -31,6 +42,21 @@ var_dump((new class { public $var = A[1][0][2] ?? 4; })->var); ?> --EXPECTF-- + +Deprecated: Array and string offset access syntax with curly braces is deprecated in %s line %d + +Deprecated: Array and string offset access syntax with curly braces is deprecated in %s line %d + +Deprecated: Array and string offset access syntax with curly braces is deprecated in %s line %d + +Deprecated: Array and string offset access syntax with curly braces is deprecated in %s line %d + +Deprecated: Array and string offset access syntax with curly braces is deprecated in %s line %d +int(1) +int(2) +int(3) +array(0) { +} int(1) int(2) int(3) diff --git a/Zend/zend_ast.c b/Zend/zend_ast.c index dcbe531323fad..685189ed7d114 100644 --- a/Zend/zend_ast.c +++ b/Zend/zend_ast.c @@ -714,7 +714,7 @@ ZEND_API int ZEND_FASTCALL zend_ast_evaluate(zval *result, zend_ast *ast, zend_c zval_ptr_dtor_nogc(&op1); ret = FAILURE; } else { - zend_fetch_dimension_const(result, &op1, &op2, (ast->attr == ZEND_DIM_IS) ? BP_VAR_IS : BP_VAR_R); + zend_fetch_dimension_const(result, &op1, &op2, (ast->attr & ZEND_DIM_IS) ? BP_VAR_IS : BP_VAR_R); zval_ptr_dtor_nogc(&op1); zval_ptr_dtor_nogc(&op2); diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index a4fbf6b682f04..1f992f8aa7ef8 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -8748,7 +8748,7 @@ void zend_eval_const_expr(zend_ast **ast_ptr) /* {{{ */ case ZEND_AST_COALESCE: /* Set isset fetch indicator here, opcache disallows runtime altering of the AST */ if (ast->child[0]->kind == ZEND_AST_DIM) { - ast->child[0]->attr = ZEND_DIM_IS; + ast->child[0]->attr |= ZEND_DIM_IS; } zend_eval_const_expr(&ast->child[0]); @@ -8802,13 +8802,13 @@ void zend_eval_const_expr(zend_ast **ast_ptr) /* {{{ */ zend_error_noreturn(E_COMPILE_ERROR, "Cannot use [] for reading"); } - if (ast->attr == ZEND_ALTERNATIVE_ARRAY_SYNTAX) { + if (ast->attr & ZEND_ALTERNATIVE_ARRAY_SYNTAX) { zend_error(E_DEPRECATED, "Array and string offset access syntax with curly braces is deprecated"); } /* Set isset fetch indicator here, opcache disallows runtime altering of the AST */ - if (ast->attr == ZEND_DIM_IS && ast->child[0]->kind == ZEND_AST_DIM) { - ast->child[0]->attr = ZEND_DIM_IS; + if (ast->attr & ZEND_DIM_IS && ast->child[0]->kind == ZEND_AST_DIM) { + ast->child[0]->attr |= ZEND_DIM_IS; } zend_eval_const_expr(&ast->child[0]); From 8131dbcad598a25b0863bb2eed088c094f20fdfe Mon Sep 17 00:00:00 2001 From: Theodore Brown Date: Tue, 16 Jul 2019 23:13:51 -0500 Subject: [PATCH 12/15] Implement directly in parser to avoid compiler complications --- Zend/zend_ast.c | 2 +- Zend/zend_compile.c | 14 +++----------- Zend/zend_compile.h | 3 --- Zend/zend_language_parser.y | 5 ++++- 4 files changed, 8 insertions(+), 16 deletions(-) diff --git a/Zend/zend_ast.c b/Zend/zend_ast.c index 685189ed7d114..dcbe531323fad 100644 --- a/Zend/zend_ast.c +++ b/Zend/zend_ast.c @@ -714,7 +714,7 @@ ZEND_API int ZEND_FASTCALL zend_ast_evaluate(zval *result, zend_ast *ast, zend_c zval_ptr_dtor_nogc(&op1); ret = FAILURE; } else { - zend_fetch_dimension_const(result, &op1, &op2, (ast->attr & ZEND_DIM_IS) ? BP_VAR_IS : BP_VAR_R); + zend_fetch_dimension_const(result, &op1, &op2, (ast->attr == ZEND_DIM_IS) ? BP_VAR_IS : BP_VAR_R); zval_ptr_dtor_nogc(&op1); zval_ptr_dtor_nogc(&op2); diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 1f992f8aa7ef8..1e57c7bf095da 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -2371,10 +2371,6 @@ static inline void zend_emit_assign_znode(zend_ast *var_ast, znode *value_node) static zend_op *zend_delayed_compile_dim(znode *result, zend_ast *ast, uint32_t type) /* {{{ */ { - if (ast->attr == ZEND_ALTERNATIVE_ARRAY_SYNTAX) { - zend_error(E_DEPRECATED, "Array and string offset access syntax with curly braces is deprecated"); - } - zend_ast *var_ast = ast->child[0]; zend_ast *dim_ast = ast->child[1]; zend_op *opline; @@ -8748,7 +8744,7 @@ void zend_eval_const_expr(zend_ast **ast_ptr) /* {{{ */ case ZEND_AST_COALESCE: /* Set isset fetch indicator here, opcache disallows runtime altering of the AST */ if (ast->child[0]->kind == ZEND_AST_DIM) { - ast->child[0]->attr |= ZEND_DIM_IS; + ast->child[0]->attr = ZEND_DIM_IS; } zend_eval_const_expr(&ast->child[0]); @@ -8802,13 +8798,9 @@ void zend_eval_const_expr(zend_ast **ast_ptr) /* {{{ */ zend_error_noreturn(E_COMPILE_ERROR, "Cannot use [] for reading"); } - if (ast->attr & ZEND_ALTERNATIVE_ARRAY_SYNTAX) { - zend_error(E_DEPRECATED, "Array and string offset access syntax with curly braces is deprecated"); - } - /* Set isset fetch indicator here, opcache disallows runtime altering of the AST */ - if (ast->attr & ZEND_DIM_IS && ast->child[0]->kind == ZEND_AST_DIM) { - ast->child[0]->attr |= ZEND_DIM_IS; + if (ast->attr == ZEND_DIM_IS && ast->child[0]->kind == ZEND_AST_DIM) { + ast->child[0]->attr = ZEND_DIM_IS; } zend_eval_const_expr(&ast->child[0]); diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h index 55bf0ee8ea49d..c9b827041373f 100644 --- a/Zend/zend_compile.h +++ b/Zend/zend_compile.h @@ -991,9 +991,6 @@ static zend_always_inline int zend_check_arg_send_type(const zend_function *zf, #define ZEND_ARRAY_NOT_PACKED (1<<1) #define ZEND_ARRAY_SIZE_SHIFT 2 -/* Array/string access syntax with curly braces is used */ -#define ZEND_ALTERNATIVE_ARRAY_SYNTAX 2 - /* Attribute for ternary inside parentheses */ #define ZEND_PARENTHESIZED_CONDITIONAL 1 diff --git a/Zend/zend_language_parser.y b/Zend/zend_language_parser.y index 975b88a7e3b8c..343117fa2bc37 100644 --- a/Zend/zend_language_parser.y +++ b/Zend/zend_language_parser.y @@ -1155,7 +1155,10 @@ callable_variable: | constant '[' optional_expr ']' { $$ = zend_ast_create(ZEND_AST_DIM, $1, $3); } | dereferencable '{' expr '}' - { $$ = zend_ast_create_ex(ZEND_AST_DIM, ZEND_ALTERNATIVE_ARRAY_SYNTAX, $1, $3); } + { + $$ = zend_ast_create(ZEND_AST_DIM, $1, $3); + zend_error(E_DEPRECATED, "Array and string offset access syntax with curly braces is deprecated"); + } | dereferencable T_OBJECT_OPERATOR property_name argument_list { $$ = zend_ast_create(ZEND_AST_METHOD_CALL, $1, $3, $4); } | function_call { $$ = $1; } From 5d2ae42e0b7c1e487e31b18b41bfc2c8d0a9f35c Mon Sep 17 00:00:00 2001 From: Theodore Brown Date: Wed, 17 Jul 2019 10:46:02 -0500 Subject: [PATCH 13/15] Revert "Implement directly in parser to avoid compiler complications" This reverts commit 8131dbcad598a25b0863bb2eed088c094f20fdfe. --- Zend/zend_ast.c | 2 +- Zend/zend_compile.c | 14 +++++++++++--- Zend/zend_compile.h | 3 +++ Zend/zend_language_parser.y | 5 +---- 4 files changed, 16 insertions(+), 8 deletions(-) diff --git a/Zend/zend_ast.c b/Zend/zend_ast.c index dcbe531323fad..685189ed7d114 100644 --- a/Zend/zend_ast.c +++ b/Zend/zend_ast.c @@ -714,7 +714,7 @@ ZEND_API int ZEND_FASTCALL zend_ast_evaluate(zval *result, zend_ast *ast, zend_c zval_ptr_dtor_nogc(&op1); ret = FAILURE; } else { - zend_fetch_dimension_const(result, &op1, &op2, (ast->attr == ZEND_DIM_IS) ? BP_VAR_IS : BP_VAR_R); + zend_fetch_dimension_const(result, &op1, &op2, (ast->attr & ZEND_DIM_IS) ? BP_VAR_IS : BP_VAR_R); zval_ptr_dtor_nogc(&op1); zval_ptr_dtor_nogc(&op2); diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 1e57c7bf095da..1f992f8aa7ef8 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -2371,6 +2371,10 @@ static inline void zend_emit_assign_znode(zend_ast *var_ast, znode *value_node) static zend_op *zend_delayed_compile_dim(znode *result, zend_ast *ast, uint32_t type) /* {{{ */ { + if (ast->attr == ZEND_ALTERNATIVE_ARRAY_SYNTAX) { + zend_error(E_DEPRECATED, "Array and string offset access syntax with curly braces is deprecated"); + } + zend_ast *var_ast = ast->child[0]; zend_ast *dim_ast = ast->child[1]; zend_op *opline; @@ -8744,7 +8748,7 @@ void zend_eval_const_expr(zend_ast **ast_ptr) /* {{{ */ case ZEND_AST_COALESCE: /* Set isset fetch indicator here, opcache disallows runtime altering of the AST */ if (ast->child[0]->kind == ZEND_AST_DIM) { - ast->child[0]->attr = ZEND_DIM_IS; + ast->child[0]->attr |= ZEND_DIM_IS; } zend_eval_const_expr(&ast->child[0]); @@ -8798,9 +8802,13 @@ void zend_eval_const_expr(zend_ast **ast_ptr) /* {{{ */ zend_error_noreturn(E_COMPILE_ERROR, "Cannot use [] for reading"); } + if (ast->attr & ZEND_ALTERNATIVE_ARRAY_SYNTAX) { + zend_error(E_DEPRECATED, "Array and string offset access syntax with curly braces is deprecated"); + } + /* Set isset fetch indicator here, opcache disallows runtime altering of the AST */ - if (ast->attr == ZEND_DIM_IS && ast->child[0]->kind == ZEND_AST_DIM) { - ast->child[0]->attr = ZEND_DIM_IS; + if (ast->attr & ZEND_DIM_IS && ast->child[0]->kind == ZEND_AST_DIM) { + ast->child[0]->attr |= ZEND_DIM_IS; } zend_eval_const_expr(&ast->child[0]); diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h index c9b827041373f..55bf0ee8ea49d 100644 --- a/Zend/zend_compile.h +++ b/Zend/zend_compile.h @@ -991,6 +991,9 @@ static zend_always_inline int zend_check_arg_send_type(const zend_function *zf, #define ZEND_ARRAY_NOT_PACKED (1<<1) #define ZEND_ARRAY_SIZE_SHIFT 2 +/* Array/string access syntax with curly braces is used */ +#define ZEND_ALTERNATIVE_ARRAY_SYNTAX 2 + /* Attribute for ternary inside parentheses */ #define ZEND_PARENTHESIZED_CONDITIONAL 1 diff --git a/Zend/zend_language_parser.y b/Zend/zend_language_parser.y index 343117fa2bc37..975b88a7e3b8c 100644 --- a/Zend/zend_language_parser.y +++ b/Zend/zend_language_parser.y @@ -1155,10 +1155,7 @@ callable_variable: | constant '[' optional_expr ']' { $$ = zend_ast_create(ZEND_AST_DIM, $1, $3); } | dereferencable '{' expr '}' - { - $$ = zend_ast_create(ZEND_AST_DIM, $1, $3); - zend_error(E_DEPRECATED, "Array and string offset access syntax with curly braces is deprecated"); - } + { $$ = zend_ast_create_ex(ZEND_AST_DIM, ZEND_ALTERNATIVE_ARRAY_SYNTAX, $1, $3); } | dereferencable T_OBJECT_OPERATOR property_name argument_list { $$ = zend_ast_create(ZEND_AST_METHOD_CALL, $1, $3, $4); } | function_call { $$ = $1; } From e80b265e29eb152192c4d517777031b9488ad67c Mon Sep 17 00:00:00 2001 From: Theodore Brown Date: Thu, 18 Jul 2019 09:42:36 -0500 Subject: [PATCH 14/15] Rename flag to clarify bitmask usage --- Zend/zend_compile.c | 4 ++-- Zend/zend_compile.h | 6 ++---- Zend/zend_language_parser.y | 2 +- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 1f992f8aa7ef8..92905ba0c3276 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -2371,7 +2371,7 @@ static inline void zend_emit_assign_znode(zend_ast *var_ast, znode *value_node) static zend_op *zend_delayed_compile_dim(znode *result, zend_ast *ast, uint32_t type) /* {{{ */ { - if (ast->attr == ZEND_ALTERNATIVE_ARRAY_SYNTAX) { + if (ast->attr == ZEND_DIM_ALTERNATIVE_SYNTAX) { zend_error(E_DEPRECATED, "Array and string offset access syntax with curly braces is deprecated"); } @@ -8802,7 +8802,7 @@ void zend_eval_const_expr(zend_ast **ast_ptr) /* {{{ */ zend_error_noreturn(E_COMPILE_ERROR, "Cannot use [] for reading"); } - if (ast->attr & ZEND_ALTERNATIVE_ARRAY_SYNTAX) { + if (ast->attr & ZEND_DIM_ALTERNATIVE_SYNTAX) { zend_error(E_DEPRECATED, "Array and string offset access syntax with curly braces is deprecated"); } diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h index 55bf0ee8ea49d..2d7c18fc584e2 100644 --- a/Zend/zend_compile.h +++ b/Zend/zend_compile.h @@ -924,7 +924,8 @@ void zend_assert_valid_class_name(const zend_string *const_name); #define ZEND_SEND_BY_REF 1u #define ZEND_SEND_PREFER_REF 2u -#define ZEND_DIM_IS 1 +#define ZEND_DIM_IS (1 << 0) /* isset fetch needed for null coalesce */ +#define ZEND_DIM_ALTERNATIVE_SYNTAX (1 << 1) /* deprecated curly brace usage */ #define IS_CONSTANT_UNQUALIFIED 0x010 #define IS_CONSTANT_CLASS 0x080 /* __CLASS__ in trait */ @@ -991,9 +992,6 @@ static zend_always_inline int zend_check_arg_send_type(const zend_function *zf, #define ZEND_ARRAY_NOT_PACKED (1<<1) #define ZEND_ARRAY_SIZE_SHIFT 2 -/* Array/string access syntax with curly braces is used */ -#define ZEND_ALTERNATIVE_ARRAY_SYNTAX 2 - /* Attribute for ternary inside parentheses */ #define ZEND_PARENTHESIZED_CONDITIONAL 1 diff --git a/Zend/zend_language_parser.y b/Zend/zend_language_parser.y index 975b88a7e3b8c..ab97c56bde46b 100644 --- a/Zend/zend_language_parser.y +++ b/Zend/zend_language_parser.y @@ -1155,7 +1155,7 @@ callable_variable: | constant '[' optional_expr ']' { $$ = zend_ast_create(ZEND_AST_DIM, $1, $3); } | dereferencable '{' expr '}' - { $$ = zend_ast_create_ex(ZEND_AST_DIM, ZEND_ALTERNATIVE_ARRAY_SYNTAX, $1, $3); } + { $$ = zend_ast_create_ex(ZEND_AST_DIM, ZEND_DIM_ALTERNATIVE_SYNTAX, $1, $3); } | dereferencable T_OBJECT_OPERATOR property_name argument_list { $$ = zend_ast_create(ZEND_AST_METHOD_CALL, $1, $3, $4); } | function_call { $$ = $1; } From 4c791b13fcc39fdcfb38b0b3d9b4336800fbc74c Mon Sep 17 00:00:00 2001 From: Theodore Brown Date: Thu, 18 Jul 2019 10:15:24 -0500 Subject: [PATCH 15/15] Prevent duplicate warning from const evaluation and compilation --- Zend/zend_compile.c | 1 + tests/strings/offsets_general.phpt | 11 +++++++---- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 92905ba0c3276..3e5f96b6f3c52 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -8803,6 +8803,7 @@ void zend_eval_const_expr(zend_ast **ast_ptr) /* {{{ */ } if (ast->attr & ZEND_DIM_ALTERNATIVE_SYNTAX) { + ast->attr &= ~ZEND_DIM_ALTERNATIVE_SYNTAX; /* remove flag to avoid duplicate warning */ zend_error(E_DEPRECATED, "Array and string offset access syntax with curly braces is deprecated"); } diff --git a/tests/strings/offsets_general.phpt b/tests/strings/offsets_general.phpt index 335b53489d742..b2ceea088a2f5 100644 --- a/tests/strings/offsets_general.phpt +++ b/tests/strings/offsets_general.phpt @@ -14,12 +14,12 @@ var_dump(isset($string["foo"]["bar"])); const FOO_DEPRECATED = "BAR"{0}; var_dump(FOO_DEPRECATED); -var_dump($string{0}); +var_dump([$string{0}]); // 1 notice var_dump($string{1}); var_dump(isset($string{0})); -var_dump(isset($string{0}{0})); +var_dump(isset($string{0}{0})); // 2 notices var_dump($string{"foo"}); -var_dump(isset($string{"foo"}{"bar"})); +var_dump(isset($string{"foo"}{"bar"})); // 2 notices ?> --EXPECTF-- @@ -50,7 +50,10 @@ Warning: Illegal string offset 'foo' in %s line %d string(1) "f" bool(false) string(1) "B" -string(1) "f" +array(1) { + [0]=> + string(1) "f" +} string(1) "o" bool(true) bool(true)