/** * Compiles the file * * @param Compiler $compiler * @param StringsManager $stringsManager */ public function compile(Compiler $compiler, StringsManager $stringsManager) { if (!$this->_ir) { throw new CompilerException('IR related to compiled file is missing'); } /** * External classes should not be compiled as part of the extension */ if ($this->_external) { return; } /** * Compilation context stores common objects required by compilation entities */ $compilationContext = new CompilationContext(); /** * Set global compiler in the compilation context */ $compilationContext->compiler = $compiler; /** * Set global config in the compilation context */ $compilationContext->config = $this->_config; /** * Set global logger in the compilation context */ $compilationContext->logger = $this->_logger; /** * Set global strings manager */ $compilationContext->stringsManager = $stringsManager; $compilationContext->backend = $compiler->backend; /** * Headers manager */ $headersManager = new HeadersManager(); $compilationContext->headersManager = $headersManager; /** * Main code-printer for the file */ $codePrinter = new CodePrinter(); $compilationContext->codePrinter = $codePrinter; /** * Alias manager */ $compilationContext->aliasManager = $this->_aliasManager; $codePrinter->outputBlankLine(); $class = false; $interface = false; foreach ($this->_ir as $topStatement) { switch ($topStatement['type']) { case 'class': if ($interface || $class) { throw new CompilerException("More than one class defined in the same file", $topStatement); } $class = true; $this->compileClass($compilationContext, $this->_namespace, $topStatement); break; case 'interface': if ($interface || $class) { throw new CompilerException("More than one class defined in the same file", $topStatement); } $class = true; $this->compileClass($compilationContext, $this->_namespace, $topStatement); break; case 'comment': $this->compileComment($compilationContext, $topStatement); break; } } /* ensure functions are handled last */ foreach ($this->_functionDefinitions as $funcDef) { $this->compileFunction($compilationContext, $funcDef); } /* apply headers */ $this->applyClassHeaders($compilationContext); $classDefinition = $this->_classDefinition; if (!$classDefinition) { $this->_ir = null; return; } $classDefinition->setOriginalNode($this->_originalNode); $completeName = $classDefinition->getCompleteName(); $path = str_replace('\\', DIRECTORY_SEPARATOR, strtolower($completeName)); $filePath = 'ext/' . $path . '.zep.c'; $filePathHeader = 'ext/' . $path . '.zep.h'; if (strpos($path, DIRECTORY_SEPARATOR)) { $dirname = dirname($filePath); if (!is_dir($dirname)) { mkdir($dirname, 0755, true); } } if ($codePrinter) { /** * If the file does not exists we create it for the first time */ if (!file_exists($filePath)) { file_put_contents($filePath, $codePrinter->getOutput()); if ($compilationContext->headerPrinter) { file_put_contents($filePathHeader, $compilationContext->headerPrinter->getOutput()); } } else { $fileSystem = $compiler->getFileSystem(); /** * Use md5 hash to avoid rewrite the file again and again when it hasn't changed * thus avoiding unnecessary recompilations */ $output = $codePrinter->getOutput(); $hash = $fileSystem->getHashFile('md5', $filePath, true); if (md5($output) != $hash) { file_put_contents($filePath, $output); } if ($compilationContext->headerPrinter) { $output = $compilationContext->headerPrinter->getOutput(); $hash = $fileSystem->getHashFile('md5', $filePathHeader, true); if (md5($output) != $hash) { file_put_contents($filePathHeader, $output); } } } } /** * Add to file compiled */ $this->_compiledFile = $path . '.c'; $this->_ir = null; }
/** * Compiles the method * * @param CompilationContext $compilationContext * @return null * @throws CompilerException */ public function compile(CompilationContext $compilationContext) { /** * Set the method currently being compiled */ $compilationContext->currentMethod = $this; /** * Initialize the method warm-up to null */ $compilationContext->methodWarmUp = null; /** * Assign pre-made compilation passses */ $localContext = $this->localContext; $typeInference = $this->typeInference; $callGathererPass = $this->callGathererPass; /** * Every method has its own symbol table */ $symbolTable = new SymbolTable($compilationContext); if ($localContext) { $symbolTable->setLocalContext($localContext); } /** * Parameters has an additional extra mutation */ $parameters = $this->parameters; if ($localContext) { if (is_object($parameters)) { foreach ($parameters->getParameters() as $parameter) { $localContext->increaseMutations($parameter['name']); } } } /** * Initialization of parameters happens in a fictitious external branch */ $branch = new Branch(); $branch->setType(Branch::TYPE_EXTERNAL); /** * BranchManager helps to create graphs of conditional/loop/root/jump branches */ $branchManager = new BranchManager(); $branchManager->addBranch($branch); /** * Cache Manager manages function calls, method calls and class entries caches */ $cacheManager = new CacheManager(); $cacheManager->setGatherer($callGathererPass); $compilationContext->branchManager = $branchManager; $compilationContext->cacheManager = $cacheManager; $compilationContext->typeInference = $typeInference; $compilationContext->symbolTable = $symbolTable; $oldCodePrinter = $compilationContext->codePrinter; /** * Change the code printer to a single method instance */ $codePrinter = new CodePrinter(); $compilationContext->codePrinter = $codePrinter; /** * Set an empty function cache */ $compilationContext->functionCache = null; /** * Reset try/catch and loop counter */ $compilationContext->insideCycle = 0; $compilationContext->insideTryCatch = 0; $compilationContext->currentTryCatch = 0; if (is_object($parameters)) { /** * Round 1. Create variables in parameters in the symbol table */ $classCastChecks = array(); foreach ($parameters->getParameters() as $parameter) { /** * Change dynamic variables to low level types */ if ($typeInference) { if (isset($parameter['data-type'])) { if ($parameter['data-type'] == 'variable') { $type = $typeInference->getInferedType($parameter['name']); if (is_string($type)) { /* promote polymorphic parameters to low level types */ } } } else { $type = $typeInference->getInferedType($parameter['name']); if (is_string($type)) { /* promote polymorphic parameters to low level types */ } } } $symbolParam = null; if (isset($parameter['data-type'])) { switch ($parameter['data-type']) { case 'object': case 'callable': case 'resource': case 'variable': $symbol = $symbolTable->addVariable($parameter['data-type'], $parameter['name'], $compilationContext); break; default: $symbol = $symbolTable->addVariable($parameter['data-type'], $parameter['name'], $compilationContext); $symbolParam = $symbolTable->addVariable('variable', $parameter['name'] . '_param', $compilationContext); if ($parameter['data-type'] == 'string' || $parameter['data-type'] == 'array') { $symbol->setMustInitNull(true); } break; } } else { $symbol = $symbolTable->addVariable('variable', $parameter['name'], $compilationContext); } /** * Some parameters can be read-only */ if (isset($parameter['const']) && $parameter['const']) { $symbol->setReadOnly(true); if (is_object($symbolParam)) { $symbolParam->setReadOnly(true); } } if (is_object($symbolParam)) { /** * Parameters are marked as 'external' */ $symbolParam->setIsExternal(true); /** * Assuming they're initialized */ $symbolParam->setIsInitialized(true, $compilationContext, $parameter); /** * Initialize auxiliar parameter zvals to null */ $symbolParam->setMustInitNull(true); /** * Increase uses */ $symbolParam->increaseUses(); } else { if (isset($parameter['default'])) { if (isset($parameter['data-type'])) { if ($parameter['data-type'] == 'variable') { $symbol->setMustInitNull(true); } } else { $symbol->setMustInitNull(true); } } } /** * Original node where the variable was declared */ $symbol->setOriginal($parameter); /** * Parameters are marked as 'external' */ $symbol->setIsExternal(true); /** * Assuming they're initialized */ $symbol->setIsInitialized(true, $compilationContext, $parameter); /** * Variables with class/type must be objects across the execution */ if (isset($parameter['cast'])) { $symbol->setDynamicTypes('object'); $symbol->setClassTypes($compilationContext->getFullName($parameter['cast']['value'])); $classCastChecks[] = array($symbol, $parameter); } else { if (isset($parameter['data-type'])) { if ($parameter['data-type'] == 'variable') { $symbol->setDynamicTypes('undefined'); } } else { $symbol->setDynamicTypes('undefined'); } } } } /** * Initialize the properties within create_object, handler code */ if ($this->getName() == 'zephir_init_properties') { $codePrinter->increaseLevel(); $codePrinter->output('{'); $codePrinter->increaseLevel(); $codePrinter->output('zval *this_ptr = NULL;'); $codePrinter->output('ZEPHIR_CREATE_OBJECT(this_ptr, class_type);'); $codePrinter->decreaseLevel(); } /** * Compile the block of statements if any */ if (is_object($this->statements)) { if ($this->hasModifier('static')) { $compilationContext->staticContext = true; } else { $compilationContext->staticContext = false; } /** * Compile the statements block as a 'root' branch */ $this->statements->compile($compilationContext, false, Branch::TYPE_ROOT); } /** * Initialize default values in dynamic variables */ $initVarCode = ""; foreach ($symbolTable->getVariables() as $variable) { /** * Initialize 'dynamic' variables with default values */ if ($variable->getType() == 'variable') { if ($variable->getNumberUses() > 0) { if ($variable->getName() != 'this_ptr' && $variable->getName() != 'return_value' && $variable->getName() != 'return_value_ptr') { $defaultValue = $variable->getDefaultInitValue(); if (is_array($defaultValue)) { $symbolTable->mustGrownStack(true); switch ($defaultValue['type']) { case 'int': case 'uint': case 'long': $initVarCode .= "\t" . 'ZEPHIR_INIT_VAR(' . $variable->getName() . ');' . PHP_EOL; $initVarCode .= "\t" . 'ZVAL_LONG(' . $variable->getName() . ', ' . $defaultValue['value'] . ');' . PHP_EOL; break; case 'char': case 'uchar': if (strlen($defaultValue['value']) > 2) { if (strlen($defaultValue['value']) > 10) { throw new CompilerException("Invalid char literal: '" . substr($defaultValue['value'], 0, 10) . "...'", $defaultValue); } else { throw new CompilerException("Invalid char literal: '" . $defaultValue['value'] . "'", $defaultValue); } } $initVarCode .= "\t" . 'ZEPHIR_INIT_VAR(' . $variable->getName() . ');' . PHP_EOL; $initVarCode .= "\t" . 'ZVAL_LONG(' . $variable->getName() . ', \'' . $defaultValue['value'] . '\');' . PHP_EOL; break; case 'null': $initVarCode .= "\t" . 'ZEPHIR_INIT_VAR(' . $variable->getName() . ');' . PHP_EOL; $initVarCode .= "\t" . 'ZVAL_NULL(' . $variable->getName() . ');' . PHP_EOL; break; case 'double': $initVarCode .= "\t" . 'ZEPHIR_INIT_VAR(' . $variable->getName() . ');' . PHP_EOL; $initVarCode .= "\t" . 'ZVAL_DOUBLE(' . $variable->getName() . ', ' . $defaultValue['value'] . ');' . PHP_EOL; break; case 'string': $initVarCode .= "\t" . 'ZEPHIR_INIT_VAR(' . $variable->getName() . ');' . PHP_EOL; $initVarCode .= "\t" . 'ZVAL_STRING(' . $variable->getName() . ', "' . Utils::addSlashes($defaultValue['value'], true) . '", 1);' . PHP_EOL; break; case 'array': case 'empty-array': $initVarCode .= "\t" . 'ZEPHIR_INIT_VAR(' . $variable->getName() . ');' . PHP_EOL; $initVarCode .= "\t" . 'array_init(' . $variable->getName() . ');' . PHP_EOL; break; default: throw new CompilerException('Invalid default type: ' . $defaultValue['type'] . ' for data type: ' . $variable->getType(), $variable->getOriginal()); } } } } continue; } /** * Initialize 'string' variables with default values */ if ($variable->getType() == 'string') { if ($variable->getNumberUses() > 0) { $defaultValue = $variable->getDefaultInitValue(); if (is_array($defaultValue)) { $symbolTable->mustGrownStack(true); switch ($defaultValue['type']) { case 'string': $initVarCode .= "\t" . 'ZEPHIR_INIT_VAR(' . $variable->getName() . ');' . PHP_EOL; $initVarCode .= "\t" . 'ZVAL_STRING(' . $variable->getName() . ', "' . Utils::addSlashes($defaultValue['value'], true) . '", 1);' . PHP_EOL; break; case 'null': $initVarCode .= "\t" . 'ZEPHIR_INIT_VAR(' . $variable->getName() . ');' . PHP_EOL; $initVarCode .= "\t" . 'ZVAL_EMPTY_STRING(' . $variable->getName() . ');' . PHP_EOL; break; default: throw new CompilerException('Invalid default type: ' . $defaultValue['type'] . ' for data type: ' . $variable->getType(), $variable->getOriginal()); } } } continue; } /** * Initialize 'array' variables with default values */ if ($variable->getType() == 'array') { if ($variable->getNumberUses() > 0) { $defaultValue = $variable->getDefaultInitValue(); if (is_array($defaultValue)) { $symbolTable->mustGrownStack(true); switch ($defaultValue['type']) { case 'null': $initVarCode .= "\t" . 'ZEPHIR_INIT_VAR(' . $variable->getName() . ');' . PHP_EOL; $initVarCode .= "\t" . 'ZVAL_NULL(' . $variable->getName() . ');' . PHP_EOL; break; case 'array': case 'empty-array': $initVarCode .= "\t" . 'ZEPHIR_INIT_VAR(' . $variable->getName() . ');' . PHP_EOL; $initVarCode .= "\t" . 'array_init(' . $variable->getName() . ');' . PHP_EOL; break; default: throw new CompilerException('Invalid default type: ' . $defaultValue['type'] . ' for data type: ' . $variable->getType(), $variable->getOriginal()); } } } } } /** * Fetch parameters from vm-top */ $initCode = ""; $code = ""; if (is_object($parameters)) { /** * Round 2. Fetch the parameters in the method */ $params = array(); $requiredParams = array(); $optionalParams = array(); $numberRequiredParams = 0; $numberOptionalParams = 0; foreach ($parameters->getParameters() as $parameter) { if (isset($parameter['data-type'])) { $dataType = $parameter['data-type']; } else { $dataType = 'variable'; } switch ($dataType) { case 'object': case 'callable': case 'resource': case 'variable': if (!$this->isInternal()) { $params[] = '&' . $parameter['name']; } else { $params[] = $parameter['name']; } break; default: if (!$this->isInternal()) { $params[] = '&' . $parameter['name'] . '_param'; } else { $params[] = $parameter['name'] . '_param'; } break; } if (isset($parameter['default'])) { $optionalParams[] = $parameter; $numberOptionalParams++; } else { $requiredParams[] = $parameter; $numberRequiredParams++; } } /** * Pass the write detector to the method statement block to check if the parameter * variable is modified so as do the proper separation */ $parametersToSeparate = array(); if (is_object($this->statements)) { /** * If local context is not available */ if (!$localContext) { $writeDetector = new WriteDetector(); } foreach ($parameters->getParameters() as $parameter) { if (isset($parameter['data-type'])) { $dataType = $parameter['data-type']; } else { $dataType = 'variable'; } switch ($dataType) { case 'variable': case 'string': case 'array': case 'resource': case 'object': case 'callable': $name = $parameter['name']; if (!$localContext) { if ($writeDetector->detect($name, $this->statements->getStatements())) { $parametersToSeparate[$name] = true; } } else { if ($localContext->getNumberOfMutations($name) > 1) { $parametersToSeparate[$name] = true; } } break; } } } /** * Initialize required parameters */ foreach ($requiredParams as $parameter) { if (isset($parameter['mandatory'])) { $mandatory = $parameter['mandatory']; } else { $mandatory = 0; } if (isset($parameter['data-type'])) { $dataType = $parameter['data-type']; } else { $dataType = 'variable'; } if ($dataType != 'variable') { /** * Assign value from zval to low level type */ if ($mandatory) { $initCode .= $this->checkStrictType($parameter, $compilationContext); } else { $initCode .= $this->assignZvalValue($parameter, $compilationContext); } } switch ($dataType) { case 'variable': case 'resource': case 'object': case 'callable': if (isset($parametersToSeparate[$parameter['name']])) { $symbolTable->mustGrownStack(true); $initCode .= "\t" . "ZEPHIR_SEPARATE_PARAM(" . $parameter['name'] . ");" . PHP_EOL; } break; } } /** * Initialize optional parameters */ foreach ($optionalParams as $parameter) { if (isset($parameter['mandatory'])) { $mandatory = $parameter['mandatory']; } else { $mandatory = 0; } if (isset($parameter['data-type'])) { $dataType = $parameter['data-type']; } else { $dataType = 'variable'; } switch ($dataType) { case 'object': case 'callable': case 'resource': case 'variable': $name = $parameter['name']; break; default: $name = $parameter['name'] . '_param'; break; } /** * Assign the default value according to the variable's type */ $initCode .= "\t" . 'if (!' . $name . ') {' . PHP_EOL; $initCode .= $this->assignDefaultValue($parameter, $compilationContext); if (isset($parametersToSeparate[$name]) || $dataType != 'variable') { $initCode .= "\t" . '} else {' . PHP_EOL; if (isset($parametersToSeparate[$name])) { $initCode .= "\t\t" . "ZEPHIR_SEPARATE_PARAM(" . $name . ");" . PHP_EOL; } else { if ($mandatory) { $initCode .= $this->checkStrictType($parameter, $compilationContext, $mandatory); } else { $initCode .= "\t" . $this->assignZvalValue($parameter, $compilationContext); } } } $initCode .= "\t" . '}' . PHP_EOL; } /** * Fetch the parameters to zval pointers */ $codePrinter->preOutputBlankLine(); if (!$this->isInternal()) { $compilationContext->headersManager->add('kernel/memory'); if ($symbolTable->getMustGrownStack()) { $code .= "\t" . 'zephir_fetch_params(1, ' . $numberRequiredParams . ', ' . $numberOptionalParams . ', ' . join(', ', $params) . ');' . PHP_EOL; } else { $code .= "\t" . 'zephir_fetch_params(0, ' . $numberRequiredParams . ', ' . $numberOptionalParams . ', ' . join(', ', $params) . ');' . PHP_EOL; } } else { foreach ($params as $param) { $code .= "\t" . $param . ' = ' . $param . '_ext;' . PHP_EOL; } } $code .= PHP_EOL; } $code .= $initCode . $initVarCode; $codePrinter->preOutput($code); /** * Fetch used superglobals */ foreach ($symbolTable->getVariables() as $name => $variable) { if ($symbolTable->isSuperGlobal($name)) { $codePrinter->preOutput("\t" . 'zephir_get_global(&' . $name . ', SS("' . $name . '") TSRMLS_CC);'); } } /** * Grow the stack if needed */ if ($symbolTable->getMustGrownStack()) { $compilationContext->headersManager->add('kernel/memory'); $codePrinter->preOutput("\t" . 'ZEPHIR_MM_GROW();'); } /** * Check if there are unused variables */ $usedVariables = array(); $completeName = $compilationContext->classDefinition->getCompleteName(); foreach ($symbolTable->getVariables() as $variable) { if ($variable->getNumberUses() <= 0) { if ($variable->isExternal() == false) { $compilationContext->logger->warning('Variable "' . $variable->getName() . '" declared but not used in ' . $completeName . '::' . $this->getName(), "unused-variable", $variable->getOriginal()); continue; } $compilationContext->logger->warning('Variable "' . $variable->getName() . '" declared but not used in ' . $completeName . '::' . $this->getName(), "unused-variable-external", $variable->getOriginal()); } if ($variable->getName() != 'this_ptr' && $variable->getName() != 'return_value' && $variable->getName() != 'return_value_ptr') { $type = $variable->getType(); if (!isset($usedVariables[$type])) { $usedVariables[$type] = array(); } $usedVariables[$type][] = $variable; } } /** * Check if there are assigned but not used variables * Warn whenever a variable is unused aside from its declaration. */ foreach ($symbolTable->getVariables() as $variable) { if ($variable->isExternal() == true || $variable->isTemporal()) { continue; } if ($variable->getName() == 'this_ptr' || $variable->getName() == 'return_value' || $variable->getName() == 'return_value_ptr' || $variable->getName() == 'ZEPHIR_LAST_CALL_STATUS') { continue; } if (!$variable->isUsed()) { $node = $variable->getLastUsedNode(); if (is_array($node)) { $compilationContext->logger->warning('Variable "' . $variable->getName() . '" assigned but not used in ' . $completeName . '::' . $this->getName(), "unused-variable", $node); } else { $compilationContext->logger->warning('Variable "' . $variable->getName() . '" assigned but not used in ' . $completeName . '::' . $this->getName(), "unused-variable", $variable->getOriginal()); } } } if (count($usedVariables)) { $codePrinter->preOutputBlankLine(); } /** * Generate the variable definition for variables used */ foreach ($usedVariables as $type => $variables) { $pointer = null; switch ($type) { case 'int': $code = 'int '; break; case 'uint': $code = 'unsigned int '; break; case 'char': $code = 'char '; break; case 'uchar': $code = 'unsigned char '; break; case 'long': $code = 'long '; break; case 'ulong': $code = 'unsigned long '; break; case 'bool': $code = 'zend_bool '; break; case 'double': $code = 'double '; break; case 'string': case 'variable': case 'array': case 'null': $pointer = '*'; $code = 'zval '; break; case 'HashTable': $pointer = '*'; $code = 'HashTable '; break; case 'HashPosition': $code = 'HashPosition '; break; case 'zend_class_entry': $pointer = '*'; $code = 'zend_class_entry '; break; case 'zend_function': $pointer = '*'; $code = 'zend_function '; break; case 'zend_object_iterator': $pointer = '*'; $code = 'zend_object_iterator '; break; case 'zend_property_info': $pointer = '*'; $code = 'zend_property_info '; break; case 'zephir_fcall_cache_entry': $pointer = '*'; $code = 'zephir_fcall_cache_entry '; break; case 'static_zephir_fcall_cache_entry': $pointer = '*'; $code = 'zephir_nts_static zephir_fcall_cache_entry '; break; case 'static_zend_class_entry': $pointer = '*'; $code = 'zephir_nts_static zend_class_entry '; break; case 'zephir_ce_guard': $code = 'zephir_nts_static zend_bool '; break; default: throw new CompilerException("Unsupported type in declare: " . $type); } $groupVariables = array(); $defaultValues = array(); /** * @var $variables Variable[] */ foreach ($variables as $variable) { $isComplex = $type == 'variable' || $type == 'string' || $type == 'array' || $type == 'resource' || $type == 'callable' || $type == 'object'; if ($isComplex && $variable->mustInitNull()) { if ($variable->isLocalOnly()) { $groupVariables[] = $variable->getName() . ' = zval_used_for_init'; } else { if ($variable->isDoublePointer()) { $groupVariables[] = $pointer . $pointer . $variable->getName() . ' = NULL'; } else { $groupVariables[] = $pointer . $variable->getName() . ' = NULL'; } } continue; } if ($variable->isLocalOnly()) { $groupVariables[] = $variable->getName(); continue; } if ($variable->isDoublePointer()) { if ($variable->mustInitNull()) { $groupVariables[] = $pointer . $pointer . $variable->getName() . ' = NULL'; } else { $groupVariables[] = $pointer . $pointer . $variable->getName(); } continue; } $defaultValue = $variable->getDefaultInitValue(); if ($defaultValue !== null) { switch ($type) { case 'variable': case 'string': case 'array': case 'resource': case 'callable': case 'object': $groupVariables[] = $pointer . $variable->getName(); break; case 'char': if (strlen($defaultValue) > 4) { if (strlen($defaultValue) > 10) { throw new CompilerException("Invalid char literal: '" . substr($defaultValue, 0, 10) . "...'", $variable->getOriginal()); } else { throw new CompilerException("Invalid char literal: '" . $defaultValue . "'", $variable->getOriginal()); } } /* no break */ /* no break */ default: $groupVariables[] = $pointer . $variable->getName() . ' = ' . $defaultValue; break; } continue; } if ($variable->mustInitNull() && $pointer) { $groupVariables[] = $pointer . $variable->getName() . ' = NULL'; continue; } $groupVariables[] = $pointer . $variable->getName(); } $codePrinter->preOutput("\t" . $code . join(', ', $groupVariables) . ';'); } /** * Finalize the method compilation */ if (is_object($this->statements)) { /** * If the last statement is not a 'return' or 'throw' we need to * restore the memory stack if needed */ $lastType = $this->statements->getLastStatementType(); if ($lastType != 'return' && $lastType != 'throw' && !$this->hasChildReturnStatementType($this->statements->getLastStatement())) { if ($symbolTable->getMustGrownStack()) { $compilationContext->headersManager->add('kernel/memory'); $codePrinter->output("\t" . 'ZEPHIR_MM_RESTORE();'); } /** * If a method has return-type hints we need to ensure the last statement is a 'return' statement */ if ($this->hasReturnTypes()) { throw new CompilerException('Reached end of the method without returning a valid type specified in the return-type hints', $this->expression['return-type']); } } } if ($this->getName() == 'zephir_init_properties') { $codePrinter->increaseLevel(); $codePrinter->output('return Z_OBJVAL_P(this_ptr);'); $codePrinter->decreaseLevel(); $codePrinter->output('}'); $codePrinter->decreaseLevel(); } /** * Remove macros that grow/restore the memory frame stack if it wasn't used */ $code = $this->removeMemoryStackReferences($symbolTable, $codePrinter->getOutput()); /** * Restore the compilation context */ $oldCodePrinter->output($code); $compilationContext->codePrinter = $oldCodePrinter; $compilationContext->branchManager = null; $compilationContext->cacheManager = null; $compilationContext->typeInference = null; $codePrinter->clear(); return null; }
/** * Compiles the method * * @param CompilationContext $compilationContext * @return null * @throws CompilerException */ public function compile(CompilationContext $compilationContext) { /** * Set the method currently being compiled */ $compilationContext->currentMethod = $this; /** * Initialize the method warm-up to null */ $compilationContext->methodWarmUp = null; /** * Assign pre-made compilation passses */ $localContext = $this->localContext; $typeInference = $this->typeInference; $callGathererPass = $this->callGathererPass; /** * Every method has its own symbol table */ $symbolTable = new SymbolTable($compilationContext); if ($localContext) { $symbolTable->setLocalContext($localContext); } /** * Parameters has an additional extra mutation */ $parameters = $this->parameters; if ($localContext) { if (is_object($parameters)) { foreach ($parameters->getParameters() as $parameter) { $localContext->increaseMutations($parameter['name']); } } } /** * Initialization of parameters happens in a fictitious external branch */ $branch = new Branch(); $branch->setType(Branch::TYPE_EXTERNAL); /** * BranchManager helps to create graphs of conditional/loop/root/jump branches */ $branchManager = new BranchManager(); $branchManager->addBranch($branch); /** * Cache Manager manages function calls, method calls and class entries caches */ $cacheManager = new CacheManager(); $cacheManager->setGatherer($callGathererPass); $compilationContext->branchManager = $branchManager; $compilationContext->cacheManager = $cacheManager; $compilationContext->typeInference = $typeInference; $compilationContext->symbolTable = $symbolTable; $oldCodePrinter = $compilationContext->codePrinter; /** * Change the code printer to a single method instance */ $codePrinter = new CodePrinter(); $compilationContext->codePrinter = $codePrinter; /** * Set an empty function cache */ $compilationContext->functionCache = null; /** * Reset try/catch and loop counter */ $compilationContext->insideCycle = 0; $compilationContext->insideTryCatch = 0; $compilationContext->currentTryCatch = 0; if (is_object($parameters)) { /** * Round 1. Create variables in parameters in the symbol table */ $classCastChecks = array(); $substituteVars = array(); foreach ($parameters->getParameters() as $parameter) { /** * Change dynamic variables to low level types */ if ($typeInference) { if (isset($parameter['data-type'])) { if ($parameter['data-type'] == 'variable') { $type = $typeInference->getInferedType($parameter['name']); if (is_string($type)) { /* promote polymorphic parameters to low level types */ } } } else { $type = $typeInference->getInferedType($parameter['name']); if (is_string($type)) { /* promote polymorphic parameters to low level types */ } } } $symbolParam = null; if (isset($parameter['data-type'])) { switch ($parameter['data-type']) { case 'object': case 'callable': case 'resource': case 'variable': $symbol = $symbolTable->addVariable($parameter['data-type'], $parameter['name'], $compilationContext); /* TODO: Move this to the respective backend, which requires refactoring how this works */ if ($compilationContext->backend->isZE3()) { $symbol->setIsDoublePointer(true); $substituteVars[$parameter['name']] = $symbolTable->addVariable('variable', $parameter['name'] . '_sub', $compilationContext); } break; default: $symbol = $symbolTable->addVariable($parameter['data-type'], $parameter['name'], $compilationContext); $symbolParam = $symbolTable->addVariable('variable', $parameter['name'] . '_param', $compilationContext); /* TODO: Move this to the respective backend, which requires refactoring how this works */ if ($compilationContext->backend->isZE3()) { $symbolParam->setIsDoublePointer(true); } if ($parameter['data-type'] == 'string' || $parameter['data-type'] == 'array') { $symbol->setMustInitNull(true); } break; } } else { $symbol = $symbolTable->addVariable('variable', $parameter['name'], $compilationContext); } /* ZE3 only */ if (isset($substituteVars[$parameter['name']])) { $substituteVar = $substituteVars[$parameter['name']]; $substituteVar->increaseUses(); } /** * Some parameters can be read-only */ if (isset($parameter['const']) && $parameter['const']) { $symbol->setReadOnly(true); if (is_object($symbolParam)) { $symbolParam->setReadOnly(true); } } if (is_object($symbolParam)) { /** * Parameters are marked as 'external' */ $symbolParam->setIsExternal(true); /** * Assuming they're initialized */ $symbolParam->setIsInitialized(true, $compilationContext, $parameter); /** * Initialize auxiliar parameter zvals to null */ $symbolParam->setMustInitNull(true); /** * Increase uses */ $symbolParam->increaseUses(); } else { if (isset($parameter['default'])) { if (isset($parameter['data-type'])) { if ($parameter['data-type'] == 'variable') { $symbol->setMustInitNull(true); } } else { $symbol->setMustInitNull(true); } } } /** * Original node where the variable was declared */ $symbol->setOriginal($parameter); /** * Parameters are marked as 'external' */ $symbol->setIsExternal(true); /** * Assuming they're initialized */ $symbol->setIsInitialized(true, $compilationContext, $parameter); /** * Variables with class/type must be objects across the execution */ if (isset($parameter['cast'])) { $symbol->setDynamicTypes('object'); $symbol->setClassTypes($compilationContext->getFullName($parameter['cast']['value'])); $classCastChecks[] = array($symbol, $parameter); } else { if (isset($parameter['data-type'])) { if ($parameter['data-type'] == 'variable') { $symbol->setDynamicTypes('undefined'); } } else { $symbol->setDynamicTypes('undefined'); } } } } $compilationContext->backend->onPreCompile($this, $compilationContext); /** * Compile the block of statements if any */ if (is_object($this->statements)) { if ($this->hasModifier('static')) { $compilationContext->staticContext = true; } else { $compilationContext->staticContext = false; } /** * Compile the statements block as a 'root' branch */ $this->statements->compile($compilationContext, false, Branch::TYPE_ROOT); } /** * Initialize variable default values */ $initVarCode = $compilationContext->backend->initializeVariableDefaults($symbolTable->getVariables(), $compilationContext); /** * Fetch parameters from vm-top */ $initCode = ""; $code = ""; if (is_object($parameters)) { /** * Round 2. Fetch the parameters in the method */ $params = array(); $requiredParams = array(); $optionalParams = array(); $numberRequiredParams = 0; $numberOptionalParams = 0; foreach ($parameters->getParameters() as $parameter) { if (isset($parameter['data-type'])) { $dataType = $parameter['data-type']; } else { $dataType = 'variable'; } switch ($dataType) { case 'object': case 'callable': case 'resource': case 'variable': if (!$this->isInternal()) { $params[] = '&' . $parameter['name']; } else { $params[] = $parameter['name']; } break; default: if (!$this->isInternal()) { $params[] = '&' . $parameter['name'] . '_param'; } else { $params[] = $parameter['name'] . '_param'; } break; } if (isset($parameter['default'])) { $optionalParams[] = $parameter; $numberOptionalParams++; } else { $requiredParams[] = $parameter; $numberRequiredParams++; } } /** * Pass the write detector to the method statement block to check if the parameter * variable is modified so as do the proper separation */ $parametersToSeparate = array(); if (is_object($this->statements)) { /** * If local context is not available */ if (!$localContext) { $writeDetector = new WriteDetector(); } foreach ($parameters->getParameters() as $parameter) { if (isset($parameter['data-type'])) { $dataType = $parameter['data-type']; } else { $dataType = 'variable'; } switch ($dataType) { case 'variable': case 'string': case 'array': case 'resource': case 'object': case 'callable': $name = $parameter['name']; if (!$localContext) { if ($writeDetector->detect($name, $this->statements->getStatements())) { $parametersToSeparate[$name] = true; } } else { if ($localContext->getNumberOfMutations($name) > 1) { $parametersToSeparate[$name] = true; } } break; } } } /** * Initialize required parameters */ foreach ($requiredParams as $parameter) { if (isset($parameter['mandatory'])) { $mandatory = $parameter['mandatory']; } else { $mandatory = 0; } if (isset($parameter['data-type'])) { $dataType = $parameter['data-type']; } else { $dataType = 'variable'; } if ($dataType != 'variable') { /** * Assign value from zval to low level type */ if ($mandatory) { $initCode .= $this->checkStrictType($parameter, $compilationContext); } else { $initCode .= $this->assignZvalValue($parameter, $compilationContext); } } switch ($dataType) { case 'variable': case 'resource': case 'object': case 'callable': if (isset($parametersToSeparate[$parameter['name']])) { $symbolTable->mustGrownStack(true); $initCode .= "\t" . "ZEPHIR_SEPARATE_PARAM(" . $parameter['name'] . ");" . PHP_EOL; } break; } } /** * Initialize optional parameters */ foreach ($optionalParams as $parameter) { if (isset($parameter['mandatory'])) { $mandatory = $parameter['mandatory']; } else { $mandatory = 0; } if (isset($parameter['data-type'])) { $dataType = $parameter['data-type']; } else { $dataType = 'variable'; } switch ($dataType) { case 'object': case 'callable': case 'resource': case 'variable': $name = $parameter['name']; break; default: $name = $parameter['name'] . '_param'; break; } /** * Assign the default value according to the variable's type */ $targetVar = $compilationContext->symbolTable->getVariableForWrite($name, $compilationContext); $initCode .= "\t" . $compilationContext->backend->ifVariableValueUndefined($targetVar, $compilationContext, false, false) . PHP_EOL; if ($compilationContext->backend->isZE3()) { if ($targetVar->isDoublePointer() && isset($substituteVars[$parameter['name']])) { $substituteVar = $substituteVars[$parameter['name']]; $initCode .= "\t\t" . $targetVar->getName() . ' = &' . $substituteVar->getName() . ';' . PHP_EOL; } } $initCode .= $this->assignDefaultValue($parameter, $compilationContext); if (isset($parametersToSeparate[$name]) || $dataType != 'variable') { $initCode .= "\t" . '} else {' . PHP_EOL; if (isset($parametersToSeparate[$name])) { $initCode .= "\t\t" . "ZEPHIR_SEPARATE_PARAM(" . $name . ");" . PHP_EOL; } else { if ($mandatory) { $initCode .= $this->checkStrictType($parameter, $compilationContext, $mandatory); } else { $initCode .= "\t" . $this->assignZvalValue($parameter, $compilationContext); } } } $initCode .= "\t" . '}' . PHP_EOL; } /** * Fetch the parameters to zval pointers */ $codePrinter->preOutputBlankLine(); if (!$this->isInternal()) { $compilationContext->headersManager->add('kernel/memory'); if ($symbolTable->getMustGrownStack()) { $code .= "\t" . 'zephir_fetch_params(1, ' . $numberRequiredParams . ', ' . $numberOptionalParams . ', ' . join(', ', $params) . ');' . PHP_EOL; } else { $code .= "\t" . 'zephir_fetch_params(0, ' . $numberRequiredParams . ', ' . $numberOptionalParams . ', ' . join(', ', $params) . ');' . PHP_EOL; } } else { foreach ($params as $param) { /* TODO: Migrate all this code to codeprinter, get rid of temp code printer */ $tempCodePrinter = new CodePrinter(); $realCodePrinter = $compilationContext->codePrinter; $compilationContext->codePrinter = $tempCodePrinter; $paramVar = $compilationContext->symbolTable->getVariableForRead($param, $compilationContext); $compilationContext->backend->assignZval($paramVar, $param . '_ext', $compilationContext); $code .= "\t" . $tempCodePrinter->getOutput() . PHP_EOL; $compilationContext->codePrinter = $realCodePrinter; } } $code .= PHP_EOL; } $code .= $initCode . $initVarCode; $codePrinter->preOutput($code); /** * Fetch used superglobals */ foreach ($symbolTable->getVariables() as $name => $variable) { if ($symbolTable->isSuperGlobal($name)) { $globalVar = $symbolTable->getVariable($name); $codePrinter->preOutput("\t" . $compilationContext->backend->fetchGlobal($globalVar, $compilationContext, false)); } } /** * Grow the stack if needed */ if ($symbolTable->getMustGrownStack()) { $compilationContext->headersManager->add('kernel/memory'); $codePrinter->preOutput("\t" . 'ZEPHIR_MM_GROW();'); } /** * Check if there are unused variables */ $usedVariables = array(); $classDefinition = $this->getClassDefinition(); if ($classDefinition) { $completeName = $classDefinition->getCompleteName(); } else { $completeName = '[unknown]'; } foreach ($symbolTable->getVariables() as $variable) { if ($variable->getNumberUses() <= 0) { if ($variable->isExternal() == false) { $compilationContext->logger->warning('Variable "' . $variable->getName() . '" declared but not used in ' . $completeName . '::' . $this->getName(), "unused-variable", $variable->getOriginal()); continue; } $compilationContext->logger->warning('Variable "' . $variable->getName() . '" declared but not used in ' . $completeName . '::' . $this->getName(), "unused-variable-external", $variable->getOriginal()); } if ($variable->getName() != 'this_ptr' && $variable->getName() != 'return_value' && $variable->getName() != 'return_value_ptr') { $type = $variable->getType(); if (!isset($usedVariables[$type])) { $usedVariables[$type] = array(); } $usedVariables[$type][] = $variable; } } /** * Check if there are assigned but not used variables * Warn whenever a variable is unused aside from its declaration. */ foreach ($symbolTable->getVariables() as $variable) { if ($variable->isExternal() == true || $variable->isTemporal()) { continue; } if ($variable->getName() == 'this_ptr' || $variable->getName() == 'return_value' || $variable->getName() == 'return_value_ptr' || $variable->getName() == 'ZEPHIR_LAST_CALL_STATUS') { continue; } if (!$variable->isUsed()) { $node = $variable->getLastUsedNode(); if (is_array($node)) { $compilationContext->logger->warning('Variable "' . $variable->getName() . '" assigned but not used in ' . $completeName . '::' . $this->getName(), "unused-variable", $node); } else { $compilationContext->logger->warning('Variable "' . $variable->getName() . '" assigned but not used in ' . $completeName . '::' . $this->getName(), "unused-variable", $variable->getOriginal()); } } } if (count($usedVariables)) { $codePrinter->preOutputBlankLine(); } /** * Generate the variable definition for variables used */ $initCode = "\t" . implode(PHP_EOL . "\t", $compilationContext->backend->declareVariables($this, $usedVariables, $compilationContext)); if ($initCode) { $codePrinter->preOutput($initCode); } /** * Finalize the method compilation */ if (is_object($this->statements)) { /** * If the last statement is not a 'return' or 'throw' we need to * restore the memory stack if needed */ $lastType = $this->statements->getLastStatementType(); if ($lastType != 'return' && $lastType != 'throw' && !$this->hasChildReturnStatementType($this->statements->getLastStatement())) { if ($symbolTable->getMustGrownStack()) { $compilationContext->headersManager->add('kernel/memory'); $codePrinter->output("\t" . 'ZEPHIR_MM_RESTORE();'); } /** * If a method has return-type hints we need to ensure the last statement is a 'return' statement */ if ($this->hasReturnTypes()) { throw new CompilerException('Reached end of the method without returning a valid type specified in the return-type hints', $this->expression['return-type']); } } } $compilationContext->backend->onPostCompile($this, $compilationContext); /** * Remove macros that grow/restore the memory frame stack if it wasn't used */ $code = $this->removeMemoryStackReferences($symbolTable, $codePrinter->getOutput()); /** * Restore the compilation context */ $oldCodePrinter->output($code); $compilationContext->codePrinter = $oldCodePrinter; $compilationContext->branchManager = null; $compilationContext->cacheManager = null; $compilationContext->typeInference = null; $codePrinter->clear(); return null; }
/** * Compiles a class/interface * * @param CompilationContext $compilationContext * * @throws CompilerException * @throws Exception */ public function compile(CompilationContext $compilationContext) { /** * Sets the current object as global class definition */ $compilationContext->classDefinition = $this; /** * Get the global codePrinter */ $codePrinter = $compilationContext->codePrinter; /** * The ZEPHIR_INIT_CLASS defines properties and constants exported by the class */ $initClassName = $this->getCNamespace() . '_' . $this->getName(); $codePrinter->output('ZEPHIR_INIT_CLASS(' . $initClassName . ') {'); $codePrinter->outputBlankLine(); $codePrinter->increaseLevel(); /** * Method entry */ $methods =& $this->methods; $initMethod = $this->getLocalOrParentInitMethod(); if (count($methods) || $initMethod) { $methodEntry = strtolower($this->getCNamespace()) . '_' . strtolower($this->getName()) . '_method_entry'; } else { $methodEntry = 'NULL'; } foreach ($methods as $method) { $method->setupOptimized($compilationContext); } $namespace = str_replace('\\', '_', $compilationContext->config->get('namespace')); $flags = '0'; if ($this->isAbstract()) { $flags = 'ZEND_ACC_EXPLICIT_ABSTRACT_CLASS'; } if ($this->isFinal()) { if ($flags == '0') { $flags = 'ZEND_ACC_FINAL_CLASS'; } else { $flags .= '|ZEND_ACC_FINAL_CLASS'; } } /** * Register the class with extends + interfaces */ $classExtendsDefinition = null; if ($this->extendsClass) { $classExtendsDefinition = $this->extendsClassDefinition; if ($classExtendsDefinition instanceof ClassDefinition && !$classExtendsDefinition->isBundled()) { $classEntry = $classExtendsDefinition->getClassEntry($compilationContext); } else { $classEntry = $this->getClassEntryByClassName($classExtendsDefinition->getName(), $compilationContext); } if ($this->getType() == 'class') { $codePrinter->output('ZEPHIR_REGISTER_CLASS_EX(' . $this->getNCNamespace() . ', ' . $this->getName() . ', ' . $namespace . ', ' . strtolower($this->getSCName($namespace)) . ', ' . $classEntry . ', ' . $methodEntry . ', ' . $flags . ');'); $codePrinter->outputBlankLine(); } else { $codePrinter->output('ZEPHIR_REGISTER_INTERFACE_EX(' . $this->getNCNamespace() . ', ' . $this->getName() . ', ' . $namespace . ', ' . strtolower($this->getSCName($namespace)) . ', ' . $classEntry . ', ' . $methodEntry . ');'); $codePrinter->outputBlankLine(); } } else { if ($this->getType() == 'class') { $codePrinter->output('ZEPHIR_REGISTER_CLASS(' . $this->getNCNamespace() . ', ' . $this->getName() . ', ' . $namespace . ', ' . strtolower($this->getSCName($namespace)) . ', ' . $methodEntry . ', ' . $flags . ');'); } else { $codePrinter->output('ZEPHIR_REGISTER_INTERFACE(' . $this->getNCNamespace() . ', ' . $this->getName() . ', ' . $namespace . ', ' . strtolower($this->getSCName($namespace)) . ', ' . $methodEntry . ');'); } $codePrinter->outputBlankLine(); } /** * Compile properties * @var $property ClassProperty */ foreach ($this->getProperties() as $property) { $docBlock = $property->getDocBlock(); if ($docBlock) { $codePrinter->outputDocBlock($docBlock, true); } $property->compile($compilationContext); $codePrinter->outputBlankLine(); } $initMethod = $this->getInitMethod(); if ($initMethod) { $codePrinter->output($namespace . '_' . strtolower($this->getSCName($namespace)) . '_ce->create_object = ' . $initMethod->getName() . ';'); } /** * Compile constants * @var $constant ClassConstant */ foreach ($this->getConstants() as $constant) { $docBlock = $constant->getDocBlock(); if ($docBlock) { $codePrinter->outputDocBlock($docBlock, true); } $constant->compile($compilationContext); $codePrinter->outputBlankLine(); } /** * Implemented interfaces */ $interfaces = $this->interfaces; $compiler = $compilationContext->compiler; if (is_array($interfaces)) { $codePrinter->outputBlankLine(true); foreach ($interfaces as $interface) { /** * Try to find the interface */ $classEntry = false; if ($compiler->isInterface($interface)) { $classInterfaceDefinition = $compiler->getClassDefinition($interface); $classEntry = $classInterfaceDefinition->getClassEntry($compilationContext); } else { if ($compiler->isBundledInterface($interface)) { $classInterfaceDefinition = $compiler->getInternalClassDefinition($interface); $classEntry = $this->getClassEntryByClassName($classInterfaceDefinition->getName(), $compilationContext); } } if (!$classEntry) { if ($compiler->isClass($interface)) { throw new CompilerException("Cannot locate interface " . $interface . " when implementing interfaces on " . $this->getCompleteName() . '. ' . $interface . ' is currently a class', $this->originalNode); } else { throw new CompilerException("Cannot locate interface " . $interface . " when implementing interfaces on " . $this->getCompleteName(), $this->originalNode); } } /** * We don't check if abstract classes implement the methods in their interfaces */ if (!$this->isAbstract() && !$this->isInterface()) { $this->checkInterfaceImplements($this, $classInterfaceDefinition); } $codePrinter->output('zend_class_implements(' . $this->getClassEntry() . ' TSRMLS_CC, 1, ' . $classEntry . ');'); } } if (!$this->isAbstract() && !$this->isInterface()) { /** * Interfaces in extended classes may have */ if ($classExtendsDefinition) { if ($classExtendsDefinition instanceof ClassDefinition && !$classExtendsDefinition->isBundled()) { $interfaces = $classExtendsDefinition->getImplementedInterfaces(); if (is_array($interfaces)) { foreach ($interfaces as $interface) { $classInterfaceDefinition = null; if ($compiler->isInterface($interface)) { $classInterfaceDefinition = $compiler->getClassDefinition($interface); } else { if ($compiler->isBundledInterface($interface)) { $classInterfaceDefinition = $compiler->getInternalClassDefinition($interface); } } if ($classInterfaceDefinition) { $this->checkInterfaceImplements($this, $classInterfaceDefinition); } } } } } } $codePrinter->output('return SUCCESS;'); $codePrinter->outputBlankLine(); $codePrinter->decreaseLevel(); $codePrinter->output('}'); $codePrinter->outputBlankLine(); /** * Compile methods */ foreach ($methods as $method) { $docBlock = $method->getDocBlock(); if ($docBlock) { $codePrinter->outputDocBlock($docBlock); } if ($this->getType() == 'class') { if (!$method->isInternal()) { $codePrinter->output('PHP_METHOD(' . $this->getCNamespace() . '_' . $this->getName() . ', ' . $method->getName() . ') {'); } else { $codePrinter->output($compilationContext->backend->getInternalSignature($method, $compilationContext) . ' {'); } $codePrinter->outputBlankLine(); if (!$method->isAbstract()) { $method->compile($compilationContext); } $codePrinter->output('}'); $codePrinter->outputBlankLine(); } else { $codePrinter->output('ZEPHIR_DOC_METHOD(' . $this->getCNamespace() . '_' . $this->getName() . ', ' . $method->getName() . ');'); $codePrinter->outputBlankLine(); } } /** * Check whether classes must be exported */ $exportClasses = $compilationContext->config->get('export-classes', 'extra'); if ($exportClasses) { $exportAPI = 'extern ZEPHIR_API'; } else { $exportAPI = 'extern'; } /** * Create a code printer for the header file */ $codePrinter = new CodePrinter(); $codePrinter->outputBlankLine(); $codePrinter->output($exportAPI . ' zend_class_entry *' . $this->getClassEntry() . ';'); $codePrinter->outputBlankLine(); $codePrinter->output('ZEPHIR_INIT_CLASS(' . $this->getCNamespace() . '_' . $this->getName() . ');'); $codePrinter->outputBlankLine(); if ($this->getType() == 'class') { if (count($methods)) { foreach ($methods as $method) { if (!$method->isInternal()) { $codePrinter->output('PHP_METHOD(' . $this->getCNamespace() . '_' . $this->getName() . ', ' . $method->getName() . ');'); } else { $internalSignature = $compilationContext->backend->getInternalSignature($method, $compilationContext); $codePrinter->output($internalSignature . ';'); } } $codePrinter->outputBlankLine(); } } /** * Create argument info */ foreach ($methods as $method) { $parameters = $method->getParameters(); if (count($parameters)) { $codePrinter->output('ZEND_BEGIN_ARG_INFO_EX(arginfo_' . strtolower($this->getCNamespace() . '_' . $this->getName() . '_' . $method->getName()) . ', 0, 0, ' . $method->getNumberOfRequiredParameters() . ')'); foreach ($parameters->getParameters() as $parameter) { switch ($parameter['data-type']) { case 'array': $codePrinter->output("\t" . 'ZEND_ARG_ARRAY_INFO(0, ' . $parameter['name'] . ', ' . (isset($parameter['default']) ? 1 : 0) . ')'); break; case 'variable': if (isset($parameter['cast'])) { switch ($parameter['cast']['type']) { case 'variable': $value = $parameter['cast']['value']; $codePrinter->output("\t" . 'ZEND_ARG_OBJ_INFO(0, ' . $parameter['name'] . ', ' . Utils::escapeClassName($compilationContext->getFullName($value)) . ', ' . (isset($parameter['default']) ? 1 : 0) . ')'); break; default: throw new Exception('Unexpected exception'); } } else { $codePrinter->output("\t" . 'ZEND_ARG_INFO(0, ' . $parameter['name'] . ')'); } break; default: $codePrinter->output("\t" . 'ZEND_ARG_INFO(0, ' . $parameter['name'] . ')'); break; } } $codePrinter->output('ZEND_END_ARG_INFO()'); $codePrinter->outputBlankLine(); } } if (count($methods)) { $codePrinter->output('ZEPHIR_INIT_FUNCS(' . strtolower($this->getCNamespace() . '_' . $this->getName()) . '_method_entry) {'); foreach ($methods as $method) { $parameters = $method->getParameters(); if ($this->getType() == 'class') { if (!$method->isInternal()) { if (count($parameters)) { $codePrinter->output("\t" . 'PHP_ME(' . $this->getCNamespace() . '_' . $this->getName() . ', ' . $method->getName() . ', arginfo_' . strtolower($this->getCNamespace() . '_' . $this->getName() . '_' . $method->getName()) . ', ' . $method->getModifiers() . ')'); } else { $codePrinter->output("\t" . 'PHP_ME(' . $this->getCNamespace() . '_' . $this->getName() . ', ' . $method->getName() . ', NULL, ' . $method->getModifiers() . ')'); } } } else { if ($method->isStatic()) { if (count($parameters)) { $codePrinter->output("\t" . 'ZEND_FENTRY(' . $method->getName() . ', NULL, arginfo_' . strtolower($this->getCNamespace() . '_' . $this->getName() . '_' . $method->getName()) . ', ZEND_ACC_STATIC|ZEND_ACC_ABSTRACT|ZEND_ACC_PUBLIC)'); } else { $codePrinter->output("\t" . 'ZEND_FENTRY(' . $method->getName() . ', NULL, NULL, ZEND_ACC_STATIC|ZEND_ACC_ABSTRACT|ZEND_ACC_PUBLIC)'); } } else { if (count($parameters)) { $codePrinter->output("\t" . 'PHP_ABSTRACT_ME(' . $this->getCNamespace() . '_' . $this->getName() . ', ' . $method->getName() . ', arginfo_' . strtolower($this->getCNamespace() . '_' . $this->getName() . '_' . $method->getName()) . ')'); } else { $codePrinter->output("\t" . 'PHP_ABSTRACT_ME(' . $this->getCNamespace() . '_' . $this->getName() . ', ' . $method->getName() . ', NULL)'); } } } } $codePrinter->output("\t" . 'PHP_FE_END'); $codePrinter->output('};'); } $compilationContext->headerPrinter = $codePrinter; }
/** * Compiles the file * * @param Compiler $compiler * @param StringsManager $stringsManager */ public function compile(Compiler $compiler, StringsManager $stringsManager) { /** * Compilation context stores common objects required by compilation entities */ $compilationContext = new CompilationContext(); /** * Set global compiler in the compilation context */ $compilationContext->compiler = $compiler; /** * Set global config in the compilation context */ $compilationContext->config = $this->config; /** * Set global logger in the compilation context */ $compilationContext->logger = $this->logger; /** * Set global strings manager */ $compilationContext->stringsManager = $stringsManager; $compilationContext->backend = $compiler->backend; /** * Headers manager */ $headersManager = new HeadersManager(); $compilationContext->headersManager = $headersManager; /** * Main code-printer for the file */ $codePrinter = new CodePrinter(); $compilationContext->codePrinter = $codePrinter; /** * Alias manager */ $compilationContext->aliasManager = new AliasManager(); $codePrinter->outputBlankLine(); $classDefinition = $this->classDefinition; $this->compileClass($classDefinition, $compilationContext); $completeName = $classDefinition->getCompleteName(); $path = str_replace('\\', DIRECTORY_SEPARATOR, strtolower($completeName)); $filePath = 'ext/' . $path . '.zep.c'; $filePathHeader = 'ext/' . $path . '.zep.h'; if (strpos($path, DIRECTORY_SEPARATOR)) { $dirname = dirname($filePath); if (!is_dir($dirname)) { mkdir($dirname, 0755, true); } } if ($codePrinter) { /** * If the file does not exists we create it for the first time */ if (!file_exists($filePath)) { file_put_contents($filePath, $codePrinter->getOutput()); if ($compilationContext->headerPrinter) { file_put_contents($filePathHeader, $compilationContext->headerPrinter->getOutput()); } } else { /** * Use md5 hash to avoid rewrite the file again and again when it hasn't changed * thus avoiding unnecessary recompilations */ $output = $codePrinter->getOutput(); $hash = hash_file('md5', $filePath); if (md5($output) != $hash) { file_put_contents($filePath, $output); } if ($compilationContext->headerPrinter) { $output = $compilationContext->headerPrinter->getOutput(); $hash = hash_file('md5', $filePathHeader); if (md5($output) != $hash) { file_put_contents($filePathHeader, $output); } } } } /** * Add to file compiled */ $this->compiledFile = $path . '.c'; }
public function initializeVariableDefaults($variables, CompilationContext $compilationContext) { $codePrinter = new CodePrinter(); $codePrinter->increaseLevel(); $oldCodePrinter = $compilationContext->codePrinter; $compilationContext->codePrinter = $codePrinter; /* Initialize default values in dynamic variables */ foreach ($variables as $variable) { /** * Initialize 'dynamic' variables with default values */ if ($variable->getType() == 'variable') { if ($variable->getNumberUses() > 0) { if ($variable->getName() != 'this_ptr' && $variable->getName() != 'return_value' && $variable->getName() != 'return_value_ptr') { $defaultValue = $variable->getDefaultInitValue(); if (is_array($defaultValue)) { $symbolTable->mustGrownStack(true); $compilationContext->backend->initVar($variable, $compilationContext); switch ($defaultValue['type']) { case 'int': case 'uint': case 'long': $compilationContext->backend->assignLong($variable, $defaultValue['value'], $compilationContext); break; case 'bool': $compilationContext->backend->assignBool($variable, $defaultValue['value'], $compilationContext); break; case 'char': case 'uchar': if (strlen($defaultValue['value']) > 2) { if (strlen($defaultValue['value']) > 10) { throw new CompilerException("Invalid char literal: '" . substr($defaultValue['value'], 0, 10) . "...'", $defaultValue); } else { throw new CompilerException("Invalid char literal: '" . $defaultValue['value'] . "'", $defaultValue); } } $compilationContext->backend->assignLong($variable, '\'' . $defaultValue['value'] . '\'', $compilationContext); break; case 'null': $compilationContext->backend->assignNull($variable, $compilationContext); break; case 'double': $compilationContext->backend->assignDouble($variable, $defaultValue['value'], $compilationContext); break; case 'string': $compilationContext->backend->assignString($variable, Utils::addSlashes($defaultValue['value'], true), $compilationContext); break; case 'array': case 'empty-array': $compilationContext->backend->initArray($variable, $compilationContext, null); break; default: throw new CompilerException('Invalid default type: ' . $defaultValue['type'] . ' for data type: ' . $variable->getType(), $variable->getOriginal()); } } } } continue; } /** * Initialize 'string' variables with default values */ if ($variable->getType() == 'string') { if ($variable->getNumberUses() > 0) { $defaultValue = $variable->getDefaultInitValue(); if (is_array($defaultValue)) { $symbolTable->mustGrownStack(true); $compilationContext->backend->initVar($variable, $compilationContext); switch ($defaultValue['type']) { case 'string': $compilationContext->backend->assignString($variable, Utils::addSlashes($defaultValue['value'], true), $compilationContext); break; case 'null': $compilationContext->backend->assignString($variable, null, $compilationContext); break; default: throw new CompilerException('Invalid default type: ' . $defaultValue['type'] . ' for data type: ' . $variable->getType(), $variable->getOriginal()); } } } continue; } /** * Initialize 'array' variables with default values */ if ($variable->getType() == 'array') { if ($variable->getNumberUses() > 0) { $defaultValue = $variable->getDefaultInitValue(); if (is_array($defaultValue)) { $symbolTable->mustGrownStack(true); $compilationContext->backend->initVar($variable, $compilationContext); switch ($defaultValue['type']) { case 'null': $compilationContext->backend->assignNull($variable, $compilationContext); break; case 'array': case 'empty-array': $compilationContext->backend->initArray($variable, $compilationContext, null); break; default: throw new CompilerException('Invalid default type: ' . $defaultValue['type'] . ' for data type: ' . $variable->getType(), $variable->getOriginal()); } } } } } $compilationContext->codePrinter = $oldCodePrinter; return $codePrinter->getOutput(); }
public function generateFunctionInformation() { $headerPrinter = new CodePrinter(); $entryPrinter = new CodePrinter(); /** * Create argument info */ foreach ($this->functionDefinitions as $func) { $funcName = $func->getInternalName(); $argInfoName = 'arginfo_' . strtolower($funcName); $headerPrinter->output('PHP_FUNCTION(' . $funcName . ');'); $parameters = $func->getParameters(); if (count($parameters)) { $headerPrinter->output('ZEND_BEGIN_ARG_INFO_EX(' . $argInfoName . ', 0, 0, ' . $func->getNumberOfRequiredParameters() . ')'); foreach ($parameters->getParameters() as $parameter) { switch ($parameter['data-type']) { case 'array': $headerPrinter->output("\t" . 'ZEND_ARG_ARRAY_INFO(0, ' . $parameter['name'] . ', ' . (isset($parameter['default']) ? 1 : 0) . ')'); break; case 'variable': if (isset($parameter['cast'])) { switch ($parameter['cast']['type']) { case 'variable': $value = $parameter['cast']['value']; $headerPrinter->output("\t" . 'ZEND_ARG_OBJ_INFO(0, ' . $parameter['name'] . ', ' . Utils::escapeClassName($compilationContext->getFullName($value)) . ', ' . (isset($parameter['default']) ? 1 : 0) . ')'); break; default: throw new Exception('Unexpected exception'); } } else { $headerPrinter->output("\t" . 'ZEND_ARG_INFO(0, ' . $parameter['name'] . ')'); } break; default: $headerPrinter->output("\t" . 'ZEND_ARG_INFO(0, ' . $parameter['name'] . ')'); break; } } $headerPrinter->output('ZEND_END_ARG_INFO()'); $headerPrinter->outputBlankLine(); } /** Generate FE's */ $paramData = count($parameters) ? $argInfoName : 'NULL'; if ($func->isGlobal()) { $entryPrinter->output('ZEND_NAMED_FE(' . $func->getName() . ', ZEND_FN(' . $funcName . '), ' . $paramData . ')'); } else { $entryPrinter->output('ZEND_NS_NAMED_FE("' . str_replace('\\', '\\\\', $func->getNamespace()) . '", ' . $func->getName() . ', ZEND_FN(' . $funcName . '), ' . $paramData . ')'); } } $entryPrinter->output('ZEND_FE_END'); return array($headerPrinter->getOutput(), $entryPrinter->getOutput()); }
public function genFcallCode() { $codePrinter = new CodePrinter(); $codePrinter->output('#ifndef ZEPHIR_KERNEL_FCALL_INTERNAL_H'); $codePrinter->output('#define ZEPHIR_KERNEL_FCALL_INTERNAL_H'); $codePrinter->increaseLevel(); ksort($this->requiredMacros); foreach ($this->requiredMacros as $name => $info) { list($scope, $mode, $paramCount) = $info; $paramsStr = ''; $retParam = ''; $retValueUsed = '0'; $params = array(); $zvals = array(); $initStatements = array(); $postStatements = array(); for ($i = 0; $i < $paramCount; ++$i) { $params[] = 'p' . $i; } if ($paramCount) { $paramsStr = ', ' . implode(', ', $params); } if ($mode == 'CALL_INTERNAL_METHOD_P') { $retValueUsed = '1'; $retParam = 'return_value_ptr'; $initStatements[] = 'ZEPHIR_INIT_NVAR(*(return_value_ptr)); \\'; } $objParam = $scope ? 'scope_ce, ' : 'object, '; $macroName = $name . '(' . ($retParam ? $retParam . ', ' : '') . $objParam . 'method' . $paramsStr . ')'; $codePrinter->output('#define ' . $macroName . ' \\'); if (!$retParam) { $retParam = 'return_value'; } $codePrinter->increaseLevel(); $codePrinter->output('do { \\'); $codePrinter->increaseLevel(); if ($mode == 'CALL_INTERNAL_METHOD_NORETURN_P') { $codePrinter->output('zval *rv = NULL; \\'); $codePrinter->output('zval **rvp = &rv; \\'); $codePrinter->output('ALLOC_INIT_ZVAL(rv); \\'); $retParam = 'rvp'; } $codePrinter->output('ZEPHIR_BACKUP_SCOPE() \\'); $codePrinter->output('ZEPHIR_BACKUP_THIS_PTR() \\'); if (!$scope) { $codePrinter->output('ZEPHIR_SET_THIS(object); \\'); $codePrinter->output('ZEPHIR_SET_SCOPE((Z_TYPE_P(object) == IS_OBJECT ? Z_OBJCE_P(object) : NULL), (Z_TYPE_P(object) == IS_OBJECT ? Z_OBJCE_P(object) : NULL)); \\'); } else { $codePrinter->output('ZEPHIR_SET_THIS(NULL); \\'); $codePrinter->output('ZEPHIR_SET_SCOPE(scope_ce, scope_ce); \\'); } /* Create new zval's for parameters */ for ($i = 0; $i < $paramCount; ++$i) { //$zv = '_' . $params[$i]; //$zvals[] = $zv; //$initStatements[] = 'ALLOC_ZVAL(' . $zv . '); \\'; //$initStatements[] = 'INIT_PZVAL_COPY(' . $zv . ', ' . $params[$i] . '); \\'; //$postStatements[] = 'zval_ptr_dtor(&' . $zv . '); \\'; $zv = $params[$i]; $zvals[] = $zv; $initStatements[] = 'Z_ADDREF_P(' . $zv . '); \\'; $postStatements[] = 'Z_DELREF_P(' . $zv . '); \\'; } if ($i) { //$codePrinter->output('zval *' . implode(', *', $zvals) . '; \\'); } foreach ($initStatements as $statement) { $codePrinter->output($statement); } $zvalStr = $i ? ', ' . implode(', ', $zvals) : ''; $retExpr = ''; if ($retParam) { if ($retParam == 'return_value') { $retExpr = ', return_value, return_value_ptr'; } else { $retExpr = ', *' . $retParam . ', ' . $retParam; } } $codePrinter->output('method(0' . $retExpr . ', ' . ($scope ? 'NULL, ' : $objParam) . $retValueUsed . $zvalStr . ' TSRMLS_CC); \\'); if ($mode == 'CALL_INTERNAL_METHOD_NORETURN_P') { $postStatements[] = 'zval_ptr_dtor(rvp); \\'; } foreach ($postStatements as $statement) { $codePrinter->output($statement); } $codePrinter->output('ZEPHIR_LAST_CALL_STATUS = EG(exception) ? FAILURE : SUCCESS; \\'); $codePrinter->output('ZEPHIR_RESTORE_THIS_PTR(); \\'); $codePrinter->output('ZEPHIR_RESTORE_SCOPE(); \\'); $codePrinter->decreaseLevel(); $codePrinter->output('} while (0)'); $codePrinter->decreaseLevel(); $codePrinter->output(''); } $codePrinter->decreaseLevel(); $codePrinter->output("#endif"); Utils::checkAndWriteIfNeeded($codePrinter->getOutput(), 'ext/kernel/fcall_internal.h'); }
/** * Compiles the method * * @param CompilationContext $compilationContext * @return null * @throws CompilerException */ public function compile(CompilationContext $compilationContext) { /** * Set the method currently being compiled */ $compilationContext->currentMethod = $this; if (is_object($this->_statements)) { /** * This pass checks for zval variables than can be potentially * used without allocating memory and track it * these variables are stored in the stack */ if ($compilationContext->config->get('local-context-pass', 'optimizations')) { $localContext = new LocalContextPass(); $localContext->pass($this->_statements); } else { $localContext = null; } /** * This pass tries to infer types for dynamic variables * replacing them by low level variables */ if ($compilationContext->config->get('static-type-inference', 'optimizations')) { $typeInference = new StaticTypeInference(); $typeInference->pass($this->_statements); if ($compilationContext->config->get('static-type-inference-second-pass', 'optimizations')) { $typeInference->reduce(); $typeInference->pass($this->_statements); } } else { $typeInference = null; } /** * This pass counts how many times a specific */ if ($compilationContext->config->get('call-gatherer-pass', 'optimizations')) { $callGathererPass = new CallGathererPass($compilationContext); $callGathererPass->pass($this->_statements); } else { $callGathererPass = null; } } else { $localContext = null; $typeInference = null; $callGathererPass = null; } /** * Every method has its own symbol table */ $symbolTable = new SymbolTable($compilationContext); if ($localContext) { $symbolTable->setLocalContext($localContext); } /** * Parameters has an additional extra mutation */ $parameters = $this->_parameters; if ($localContext) { if (is_object($parameters)) { foreach ($parameters->getParameters() as $parameter) { $localContext->increaseMutations($parameter['name']); } } } /** * Initialization of parameters happens in a fictitious external branch */ $branch = new Branch(); $branch->setType(Branch::TYPE_EXTERNAL); /** * BranchManager helps to create graphs of conditional/loop/root/jump branches */ $branchManager = new BranchManager(); $branchManager->addBranch($branch); /** * Cache Manager manages both function and method call caches */ $cacheManager = new CacheManager(); $cacheManager->setGatherer($callGathererPass); $compilationContext->branchManager = $branchManager; $compilationContext->cacheManager = $cacheManager; $compilationContext->typeInference = $typeInference; $compilationContext->symbolTable = $symbolTable; $oldCodePrinter = $compilationContext->codePrinter; /** * Change the code printer to a single method instance */ $codePrinter = new CodePrinter(); $compilationContext->codePrinter = $codePrinter; /** * Set an empty function cache */ $compilationContext->functionCache = null; /** * Reset try/catch and loop counter */ $compilationContext->insideCycle = 0; $compilationContext->insideTryCatch = 0; if (is_object($parameters)) { /** * Round 1. Create variables in parameters in the symbol table */ $classCastChecks = array(); foreach ($parameters->getParameters() as $parameter) { /** * Change dynamic variables to low level types */ if ($typeInference) { if (isset($parameter['data-type'])) { if ($parameter['data-type'] == 'variable') { $type = $typeInference->getInferedType($parameter['name']); if (is_string($type)) { /* promote polymorphic parameters to low level types */ } } } else { $type = $typeInference->getInferedType($parameter['name']); if (is_string($type)) { /* promote polymorphic parameters to low level types */ } } } $symbolParam = null; if (isset($parameter['data-type'])) { switch ($parameter['data-type']) { case 'object': case 'callable': case 'resource': case 'variable': $symbol = $symbolTable->addVariable($parameter['data-type'], $parameter['name'], $compilationContext); break; default: $symbol = $symbolTable->addVariable($parameter['data-type'], $parameter['name'], $compilationContext); $symbolParam = $symbolTable->addVariable('variable', $parameter['name'] . '_param', $compilationContext); if ($parameter['data-type'] == 'string' || $parameter['data-type'] == 'array') { $symbol->setMustInitNull(true); } break; } } else { $symbol = $symbolTable->addVariable('variable', $parameter['name'], $compilationContext); } /** * Some parameters can be read-only */ if (isset($parameter['const']) && $parameter['const']) { $symbol->setReadOnly(true); if (is_object($symbolParam)) { $symbolParam->setReadOnly(true); } } if (is_object($symbolParam)) { /** * Parameters are marked as 'external' */ $symbolParam->setIsExternal(true); /** * Assuming they're initialized */ $symbolParam->setIsInitialized(true, $compilationContext, $parameter); /** * Initialize auxiliar parameter zvals to null */ $symbolParam->setMustInitNull(true); /** * Increase uses */ $symbolParam->increaseUses(); } else { if (isset($parameter['default'])) { if (isset($parameter['data-type'])) { if ($parameter['data-type'] == 'variable') { $symbol->setMustInitNull(true); } } else { $symbol->setMustInitNull(true); } } } /** * Original node where the variable was declared */ $symbol->setOriginal($parameter); /** * Parameters are marked as 'external' */ $symbol->setIsExternal(true); /** * Assuming they're initialized */ $symbol->setIsInitialized(true, $compilationContext, $parameter); /** * Variables with class/type must be objects across the execution */ if (isset($parameter['cast'])) { $symbol->setDynamicTypes('object'); $symbol->setClassTypes($compilationContext->getFullName($parameter['cast']['value'])); $classCastChecks[] = array($symbol, $parameter); } else { if (isset($parameter['data-type'])) { if ($parameter['data-type'] == 'variable') { $symbol->setDynamicTypes('undefined'); } } else { $symbol->setDynamicTypes('undefined'); } } } $compilationContext->codePrinter->increaseLevel(); /** * Checks that a class-hinted variable meets its declaration */ foreach ($classCastChecks as $classCastCheck) { foreach ($classCastCheck[0]->getClassTypes() as $className) { /** * If the parameter is nullable check it must pass the 'instanceof' validation */ if (!isset($classCastCheck[1]['default'])) { $evalExpr = new UnaryOperatorBuilder('not', new BinaryOperatorBuilder('instanceof', new VariableBuilder($classCastCheck[0]->getName()), new VariableBuilder('\\' . $className))); } else { $evalExpr = new BinaryOperatorBuilder('and', new BinaryOperatorBuilder('not-equals', new TypeOfOperatorBuilder(new VariableBuilder($classCastCheck[0]->getName())), new LiteralBuilder("string", "null")), new UnaryOperatorBuilder('not', new BinaryOperatorBuilder('instanceof', new VariableBuilder($classCastCheck[0]->getName()), new VariableBuilder('\\' . $className)))); } $ifCheck = new IfStatementBuilder($evalExpr, new StatementsBlockBuilder(array(new ThrowStatementBuilder(new NewInstanceOperatorBuilder('\\InvalidArgumentException', array(new ParameterBuilder(new LiteralBuilder("string", "Parameter '" . $classCastCheck[0]->getName() . "' must be an instance of '" . Utils::escapeClassName($className) . "'")))))))); $ifStatement = new IfStatement($ifCheck->get()); $ifStatement->compile($compilationContext); } } $compilationContext->codePrinter->decreaseLevel(); } /** * Compile the block of statements if any */ if (is_object($this->_statements)) { if ($this->hasModifier('static')) { $compilationContext->staticContext = true; } else { $compilationContext->staticContext = false; } /** * Compile the statements block as a 'root' branch */ $this->_statements->compile($compilationContext, false, Branch::TYPE_ROOT); } /** * Initialize default values in dynamic variables */ $initVarCode = ""; foreach ($symbolTable->getVariables() as $variable) { /** * Initialize 'dynamic' variables with default values */ if ($variable->getType() == 'variable') { if ($variable->getNumberUses() > 0) { if ($variable->getName() != 'this_ptr' && $variable->getName() != 'return_value' && $variable->getName() != 'return_value_ptr') { $defaultValue = $variable->getDefaultInitValue(); if (is_array($defaultValue)) { $symbolTable->mustGrownStack(true); switch ($defaultValue['type']) { case 'int': case 'uint': case 'long': case 'char': case 'uchar': $initVarCode .= "\t" . 'ZEPHIR_INIT_VAR(' . $variable->getName() . ');' . PHP_EOL; $initVarCode .= "\t" . 'ZVAL_LONG(' . $variable->getName() . ', ' . $defaultValue['value'] . ');' . PHP_EOL; break; case 'null': $initVarCode .= "\t" . 'ZEPHIR_INIT_VAR(' . $variable->getName() . ');' . PHP_EOL; $initVarCode .= "\t" . 'ZVAL_NULL(' . $variable->getName() . ');' . PHP_EOL; break; case 'double': $initVarCode .= "\t" . 'ZEPHIR_INIT_VAR(' . $variable->getName() . ');' . PHP_EOL; $initVarCode .= "\t" . 'ZVAL_DOUBLE(' . $variable->getName() . ', ' . $defaultValue['value'] . ');' . PHP_EOL; break; case 'string': $initVarCode .= "\t" . 'ZEPHIR_INIT_VAR(' . $variable->getName() . ');' . PHP_EOL; $initVarCode .= "\t" . 'ZVAL_STRING(' . $variable->getName() . ', "' . Utils::addSlashes($defaultValue['value'], true) . '", 1);' . PHP_EOL; break; case 'array': case 'empty-array': $initVarCode .= "\t" . 'ZEPHIR_INIT_VAR(' . $variable->getName() . ');' . PHP_EOL; $initVarCode .= "\t" . 'array_init(' . $variable->getName() . ');' . PHP_EOL; break; default: throw new CompilerException('Invalid default type: ' . $defaultValue['type'] . ' for data type: ' . $variable->getType(), $variable->getOriginal()); } } } } continue; } /** * Initialize 'string' variables with default values */ if ($variable->getType() == 'string') { if ($variable->getNumberUses() > 0) { $defaultValue = $variable->getDefaultInitValue(); if (is_array($defaultValue)) { $symbolTable->mustGrownStack(true); switch ($defaultValue['type']) { case 'string': $initVarCode .= "\t" . 'ZEPHIR_INIT_VAR(' . $variable->getName() . ');' . PHP_EOL; $initVarCode .= "\t" . 'ZVAL_STRING(' . $variable->getName() . ', "' . Utils::addSlashes($defaultValue['value'], true) . '", 1);' . PHP_EOL; break; case 'null': $initVarCode .= "\t" . 'ZEPHIR_INIT_VAR(' . $variable->getName() . ');' . PHP_EOL; $initVarCode .= "\t" . 'ZVAL_EMPTY_STRING(' . $variable->getName() . ');' . PHP_EOL; break; default: throw new CompilerException('Invalid default type: ' . $defaultValue['type'] . ' for data type: ' . $variable->getType(), $variable->getOriginal()); } } } continue; } /** * Initialize 'array' variables with default values */ if ($variable->getType() == 'array') { if ($variable->getNumberUses() > 0) { $defaultValue = $variable->getDefaultInitValue(); if (is_array($defaultValue)) { $symbolTable->mustGrownStack(true); switch ($defaultValue['type']) { case 'null': $initVarCode .= "\t" . 'ZEPHIR_INIT_VAR(' . $variable->getName() . ');' . PHP_EOL; $initVarCode .= "\t" . 'ZVAL_NULL(' . $variable->getName() . ');' . PHP_EOL; break; case 'array': case 'empty-array': $initVarCode .= "\t" . 'ZEPHIR_INIT_VAR(' . $variable->getName() . ');' . PHP_EOL; $initVarCode .= "\t" . 'array_init(' . $variable->getName() . ');' . PHP_EOL; break; default: throw new CompilerException('Invalid default type: ' . $defaultValue['type'] . ' for data type: ' . $variable->getType(), $variable->getOriginal()); } } } } } /** * Fetch parameters from vm-top */ $initCode = ""; $code = ""; if (is_object($parameters)) { /** * Round 2. Fetch the parameters in the method */ $params = array(); $requiredParams = array(); $optionalParams = array(); $numberRequiredParams = 0; $numberOptionalParams = 0; foreach ($parameters->getParameters() as $parameter) { if (isset($parameter['data-type'])) { $dataType = $parameter['data-type']; } else { $dataType = 'variable'; } switch ($dataType) { case 'object': case 'callable': case 'resource': case 'variable': $params[] = '&' . $parameter['name']; break; default: $params[] = '&' . $parameter['name'] . '_param'; break; } if (isset($parameter['default'])) { $optionalParams[] = $parameter; $numberOptionalParams++; } else { $requiredParams[] = $parameter; $numberRequiredParams++; } } /** * Pass the write detector to the method statement block to check if the parameter * variable is modified so as do the proper separation */ $parametersToSeparate = array(); if (is_object($this->_statements)) { /** * If local context is not available */ if (!$localContext) { $writeDetector = new WriteDetector(); } foreach ($parameters->getParameters() as $parameter) { if (isset($parameter['data-type'])) { $dataType = $parameter['data-type']; } else { $dataType = 'variable'; } switch ($dataType) { case 'variable': case 'string': case 'array': case 'resource': case 'object': case 'callable': $name = $parameter['name']; if (!$localContext) { if ($writeDetector->detect($name, $this->_statements->getStatements())) { $parametersToSeparate[$name] = true; } } else { if ($localContext->getNumberOfMutations($name) > 1) { $parametersToSeparate[$name] = true; } } break; } } } /** * Initialize required parameters */ foreach ($requiredParams as $parameter) { if (isset($parameter['mandatory'])) { $mandatory = $parameter['mandatory']; } else { $mandatory = 0; } if (isset($parameter['data-type'])) { $dataType = $parameter['data-type']; } else { $dataType = 'variable'; } if ($dataType != 'variable') { /** * Assign value from zval to low level type */ if ($mandatory) { $initCode .= $this->checkStrictType($parameter, $compilationContext); } else { $initCode .= $this->assignZvalValue($parameter, $compilationContext); } } switch ($dataType) { case 'variable': case 'string': case 'array': case 'resource': case 'object': case 'callable': if (isset($parametersToSeparate[$parameter['name']])) { $symbolTable->mustGrownStack(true); $initCode .= "\t" . "ZEPHIR_SEPARATE_PARAM(" . $parameter['name'] . ");" . PHP_EOL; } break; } } /** * Initialize optional parameters */ foreach ($optionalParams as $parameter) { if (isset($parameter['mandatory'])) { $mandatory = $parameter['mandatory']; } else { $mandatory = 0; } if (isset($parameter['data-type'])) { $dataType = $parameter['data-type']; } else { $dataType = 'variable'; } switch ($dataType) { case 'object': case 'callable': case 'resource': case 'variable': $name = $parameter['name']; break; default: $name = $parameter['name'] . '_param'; break; } /** * Assign the default value according to the variable's type */ $initCode .= "\t" . 'if (!' . $name . ') {' . PHP_EOL; $initCode .= $this->assignDefaultValue($parameter, $compilationContext); if (isset($parametersToSeparate[$name]) || $dataType != 'variable') { $initCode .= "\t" . '} else {' . PHP_EOL; if (isset($parametersToSeparate[$name])) { $initCode .= "\t\t" . "ZEPHIR_SEPARATE_PARAM(" . $name . ");" . PHP_EOL; } else { if ($mandatory) { $initCode .= $this->checkStrictType($parameter, $compilationContext, $mandatory); } else { $initCode .= "\t" . $this->assignZvalValue($parameter, $compilationContext); } } } $initCode .= "\t" . '}' . PHP_EOL; } /** * Fetch the parameters to zval pointers */ $codePrinter->preOutputBlankLine(); $compilationContext->headersManager->add('kernel/memory'); if ($symbolTable->getMustGrownStack()) { $code .= "\t" . 'zephir_fetch_params(1, ' . $numberRequiredParams . ', ' . $numberOptionalParams . ', ' . join(', ', $params) . ');' . PHP_EOL; } else { $code .= "\t" . 'zephir_fetch_params(0, ' . $numberRequiredParams . ', ' . $numberOptionalParams . ', ' . join(', ', $params) . ');' . PHP_EOL; } $code .= PHP_EOL; } $code .= $initCode . $initVarCode; $codePrinter->preOutput($code); /** * Grow the stack if needed */ if ($symbolTable->getMustGrownStack()) { $compilationContext->headersManager->add('kernel/memory'); $codePrinter->preOutput("\t" . 'ZEPHIR_MM_GROW();'); } /** * Check if there are unused variables */ $usedVariables = array(); $completeName = $compilationContext->classDefinition->getCompleteName(); foreach ($symbolTable->getVariables() as $variable) { if ($variable->getNumberUses() <= 0) { if ($variable->isExternal() == false) { $compilationContext->logger->warning('Variable "' . $variable->getName() . '" declared but not used in ' . $completeName . '::' . $this->getName(), "unused-variable", $variable->getOriginal()); continue; } $compilationContext->logger->warning('Variable "' . $variable->getName() . '" declared but not used in ' . $completeName . '::' . $this->getName(), "unused-variable-external", $variable->getOriginal()); } if ($variable->getName() != 'this_ptr' && $variable->getName() != 'return_value' && $variable->getName() != 'return_value_ptr') { $type = $variable->getType(); if (!isset($usedVariables[$type])) { $usedVariables[$type] = array(); } $usedVariables[$type][] = $variable; } } if (count($usedVariables)) { $codePrinter->preOutputBlankLine(); } /** * Generate the variable definition for variables used */ foreach ($usedVariables as $type => $variables) { $pointer = null; switch ($type) { case 'int': $code = 'int '; break; case 'uint': $code = 'unsigned int '; break; case 'char': $code = 'char '; break; case 'uchar': $code = 'unsigned char '; break; case 'long': $code = 'long '; break; case 'ulong': $code = 'unsigned long '; break; case 'bool': $code = 'zend_bool '; break; case 'double': $code = 'double '; break; case 'string': case 'variable': case 'array': case 'null': $pointer = '*'; $code = 'zval '; break; case 'HashTable': $pointer = '*'; $code = 'HashTable '; break; case 'HashPosition': $code = 'HashPosition '; break; case 'zend_class_entry': $pointer = '*'; $code = 'zend_class_entry '; break; case 'zend_function': $pointer = '*'; $code = 'zend_function '; break; case 'zend_object_iterator': $pointer = '*'; $code = 'zend_object_iterator '; break; case 'zend_property_info': $pointer = '*'; $code = 'zend_property_info '; break; case 'zephir_fcall_cache_entry': $pointer = '*'; $code = 'zephir_fcall_cache_entry '; break; case 'static_zephir_fcall_cache_entry': $pointer = '*'; $code = 'zephir_nts_static zephir_fcall_cache_entry '; break; default: throw new CompilerException("Unsupported type in declare: " . $type); } $groupVariables = array(); $defaultValues = array(); /** * @var $variables Variable[] */ foreach ($variables as $variable) { if (($type == 'variable' || $type == 'string' || $type == 'array' || $type == 'resource' || $type == 'callable' || $type == 'object') && $variable->mustInitNull()) { if ($variable->isLocalOnly()) { $groupVariables[] = $variable->getName() . ' = zval_used_for_init'; } else { if ($variable->isDoublePointer()) { $groupVariables[] = $pointer . $pointer . $variable->getName() . ' = NULL'; } else { $groupVariables[] = $pointer . $variable->getName() . ' = NULL'; } } } else { if ($variable->isLocalOnly()) { $groupVariables[] = $variable->getName(); } else { if ($variable->isDoublePointer()) { if ($variable->mustInitNull()) { $groupVariables[] = $pointer . $pointer . $variable->getName() . ' = NULL'; } else { $groupVariables[] = $pointer . $pointer . $variable->getName(); } } else { $defaultValue = $variable->getDefaultInitValue(); if ($defaultValue !== null) { switch ($type) { case 'variable': case 'string': case 'array': case 'resource': case 'callable': case 'object': $groupVariables[] = $pointer . $variable->getName(); break; default: $groupVariables[] = $pointer . $variable->getName() . ' = ' . $defaultValue; break; } } else { if ($variable->mustInitNull() && $pointer) { $groupVariables[] = $pointer . $variable->getName() . ' = NULL'; } else { $groupVariables[] = $pointer . $variable->getName(); } } } } } } $codePrinter->preOutput("\t" . $code . join(', ', $groupVariables) . ';'); } /** * Finalize the method compilation */ if (is_object($this->_statements)) { /** * If the last statement is not a 'return' or 'throw' we need to * restore the memory stack if needed */ $lastType = $this->_statements->getLastStatementType(); if ($lastType != 'return' && $lastType != 'throw' && !$this->hasChildReturnStatementType($this->_statements->getLastStatement())) { if ($symbolTable->getMustGrownStack()) { $compilationContext->headersManager->add('kernel/memory'); $codePrinter->output("\t" . 'ZEPHIR_MM_RESTORE();'); } /** * If a method has return-type hints we need to ensure the last statement is a 'return' statement */ if ($this->hasReturnTypes()) { throw new CompilerException('Reached end of the method without returning a valid type specified in the return-type hints', $this->_expression['return-type']); } } } /** * Remove macros that restore the memory stack if it wasn't used */ $code = $this->removeMemoryStackReferences($symbolTable, $codePrinter->getOutput()); /** * Restore the compilation context */ $oldCodePrinter->output($code); $compilationContext->codePrinter = $oldCodePrinter; $compilationContext->branchManager = null; $compilationContext->cacheManager = null; $compilationContext->typeInference = null; $codePrinter->clear(); return null; }
/** * Throws an exception escaping the data * * @param CodePrinter $printer * @param string $class * @param string $message * @param array $expression */ private function throwStringException(CodePrinter $printer, $class, $message, $expression) { $message = Utils::addSlashes($message); $path = Compiler::getShortUserPath($expression['file']); $printer->output(sprintf('ZEPHIR_THROW_EXCEPTION_DEBUG_STR(%s, "%s", "%s", %s);', $class, $message, $path, $expression['line'])); $printer->output('return;'); }