comparison vendor/nikic/php-parser/lib/PhpParser/NodeVisitor/NameResolver.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 4c8ae668cc8c
children 129ea1e6d783
comparison
equal deleted inserted replaced
12:7a779792577d 13:5fb285c0d0e3
1 <?php 1 <?php declare(strict_types=1);
2 2
3 namespace PhpParser\NodeVisitor; 3 namespace PhpParser\NodeVisitor;
4 4
5 use PhpParser\Error; 5 use PhpParser\Error;
6 use PhpParser\ErrorHandler; 6 use PhpParser\ErrorHandler;
7 use PhpParser\NameContext;
7 use PhpParser\Node; 8 use PhpParser\Node;
8 use PhpParser\Node\Expr; 9 use PhpParser\Node\Expr;
9 use PhpParser\Node\Name; 10 use PhpParser\Node\Name;
10 use PhpParser\Node\Name\FullyQualified; 11 use PhpParser\Node\Name\FullyQualified;
11 use PhpParser\Node\Stmt; 12 use PhpParser\Node\Stmt;
12 use PhpParser\NodeVisitorAbstract; 13 use PhpParser\NodeVisitorAbstract;
13 14
14 class NameResolver extends NodeVisitorAbstract 15 class NameResolver extends NodeVisitorAbstract
15 { 16 {
16 /** @var null|Name Current namespace */ 17 /** @var NameContext Naming context */
17 protected $namespace; 18 protected $nameContext;
18
19 /** @var array Map of format [aliasType => [aliasName => originalName]] */
20 protected $aliases;
21
22 /** @var ErrorHandler Error handler */
23 protected $errorHandler;
24 19
25 /** @var bool Whether to preserve original names */ 20 /** @var bool Whether to preserve original names */
26 protected $preserveOriginalNames; 21 protected $preserveOriginalNames;
27 22
23 /** @var bool Whether to replace resolved nodes in place, or to add resolvedNode attributes */
24 protected $replaceNodes;
25
28 /** 26 /**
29 * Constructs a name resolution visitor. 27 * Constructs a name resolution visitor.
30 * 28 *
31 * Options: If "preserveOriginalNames" is enabled, an "originalName" attribute will be added to 29 * Options:
32 * all name nodes that underwent resolution. 30 * * preserveOriginalNames (default false): An "originalName" attribute will be added to
31 * all name nodes that underwent resolution.
32 * * replaceNodes (default true): Resolved names are replaced in-place. Otherwise, a
33 * resolvedName attribute is added. (Names that cannot be statically resolved receive a
34 * namespacedName attribute, as usual.)
33 * 35 *
34 * @param ErrorHandler|null $errorHandler Error handler 36 * @param ErrorHandler|null $errorHandler Error handler
35 * @param array $options Options 37 * @param array $options Options
36 */ 38 */
37 public function __construct(ErrorHandler $errorHandler = null, array $options = []) { 39 public function __construct(ErrorHandler $errorHandler = null, array $options = []) {
38 $this->errorHandler = $errorHandler ?: new ErrorHandler\Throwing; 40 $this->nameContext = new NameContext($errorHandler ?? new ErrorHandler\Throwing);
39 $this->preserveOriginalNames = !empty($options['preserveOriginalNames']); 41 $this->preserveOriginalNames = $options['preserveOriginalNames'] ?? false;
42 $this->replaceNodes = $options['replaceNodes'] ?? true;
43 }
44
45 /**
46 * Get name resolution context.
47 *
48 * @return NameContext
49 */
50 public function getNameContext() : NameContext {
51 return $this->nameContext;
40 } 52 }
41 53
42 public function beforeTraverse(array $nodes) { 54 public function beforeTraverse(array $nodes) {
43 $this->resetState(); 55 $this->nameContext->startNamespace();
56 return null;
44 } 57 }
45 58
46 public function enterNode(Node $node) { 59 public function enterNode(Node $node) {
47 if ($node instanceof Stmt\Namespace_) { 60 if ($node instanceof Stmt\Namespace_) {
48 $this->resetState($node->name); 61 $this->nameContext->startNamespace($node->name);
49 } elseif ($node instanceof Stmt\Use_) { 62 } elseif ($node instanceof Stmt\Use_) {
50 foreach ($node->uses as $use) { 63 foreach ($node->uses as $use) {
51 $this->addAlias($use, $node->type, null); 64 $this->addAlias($use, $node->type, null);
52 } 65 }
53 } elseif ($node instanceof Stmt\GroupUse) { 66 } elseif ($node instanceof Stmt\GroupUse) {
98 foreach ($node->types as &$type) { 111 foreach ($node->types as &$type) {
99 $type = $this->resolveClassName($type); 112 $type = $this->resolveClassName($type);
100 } 113 }
101 } elseif ($node instanceof Expr\FuncCall) { 114 } elseif ($node instanceof Expr\FuncCall) {
102 if ($node->name instanceof Name) { 115 if ($node->name instanceof Name) {
103 $node->name = $this->resolveOtherName($node->name, Stmt\Use_::TYPE_FUNCTION); 116 $node->name = $this->resolveName($node->name, Stmt\Use_::TYPE_FUNCTION);
104 } 117 }
105 } elseif ($node instanceof Expr\ConstFetch) { 118 } elseif ($node instanceof Expr\ConstFetch) {
106 $node->name = $this->resolveOtherName($node->name, Stmt\Use_::TYPE_CONSTANT); 119 $node->name = $this->resolveName($node->name, Stmt\Use_::TYPE_CONSTANT);
107 } elseif ($node instanceof Stmt\TraitUse) { 120 } elseif ($node instanceof Stmt\TraitUse) {
108 foreach ($node->traits as &$trait) { 121 foreach ($node->traits as &$trait) {
109 $trait = $this->resolveClassName($trait); 122 $trait = $this->resolveClassName($trait);
110 } 123 }
111 124
119 $insteadof = $this->resolveClassName($insteadof); 132 $insteadof = $this->resolveClassName($insteadof);
120 } 133 }
121 } 134 }
122 } 135 }
123 } 136 }
124 } 137
125 138 return null;
126 protected function resetState(Name $namespace = null) { 139 }
127 $this->namespace = $namespace; 140
128 $this->aliases = array( 141 private function addAlias(Stmt\UseUse $use, $type, Name $prefix = null) {
129 Stmt\Use_::TYPE_NORMAL => array(),
130 Stmt\Use_::TYPE_FUNCTION => array(),
131 Stmt\Use_::TYPE_CONSTANT => array(),
132 );
133 }
134
135 protected function addAlias(Stmt\UseUse $use, $type, Name $prefix = null) {
136 // Add prefix for group uses 142 // Add prefix for group uses
137 $name = $prefix ? Name::concat($prefix, $use->name) : $use->name; 143 $name = $prefix ? Name::concat($prefix, $use->name) : $use->name;
138 // Type is determined either by individual element or whole use declaration 144 // Type is determined either by individual element or whole use declaration
139 $type |= $use->type; 145 $type |= $use->type;
140 146
141 // Constant names are case sensitive, everything else case insensitive 147 $this->nameContext->addAlias(
142 if ($type === Stmt\Use_::TYPE_CONSTANT) { 148 $name, (string) $use->getAlias(), $type, $use->getAttributes()
143 $aliasName = $use->alias; 149 );
144 } else {
145 $aliasName = strtolower($use->alias);
146 }
147
148 if (isset($this->aliases[$type][$aliasName])) {
149 $typeStringMap = array(
150 Stmt\Use_::TYPE_NORMAL => '',
151 Stmt\Use_::TYPE_FUNCTION => 'function ',
152 Stmt\Use_::TYPE_CONSTANT => 'const ',
153 );
154
155 $this->errorHandler->handleError(new Error(
156 sprintf(
157 'Cannot use %s%s as %s because the name is already in use',
158 $typeStringMap[$type], $name, $use->alias
159 ),
160 $use->getAttributes()
161 ));
162 return;
163 }
164
165 $this->aliases[$type][$aliasName] = $name;
166 } 150 }
167 151
168 /** @param Stmt\Function_|Stmt\ClassMethod|Expr\Closure $node */ 152 /** @param Stmt\Function_|Stmt\ClassMethod|Expr\Closure $node */
169 private function resolveSignature($node) { 153 private function resolveSignature($node) {
170 foreach ($node->params as $param) { 154 foreach ($node->params as $param) {
182 return $this->resolveClassName($node); 166 return $this->resolveClassName($node);
183 } 167 }
184 return $node; 168 return $node;
185 } 169 }
186 170
187 protected function resolveClassName(Name $name) { 171 /**
172 * Resolve name, according to name resolver options.
173 *
174 * @param Name $name Function or constant name to resolve
175 * @param int $type One of Stmt\Use_::TYPE_*
176 *
177 * @return Name Resolved name, or original name with attribute
178 */
179 protected function resolveName(Name $name, int $type) : Name {
180 if (!$this->replaceNodes) {
181 $resolvedName = $this->nameContext->getResolvedName($name, $type);
182 if (null !== $resolvedName) {
183 $name->setAttribute('resolvedName', $resolvedName);
184 } else {
185 $name->setAttribute('namespacedName', FullyQualified::concat(
186 $this->nameContext->getNamespace(), $name, $name->getAttributes()));
187 }
188 return $name;
189 }
190
188 if ($this->preserveOriginalNames) { 191 if ($this->preserveOriginalNames) {
189 // Save the original name 192 // Save the original name
190 $originalName = $name; 193 $originalName = $name;
191 $name = clone $originalName; 194 $name = clone $originalName;
192 $name->setAttribute('originalName', $originalName); 195 $name->setAttribute('originalName', $originalName);
193 } 196 }
194 197
195 // don't resolve special class names 198 $resolvedName = $this->nameContext->getResolvedName($name, $type);
196 if (in_array(strtolower($name->toString()), array('self', 'parent', 'static'))) { 199 if (null !== $resolvedName) {
197 if (!$name->isUnqualified()) { 200 return $resolvedName;
198 $this->errorHandler->handleError(new Error( 201 }
199 sprintf("'\\%s' is an invalid class name", $name->toString()), 202
200 $name->getAttributes() 203 // unqualified names inside a namespace cannot be resolved at compile-time
201 )); 204 // add the namespaced version of the name as an attribute
202 } 205 $name->setAttribute('namespacedName', FullyQualified::concat(
203 return $name; 206 $this->nameContext->getNamespace(), $name, $name->getAttributes()));
204 } 207 return $name;
205 208 }
206 // fully qualified names are already resolved 209
207 if ($name->isFullyQualified()) { 210 protected function resolveClassName(Name $name) {
208 return $name; 211 return $this->resolveName($name, Stmt\Use_::TYPE_NORMAL);
209 }
210
211 $aliasName = strtolower($name->getFirst());
212 if (!$name->isRelative() && isset($this->aliases[Stmt\Use_::TYPE_NORMAL][$aliasName])) {
213 // resolve aliases (for non-relative names)
214 $alias = $this->aliases[Stmt\Use_::TYPE_NORMAL][$aliasName];
215 return FullyQualified::concat($alias, $name->slice(1), $name->getAttributes());
216 }
217
218 // if no alias exists prepend current namespace
219 return FullyQualified::concat($this->namespace, $name, $name->getAttributes());
220 }
221
222 protected function resolveOtherName(Name $name, $type) {
223 if ($this->preserveOriginalNames) {
224 // Save the original name
225 $originalName = $name;
226 $name = clone $originalName;
227 $name->setAttribute('originalName', $originalName);
228 }
229
230 // fully qualified names are already resolved
231 if ($name->isFullyQualified()) {
232 return $name;
233 }
234
235 // resolve aliases for qualified names
236 $aliasName = strtolower($name->getFirst());
237 if ($name->isQualified() && isset($this->aliases[Stmt\Use_::TYPE_NORMAL][$aliasName])) {
238 $alias = $this->aliases[Stmt\Use_::TYPE_NORMAL][$aliasName];
239 return FullyQualified::concat($alias, $name->slice(1), $name->getAttributes());
240 }
241
242 if ($name->isUnqualified()) {
243 if ($type === Stmt\Use_::TYPE_CONSTANT) {
244 // constant aliases are case-sensitive, function aliases case-insensitive
245 $aliasName = $name->getFirst();
246 }
247
248 if (isset($this->aliases[$type][$aliasName])) {
249 // resolve unqualified aliases
250 return new FullyQualified($this->aliases[$type][$aliasName], $name->getAttributes());
251 }
252
253 if (null === $this->namespace) {
254 // outside of a namespace unaliased unqualified is same as fully qualified
255 return new FullyQualified($name, $name->getAttributes());
256 }
257
258 // unqualified names inside a namespace cannot be resolved at compile-time
259 // add the namespaced version of the name as an attribute
260 $name->setAttribute('namespacedName',
261 FullyQualified::concat($this->namespace, $name, $name->getAttributes()));
262 return $name;
263 }
264
265 // if no alias exists prepend current namespace
266 return FullyQualified::concat($this->namespace, $name, $name->getAttributes());
267 } 212 }
268 213
269 protected function addNamespacedName(Node $node) { 214 protected function addNamespacedName(Node $node) {
270 $node->namespacedName = Name::concat($this->namespace, $node->name); 215 $node->namespacedName = Name::concat(
216 $this->nameContext->getNamespace(), (string) $node->name);
271 } 217 }
272 } 218 }