annotate vendor/nikic/php-parser/lib/PhpParser/ConstExprEvaluator.php @ 5:12f9dff5fda9 tip

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