1<?php declare(strict_types=1); 2 3namespace PhpParser; 4 5use PhpParser\Node\Expr; 6use PhpParser\Node\Scalar; 7 8class ConstExprEvaluatorTest extends \PHPUnit\Framework\TestCase { 9 /** @dataProvider provideTestEvaluate */ 10 public function testEvaluate($exprString, $expected): void { 11 $parser = new Parser\Php7(new Lexer()); 12 $expr = $parser->parse('<?php ' . $exprString . ';')[0]->expr; 13 $evaluator = new ConstExprEvaluator(); 14 $this->assertSame($expected, $evaluator->evaluateDirectly($expr)); 15 } 16 17 public static function provideTestEvaluate() { 18 return [ 19 ['1', 1], 20 ['1.0', 1.0], 21 ['"foo"', "foo"], 22 ['[0, 1]', [0, 1]], 23 ['["foo" => "bar"]', ["foo" => "bar"]], 24 ['[...["bar"]]', ["bar"]], 25 ['[...["foo" => "bar"]]', ["foo" => "bar"]], 26 ['["a", "b" => "b", ...["b" => "bb", "c"]]', ["a", "b" => "bb", "c"]], 27 ['NULL', null], 28 ['False', false], 29 ['true', true], 30 ['+1', 1], 31 ['-1', -1], 32 ['~0', -1], 33 ['!true', false], 34 ['[0][0]', 0], 35 ['"a"[0]', "a"], 36 ['true ? 1 : (1/0)', 1], 37 ['false ? (1/0) : 1', 1], 38 ['42 ?: (1/0)', 42], 39 ['false ?: 42', 42], 40 ['false ?? 42', false], 41 ['null ?? 42', 42], 42 ['[0][0] ?? 42', 0], 43 ['[][0] ?? 42', 42], 44 ['0b11 & 0b10', 0b10], 45 ['0b11 | 0b10', 0b11], 46 ['0b11 ^ 0b10', 0b01], 47 ['1 << 2', 4], 48 ['4 >> 2', 1], 49 ['"a" . "b"', "ab"], 50 ['4 + 2', 6], 51 ['4 - 2', 2], 52 ['4 * 2', 8], 53 ['4 / 2', 2], 54 ['4 % 2', 0], 55 ['4 ** 2', 16], 56 ['1 == 1.0', true], 57 ['1 != 1.0', false], 58 ['1 < 2.0', true], 59 ['1 <= 2.0', true], 60 ['1 > 2.0', false], 61 ['1 >= 2.0', false], 62 ['1 <=> 2.0', -1], 63 ['1 === 1.0', false], 64 ['1 !== 1.0', true], 65 ['true && true', true], 66 ['true and true', true], 67 ['false && (1/0)', false], 68 ['false and (1/0)', false], 69 ['false || false', false], 70 ['false or false', false], 71 ['true || (1/0)', true], 72 ['true or (1/0)', true], 73 ['true xor false', true], 74 ]; 75 } 76 77 public function testEvaluateFails(): void { 78 $this->expectException(ConstExprEvaluationException::class); 79 $this->expectExceptionMessage('Expression of type Expr_Variable cannot be evaluated'); 80 $evaluator = new ConstExprEvaluator(); 81 $evaluator->evaluateDirectly(new Expr\Variable('a')); 82 } 83 84 public function testEvaluateFallback(): void { 85 $evaluator = new ConstExprEvaluator(function (Expr $expr) { 86 if ($expr instanceof Scalar\MagicConst\Line) { 87 return 42; 88 } 89 throw new ConstExprEvaluationException(); 90 }); 91 $expr = new Expr\BinaryOp\Plus( 92 new Scalar\Int_(8), 93 new Scalar\MagicConst\Line() 94 ); 95 $this->assertSame(50, $evaluator->evaluateDirectly($expr)); 96 } 97 98 /** 99 * @dataProvider provideTestEvaluateSilently 100 */ 101 public function testEvaluateSilently($expr, $exception, $msg): void { 102 $evaluator = new ConstExprEvaluator(); 103 104 try { 105 $evaluator->evaluateSilently($expr); 106 } catch (ConstExprEvaluationException $e) { 107 $this->assertSame( 108 'An error occurred during constant expression evaluation', 109 $e->getMessage() 110 ); 111 112 $prev = $e->getPrevious(); 113 $this->assertInstanceOf($exception, $prev); 114 $this->assertSame($msg, $prev->getMessage()); 115 } 116 } 117 118 public static function provideTestEvaluateSilently() { 119 return [ 120 [ 121 new Expr\BinaryOp\Mod(new Scalar\Int_(42), new Scalar\Int_(0)), 122 \Error::class, 123 'Modulo by zero' 124 ], 125 [ 126 new Expr\BinaryOp\Plus(new Scalar\Int_(42), new Scalar\String_("1foo")), 127 \ErrorException::class, 128 \PHP_VERSION_ID >= 80000 129 ? 'A non-numeric value encountered' 130 : 'A non well formed numeric value encountered' 131 ], 132 ]; 133 } 134} 135