diff --git a/Library/AbstractCompilerFile.php b/Library/AbstractCompilerFile.php new file mode 100644 index 000000000..e99245b10 --- /dev/null +++ b/Library/AbstractCompilerFile.php @@ -0,0 +1,63 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Zephir; + +abstract class AbstractCompilerFile +{ + /** + * @var string|null + */ + protected ?string $compiledFile = null; + + /** + * @var bool + */ + protected bool $external = false; + + /** + * Returns the path to the compiled file. + * + * @return string|null + */ + public function getCompiledFile(): ?string + { + return $this->compiledFile; + } + + /** + * @return bool + */ + public function isExternal(): bool + { + return $this->external; + } + + /** + * Sets if the class belongs to an external dependency or not. + * + * @param mixed $external + */ + public function setIsExternal($external): void + { + $this->external = (bool) $external; + } + + /** + * Compiles the file. + * + * @param Compiler $compiler + * @param StringsManager $stringsManager + */ + abstract public function compile(Compiler $compiler, StringsManager $stringsManager): void; +} diff --git a/Library/Compiler.php b/Library/Compiler.php index 74c744398..7c668cb66 100644 --- a/Library/Compiler.php +++ b/Library/Compiler.php @@ -745,6 +745,7 @@ public function generate(bool $fromGenerate = false): bool $files = []; $hash = ''; + /** @var AbstractCompilerFile $compileFile */ foreach ($this->files as $compileFile) { /** * Only compile classes in the local extension, ignore external classes @@ -771,6 +772,7 @@ public function generate(bool $fromGenerate = false): bool /** * Round 3.2. Compile anonymous classes */ + /** @var AbstractCompilerFile $compileFile */ foreach ($this->anonymousFiles as $compileFile) { $compileFile->compile($this, $this->stringManager); $compiledFile = $compileFile->getCompiledFile(); @@ -1314,13 +1316,13 @@ public function processExtensionGlobals(string $namespace): array * Process compound structures */ foreach ($structures as $structureName => $internalStructure) { - if (preg_match('/^[0-9a-zA-Z\_]$/', $structureName)) { + if (preg_match('/^[0-9a-zA-Z_]$/', $structureName)) { throw new Exception("Struct name: '".$structureName."' contains invalid characters"); } $structBuilder = new Struct('_zephir_struct_'.$structureName, $structureName); foreach ($internalStructure as $field => $global) { - if (preg_match('/^[0-9a-zA-Z\_]$/', $field)) { + if (preg_match('/^[0-9a-zA-Z_]$/', $field)) { throw new Exception("Struct field name: '".$field."' contains invalid characters"); } @@ -1343,7 +1345,7 @@ public function processExtensionGlobals(string $namespace): array * Process single variables */ foreach ($variables as $name => $global) { - if (preg_match('/^[0-9a-zA-Z\_]$/', $name)) { + if (preg_match('/^[0-9a-zA-Z_]$/', $name)) { throw new Exception("Extension global variable name: '".$name."' contains invalid characters"); } @@ -2165,11 +2167,12 @@ private function loadConstantsSources(array $constantsSources) } foreach (file($constantsSource) as $line) { - if (preg_match('/^\#define[ \t]+([A-Z0-9\_]+)[ \t]+([0-9]+)/', $line, $matches)) { + if (preg_match('/^#define[ \t]+([A-Z0-9_]+)[ \t]+([0-9]+)/', $line, $matches)) { $this->constants[$matches[1]] = ['int', $matches[2]]; continue; } - if (preg_match('/^\#define[ \t]+([A-Z0-9\_]+)[ \t]+(\'(.){1}\')/', $line, $matches)) { + + if (preg_match('/^#define[ \t]+([A-Z0-9_]+)[ \t]+(\'(.)\')/', $line, $matches)) { $this->constants[$matches[1]] = ['char', $matches[3]]; } } diff --git a/Library/CompilerFile.php b/Library/CompilerFile.php index e634da6fd..c4f5241b2 100644 --- a/Library/CompilerFile.php +++ b/Library/CompilerFile.php @@ -23,13 +23,15 @@ use Zephir\Exception\ParseException; use Zephir\FileSystem\FileSystemInterface; +use function count; +use function dirname; use function is_array; /** * This class represents every file compiled in a project. * Every file may contain a class or an interface. */ -final class CompilerFile implements FileInterface +final class CompilerFile extends AbstractCompilerFile implements FileInterface { use LoggerAwareTrait; @@ -48,11 +50,6 @@ final class CompilerFile implements FileInterface */ private ?string $filePath = null; - /** - * @var bool - */ - private bool $external = false; - /** * Original internal representation (IR) of the file. * @@ -65,11 +62,6 @@ final class CompilerFile implements FileInterface */ private $originalNode; - /** - * @var string|null - */ - private ?string $compiledFile = null; - /** * @var ClassDefinition|null */ @@ -135,40 +127,20 @@ public function setClassName(string $className) } /** - * {@inheritdoc} - * - * @return ClassDefinition + * @return ClassDefinition|null */ - public function getClassDefinition() + public function getClassDefinition(): ?ClassDefinition { $this->classDefinition->setAliasManager($this->aliasManager); return $this->classDefinition; } - public function getFunctionDefinitions() + public function getFunctionDefinitions(): array { return $this->functionDefinitions; } - /** - * Sets if the class belongs to an external dependency or not. - * - * @param bool $external - */ - public function setIsExternal($external): void - { - $this->external = (bool) $external; - } - - /** - * @return bool - */ - public function isExternal(): bool - { - return $this->external; - } - /** * Adds a function to the function definitions. * @@ -244,6 +216,7 @@ public function compileClass(CompilationContext $compilationContext): void * * @param CompilationContext $compilationContext * @param FunctionDefinition $functionDefinition + * @throws Exception */ public function compileFunction(CompilationContext $compilationContext, FunctionDefinition $functionDefinition) { @@ -269,7 +242,7 @@ public function compileFunction(CompilationContext $compilationContext, Function * @param CompilationContext $compilationContext * @param array $topStatement */ - public function compileComment(CompilationContext $compilationContext, $topStatement) + public function compileComment(CompilationContext $compilationContext, array $topStatement): void { $compilationContext->codePrinter->output('/'.$topStatement['value'].'/'); } @@ -278,10 +251,10 @@ public function compileComment(CompilationContext $compilationContext, $topState * Creates a definition for an interface. * * @param string $namespace - * @param array $topStatement - * @param array $docblock + * @param array $topStatement + * @param array|null $docblock */ - public function preCompileInterface($namespace, $topStatement, $docblock) + public function preCompileInterface(string $namespace, array $topStatement, ?array $docblock = null) { $classDefinition = new ClassDefinition($namespace, $topStatement['name']); $classDefinition->setIsExternal($this->external); @@ -343,11 +316,11 @@ public function preCompileInterface($namespace, $topStatement, $docblock) * Creates a definition for a class. * * @param CompilationContext $compilationContext - * @param string $namespace - * @param array $topStatement - * @param array $docblock + * @param string $namespace + * @param array $topStatement + * @param array|null $docblock */ - public function preCompileClass(CompilationContext $compilationContext, $namespace, $topStatement, $docblock) + public function preCompileClass(CompilationContext $compilationContext, string $namespace, array $topStatement, ?array $docblock = null) { $classDefinition = new ClassDefinition($namespace, $topStatement['name']); $classDefinition->setIsExternal($this->external); @@ -637,16 +610,6 @@ public function preCompile(Compiler $compiler) $this->ir = $ir; } - /** - * Returns the path to the compiled file. - * - * @return string - */ - public function getCompiledFile(): string - { - return $this->compiledFile; - } - /** * Check dependencies. * @@ -712,24 +675,28 @@ public function checkDependencies(Compiler $compiler) foreach ($classDefinition->getImplementedInterfaces() as $interface) { if ($compiler->isInterface($interface)) { $interfaceDefinitions[$interface] = $compiler->getClassDefinition($interface); - } else { - if ($compiler->isBundledInterface($interface)) { - $interfaceDefinitions[$interface] = $compiler->getInternalClassDefinition($interface); - } else { - if ($extendedClass !== null) { - $classDefinition->setExtendsClassDefinition(new ClassDefinitionRuntime($extendedClass)); - } - $this->logger->warning( - sprintf( - 'Cannot locate class "%s" when extending interface "%s"', - $interface, - $classDefinition->getCompleteName() - ), - ['nonexistent-class', $this->originalNode] - ); - } + continue; } + + if ($compiler->isBundledInterface($interface)) { + $interfaceDefinitions[$interface] = $compiler->getInternalClassDefinition($interface); + + continue; + } + + if ($extendedClass !== null) { + $classDefinition->setExtendsClassDefinition(new ClassDefinitionRuntime($extendedClass)); + } + + $this->logger->warning( + sprintf( + 'Cannot locate class "%s" when extending interface "%s"', + $interface, + $classDefinition->getCompleteName() + ), + ['nonexistent-class', $this->originalNode] + ); } if (count($interfaceDefinitions) > 0) { @@ -740,12 +707,13 @@ public function checkDependencies(Compiler $compiler) /** * Compiles the file. * - * @param Compiler $compiler + * @param Compiler $compiler * @param StringsManager $stringsManager * - * @throws CompilerException + * @throws Exception + * @throws ReflectionException */ - public function compile(Compiler $compiler, StringsManager $stringsManager) + public function compile(Compiler $compiler, StringsManager $stringsManager): void { if (!$this->ir) { throw new CompilerException('Unable to locate the intermediate representation of the compiled file'); @@ -843,22 +811,20 @@ public function compile(Compiler $compiler, StringsManager $stringsManager) $classDefinition->setOriginalNode($this->originalNode); - $completeName = $classDefinition->getCompleteName(); - - $path = str_replace('\\', \DIRECTORY_SEPARATOR, strtolower($completeName)); + $path = str_replace('\\', \DIRECTORY_SEPARATOR, strtolower($classDefinition->getCompleteName())); $filePath = 'ext/'.$path.'.zep.c'; $filePathHeader = 'ext/'.$path.'.zep.h'; if (strpos($path, \DIRECTORY_SEPARATOR)) { - $dirname = \dirname($filePath); + $dirname = dirname($filePath); if (!is_dir($dirname)) { mkdir($dirname, 0755, true); } } /** - * If the file does not exists we create it for the first time + * If the file does not exist we create it for the first time */ if (!file_exists($filePath)) { file_put_contents($filePath, $codePrinter->getOutput()); @@ -896,7 +862,7 @@ public function applyClassHeaders(CompilationContext $compilationContext) { $classDefinition = $this->classDefinition; - $separators = str_repeat('../', \count(explode('\\', $classDefinition->getCompleteName())) - 1); + $separators = str_repeat('../', count(explode('\\', $classDefinition->getCompleteName())) - 1); $code = ''.PHP_EOL; $code .= '#ifdef HAVE_CONFIG_H'.PHP_EOL; @@ -926,11 +892,11 @@ public function applyClassHeaders(CompilationContext $compilationContext) } } - if (\count($this->headerCBlocks) > 0) { + if (count($this->headerCBlocks) > 0) { $code .= implode(PHP_EOL, $this->headerCBlocks).PHP_EOL; } - /* + /** * Prepend the required files to the header */ $compilationContext->codePrinter->preOutput($code); @@ -954,7 +920,7 @@ public function getFilePath(): string * * @throws CompilerException */ - protected function processShortcuts(array $property, ClassDefinition $classDefinition) + protected function processShortcuts(array $property, ClassDefinition $classDefinition): void { foreach ($property['shortcuts'] as $shortcut) { if ('_' == substr($property['name'], 0, 1)) { @@ -1026,7 +992,7 @@ protected function processShortcuts(array $property, ClassDefinition $classDefin 'type' => 'parameter', 'name' => $name, 'const' => 0, - 'data-type' => 1 == \count($returnsType) ? $returnsType[0] : 'variable', + 'data-type' => 1 == count($returnsType) ? $returnsType[0] : 'variable', 'mandatory' => 0, ], ]), diff --git a/Library/CompilerFileAnonymous.php b/Library/CompilerFileAnonymous.php index b4b54557e..7d904cdb7 100644 --- a/Library/CompilerFileAnonymous.php +++ b/Library/CompilerFileAnonymous.php @@ -24,12 +24,11 @@ * This class represents an anonymous file created to dump * the code produced by an internal closure */ -final class CompilerFileAnonymous implements FileInterface +final class CompilerFileAnonymous extends AbstractCompilerFile implements FileInterface { use LoggerAwareTrait; protected ?string $namespace = null; - protected ?string $compiledFile = null; protected bool $external = false; protected array $headerCBlocks = []; protected ?CompilationContext $context = null; @@ -37,8 +36,6 @@ final class CompilerFileAnonymous implements FileInterface protected Config $config; /** - * CompilerFileAnonymous constructor. - * * @param ClassDefinition $classDefinition * @param Config $config * @param CompilationContext|null $context @@ -59,24 +56,6 @@ public function getClassDefinition(): ClassDefinition return $this->classDefinition; } - /** - * Sets if the class belongs to an external dependency or not. - * - * @param bool $external - */ - public function setIsExternal($external) - { - $this->external = (bool) $external; - } - - /** - * @return bool - */ - public function isExternal(): bool - { - return $this->external; - } - /** * Compiles the class/interface contained in the file. * @@ -96,21 +75,21 @@ private function compileClass(CompilationContext $compilationContext) $separators = str_repeat('../', count(explode('\\', $classDefinition->getCompleteName())) - 1); - $code = ''.PHP_EOL; + $code = PHP_EOL; $code .= '#ifdef HAVE_CONFIG_H'.PHP_EOL; $code .= '#include "'.$separators.'ext_config.h"'.PHP_EOL; $code .= '#endif'.PHP_EOL; - $code .= ''.PHP_EOL; + $code .= PHP_EOL; $code .= '#include '.PHP_EOL; $code .= '#include "'.$separators.'php_ext.h"'.PHP_EOL; $code .= '#include "'.$separators.'ext.h"'.PHP_EOL; - $code .= ''.PHP_EOL; + $code .= PHP_EOL; $code .= '#include '.PHP_EOL; $code .= '#include '.PHP_EOL; $code .= '#include '.PHP_EOL; - $code .= ''.PHP_EOL; + $code .= PHP_EOL; $code .= '#include "kernel/main.h"'.PHP_EOL; @@ -133,12 +112,13 @@ private function compileClass(CompilationContext $compilationContext) /** * Compiles the file. * - * @param Compiler $compiler + * @param Compiler $compiler * @param StringsManager $stringsManager * * @throws Exception + * @throws ReflectionException */ - public function compile(Compiler $compiler, StringsManager $stringsManager) + public function compile(Compiler $compiler, StringsManager $stringsManager): void { /** * Compilation context stores common objects required by compilation entities. @@ -221,16 +201,6 @@ public function compile(Compiler $compiler, StringsManager $stringsManager) $this->compiledFile = $path.'.c'; } - /** - * Returns the path to the compiled file. - * - * @return string - */ - public function getCompiledFile() - { - return $this->compiledFile; - } - /** * {@inheritdoc} * diff --git a/kernels/ZendEngine3/fcall.c b/kernels/ZendEngine3/fcall.c index 447d28104..1df70c4e8 100755 --- a/kernels/ZendEngine3/fcall.c +++ b/kernels/ZendEngine3/fcall.c @@ -118,14 +118,14 @@ static int zephir_make_fcall_key(zend_string* s, zephir_call_type type, zend_cla if (Z_TYPE_P(function) == IS_STRING) { mth = Z_STRVAL_P(function); mth_len = Z_STRLEN_P(function); - } - else if (Z_TYPE_P(function) == IS_ARRAY) { + } else if (Z_TYPE_P(function) == IS_ARRAY) { zval *method; HashTable *function_hash = Z_ARRVAL_P(function); + if ( - function_hash->nNumOfElements == 2 - && ((method = zend_hash_index_find(function_hash, 1)) != NULL) - && Z_TYPE_P(method) == IS_STRING + function_hash->nNumOfElements == 2 && + ((method = zend_hash_index_find(function_hash, 1)) != NULL) && + Z_TYPE_P(method) == IS_STRING ) { mth = Z_STRVAL_P(method); mth_len = Z_STRLEN_P(method); @@ -279,47 +279,47 @@ static void populate_fcic(zend_fcall_info_cache* fcic, zephir_call_type type, ze break; case zephir_fcall_ce: -#if PHP_VERSION_ID >= 80000 - if (ce && Z_TYPE_P(func) == IS_STRING) { - fcic->function_handler = zend_hash_find_ptr(&ce->function_table, Z_STR_P(func)); + fcic->calling_scope = ce; + fcic->called_scope = ce; - fcic->calling_scope = ce; - } else if (calling_scope && Z_TYPE_P(func) == IS_STRING) { - fcic->function_handler = zend_hash_find_ptr(&calling_scope->function_table, Z_STR_P(func)); - fcic->calling_scope = calling_scope; +#if PHP_VERSION_ID >= 80000 + if (Z_TYPE_P(func) == IS_STRING) { + if (ce) { + fcic->function_handler = zend_hash_find_ptr(&ce->function_table, Z_STR_P(func)); + } else if (calling_scope) { + fcic->function_handler = zend_hash_find_ptr(&calling_scope->function_table, Z_STR_P(func)); + fcic->calling_scope = calling_scope; + } } #endif - // TODO: Check for PHP 7.4 and PHP 8.0, as it rewrite from above - fcic->calling_scope = ce; - fcic->called_scope = ce; break; case zephir_fcall_function: case zephir_fcall_method: if (Z_TYPE_P(func) == IS_OBJECT) { + if (Z_TYPE_P(this_ptr) == IS_OBJECT) { + fcic->calling_scope = Z_OBJCE_P(this_ptr); + } + #if PHP_VERSION_ID >= 80000 - if (Z_OBJ_HANDLER_P(func, get_closure) && Z_OBJ_HANDLER_P(func, get_closure)(Z_OBJ_P(func), &fcic->calling_scope, &fcic->function_handler, &fcic->object, 0) == SUCCESS) { + if (Z_OBJ_HANDLER_P(func, get_closure)(Z_OBJ_P(func), &fcic->calling_scope, &fcic->function_handler, &fcic->object, 1) == SUCCESS) { #else - if (Z_OBJ_HANDLER_P(func, get_closure) && Z_OBJ_HANDLER_P(func, get_closure)(func, &fcic->calling_scope, &fcic->function_handler, &fcic->object) == SUCCESS) { + if (Z_OBJ_HANDLER_P(func, get_closure)(func, &fcic->calling_scope, &fcic->function_handler, &fcic->object) == SUCCESS) { #endif fcic->called_scope = fcic->calling_scope; break; } - - return; - } + } else if (Z_TYPE_P(func) == IS_STRING) { + if (ce) { + fcic->calling_scope = ce; #if PHP_VERSION_ID >= 80000 - if (ce && Z_TYPE_P(func) == IS_STRING) { - fcic->function_handler = zend_hash_find_ptr(&ce->function_table, Z_STR_P(func)); - } + fcic->function_handler = zend_hash_find_ptr(&ce->function_table, Z_STR_P(func)); #endif - fcic->calling_scope = this_ptr ? Z_OBJCE_P(this_ptr) : NULL; - fcic->called_scope = fcic->calling_scope; - break; + } + } - default: - return; + break; } } @@ -449,7 +449,8 @@ int zephir_call_user_function( zval_ptr_dtor(&callable); } - /* Skip caching IF: + /** + * Skip caching IF: * call failed OR there was an exception (to be safe) OR cache key is not defined OR * fcall cache was de-initialized OR we have a slot cache */ @@ -482,10 +483,15 @@ int zephir_call_user_function( return status; } -int zephir_call_func_aparams(zval *return_value_ptr, const char *func_name, uint32_t func_length, - zephir_fcall_cache_entry **cache_entry, int cache_slot, - uint32_t param_count, zval **params) -{ +int zephir_call_func_aparams( + zval *return_value_ptr, + const char *func_name, + uint32_t func_length, + zephir_fcall_cache_entry **cache_entry, + int cache_slot, + uint32_t param_count, + zval **params +) { int status; zval rv, *rvp = return_value_ptr ? return_value_ptr : &rv; @@ -517,10 +523,14 @@ int zephir_call_func_aparams(zval *return_value_ptr, const char *func_name, uint return status; } -int zephir_call_zval_func_aparams(zval *return_value_ptr, zval *func_name, - zephir_fcall_cache_entry **cache_entry, int cache_slot, - uint32_t param_count, zval **params) -{ +int zephir_call_zval_func_aparams( + zval *return_value_ptr, + zval *func_name, + zephir_fcall_cache_entry **cache_entry, + int cache_slot, + uint32_t param_count, + zval **params +) { int status; zval rv, *rvp = return_value_ptr ? return_value_ptr : &rv; @@ -700,8 +710,7 @@ void zephir_eval_php(zval *str, zval *retval_ptr, char *context) #endif CG(compiler_options) = original_compiler_options; - if (new_op_array) - { + if (new_op_array) { EG(no_extensions) = 1; zend_try { zend_execute(new_op_array, &local_retval); diff --git a/kernels/ZendEngine3/fcall.h b/kernels/ZendEngine3/fcall.h index 258136075..26c52a4e2 100755 --- a/kernels/ZendEngine3/fcall.h +++ b/kernels/ZendEngine3/fcall.h @@ -311,9 +311,15 @@ int zephir_call_class_method_aparams( uint32_t param_count, zval **params) ZEPHIR_ATTR_WARN_UNUSED_RESULT; -ZEPHIR_ATTR_WARN_UNUSED_RESULT static inline int zephir_return_call_function(zval *return_value, - const char *func, uint32_t func_len, zephir_fcall_cache_entry **cache_entry, int cache_slot, uint32_t param_count, zval **params) -{ +ZEPHIR_ATTR_WARN_UNUSED_RESULT static inline int zephir_return_call_function( + zval *return_value, + const char *func, + uint32_t func_len, + zephir_fcall_cache_entry **cache_entry, + int cache_slot, + uint32_t param_count, + zval **params +) { zval rv, *rvp = return_value ? return_value : &rv; int status; diff --git a/stub/closures.zep b/stub/closures.zep index f37c3d88e..bf1366a20 100644 --- a/stub/closures.zep +++ b/stub/closures.zep @@ -97,4 +97,26 @@ class Closures { return call_user_func(this->_function, this->_argument); } + + /** + * @issue https://github.com/zephir-lang/zephir/issues/2321 + */ + public function issue2321CallPrivateCallback() -> string + { + return this->issue2321filterQuery("filtered_value"); + } + + private function issue2321filterQuery(string value) -> string + { + return preg_replace_callback( + "/(?:[^%:!\$&\'\(\)\*\+,;=@\/\?]+|%(?![A-Fa-f0-9]{2}))/u", + [this, "issue2321doUrlEncode"], + value + ); + } + + private function issue2321doUrlEncode(array matches) -> string + { + return rawurlencode(matches[0]); + } } diff --git a/tests/Extension/ClosureTest.php b/tests/Extension/ClosureTest.php index e84ce190d..bf80607bf 100644 --- a/tests/Extension/ClosureTest.php +++ b/tests/Extension/ClosureTest.php @@ -40,4 +40,11 @@ public function testIssue1036(): void $this->assertTrue($test->issue1036Call()); } + + public function testIssue2321CallPrivateCallbackViaPrivateMethod(): void + { + $test = new Closures(); + + $this->assertSame('filtered_value', $test->issue2321CallPrivateCallback()); + } }