comparison vendor/nikic/php-parser/lib/PhpParser/ConstExprEvaluator.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
comparison
equal deleted inserted replaced
12:7a779792577d 13:5fb285c0d0e3
1 <?php
2
3 namespace PhpParser;
4
5 use PhpParser\Node\Expr;
6 use PhpParser\Node\Scalar;
7
8 /**
9 * Evaluates constant expressions.
10 *
11 * This evaluator is able to evaluate all constant expressions (as defined by PHP), which can be
12 * evaluated without further context. If a subexpression is not of this type, a user-provided
13 * fallback evaluator is invoked. To support all constant expressions that are also supported by
14 * PHP (and not already handled by this class), the fallback evaluator must be able to handle the
15 * following node types:
16 *
17 * * All Scalar\MagicConst\* nodes.
18 * * Expr\ConstFetch nodes. Only null/false/true are already handled by this class.
19 * * Expr\ClassConstFetch nodes.
20 *
21 * The fallback evaluator should throw ConstExprEvaluationException for nodes it cannot evaluate.
22 *
23 * The evaluation is dependent on runtime configuration in two respects: Firstly, floating
24 * point to string conversions are affected by the precision ini setting. Secondly, they are also
25 * affected by the LC_NUMERIC locale.
26 */
27 class ConstExprEvaluator
28 {
29 private $fallbackEvaluator;
30
31 /**
32 * Create a constant expression evaluator.
33 *
34 * The provided fallback evaluator is invoked whenever a subexpression cannot be evaluated. See
35 * class doc comment for more information.
36 *
37 * @param callable|null $fallbackEvaluator To call if subexpression cannot be evaluated
38 */
39 public function __construct(callable $fallbackEvaluator = null) {
40 $this->fallbackEvaluator = $fallbackEvaluator ?? function(Expr $expr) {
41 throw new ConstExprEvaluationException(
42 "Expression of type {$expr->getType()} cannot be evaluated"
43 );
44 };
45 }
46
47 /**
48 * Silently evaluates a constant expression into a PHP value.
49 *
50 * Thrown Errors, warnings or notices will be converted into a ConstExprEvaluationException.
51 * The original source of the exception is available through getPrevious().
52 *
53 * If some part of the expression cannot be evaluated, the fallback evaluator passed to the
54 * constructor will be invoked. By default, if no fallback is provided, an exception of type
55 * ConstExprEvaluationException is thrown.
56 *
57 * See class doc comment for caveats and limitations.
58 *
59 * @param Expr $expr Constant expression to evaluate
60 * @return mixed Result of evaluation
61 *
62 * @throws ConstExprEvaluationException if the expression cannot be evaluated or an error occurred
63 */
64 public function evaluateSilently(Expr $expr) {
65 set_error_handler(function($num, $str, $file, $line) {
66 throw new \ErrorException($str, 0, $num, $file, $line);
67 });
68
69 try {
70 return $this->evaluate($expr);
71 } catch (\Throwable $e) {
72 if (!$e instanceof ConstExprEvaluationException) {
73 $e = new ConstExprEvaluationException(
74 "An error occurred during constant expression evaluation", 0, $e);
75 }
76 throw $e;
77 } finally {
78 restore_error_handler();
79 }
80 }
81
82 /**
83 * Directly evaluates a constant expression into a PHP value.
84 *
85 * May generate Error exceptions, warnings or notices. Use evaluateSilently() to convert these
86 * into a ConstExprEvaluationException.
87 *
88 * If some part of the expression cannot be evaluated, the fallback evaluator passed to the
89 * constructor will be invoked. By default, if no fallback is provided, an exception of type
90 * ConstExprEvaluationException is thrown.
91 *
92 * See class doc comment for caveats and limitations.
93 *
94 * @param Expr $expr Constant expression to evaluate
95 * @return mixed Result of evaluation
96 *
97 * @throws ConstExprEvaluationException if the expression cannot be evaluated
98 */
99 public function evaluateDirectly(Expr $expr) {
100 return $this->evaluate($expr);
101 }
102
103 private function evaluate(Expr $expr) {
104 if ($expr instanceof Scalar\LNumber
105 || $expr instanceof Scalar\DNumber
106 || $expr instanceof Scalar\String_
107 ) {
108 return $expr->value;
109 }
110
111 if ($expr instanceof Expr\Array_) {
112 return $this->evaluateArray($expr);
113 }
114
115 // Unary operators
116 if ($expr instanceof Expr\UnaryPlus) {
117 return +$this->evaluate($expr->expr);
118 }
119 if ($expr instanceof Expr\UnaryMinus) {
120 return -$this->evaluate($expr->expr);
121 }
122 if ($expr instanceof Expr\BooleanNot) {
123 return !$this->evaluate($expr->expr);
124 }
125 if ($expr instanceof Expr\BitwiseNot) {
126 return ~$this->evaluate($expr->expr);
127 }
128
129 if ($expr instanceof Expr\BinaryOp) {
130 return $this->evaluateBinaryOp($expr);
131 }
132
133 if ($expr instanceof Expr\Ternary) {
134 return $this->evaluateTernary($expr);
135 }
136
137 if ($expr instanceof Expr\ArrayDimFetch && null !== $expr->dim) {
138 return $this->evaluate($expr->var)[$this->evaluate($expr->dim)];
139 }
140
141 if ($expr instanceof Expr\ConstFetch) {
142 return $this->evaluateConstFetch($expr);
143 }
144
145 return ($this->fallbackEvaluator)($expr);
146 }
147
148 private function evaluateArray(Expr\Array_ $expr) {
149 $array = [];
150 foreach ($expr->items as $item) {
151 if (null !== $item->key) {
152 $array[$this->evaluate($item->key)] = $this->evaluate($item->value);
153 } else {
154 $array[] = $this->evaluate($item->value);
155 }
156 }
157 return $array;
158 }
159
160 private function evaluateTernary(Expr\Ternary $expr) {
161 if (null === $expr->if) {
162 return $this->evaluate($expr->cond) ?: $this->evaluate($expr->else);
163 }
164
165 return $this->evaluate($expr->cond)
166 ? $this->evaluate($expr->if)
167 : $this->evaluate($expr->else);
168 }
169
170 private function evaluateBinaryOp(Expr\BinaryOp $expr) {
171 if ($expr instanceof Expr\BinaryOp\Coalesce
172 && $expr->left instanceof Expr\ArrayDimFetch
173 ) {
174 // This needs to be special cased to respect BP_VAR_IS fetch semantics
175 return $this->evaluate($expr->left->var)[$this->evaluate($expr->left->dim)]
176 ?? $this->evaluate($expr->right);
177 }
178
179 // The evaluate() calls are repeated in each branch, because some of the operators are
180 // short-circuiting and evaluating the RHS in advance may be illegal in that case
181 $l = $expr->left;
182 $r = $expr->right;
183 switch ($expr->getOperatorSigil()) {
184 case '&': return $this->evaluate($l) & $this->evaluate($r);
185 case '|': return $this->evaluate($l) | $this->evaluate($r);
186 case '^': return $this->evaluate($l) ^ $this->evaluate($r);
187 case '&&': return $this->evaluate($l) && $this->evaluate($r);
188 case '||': return $this->evaluate($l) || $this->evaluate($r);
189 case '??': return $this->evaluate($l) ?? $this->evaluate($r);
190 case '.': return $this->evaluate($l) . $this->evaluate($r);
191 case '/': return $this->evaluate($l) / $this->evaluate($r);
192 case '==': return $this->evaluate($l) == $this->evaluate($r);
193 case '>': return $this->evaluate($l) > $this->evaluate($r);
194 case '>=': return $this->evaluate($l) >= $this->evaluate($r);
195 case '===': return $this->evaluate($l) === $this->evaluate($r);
196 case 'and': return $this->evaluate($l) and $this->evaluate($r);
197 case 'or': return $this->evaluate($l) or $this->evaluate($r);
198 case 'xor': return $this->evaluate($l) xor $this->evaluate($r);
199 case '-': return $this->evaluate($l) - $this->evaluate($r);
200 case '%': return $this->evaluate($l) % $this->evaluate($r);
201 case '*': return $this->evaluate($l) * $this->evaluate($r);
202 case '!=': return $this->evaluate($l) != $this->evaluate($r);
203 case '!==': return $this->evaluate($l) !== $this->evaluate($r);
204 case '+': return $this->evaluate($l) + $this->evaluate($r);
205 case '**': return $this->evaluate($l) ** $this->evaluate($r);
206 case '<<': return $this->evaluate($l) << $this->evaluate($r);
207 case '>>': return $this->evaluate($l) >> $this->evaluate($r);
208 case '<': return $this->evaluate($l) < $this->evaluate($r);
209 case '<=': return $this->evaluate($l) <= $this->evaluate($r);
210 case '<=>': return $this->evaluate($l) <=> $this->evaluate($r);
211 }
212
213 throw new \Exception('Should not happen');
214 }
215
216 private function evaluateConstFetch(Expr\ConstFetch $expr) {
217 $name = $expr->name->toLowerString();
218 switch ($name) {
219 case 'null': return null;
220 case 'false': return false;
221 case 'true': return true;
222 }
223
224 return ($this->fallbackEvaluator)($expr);
225 }
226 }