private function caseGettypeFunctionCall(\PHPParser_Node $node, PhpType $type, $value, $resultEqualsValue, LinkedFlowScope $blindScope) { $restrictedType = $this->getRestrictedByGettypeResult($type, $value, $resultEqualsValue); // cannot narrow the scope if (null === $restrictedType) { return $blindScope; } $informed = $blindScope->createChildFlowScope(); $this->declareNameInScope($informed, $node, $restrictedType); return $informed; }
/** * Declares a refined type in {@code scope} for the name represented by * {@code node}. It must be possible to refine the type of the given node in * the given scope, as determined by {@link #getTypeIfRefinable}. */ protected function declareNameInScope(LinkedFlowScope $scope, \PHPParser_Node $node, PhpType $type) { switch (true) { case $node instanceof \PHPParser_Node_Expr_Variable: if (is_string($node->name)) { $scope->inferSlotType($node->name, $type); } break; case $node instanceof \PHPParser_Node_Expr_StaticPropertyFetch: case $node instanceof \PHPParser_Node_Expr_PropertyFetch: if (null !== ($qualifiedName = TypeInference::getQualifiedName($node))) { $origType = $node->getAttribute('type') ?: $this->typeRegistry->getNativeType('unknown'); $scope->inferQualifiedSlot($node, $qualifiedName, $origType, $type); } break; // Something like: if (is_array($functions = getFunctionNames())) { } // Something like: if (is_array($functions = getFunctionNames())) { } case $node instanceof \PHPParser_Node_Expr_Assign: case $node instanceof \PHPParser_Node_Expr_AssignRef: $this->declareNameInScope($scope, $node->var, $type); break; case $node instanceof \PHPParser_Node_Expr_ArrayDimFetch: $dim = \Scrutinizer\PhpAnalyzer\PhpParser\NodeUtil::getValue($node->dim); if ($dim->isDefined() && null !== ($slotType = $node->var->getAttribute('type')) && $slotType->isArrayType()) { $newSlotType = $slotType->inferItemType($dim->get(), $type); $this->declareNameInScope($scope, $node->var, $newSlotType); } break; } }
private function getPropertyType(PhpType $objType = null, $propName, \PHPParser_Node $n, LinkedFlowScope $scope) { // Check whether the scope contains inferred type information about the property, // or fallback to the wider property type if not available. Scopes could // contain information about properties if we have reverse interpreted // the property previously. $qualifiedName = self::getQualifiedName($n); $var = $scope->getSlot($qualifiedName); if (null !== $var && null !== ($varType = $var->getType())) { if ($varType->equals($this->typeRegistry->getNativeType('unknown')) && $var !== $this->syntacticScope->getSlot($qualifiedName)) { // If the type of this qualified name has been checked in this scope, // then use CHECKED_UNKNOWN_TYPE instead to indicate that. // TODO: The checked unknown type has not really proved useful in // practice. Consider removing it entirely. return $this->typeRegistry->getNativeType('unknown_checked'); } return $varType; } $propertyType = null; if (null !== $objType && null !== ($objType = $objType->toMaybeObjectType()) && $objType instanceof Clazz && $objType->hasProperty($propName)) { $propertyType = $objType->getProperty($propName)->getPhpType(); } return $propertyType ?: $this->typeRegistry->getNativeType('unknown'); }
private function newScope() { $globalScope = new Scope($this->getMock('PHPParser_Node')); $this->functionScope = new Scope($this->getMock('PHPParser_Node'), $globalScope); return LinkedFlowScope::createLatticeElement($this->functionScope); }
/** * @return LinkedFlowScope */ public function createChildFlowScope() { $this->frozen = true; if ($this->depth > self::MAX_DEPTH) { if (null === $this->flattened) { $this->flattened = FlatFlowScopeCache::createFromLinkedScope($this); } return new LinkedFlowScope($this->flattened); } return LinkedFlowScope::createFromLinkedFlowScope($this); }
/** * Helper Constructor. * * This is called when we join two flow scope chains. For example, when we combine the outcomes of the ON_TRUE * and the ON_FALSE branch of an IF clause. * * @param \Scrutinizer\PhpAnalyzer\DataFlow\TypeInference\LinkedFlowScope $joinedScopeA * @param \Scrutinizer\PhpAnalyzer\DataFlow\TypeInference\LinkedFlowScope $joinedScopeB * * @return FlatFlowScopeCache */ public static function createFromLinkedScopes(LinkedFlowScope $joinedScopeA, LinkedFlowScope $joinedScopeB) { $cache = new self(); // Always prefer the "real" scope to the faked-out bottom scope. $cache->functionScope = $joinedScopeA->flowsFromBottom() ? $joinedScopeB->getFunctionScope() : $joinedScopeA->getFunctionScope(); $slotsA = $cache->symbols = $joinedScopeA->allFlowSlots(); $slotsB = $joinedScopeB->allFlowSlots(); // There are 5 different join cases: // 1) The type is declared in joinedScopeA, not in joinedScopeB, // and not in functionScope. Just use the one in A. // 2) The type is declared in joinedScopeB, not in joinedScopeA, // and not in functionScope. Just use the one in B. // 3) The type is declared in functionScope and joinedScopeA, but // not in joinedScopeB. Join the two types. // 4) The type is declared in functionScope and joinedScopeB, but // not in joinedScopeA. Join the two types. // 5) The type is declared in joinedScopeA and joinedScopeB. Join // the two types. $symbolNames = array_unique(array_merge(array_keys($slotsA), array_keys($slotsB))); foreach ($symbolNames as $name) { $slotA = isset($slotsA[$name]) ? $slotsA[$name] : null; $slotB = isset($slotsB[$name]) ? $slotsB[$name] : null; $joinedType = null; if (null === $slotB || $slotB->getType() === null) { $fnSlot = $joinedScopeB->getFunctionScope()->getVar($name); $fnSlotType = null === $fnSlot ? null : $fnSlot->getType(); if (null === $fnSlotType) { // Case #1 -- The symbol was already inserted from A slots. } else { // Case #3 $joinedType = $slotA->getType()->getLeastSuperType($fnSlotType); } } else { if (null === $slotA || $slotA->getType() === null) { $fnSlot = $joinedScopeA->getFunctionScope()->getVar($name); $fnSlotType = null === $fnSlot ? null : $fnSlot->getType(); if (null === $fnSlotType) { // Case #2 $cache->symbols[$name] = $slotB; } else { // Case #4 $joinedType = $slotB->getType()->getLeastSuperType($fnSlotType); } } else { // Case #5 $joinedType = $slotA->getType()->getLeastSuperType($slotB->getType()); } } if (null !== $joinedType) { $cache->symbols[$name] = new SimpleSlot($name, $joinedType, true); } } return $cache; }
private function assertScopesDiffer(LinkedFlowScope $a, LinkedFlowScope $b) { $this->assertFalse($a->equals($b)); $this->assertFalse($b->equals($a)); $this->assertTrue($a->equals($a)); $this->assertTrue($b->equals($b)); }