/** * @param string $fileName * @param \PhpParser\Node\Expr\Variable $node */ function run($fileName, $node, ClassLike $inside = null, Scope $scope = null) { if (gettype($node->name) == 'string' && $scope && !$scope->isGlobal()) { $name = $name = $node->name; if ($name != "GLOBALS" && $name != "_GET" && $name != "_POST" && $name != "_COOKIE" && $name != "_REQUEST" && $name != "this" && $name != "_SERVER" && $name != "_SESSION" && $name != "_FILES" && $name != "http_response_header") { $this->incTests(); if ($scope->getVarType($name) == Scope::UNDEFINED) { $this->emitError($fileName, $node, self::TYPE_UNKNOWN_VARIABLE, "Undefined variable: {$name}"); } } } }
/** * @param $fileName * @param $node * @param string $inside * @param Scope $scope * @param $method */ protected function checkMethod($fileName, $node, $inside, Scope $scope, MethodInterface $method) { if ($method->isStatic()) { $this->emitError($fileName, $node, self::TYPE_INCORRECT_DYNAMIC_CALL, "Call to static method of {$inside}::" . $method->getName() . " non-statically"); return; } $params = $method->getParameters(); $minimumArgs = $method->getMinimumRequiredParameters(); if (count($node->args) < $minimumArgs) { $this->emitError($fileName, $node, self::TYPE_SIGNATURE_COUNT, "Function call parameter count mismatch to method " . $method->getName() . " (passed " . count($node->args) . " requires {$minimumArgs})"); } if (count($node->args) > count($params) && !$method->isVariadic()) { $this->emitError($fileName, $node, self::TYPE_SIGNATURE_COUNT_EXCESS, "Too many parameters to non-variadic method " . $method->getName() . " (passed " . count($node->args) . " only takes " . count($params) . ")"); } foreach ($node->args as $index => $arg) { if ($scope && $arg->value instanceof \PhpParser\Node\Expr\Variable && $index < count($params) && $params[$index]->getType() != "") { $variableName = $arg->value->name; $type = $scope->getVarType($variableName); $expectedType = $params[$index]->getType(); if (!in_array($type, [Scope::SCALAR_TYPE, Scope::MIXED_TYPE, Scope::UNDEFINED]) && $type != "" && !$this->symbolTable->isParentClassOrInterface($expectedType, $type)) { $this->emitError($fileName, $node, self::TYPE_SIGNATURE_TYPE, "Variable passed to method " . $inside . "->" . $node->name . "() parameter \${$variableName} must be a {$expectedType}, passing {$type}"); } } } }
function pushFunctionScope(Node\FunctionLike $func) { $isStatic = true; if ($func instanceof Node\Stmt\ClassMethod) { $isStatic = $func->isStatic(); } $scope = new Scope($isStatic); foreach ($func->getParams() as $param) { $scope->setVarType(strval($param->name), strval($param->type)); } if ($func instanceof Node\Expr\Closure) { $oldScope = end($this->scopeStack); foreach ($func->uses as $variable) { $type = $oldScope->getVarType($variable->var); if ($type == Scope::UNDEFINED) { $type = Scope::MIXED_TYPE; } $scope->setVarType($variable->var, $type); } } array_push($this->scopeStack, $scope); }
/** * @param $fileName * @param \PhpParser\Node\Expr\StaticCall $call */ function run($fileName, $call, ClassLike $inside = null, Scope $scope = null) { if ($call->class instanceof Name && gettype($call->name) == "string") { $name = $call->class->toString(); if ($this->symbolTable->ignoreType($name)) { return; } $originalName = $name; $possibleDynamic = false; switch (strtolower($name)) { case 'self': $possibleDynamic = true; // Fall through // Fall through case 'static': if (!$inside) { $this->emitError($fileName, $call, self::TYPE_SCOPE_ERROR, "Can't access using self:: outside of a class"); return; } $name = $inside->namespacedName; break; case 'parent': if (!$inside) { $this->emitError($fileName, $call, self::TYPE_SCOPE_ERROR, "Can't access using parent:: outside of a class"); return; } $possibleDynamic = true; if ($inside->extends) { $name = strval($inside->extends); } else { $this->emitError($fileName, $call, self::TYPE_SCOPE_ERROR, "Can't access using parent:: in a class with no parent"); return; } break; default: if ($inside) { $currentClass = strval($inside->namespacedName); if ($this->symbolTable->isParentClassOrInterface($name, $currentClass)) { $possibleDynamic = true; } } break; } $this->incTests(); $class = $this->symbolTable->getAbstractedClass($name); if (!$class) { if (!$this->symbolTable->ignoreType($name)) { $this->emitError($fileName, $call, self::TYPE_UNKNOWN_CLASS, "Static call to unknown class {$name}::" . $call->name); } } else { $method = Util::findAbstractedMethod($name, $call->name, $this->symbolTable); if ($call->name == "__construct" && !$method) { // Find a PHP 4 style constructor (function name == class name) $method = Util::findAbstractedMethod($name, $name, $this->symbolTable); } if (!$method) { if (!Util::findAbstractedMethod($name, "__callStatic", $this->symbolTable) && (!$possibleDynamic || !Util::findAbstractedMethod($name, "__call", $this->symbolTable))) { $this->emitError($fileName, $call, self::TYPE_UNKNOWN_METHOD, "Unable to find method. {$name}::" . $call->name); } } else { if (!$method->isStatic()) { if (!$scope->isStatic() && $possibleDynamic) { if ($call->name != "__construct" && $call->class != "parent") { // echo "Static call in $fileName " . $call->getLine() . "\n"; } } else { $this->emitError($fileName, $call, self::TYPE_INCORRECT_DYNAMIC_CALL, "Attempt to call non-static method: {$name}::" . $call->name . " statically"); } } $minimumParams = $method->getMinimumRequiredParameters(); if (count($call->args) < $minimumParams) { $this->emitError($fileName, $call, self::TYPE_SIGNATURE_COUNT, "Static call to method {$name}::" . $call->name . " does not pass enough parameters (" . count($call->args) . " passed {$minimumParams} required)"); } } } } }