function isAllowed($name, ClassLike $inside = null) { $nameLower = strtolower($name); if ($nameLower == "self" && $inside instanceof Class_) { return true; } if ($nameLower != "" && !Util::isLegalNonObject($name)) { $class = $this->symbolTable->getAbstractedClass($name); if (!$class && !$this->symbolTable->ignoreType($name)) { return false; } } return true; }
function index(Config $config, OutputInterface $output, \RecursiveIteratorIterator $it2, $stubs = false) { $baseDir = $config->getBasePath(); $symbolTable = $config->getSymbolTable(); $indexer = new SymbolTableIndexer($symbolTable, $output); $traverser1 = new NodeTraverser(); $traverser1->addVisitor(new NameResolver()); $traverser2 = new NodeTraverser(); $traverser2->addVisitor($indexer); $parser = (new ParserFactory())->create(ParserFactory::PREFER_PHP7); $configArr = $config->getConfigArray(); $count = 0; foreach ($it2 as $file) { if (($file->getExtension() == "php" || $file->getExtension() == "inc") && $file->isFile()) { $name = Util::removeInitialPath($baseDir, $file->getPathname()); if (strpos($name, "phar://") === 0) { $name = str_replace(\Phar::running(), "", $name); while ($name[0] == '/') { $name = substr($name, 1); } $name = "phar://" . $name; } try { if (!$stubs && isset($configArr['ignore']) && is_array($configArr['ignore']) && Util::matchesGlobs($baseDir, $file->getPathname(), $configArr['ignore'])) { continue; } ++$count; $output->output(".", " - {$count}:" . $name); // If the $fileName is in our phar then make it a relative path so that files that we index don't // depend on the phar file existing in a particular directory. $fileData = file_get_contents($file->getPathname()); if ($config->shouldReindex()) { $symbolTable->removeFileFromIndex($file->getPathname()); } $indexer->setFilename($name); $stmts = $parser->parse($fileData); if ($stmts) { $traverser1->traverse($stmts); $traverser2->traverse($stmts); } } catch (Error $e) { $output->emitError(__CLASS__, $file, 0, ' Parse Error: ' . $e->getMessage() . "\n"); } } } return $count; }
/** * @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); } } } }
function phase2(Config $config, OutputInterface $output, $toProcess) { $traverser1 = new NodeTraverser(); $traverser1->addVisitor(new DocBlockNameResolver()); $analyzer = new StaticAnalyzer($config->getBasePath(), $config->getSymbolTable(), $output, $config); $traverser2 = new NodeTraverser(); $traverser2->addVisitor(new TraitImportingVisitor($config->getSymbolTable())); $traverser3 = new NodeTraverser(); $traverser3->addVisitor($analyzer); $parser = (new ParserFactory())->create(ParserFactory::PREFER_PHP7); $processingCount = 0; foreach ($toProcess as $file) { try { $name = Util::removeInitialPath($config->getBasePath(), $file); $output->output(".", $name); $processingCount++; //echo " - $processingCount:" . $file . "\n"; $fileData = file_get_contents($file); $stmts = $parser->parse($fileData); if ($stmts) { $analyzer->setFile($name); $stmts = $traverser1->traverse($stmts); $stmts = $traverser2->traverse($stmts); $traverser3->traverse($stmts); } } catch (Error $e) { $output->emitError(__CLASS__, $file, 0, "Parse error", $e->getMessage()); } catch (\Guardrail\Exceptions\UnknownTraitException $e) { $output->emitError(__CLASS__, $file, 0, "Unknown trait error", $e->getMessage()); } } if ($output instanceof XUnitOutput) { // print_r($output->getCounts()); } return $output->getErrorCount() > 0 ? 1 : 0; }
/** * @param $fileName * @param \PhpParser\Node\Expr\PropertyFetch $node */ function run($fileName, $node, ClassLike $inside = null, Scope $scope = null) { if ($node->var instanceof Variable) { if (is_string($node->var->name) && $node->var->name == 'this') { if ($inside instanceof Trait_) { return; } if (!$inside) { $this->emitError($fileName, $node, self::TYPE_SCOPE_ERROR, "Can't use \$this outside of a class"); return; } if (!is_string($node->name)) { // Variable method name. Yuck! return; } //echo "Access ".$node->var->name."->".$node->name."\n"; $property = Util::findProperty($inside, $node->name, $this->symbolTable); if (!$property) { //$this->emitError($fileName, $node, "Unknown property", "Accessing unknown property of $inside->namespacedName: \$this->" . $node->name); return; } } } }
/** * @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 getAccessLevel() { return Util::getMethodAccessLevel($this->method); }
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)"); } } } } }
static function isLegalNonObject($name) { return Util::isScalarType($name) || strcasecmp($name, "callable") == 0 || strcasecmp($name, "array") == 0; }
function inferPropertyFetch(Node\Expr\PropertyFetch $expr, $inside, $scope) { $class = $this->inferType($inside, $expr->var, $scope); if (!empty($class) && $class[0] != "!") { if (gettype($expr->name) == 'string') { $classDef = $this->index->getClass($class); if ($classDef) { $prop = Util::findProperty($classDef, strval($expr->name), $this->index); if ($prop) { /* $type = $prop->getAttribute("namespacedType") ?: ""; if(!empty($type)) { if($type[0]=='\\') { $type=substr($type,1); } return $type; }*/ } } } } return Scope::MIXED_TYPE; }