| 1: | <?php | 
| 2: |  | 
| 3: | namespace PhpParser; | 
| 4: |  | 
| 5: | use function array_merge; | 
| 6: | use PhpParser\Node\Expr; | 
| 7: | use PhpParser\Node\Scalar; | 
| 8: |  | 
| 9: |  | 
| 10: |  | 
| 11: |  | 
| 12: |  | 
| 13: |  | 
| 14: |  | 
| 15: |  | 
| 16: |  | 
| 17: |  | 
| 18: |  | 
| 19: |  | 
| 20: |  | 
| 21: |  | 
| 22: |  | 
| 23: |  | 
| 24: |  | 
| 25: |  | 
| 26: |  | 
| 27: |  | 
| 28: | class ConstExprEvaluator | 
| 29: | { | 
| 30: | private $fallbackEvaluator; | 
| 31: |  | 
| 32: |  | 
| 33: |  | 
| 34: |  | 
| 35: |  | 
| 36: |  | 
| 37: |  | 
| 38: |  | 
| 39: |  | 
| 40: | public function __construct(callable $fallbackEvaluator = null) { | 
| 41: | $this->fallbackEvaluator = $fallbackEvaluator ?? function(Expr $expr) { | 
| 42: | throw new ConstExprEvaluationException( | 
| 43: | "Expression of type {$expr->getType()} cannot be evaluated" | 
| 44: | ); | 
| 45: | }; | 
| 46: | } | 
| 47: |  | 
| 48: |  | 
| 49: |  | 
| 50: |  | 
| 51: |  | 
| 52: |  | 
| 53: |  | 
| 54: |  | 
| 55: |  | 
| 56: |  | 
| 57: |  | 
| 58: |  | 
| 59: |  | 
| 60: |  | 
| 61: |  | 
| 62: |  | 
| 63: |  | 
| 64: |  | 
| 65: | public function evaluateSilently(Expr $expr) { | 
| 66: | set_error_handler(function($num, $str, $file, $line) { | 
| 67: | throw new \ErrorException($str, 0, $num, $file, $line); | 
| 68: | }); | 
| 69: |  | 
| 70: | try { | 
| 71: | return $this->evaluate($expr); | 
| 72: | } catch (\Throwable $e) { | 
| 73: | if (!$e instanceof ConstExprEvaluationException) { | 
| 74: | $e = new ConstExprEvaluationException( | 
| 75: | "An error occurred during constant expression evaluation", 0, $e); | 
| 76: | } | 
| 77: | throw $e; | 
| 78: | } finally { | 
| 79: | restore_error_handler(); | 
| 80: | } | 
| 81: | } | 
| 82: |  | 
| 83: |  | 
| 84: |  | 
| 85: |  | 
| 86: |  | 
| 87: |  | 
| 88: |  | 
| 89: |  | 
| 90: |  | 
| 91: |  | 
| 92: |  | 
| 93: |  | 
| 94: |  | 
| 95: |  | 
| 96: |  | 
| 97: |  | 
| 98: |  | 
| 99: |  | 
| 100: | public function evaluateDirectly(Expr $expr) { | 
| 101: | return $this->evaluate($expr); | 
| 102: | } | 
| 103: |  | 
| 104: | private function evaluate(Expr $expr) { | 
| 105: | if ($expr instanceof Scalar\LNumber | 
| 106: | || $expr instanceof Scalar\DNumber | 
| 107: | || $expr instanceof Scalar\String_ | 
| 108: | ) { | 
| 109: | return $expr->value; | 
| 110: | } | 
| 111: |  | 
| 112: | if ($expr instanceof Expr\Array_) { | 
| 113: | return $this->evaluateArray($expr); | 
| 114: | } | 
| 115: |  | 
| 116: |  | 
| 117: | if ($expr instanceof Expr\UnaryPlus) { | 
| 118: | return +$this->evaluate($expr->expr); | 
| 119: | } | 
| 120: | if ($expr instanceof Expr\UnaryMinus) { | 
| 121: | return -$this->evaluate($expr->expr); | 
| 122: | } | 
| 123: | if ($expr instanceof Expr\BooleanNot) { | 
| 124: | return !$this->evaluate($expr->expr); | 
| 125: | } | 
| 126: | if ($expr instanceof Expr\BitwiseNot) { | 
| 127: | return ~$this->evaluate($expr->expr); | 
| 128: | } | 
| 129: |  | 
| 130: | if ($expr instanceof Expr\BinaryOp) { | 
| 131: | return $this->evaluateBinaryOp($expr); | 
| 132: | } | 
| 133: |  | 
| 134: | if ($expr instanceof Expr\Ternary) { | 
| 135: | return $this->evaluateTernary($expr); | 
| 136: | } | 
| 137: |  | 
| 138: | if ($expr instanceof Expr\ArrayDimFetch && null !== $expr->dim) { | 
| 139: | return $this->evaluate($expr->var)[$this->evaluate($expr->dim)]; | 
| 140: | } | 
| 141: |  | 
| 142: | if ($expr instanceof Expr\ConstFetch) { | 
| 143: | return $this->evaluateConstFetch($expr); | 
| 144: | } | 
| 145: |  | 
| 146: | return ($this->fallbackEvaluator)($expr); | 
| 147: | } | 
| 148: |  | 
| 149: | private function evaluateArray(Expr\Array_ $expr) { | 
| 150: | $array = []; | 
| 151: | foreach ($expr->items as $item) { | 
| 152: | if (null !== $item->key) { | 
| 153: | $array[$this->evaluate($item->key)] = $this->evaluate($item->value); | 
| 154: | } elseif ($item->unpack) { | 
| 155: | $array = array_merge($array, $this->evaluate($item->value)); | 
| 156: | } else { | 
| 157: | $array[] = $this->evaluate($item->value); | 
| 158: | } | 
| 159: | } | 
| 160: | return $array; | 
| 161: | } | 
| 162: |  | 
| 163: | private function evaluateTernary(Expr\Ternary $expr) { | 
| 164: | if (null === $expr->if) { | 
| 165: | return $this->evaluate($expr->cond) ?: $this->evaluate($expr->else); | 
| 166: | } | 
| 167: |  | 
| 168: | return $this->evaluate($expr->cond) | 
| 169: | ? $this->evaluate($expr->if) | 
| 170: | : $this->evaluate($expr->else); | 
| 171: | } | 
| 172: |  | 
| 173: | private function evaluateBinaryOp(Expr\BinaryOp $expr) { | 
| 174: | if ($expr instanceof Expr\BinaryOp\Coalesce | 
| 175: | && $expr->left instanceof Expr\ArrayDimFetch | 
| 176: | ) { | 
| 177: |  | 
| 178: | return $this->evaluate($expr->left->var)[$this->evaluate($expr->left->dim)] | 
| 179: | ?? $this->evaluate($expr->right); | 
| 180: | } | 
| 181: |  | 
| 182: |  | 
| 183: |  | 
| 184: | $l = $expr->left; | 
| 185: | $r = $expr->right; | 
| 186: | switch ($expr->getOperatorSigil()) { | 
| 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 '>':   return $this->evaluate($l) >   $this->evaluate($r); | 
| 197: | case '>=':  return $this->evaluate($l) >=  $this->evaluate($r); | 
| 198: | case '===': return $this->evaluate($l) === $this->evaluate($r); | 
| 199: | case 'and': return $this->evaluate($l) and $this->evaluate($r); | 
| 200: | case 'or':  return $this->evaluate($l) or  $this->evaluate($r); | 
| 201: | case 'xor': return $this->evaluate($l) xor $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: | case '<':   return $this->evaluate($l) <   $this->evaluate($r); | 
| 212: | case '<=':  return $this->evaluate($l) <=  $this->evaluate($r); | 
| 213: | case '<=>': return $this->evaluate($l) <=> $this->evaluate($r); | 
| 214: | } | 
| 215: |  | 
| 216: | throw new \Exception('Should not happen'); | 
| 217: | } | 
| 218: |  | 
| 219: | private function evaluateConstFetch(Expr\ConstFetch $expr) { | 
| 220: | $name = $expr->name->toLowerString(); | 
| 221: | switch ($name) { | 
| 222: | case 'null': return null; | 
| 223: | case 'false': return false; | 
| 224: | case 'true': return true; | 
| 225: | } | 
| 226: |  | 
| 227: | return ($this->fallbackEvaluator)($expr); | 
| 228: | } | 
| 229: | } | 
| 230: |  |