/** * @param $fileName * @param \PhpParser\Node\Expr\New_ $node */ function run($fileName, $node, ClassLike $inside = null, Scope $scope = null) { if ($node->class instanceof Name) { $name = $node->class->toString(); if (strcasecmp($name, "self") != 0 && strcasecmp($name, "static") != 0 && !$this->symbolTable->ignoreType($name)) { $this->incTests(); $class = $this->symbolTable->getAbstractedClass($name); if (!$class) { $this->emitError($fileName, $node, self::TYPE_UNKNOWN_CLASS, "Attempt to instantiate unknown class {$name}"); return; } if ($class->isDeclaredAbstract()) { $this->emitError($fileName, $node, self::TYPE_SIGNATURE_TYPE, "Attempt to instantiate abstract class {$name}"); return; } $method = Util::findAbstractedMethod($name, "__construct", $this->symbolTable); if (!$method) { $minParams = $maxParams = 0; } else { if ($method->getAccessLevel() == "private" && (!$inside || strcasecmp($inside->namespacedName, $name) != 0)) { $this->emitError($fileName, $node, self::TYPE_SCOPE_ERROR, "Attempt to call private constructor outside of class {$name}"); return; } $maxParams = count($method->getParameters()); $minParams = $method->getMinimumRequiredParameters(); if (strcasecmp("imagick", $name) == 0) { $minParams = 0; $maxParams = 1; } } $passedArgCount = count($node->args); if ($passedArgCount < $minParams) { $this->emitError($fileName, $node, self::TYPE_SIGNATURE_COUNT, "Call to {$name}::__construct passing {$passedArgCount} count, required count={$minParams}"); } if ($passedArgCount > $maxParams) { //$this->emitError($fileName, $node, "Parameter mismatch","Call to $name::__construct passing too many parameters ($passedArgCount instead of $maxParams)"); } } } }
/** * @param $fileName * @param \PhpParser\Node\Expr\MethodCall $node */ function run($fileName, $node, ClassLike $inside = null, Scope $scope = null) { if ($inside instanceof Trait_) { // Traits should be converted into methods in the class, so that we can check them in context. return; } if ($node->name instanceof Expr) { // Variable method name. Yuck! return; } $methodName = strval($node->name); $varName = "{expr}"; $className = ""; if ($node->var instanceof Variable && $node->var->name == "this" && !$inside) { $this->emitError($fileName, $node, self::TYPE_SCOPE_ERROR, "Can't use \$this outside of a class"); return; } if ($scope) { $className = $this->inferenceEngine->inferType($inside, $node->var, $scope); } if ($className != "" && $className[0] != "!") { if (!$this->symbolTable->getAbstractedClass($className)) { $this->emitError($fileName, $node, self::TYPE_UNKNOWN_CLASS, "Unknown class {$className} in method call to {$methodName}()"); return; } //echo $fileName." ".$node->getLine(). " : Looking up $className->$methodName\n"; $method = Util::findAbstractedSignature($className, $methodName, $this->symbolTable); if ($method) { $this->checkMethod($fileName, $node, $className, $scope, $method); } else { // If there is a magic __call method, then we can't know if it will handle these calls. if (!Util::findAbstractedMethod($className, "__call", $this->symbolTable) && !$this->symbolTable->isParentClassOrInterface("iteratoriterator", $className)) { $this->emitError($fileName, $node, self::TYPE_UNKNOWN_METHOD, "Call to unknown method of {$className}: \$" . $varName . "->" . $methodName); } } } }
/** * @param $fileName * @param \PhpParser\Node\Stmt\Class_ $node */ function run($fileName, $node, ClassLike $inside = null, Scope $scope = null) { if ($node->implements) { $arr = is_array($node->implements) ? $node->implements : [$node->implements]; foreach ($arr as $interface) { $name = $interface->toString(); $this->incTests(); if ($name) { $interface = $this->symbolTable->getAbstractedClass($name); if (!$interface) { $this->emitError($fileName, $node, self::TYPE_UNKNOWN_CLASS, $node->name . " implements unknown interface " . $name); } else { // Don't force abstract classes to implement all methods. if (!$node->isAbstract()) { foreach ($interface->getMethodNames() as $interfaceMethod) { $classMethod = $this->implementsMethod($fileName, $node, $interfaceMethod); if (!$classMethod) { if (!$node->isAbstract()) { $this->emitError($fileName, $node, self::TYPE_UNIMPLEMENTED_METHOD, $node->name . " does not implement method " . $interfaceMethod); } } else { $this->checkMethod($node, $classMethod, $interface, $interface->getMethod($interfaceMethod)); } } } } } } } if ($node->extends) { $class = new \Guardrail\Abstractions\Class_($node); $parentClass = $this->symbolTable->getAbstractedClass($node->extends); if (!$parentClass) { $this->emitError($fileName, $node->extends, self::TYPE_UNKNOWN_CLASS, "Unable to find parent " . $node->extends); } foreach ($class->getMethodNames() as $methodName) { if ($methodName != "__construct") { $method = Util::findAbstractedMethod($node->extends, $methodName, $this->symbolTable); if ($method) { $this->checkMethod($node, $class->getMethod($methodName), $parentClass, $method); } } } } }
function getAbstractedMethod($className, $methodName) { $cacheName = strtolower($className . "::" . $methodName); $ob = $this->cache->get("AClassMethod:" . $cacheName); if (!$ob) { $ob = \Guardrail\Util::findAbstractedMethod($className, $methodName, $this); if (!$ob && strpos($className, "\\") === false) { try { $refl = new \ReflectionMethod($className, $methodName); $ob = new \Guardrail\Abstractions\ReflectedClassMethod($refl); } catch (\ReflectionException $e) { $ob = null; } } if ($ob) { $this->cache->add("AClassMethod:" . $cacheName, $ob); } } return $ob; }
function getConstructorDependencies($className) { $method = Util::findAbstractedMethod($className, "__construct", $this->symbolTable); $dependencies = []; if ($method) { foreach ($method->getParameters() as $param) { $dependencies[] = strval($param->getType()); } } return $dependencies; }
/** * @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)"); } } } } }