/** * Resolves an expression * * @param CompilationContext $compilationContext * @return bool|CompiledExpression|mixed * @throws CompilerException */ public function compile(CompilationContext $compilationContext) { $expression = $this->_expression; $type = $expression['type']; $compilableExpression = null; switch ($type) { case 'null': return new LiteralCompiledExpression('null', null, $expression); case 'int': case 'integer': return new LiteralCompiledExpression('int', $expression['value'], $expression); case 'long': case 'double': case 'bool': return new LiteralCompiledExpression($type, $expression['value'], $expression); case 'string': if (!$this->_stringOperation) { if (ctype_digit($expression['value'])) { return new CompiledExpression('int', $expression['value'], $expression); } } return new LiteralCompiledExpression('string', str_replace(PHP_EOL, '\\n', $expression['value']), $expression); case 'istring': return new LiteralCompiledExpression('istring', str_replace(PHP_EOL, '\\n', $expression['value']), $expression); case 'char': if (!strlen($expression['value'])) { throw new CompilerException("Invalid empty char literal", $expression); } if (strlen($expression['value']) > 2) { if (strlen($expression['value']) > 10) { throw new CompilerException("Invalid char literal: '" . substr($expression['value'], 0, 10) . "...'", $expression); } else { throw new CompilerException("Invalid char literal: '" . $expression['value'] . "'", $expression); } } return new LiteralCompiledExpression('char', $expression['value'], $expression); case 'variable': $var = $compilationContext->symbolTable->getVariable($expression['value']); if ($var) { if ($var->getRealName() == 'this') { $var = 'this'; } else { $var = $var->getName(); } } else { $var = $expression['value']; } return new CompiledExpression('variable', $var, $expression); case 'constant': $compilableExpression = new Constants(); break; case 'empty-array': return $this->emptyArray($expression, $compilationContext); case 'array-access': $compilableExpression = new NativeArrayAccess(); $compilableExpression->setNoisy($this->isNoisy()); break; case 'property-access': $compilableExpression = new PropertyAccess(); $compilableExpression->setNoisy($this->isNoisy()); break; case 'property-string-access': case 'property-dynamic-access': $compilableExpression = new PropertyDynamicAccess(); $compilableExpression->setNoisy($this->isNoisy()); break; case 'static-constant-access': $compilableExpression = new StaticConstantAccess(); break; case 'static-property-access': $compilableExpression = new StaticPropertyAccess(); break; case 'fcall': $functionCall = new FunctionCall(); return $functionCall->compile($this, $compilationContext); case 'mcall': $methodCall = new MethodCall(); return $methodCall->compile($this, $compilationContext); case 'scall': $staticCall = new StaticCall(); return $staticCall->compile($this, $compilationContext); case 'isset': $compilableExpression = new IssetOperator(); break; case 'fetch': $compilableExpression = new FetchOperator(); break; case 'empty': $compilableExpression = new EmptyOperator(); break; case 'array': $compilableExpression = new NativeArray(); break; case 'new': $compilableExpression = new NewInstanceOperator(); break; case 'new-type': $compilableExpression = new NewInstanceTypeOperator(); break; case 'not': $compilableExpression = new NotOperator(); break; case 'bitwise_not': $compilableExpression = new BitwiseNotOperator(); break; case 'equals': $compilableExpression = new EqualsOperator(); break; case 'not-equals': $compilableExpression = new NotEqualsOperator(); break; case 'identical': $compilableExpression = new IdenticalOperator(); break; case 'not-identical': $compilableExpression = new NotIdenticalOperator(); break; case 'greater': $compilableExpression = new GreaterOperator(); break; case 'less': $compilableExpression = new LessOperator(); break; case 'less-equal': $compilableExpression = new LessEqualOperator(); break; case 'greater-equal': $compilableExpression = new GreaterEqualOperator(); break; case 'add': $compilableExpression = new AddOperator(); break; case 'minus': $compilableExpression = new MinusOperator(); break; case 'sub': $compilableExpression = new SubOperator(); break; case 'mul': $compilableExpression = new MulOperator(); break; case 'div': $compilableExpression = new DivOperator(); break; case 'mod': $compilableExpression = new ModOperator(); break; case 'and': $compilableExpression = new AndOperator(); break; case 'or': $compilableExpression = new OrOperator(); break; case 'bitwise_and': $compilableExpression = new BitwiseAndOperator(); break; case 'bitwise_or': $compilableExpression = new BitwiseOrOperator(); break; case 'bitwise_xor': $compilableExpression = new BitwiseXorOperator(); break; case 'bitwise_shiftleft': $compilableExpression = new ShiftLeftOperator(); break; case 'bitwise_shiftright': $compilableExpression = new ShiftRightOperator(); break; case 'concat': $expr = new ConcatOperator(); $expr->setExpectReturn($this->_expecting, $this->_expectingVariable); return $expr->compile($expression, $compilationContext); case 'irange': $compilableExpression = new RangeInclusiveOperator(); break; case 'erange': $compilableExpression = new RangeExclusiveOperator(); break; case 'list': if ($expression['left']['type'] == 'list') { $compilationContext->logger->warning("Unnecessary extra parentheses", "extra-parentheses", $expression); } $numberPrints = $compilationContext->codePrinter->getNumberPrints(); $expr = new Expression($expression['left']); $expr->setExpectReturn($this->_expecting, $this->_expectingVariable); $resolved = $expr->compile($compilationContext); if ($compilationContext->codePrinter->getNumberPrints() - $numberPrints <= 1) { if (strpos($resolved->getCode(), ' ') !== false) { return new CompiledExpression($resolved->getType(), '(' . $resolved->getCode() . ')', $expression); } } return $resolved; case 'cast': $compilableExpression = new CastOperator(); break; case 'type-hint': return $this->compileTypeHint($expression, $compilationContext); case 'instanceof': $compilableExpression = new InstanceOfOperator(); break; case 'clone': $compilableExpression = new CloneOperator(); break; case 'ternary': $compilableExpression = new TernaryOperator(); break; case 'short-ternary': $expr = new ShortTernaryOperator(); $expr->setReadOnly($this->isReadOnly()); $expr->setExpectReturn($this->_expecting, $this->_expectingVariable); return $expr->compile($expression, $compilationContext); case 'likely': if (!$this->_evalMode) { throw new CompilerException("'likely' operator can only be used in evaluation expressions", $expression); } $expr = new LikelyOperator(); $expr->setReadOnly($this->isReadOnly()); return $expr->compile($expression, $compilationContext); case 'unlikely': if (!$this->_evalMode) { throw new CompilerException("'unlikely' operator can only be used in evaluation expressions", $expression); } $expr = new UnlikelyOperator(); $expr->setReadOnly($this->isReadOnly()); return $expr->compile($expression, $compilationContext); case 'typeof': $compilableExpression = new TypeOfOperator(); break; case 'require': $compilableExpression = new RequireOperator(); break; case 'closure': $compilableExpression = new Closure(); break; case 'closure-arrow': $compilableExpression = new ClosureArrow(); break; case 'reference': $compilableExpression = new Reference(); break; default: throw new CompilerException("Unknown expression: " . $type, $expression); } if (!$compilableExpression) { throw new CompilerException("Unknown expression passed as compilableExpression", $expression); } $compilableExpression->setReadOnly($this->isReadOnly()); $compilableExpression->setExpectReturn($this->_expecting, $this->_expectingVariable); return $compilableExpression->compile($expression, $compilationContext); }
/** * @param CompilationContext $compilationContext * @param boolean $unreachable * @param int $branchType * @return Branch */ public function compile(CompilationContext $compilationContext, $unreachable = false, $branchType = Branch::TYPE_UNKNOWN) { $compilationContext->codePrinter->increaseLevel(); $compilationContext->currentBranch++; /** * Create a new branch */ $currentBranch = new Branch(); $currentBranch->setType($branchType); $currentBranch->setUnreachable($unreachable); /** * Activate branch in the branch manager */ $compilationContext->branchManager->addBranch($currentBranch); $this->_unreachable = $unreachable; $statements = $this->_statements; foreach ($statements as $statement) { /** * Generate GDB hints */ if ($this->_debug) { if (isset($statement['file'])) { if ($statement['type'] != 'declare' && $statement['type'] != 'comment') { $compilationContext->codePrinter->outputNoIndent('#line ' . $statement['line'] . ' "' . $statement['file'] . '"'); } } } /** * Show warnings if code is generated when the 'unreachable state' is 'on' */ if ($this->_unreachable === true) { switch ($statement['type']) { case 'echo': $compilationContext->logger->warning('Unreachable code', "unreachable-code", $statement['expressions'][0]); break; case 'let': $compilationContext->logger->warning('Unreachable code', "unreachable-code", $statement['assignments'][0]); break; case 'fetch': case 'fcall': case 'mcall': case 'scall': case 'if': case 'while': case 'do-while': case 'switch': case 'for': case 'return': case 'c-block': if (isset($statement['expr'])) { $compilationContext->logger->warning('Unreachable code', "unreachable-code", $statement['expr']); } else { $compilationContext->logger->warning('Unreachable code', "unreachable-code", $statement); } break; default: $compilationContext->logger->warning('Unreachable code', "unreachable-code", $statement); } } switch ($statement['type']) { case 'let': $letStatement = new LetStatement($statement); $letStatement->compile($compilationContext); break; case 'echo': $echoStatement = new EchoStatement($statement); $echoStatement->compile($compilationContext); break; case 'declare': $declareStatement = new DeclareStatement($statement); $declareStatement->compile($compilationContext); break; case 'if': $ifStatement = new IfStatement($statement); $ifStatement->compile($compilationContext); break; case 'while': $whileStatement = new WhileStatement($statement); $whileStatement->compile($compilationContext); break; case 'do-while': $doWhileStatement = new DoWhileStatement($statement); $doWhileStatement->compile($compilationContext); break; case 'switch': $switchStatement = new SwitchStatement($statement); $switchStatement->compile($compilationContext); break; case 'for': $forStatement = new ForStatement($statement); $forStatement->compile($compilationContext); break; case 'return': $returnStatement = new ReturnStatement($statement); $returnStatement->compile($compilationContext); $this->_unreachable = true; break; case 'require': $requireStatement = new RequireStatement($statement); $requireStatement->compile($compilationContext); break; case 'loop': $loopStatement = new LoopStatement($statement); $loopStatement->compile($compilationContext); break; case 'break': $breakStatement = new BreakStatement($statement); $breakStatement->compile($compilationContext); $this->_unreachable = true; break; case 'continue': $continueStatement = new ContinueStatement($statement); $continueStatement->compile($compilationContext); $this->_unreachable = true; break; case 'unset': $unsetStatement = new UnsetStatement($statement); $unsetStatement->compile($compilationContext); break; case 'throw': $throwStatement = new ThrowStatement($statement); $throwStatement->compile($compilationContext); $this->_unreachable = true; break; case 'try-catch': $throwStatement = new TryCatchStatement($statement); $throwStatement->compile($compilationContext); $this->_unreachable = false; break; case 'fetch': $expr = new Expression($statement['expr']); $expr->setExpectReturn(false); $compiledExpression = $expr->compile($compilationContext); $compilationContext->codePrinter->output($compiledExpression->getCode() . ';'); break; case 'mcall': $methodCall = new MethodCall(); $expr = new Expression($statement['expr']); $expr->setExpectReturn(false); $methodCall->compile($expr, $compilationContext); break; case 'fcall': $functionCall = new FunctionCall(); $expr = new Expression($statement['expr']); $expr->setExpectReturn(false); $compiledExpression = $functionCall->compile($expr, $compilationContext); switch ($compiledExpression->getType()) { case 'int': case 'double': case 'uint': case 'long': case 'ulong': case 'char': case 'uchar': case 'bool': $compilationContext->codePrinter->output($compiledExpression->getCode() . ';'); break; } break; case 'scall': $methodCall = new StaticCall(); $expr = new Expression($statement['expr']); $expr->setExpectReturn(false); $methodCall->compile($expr, $compilationContext); break; case 'cblock': $compilationContext->codePrinter->output($statement['value']); break; case 'empty': break; default: throw new Exception('Unsupported statement: ' . $statement['type']); } if ($statement['type'] != 'comment') { $this->_lastStatement = $statement; } } /** * Traverses temporal variables created in a specific branch * marking them as idle */ $compilationContext->symbolTable->markTemporalVariablesIdle($compilationContext); $compilationContext->branchManager->removeBranch($currentBranch); $compilationContext->currentBranch--; $compilationContext->codePrinter->decreaseLevel(); return $currentBranch; }
/** * 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'); } } } } /** * Set properties of the class, if constructor */ $classDefinition = $this->classDefinition; if ($this->isConstructor() || $this->getName() == 'unserialize' && in_array('Serializable', $classDefinition->getImplementedInterfaces())) { $initMethod = $classDefinition->getMethod('zephir_init_properties'); if ($initMethod && $initMethod->getClassDefinition() == $classDefinition) { $codePrinter->increaseLevel(); /* Only initialize properties if the constructor was called directly on the class, e.g. make parent:: calls not trigger property initialization */ if ($this->isConstructor()) { $codePrinter->output('if (EG(called_scope) == ' . $classDefinition->getClassEntry() . ') {'); $codePrinter->increaseLevel(); } $codePrinter->output('zephir_init_properties(this_ptr TSRMLS_CC);'); if ($this->isConstructor()) { $codePrinter->decreaseLevel(); $codePrinter->output('}'); } $codePrinter->decreaseLevel(); } $extendsClass = $classDefinition->getExtendsClassDefinition(); $parentConstructor = $extendsClass ? $extendsClass->getMethod('__construct') : null; if ($this->isConstructor() && $extendsClass && $parentConstructor && !$extendsClass->isGeneratedConstructor() && $classDefinition->isGeneratedConstructor()) { /** * Call the parent constructor (create parent::__construct call) * since no constructor was actually defined for this class, but * generated for zephir to ensure property initialization */ $callExpr = new Expression(array('type' => 'scall', 'dynamic-class' => 0, 'class' => 'parent', 'dynamic' => 0, 'name' => '__construct')); $call = new StaticCall(); $expr = $callExpr; $expr->setExpectReturn(false); $call->compile($expr, $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 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']); } } } /** * 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; }