This pass tries to check whether variables only do exist in the local context of the method block or if they're used externally which will unallow variables to be placed in the stack This pass also tracks the number of initializations a variable may have, this allows to mark variables as read-only after their last initialization. The number of mutations is relative, since assignments inside cycles/loops may perform a n-number of mutations
Example #1
0
 /**
  * Returns the last line where an 'unset' operation was made within the current method
  * This is not necessary related to the symbol table but this information is gathered
  * by the LocalContextPass
  *
  * @return int
  */
 public function getLastUnsetLine()
 {
     if ($this->localContext) {
         return $this->localContext->getLastUnsetLine();
     }
     return 0;
 }
Example #2
0
 /**
  * Pre-compiles the method making compilation pass data (static inference, local-context-pass) available to other methods
  *
  * @param CompilationContext $compilationContext
  * @return null
  * @throws CompilerException
  */
 public function preCompile(CompilationContext $compilationContext)
 {
     $localContext = null;
     $typeInference = null;
     $callGathererPass = null;
     if (is_object($this->statements)) {
         $compilationContext->currentMethod = $this;
         /**
          * 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);
         }
         /**
          * 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);
             }
         }
         /**
          * This pass counts how many times a specific
          */
         if ($compilationContext->config->get('call-gatherer-pass', 'optimizations')) {
             $callGathererPass = new CallGathererPass($compilationContext);
             $callGathererPass->pass($this->statements);
         }
     }
     $this->localContext = $localContext;
     $this->typeInference = $typeInference;
     $this->callGathererPass = $callGathererPass;
 }
Example #3
0
 /**
  * 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;
 }