Mercurial > hg > isophonics-drupal-site
diff vendor/psy/psysh/src/CodeCleaner/ValidClassNamePass.php @ 13:5fb285c0d0e3
Update Drupal core to 8.4.7 via Composer. Security update; I *think* we've
been lucky to get away with this so far, as we don't support self-registration
which seems to be used by the so-called "drupalgeddon 2" attack that 8.4.5
was vulnerable to.
author | Chris Cannam |
---|---|
date | Mon, 23 Apr 2018 09:33:26 +0100 |
parents | |
children | c2387f117808 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vendor/psy/psysh/src/CodeCleaner/ValidClassNamePass.php Mon Apr 23 09:33:26 2018 +0100 @@ -0,0 +1,394 @@ +<?php + +/* + * This file is part of Psy Shell. + * + * (c) 2012-2018 Justin Hileman + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Psy\CodeCleaner; + +use PhpParser\Node; +use PhpParser\Node\Expr; +use PhpParser\Node\Expr\ClassConstFetch; +use PhpParser\Node\Expr\New_; +use PhpParser\Node\Expr\StaticCall; +use PhpParser\Node\Stmt; +use PhpParser\Node\Stmt\Class_; +use PhpParser\Node\Stmt\Do_; +use PhpParser\Node\Stmt\If_; +use PhpParser\Node\Stmt\Interface_; +use PhpParser\Node\Stmt\Switch_; +use PhpParser\Node\Stmt\Trait_; +use PhpParser\Node\Stmt\While_; +use Psy\Exception\FatalErrorException; + +/** + * Validate that classes exist. + * + * This pass throws a FatalErrorException rather than letting PHP run + * headfirst into a real fatal error and die. + */ +class ValidClassNamePass extends NamespaceAwarePass +{ + const CLASS_TYPE = 'class'; + const INTERFACE_TYPE = 'interface'; + const TRAIT_TYPE = 'trait'; + + protected $checkTraits; + private $conditionalScopes = 0; + private $atLeastPhp55; + + public function __construct() + { + $this->checkTraits = function_exists('trait_exists'); + $this->atLeastPhp55 = version_compare(PHP_VERSION, '5.5', '>='); + } + + /** + * Validate class, interface and trait definitions. + * + * Validate them upon entering the node, so that we know about their + * presence and can validate constant fetches and static calls in class or + * trait methods. + * + * @param Node $node + */ + public function enterNode(Node $node) + { + parent::enterNode($node); + + if (self::isConditional($node)) { + $this->conditionalScopes++; + } else { + // @todo add an "else" here which adds a runtime check for instances where we can't tell + // whether a class is being redefined by static analysis alone. + if ($this->conditionalScopes === 0) { + if ($node instanceof Class_) { + $this->validateClassStatement($node); + } elseif ($node instanceof Interface_) { + $this->validateInterfaceStatement($node); + } elseif ($node instanceof Trait_) { + $this->validateTraitStatement($node); + } + } + } + } + + /** + * Validate `new` expressions, class constant fetches, and static calls. + * + * @throws FatalErrorException if a class, interface or trait is referenced which does not exist + * @throws FatalErrorException if a class extends something that is not a class + * @throws FatalErrorException if a class implements something that is not an interface + * @throws FatalErrorException if an interface extends something that is not an interface + * @throws FatalErrorException if a class, interface or trait redefines an existing class, interface or trait name + * + * @param Node $node + */ + public function leaveNode(Node $node) + { + if (self::isConditional($node)) { + $this->conditionalScopes--; + } elseif ($node instanceof New_) { + $this->validateNewExpression($node); + } elseif ($node instanceof ClassConstFetch) { + $this->validateClassConstFetchExpression($node); + } elseif ($node instanceof StaticCall) { + $this->validateStaticCallExpression($node); + } + } + + private static function isConditional(Node $node) + { + return $node instanceof If_ || + $node instanceof While_ || + $node instanceof Do_ || + $node instanceof Switch_; + } + + /** + * Validate a class definition statement. + * + * @param Class_ $stmt + */ + protected function validateClassStatement(Class_ $stmt) + { + $this->ensureCanDefine($stmt); + if (isset($stmt->extends)) { + $this->ensureClassExists($this->getFullyQualifiedName($stmt->extends), $stmt); + } + $this->ensureInterfacesExist($stmt->implements, $stmt); + } + + /** + * Validate an interface definition statement. + * + * @param Interface_ $stmt + */ + protected function validateInterfaceStatement(Interface_ $stmt) + { + $this->ensureCanDefine($stmt); + $this->ensureInterfacesExist($stmt->extends, $stmt); + } + + /** + * Validate a trait definition statement. + * + * @param Trait_ $stmt + */ + protected function validateTraitStatement(Trait_ $stmt) + { + $this->ensureCanDefine($stmt); + } + + /** + * Validate a `new` expression. + * + * @param New_ $stmt + */ + protected function validateNewExpression(New_ $stmt) + { + // if class name is an expression or an anonymous class, give it a pass for now + if (!$stmt->class instanceof Expr && !$stmt->class instanceof Class_) { + $this->ensureClassExists($this->getFullyQualifiedName($stmt->class), $stmt); + } + } + + /** + * Validate a class constant fetch expression's class. + * + * @param ClassConstFetch $stmt + */ + protected function validateClassConstFetchExpression(ClassConstFetch $stmt) + { + // there is no need to check exists for ::class const for php 5.5 or newer + if (strtolower($stmt->name) === 'class' && $this->atLeastPhp55) { + return; + } + + // if class name is an expression, give it a pass for now + if (!$stmt->class instanceof Expr) { + $this->ensureClassOrInterfaceExists($this->getFullyQualifiedName($stmt->class), $stmt); + } + } + + /** + * Validate a class constant fetch expression's class. + * + * @param StaticCall $stmt + */ + protected function validateStaticCallExpression(StaticCall $stmt) + { + // if class name is an expression, give it a pass for now + if (!$stmt->class instanceof Expr) { + $this->ensureMethodExists($this->getFullyQualifiedName($stmt->class), $stmt->name, $stmt); + } + } + + /** + * Ensure that no class, interface or trait name collides with a new definition. + * + * @throws FatalErrorException + * + * @param Stmt $stmt + */ + protected function ensureCanDefine(Stmt $stmt) + { + $name = $this->getFullyQualifiedName($stmt->name); + + // check for name collisions + $errorType = null; + if ($this->classExists($name)) { + $errorType = self::CLASS_TYPE; + } elseif ($this->interfaceExists($name)) { + $errorType = self::INTERFACE_TYPE; + } elseif ($this->traitExists($name)) { + $errorType = self::TRAIT_TYPE; + } + + if ($errorType !== null) { + throw $this->createError(sprintf('%s named %s already exists', ucfirst($errorType), $name), $stmt); + } + + // Store creation for the rest of this code snippet so we can find local + // issue too + $this->currentScope[strtolower($name)] = $this->getScopeType($stmt); + } + + /** + * Ensure that a referenced class exists. + * + * @throws FatalErrorException + * + * @param string $name + * @param Stmt $stmt + */ + protected function ensureClassExists($name, $stmt) + { + if (!$this->classExists($name)) { + throw $this->createError(sprintf('Class \'%s\' not found', $name), $stmt); + } + } + + /** + * Ensure that a referenced class _or interface_ exists. + * + * @throws FatalErrorException + * + * @param string $name + * @param Stmt $stmt + */ + protected function ensureClassOrInterfaceExists($name, $stmt) + { + if (!$this->classExists($name) && !$this->interfaceExists($name)) { + throw $this->createError(sprintf('Class \'%s\' not found', $name), $stmt); + } + } + + /** + * Ensure that a statically called method exists. + * + * @throws FatalErrorException + * + * @param string $class + * @param string $name + * @param Stmt $stmt + */ + protected function ensureMethodExists($class, $name, $stmt) + { + $this->ensureClassExists($class, $stmt); + + // let's pretend all calls to self, parent and static are valid + if (in_array(strtolower($class), ['self', 'parent', 'static'])) { + return; + } + + // ... and all calls to classes defined right now + if ($this->findInScope($class) === self::CLASS_TYPE) { + return; + } + + // if method name is an expression, give it a pass for now + if ($name instanceof Expr) { + return; + } + + if (!method_exists($class, $name) && !method_exists($class, '__callStatic')) { + throw $this->createError(sprintf('Call to undefined method %s::%s()', $class, $name), $stmt); + } + } + + /** + * Ensure that a referenced interface exists. + * + * @throws FatalErrorException + * + * @param Interface_[] $interfaces + * @param Stmt $stmt + */ + protected function ensureInterfacesExist($interfaces, $stmt) + { + foreach ($interfaces as $interface) { + /** @var string $name */ + $name = $this->getFullyQualifiedName($interface); + if (!$this->interfaceExists($name)) { + throw $this->createError(sprintf('Interface \'%s\' not found', $name), $stmt); + } + } + } + + /** + * Get a symbol type key for storing in the scope name cache. + * + * @param Stmt $stmt + * + * @return string + */ + protected function getScopeType(Stmt $stmt) + { + if ($stmt instanceof Class_) { + return self::CLASS_TYPE; + } elseif ($stmt instanceof Interface_) { + return self::INTERFACE_TYPE; + } elseif ($stmt instanceof Trait_) { + return self::TRAIT_TYPE; + } + } + + /** + * Check whether a class exists, or has been defined in the current code snippet. + * + * Gives `self`, `static` and `parent` a free pass. + * + * @param string $name + * + * @return bool + */ + protected function classExists($name) + { + // Give `self`, `static` and `parent` a pass. This will actually let + // some errors through, since we're not checking whether the keyword is + // being used in a class scope. + if (in_array(strtolower($name), ['self', 'static', 'parent'])) { + return true; + } + + return class_exists($name) || $this->findInScope($name) === self::CLASS_TYPE; + } + + /** + * Check whether an interface exists, or has been defined in the current code snippet. + * + * @param string $name + * + * @return bool + */ + protected function interfaceExists($name) + { + return interface_exists($name) || $this->findInScope($name) === self::INTERFACE_TYPE; + } + + /** + * Check whether a trait exists, or has been defined in the current code snippet. + * + * @param string $name + * + * @return bool + */ + protected function traitExists($name) + { + return $this->checkTraits && (trait_exists($name) || $this->findInScope($name) === self::TRAIT_TYPE); + } + + /** + * Find a symbol in the current code snippet scope. + * + * @param string $name + * + * @return string|null + */ + protected function findInScope($name) + { + $name = strtolower($name); + if (isset($this->currentScope[$name])) { + return $this->currentScope[$name]; + } + } + + /** + * Error creation factory. + * + * @param string $msg + * @param Stmt $stmt + * + * @return FatalErrorException + */ + protected function createError($msg, $stmt) + { + return new FatalErrorException($msg, 0, E_ERROR, null, $stmt->getLine()); + } +}