/** * 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; }
/** * Creates a new instance * * @param $expression * @param CompilationContext $compilationContext * @return CompiledExpression * @throws CompilerException */ public function compile(array $expression, CompilationContext $compilationContext) { $codePrinter = $compilationContext->codePrinter; /** * Resolves the symbol that expects the value */ $this->_literalOnly = false; $symbolVariable = $this->getExpectedNonLiteral($compilationContext, $expression); if (!$symbolVariable->isVariable()) { throw new CompilerException("Objects can only be instantiated into dynamic variables", $expression); } if ($symbolVariable->isLocalOnly()) { throw new CompilerException("Cannot use non-heap variable to store new instance", $expression); } if ($symbolVariable->getName() != 'return_value') { if ($symbolVariable->hasDifferentDynamicType(array('unknown', 'undefined', 'object', 'null'))) { $compilationContext->logger->warning('Possible attempt to use non-object in "new" operator', 'non-valid-new', $expression); } } /** * Mark variables as dynamic objects */ $symbolVariable->setDynamicTypes('object'); $dynamic = false; if ($expression['class'] == 'self' || $expression['class'] == 'static') { $className = $compilationContext->classDefinition->getCompleteName(); } else { $className = $expression['class']; $dynamic = $expression['dynamic']; if (!$dynamic) { $className = $compilationContext->getFullName($expression['class']); } } if (!$className) { throw new CompilerException("A class name is required to instantiate the object", $expression); } /** * stdclass doesn't have constructors */ $lowerClassName = strtolower($className); $isStdClass = $lowerClassName === 'stdclass' || $lowerClassName === '\\stdclass'; if ($isStdClass) { if (isset($expression['parameters']) && count($expression['parameters']) > 0) { throw new CompilerException("stdclass does not receive parameters in its constructor", $expression); } $compilationContext->backend->initObject($symbolVariable, null, $compilationContext); $symbolVariable->setClassTypes('stdclass'); } else { $classDefinition = false; if ($compilationContext->compiler->isClass($className)) { $classDefinition = $compilationContext->compiler->getClassDefinition($className); } /** * Classes inside the same extension */ if ($classDefinition) { $compilationContext->backend->initObject($symbolVariable, $classDefinition->getClassEntry($compilationContext), $compilationContext); $symbolVariable->setClassTypes($className); $symbolVariable->setAssociatedClass($classDefinition); } else { /** * Classes outside the extension */ if ($dynamic) { $classNameVariable = $compilationContext->symbolTable->getVariableForRead($className, $compilationContext, $expression); if ($classNameVariable->isNotVariableAndString()) { throw new CompilerException("Only dynamic/string variables can be used in new operator. " . $classNameVariable->getName(), $expression); } /** * Use a safe string version of the variable to avoid segfaults */ $compilationContext->headersManager->add('kernel/object'); $safeSymbolVariable = $compilationContext->symbolTable->getTempVariable('variable', $compilationContext, $expression); $safeSymbolVariable->setMustInitNull(true); $safeSymbolVariable->setIsInitialized(true, $compilationContext, $expression); $safeSymbolVariable->increaseUses(); $compilationContext->codePrinter->output('zephir_fetch_safe_class(' . $safeSymbolVariable->getName() . ', ' . $classNameVariable->getName() . ');'); $safeSymbol = $compilationContext->backend->getVariableCode($safeSymbolVariable); $classNameToFetch = 'Z_STRVAL_P(' . $safeSymbol . '), Z_STRLEN_P(' . $safeSymbol . ')'; $zendClassEntry = $compilationContext->cacheManager->getClassEntryCache()->get($classNameToFetch, true, $compilationContext); $classEntry = $zendClassEntry->getName(); } else { if (!class_exists($className, false)) { $compilationContext->logger->warning('Class "' . $className . '" does not exist at compile time', "nonexistent-class", $expression); $classNameToFetch = 'SL("' . Utils::escapeClassName($className) . '")'; $zendClassEntry = $compilationContext->cacheManager->getClassEntryCache()->get($classNameToFetch, false, $compilationContext); $classEntry = $zendClassEntry->getName(); } else { $reflectionClass = new \ReflectionClass($className); if ($reflectionClass->isInterface()) { throw new CompilerException('Interfaces cannot be instantiated', $expression); } else { if (method_exists($reflectionClass, 'isTrait')) { if ($reflectionClass->isTrait()) { throw new CompilerException('Traits cannot be instantiated', $expression); } } } $classEntry = $compilationContext->classDefinition->getClassEntryByClassName($className, $compilationContext, true); if (!$classEntry) { $classNameToFetch = 'SL("' . Utils::escapeClassName($className) . '")'; $zendClassEntry = $compilationContext->cacheManager->getClassEntryCache()->get($classNameToFetch, false, $compilationContext); $classEntry = $zendClassEntry->getName(); } else { $symbolVariable->setAssociatedClass($reflectionClass); } } $symbolVariable->setClassTypes($className); } $compilationContext->backend->initObject($symbolVariable, $classEntry, $compilationContext); } } /** * Mark variable initialized */ $symbolVariable->setIsInitialized(true, $compilationContext, $expression); /** * Don't check the constructor for stdclass instances */ if ($isStdClass) { return new CompiledExpression('variable', $symbolVariable->getRealName(), $expression); } /** * Call the constructor * For classes in the same extension we check if the class does implement a constructor * For external classes we always assume the class does implement a constructor */ $callConstructor = false; if ($compilationContext->compiler->isClass($className)) { $classDefinition = $compilationContext->compiler->getClassDefinition($className); if ($classDefinition->getType() != 'class') { throw new CompilerException("Only classes can be instantiated", $expression); } $callConstructor = $classDefinition->hasMethod("__construct"); } else { if ($compilationContext->compiler->isBundledClass($className)) { $classDefinition = $compilationContext->compiler->getInternalClassDefinition($className); $callConstructor = $classDefinition->hasMethod("__construct"); } } /* @TODO use the MethodBuilder here */ if (isset($expression['parameters'])) { $callExpr = new Expression(array('variable' => array('type' => 'variable', 'value' => $symbolVariable->getRealName()), 'name' => '__construct', 'parameters' => $expression['parameters'], 'call-type' => MethodCall::CALL_NORMAL, 'file' => $expression['file'], 'line' => $expression['line'], 'char' => $expression['char'], 'check' => $callConstructor)); } else { $callExpr = new Expression(array('variable' => array('type' => 'variable', 'value' => $symbolVariable->getRealName()), 'name' => '__construct', 'call-type' => MethodCall::CALL_NORMAL, 'file' => $expression['file'], 'line' => $expression['line'], 'char' => $expression['char'], 'check' => $callConstructor)); } /** * If we are certain that there is a constructor we call it, otherwise we checked it at runtime. */ if ($callConstructor) { $methodCall = new MethodCall(); $callExpr->setExpectReturn(false); $methodCall->compile($callExpr, $compilationContext); } else { $compilationContext->headersManager->add('kernel/fcall'); /* @todo, generate the code using builders */ $compilationContext->backend->checkConstructor($symbolVariable, $compilationContext); $codePrinter->increaseLevel(); $methodCall = new MethodCall(); $callExpr->setExpectReturn(false); $methodCall->compile($callExpr, $compilationContext); $codePrinter->decreaseLevel(); $codePrinter->output('}'); } return new CompiledExpression('variable', $symbolVariable->getRealName(), $expression); }