From 4b285641187108f65abfd4be4ff6bd5b16ca98c9 Mon Sep 17 00:00:00 2001 From: Serghei Iakovlev Date: Thu, 31 Oct 2019 01:19:23 +0200 Subject: [PATCH] Fixed casting char into another of a different type See: #1988 --- Library/Expression.php | 4 +- Library/Operators/Other/CastOperator.php | 302 ++++++++++++++++------- test/cast.zep | 86 ++++++- unit-tests/Extension/CastTest.php | 50 ++++ 4 files changed, 341 insertions(+), 101 deletions(-) diff --git a/Library/Expression.php b/Library/Expression.php index 289f6702c2..7d26b1f67b 100644 --- a/Library/Expression.php +++ b/Library/Expression.php @@ -270,9 +270,9 @@ public function emptyArray($expression, CompilationContext $compilationContext) * * @throws CompilerException|Exception * - * @return bool|CompiledExpression + * @return CompiledExpression */ - public function compile(CompilationContext $compilationContext) + public function compile(CompilationContext $compilationContext): CompiledExpression { $expression = $this->expression; $type = $expression['type']; diff --git a/Library/Operators/Other/CastOperator.php b/Library/Operators/Other/CastOperator.php index 1feec474a6..7bb81d86f3 100644 --- a/Library/Operators/Other/CastOperator.php +++ b/Library/Operators/Other/CastOperator.php @@ -16,6 +16,8 @@ use Zephir\Detectors\ReadDetector; use Zephir\Exception\CompilerException; use Zephir\Expression; +use Zephir\Exception; +use Zephir\Types; use Zephir\Operators\BaseOperator; use Zephir\Statements\Let\Variable as LetVariable; @@ -29,34 +31,38 @@ class CastOperator extends BaseOperator /** * Compiles a type cast operation. * - * @param $expression + * @param array $expression * @param CompilationContext $compilationContext * * @throws CompilerException * - * @return bool|CompiledExpression + * @return CompiledExpression */ - public function compile($expression, CompilationContext $compilationContext) + public function compile(array $expression, CompilationContext $compilationContext): CompiledExpression { - $expr = new Expression($expression['right']); - $resolved = $expr->compile($compilationContext); + try { + $expr = new Expression($expression['right']); + $resolved = $expr->compile($compilationContext); + } catch (Exception $e) { + throw new CompilerException($e->getMessage(), $expression, $e->getCode(), $e); + } switch ($expression['left']) { - case 'int': + case Types::T_INT: switch ($resolved->getType()) { - case 'null': + case Types::T_NULL: return new CompiledExpression('int', 0, $expression); - case 'int': + case Types::T_INT: return new CompiledExpression('int', $resolved->getCode(), $expression); - case 'double': + case Types::T_DOUBLE: return new CompiledExpression('int', '(int) '.$resolved->getCode(), $expression); - case 'bool': + case Types::T_BOOL: return new CompiledExpression('int', $resolved->getBooleanCode(), $expression); - case 'string': + case Types::T_STRING: $compilationContext->headersManager->add('kernel/operators'); /** @@ -74,29 +80,28 @@ public function compile($expression, CompilationContext $compilationContext) return new CompiledExpression('int', 'zephir_get_intval_ex('.$symbol.')', $expression); - case 'array': + case Types::T_ARRAY: $compilationContext->headersManager->add('kernel/operators'); $symbolVariable = $compilationContext->symbolTable->getVariableForRead($resolved->getCode(), $compilationContext, $expression); $symbol = $compilationContext->backend->getVariableCode($symbolVariable); return new CompiledExpression('int', 'zephir_get_intval('.$symbol.')', $expression); - case 'variable': + case Types::T_VARIABLE: $compilationContext->headersManager->add('kernel/operators'); $symbolVariable = $compilationContext->symbolTable->getVariableForRead($resolved->getCode(), $compilationContext, $expression); switch ($symbolVariable->getType()) { - case 'int': + case Types::T_INT: + case Types::T_CHAR: return new CompiledExpression('int', $symbolVariable->getName(), $expression); - case 'double': - return new CompiledExpression('int', '(int) ('.$symbolVariable->getName().')', $expression); - - case 'bool': + case Types::T_DOUBLE: + case Types::T_BOOL: return new CompiledExpression('int', '(int) ('.$symbolVariable->getName().')', $expression); - case 'array': - case 'variable': - case 'string': + case Types::T_ARRAY: + case Types::T_VARIABLE: + case Types::T_STRING: $symbol = $compilationContext->backend->getVariableCode($symbolVariable); return new CompiledExpression('int', 'zephir_get_intval('.$symbol.')', $expression); @@ -111,35 +116,38 @@ public function compile($expression, CompilationContext $compilationContext) } break; - case 'long': + case Types::T_LONG: switch ($resolved->getType()) { - case 'int': + case Types::T_INT: return new CompiledExpression('long', $resolved->getCode(), $expression); - case 'double': + case Types::T_DOUBLE: return new CompiledExpression('long', '(long) '.$resolved->getCode(), $expression); - case 'bool': + case Types::T_BOOL: return new CompiledExpression('long', $resolved->getBooleanCode(), $expression); - case 'array': + case Types::T_ARRAY: $compilationContext->headersManager->add('kernel/operators'); $symbolVariable = $compilationContext->symbolTable->getVariableForRead($resolved->getCode(), $compilationContext, $expression); return new CompiledExpression('long', 'zephir_get_intval('.$symbolVariable->getName().')', $expression); - case 'variable': + case Types::T_VARIABLE: $compilationContext->headersManager->add('kernel/operators'); $symbolVariable = $compilationContext->symbolTable->getVariableForRead($resolved->getCode(), $compilationContext, $expression); switch ($symbolVariable->getType()) { - case 'int': + case Types::T_INT: + case Types::T_CHAR: return new CompiledExpression('long', $symbolVariable->getName(), $expression); - case 'double': + + case Types::T_DOUBLE: return new CompiledExpression('long', '(long) ('.$symbolVariable->getName().')', $expression); - case 'variable': - $symbol = $compilationContext->backend->getVariableCode($symbolVariable); + case Types::T_VARIABLE: + $symbol = $compilationContext->backend->getVariableCode($symbolVariable); return new CompiledExpression('long', 'zephir_get_intval('.$symbol.')', $expression); + default: throw new CompilerException('Cannot cast: '.$resolved->getType().'('.$symbolVariable->getType().') to '.$expression['left'], $expression); } @@ -150,67 +158,119 @@ public function compile($expression, CompilationContext $compilationContext) } break; - case 'double': + case Types::T_DOUBLE: switch ($resolved->getType()) { - case 'null': + case Types::T_NULL: return new CompiledExpression('double', 0, $expression); - case 'bool': + case Types::T_BOOL: return new CompiledExpression('double', $resolved->getBooleanCode(), $expression); - case 'double': + case Types::T_DOUBLE: return new CompiledExpression('double', $resolved->getCode(), $expression); - case 'array': + case Types::T_ARRAY: $compilationContext->headersManager->add('kernel/operators'); - $symbolVariable = $compilationContext->symbolTable->getVariableForRead($resolved->getCode(), $compilationContext, $expression); + $symbolVariable = $compilationContext->symbolTable->getVariableForRead( + $resolved->getCode(), + $compilationContext, + $expression); $symbol = $compilationContext->backend->getVariableCode($symbolVariable); return new CompiledExpression('double', 'zephir_get_doubleval('.$symbol.')', $expression); - case 'variable': + case Types::T_VARIABLE: $compilationContext->headersManager->add('kernel/operators'); - $symbolVariable = $compilationContext->symbolTable->getVariableForRead($resolved->getCode(), $compilationContext, $expression); + $symbolVariable = $compilationContext->symbolTable->getVariableForRead( + $resolved->getCode(), + $compilationContext, + $expression + ); switch ($symbolVariable->getType()) { - case 'int': + case Types::T_INT: + case Types::T_CHAR: return new CompiledExpression('double', $symbolVariable->getName(), $expression); - case 'double': - return new CompiledExpression('double', '(double) ('.$symbolVariable->getName().')', $expression); - case 'bool': - return new CompiledExpression('double', '(double) ('.$symbolVariable->getName().')', $expression); - case 'array': - case 'variable': + + case Types::T_DOUBLE: + case Types::T_BOOL: + return new CompiledExpression( + 'double', + sprintf('(double) (%s)', $symbolVariable->getName()), + $expression); + + case Types::T_ARRAY: + case Types::T_VARIABLE: $symbol = $compilationContext->backend->getVariableCode($symbolVariable); + return new CompiledExpression( + 'double', + sprintf('zephir_get_doubleval(%s)', $symbol), + $expression + ); - return new CompiledExpression('double', 'zephir_get_doubleval('.$symbol.')', $expression); default: - throw new CompilerException('Cannot cast: '.$resolved->getType().'('.$symbolVariable->getType().') to '.$expression['left'], $expression); + throw new CompilerException( + sprintf('Cannot cast: %s(%s) to %s', + $resolved->getType(), + $symbolVariable->getType(), + $expression['left'] + ), + $expression + ); } break; default: - throw new CompilerException('Cannot cast: '.$resolved->getType().' to '.$expression['left'], $expression); + throw new CompilerException( + sprintf('Cannot cast: %s to %s', + $resolved->getType(), + $expression['left'] + ), + $expression + ); } break; - case 'bool': + case Types::T_BOOL: switch ($resolved->getType()) { - case 'int': - return new CompiledExpression('bool', '(zend_bool) '.$resolved->getCode(), $expression); - case 'bool': + case Types::T_INT: + return new CompiledExpression( + 'bool', + '(zend_bool) '.$resolved->getCode(), + $expression + ); + + case Types::T_BOOL: return new CompiledExpression('bool', $resolved->getCode(), $expression); - case 'variable': + case Types::T_VARIABLE: $compilationContext->headersManager->add('kernel/operators'); - $symbolVariable = $compilationContext->symbolTable->getVariableForRead($resolved->getCode(), $compilationContext, $expression); + $symbolVariable = $compilationContext->symbolTable->getVariableForRead( + $resolved->getCode(), + $compilationContext, + $expression + ); $symbol = $compilationContext->backend->getVariableCode($symbolVariable); + if ($symbolVariable->isTemporal()) { $symbolVariable->setIdle(true); } switch ($symbolVariable->getType()) { - case 'variable': - return new CompiledExpression('bool', 'zephir_get_boolval('.$symbol.')', $expression); + case Types::T_INT: + case Types::T_CHAR: + return new CompiledExpression( + 'bool', + sprintf('(zend_bool) %s', $symbolVariable->getName()), + $expression + ); + + case Types::T_VARIABLE: + return new CompiledExpression( + 'bool', + sprintf('zephir_get_boolval(%s)', $symbol), + $expression + ); + default: throw new CompilerException('Cannot cast: '.$resolved->getType().'('.$symbolVariable->getType().') to '.$expression['left'], $expression); } @@ -221,35 +281,75 @@ public function compile($expression, CompilationContext $compilationContext) } break; - case 'char': + case Types::T_CHAR: switch ($resolved->getType()) { - case 'variable': + case Types::T_VARIABLE: $compilationContext->headersManager->add('kernel/operators'); - $tempVariable = $compilationContext->symbolTable->getTempVariableForWrite('char', $compilationContext); - $symbolVariable = $compilationContext->symbolTable->getVariableForRead($resolved->getCode(), $compilationContext, $expression); - $variableCode = $compilationContext->backend->getVariableCode($symbolVariable); - $compilationContext->codePrinter->output($tempVariable->getName().' = (char) zephir_get_intval('.$variableCode.');'); + $symbolVariable = $compilationContext->symbolTable->getVariableForRead( + $resolved->getCode(), + $compilationContext, + $expression + ); - return new CompiledExpression('variable', $tempVariable->getName(), $expression); + $tempVariable = $compilationContext->symbolTable->getTempVariableForWrite( + 'char', + $compilationContext + ); + + switch ($symbolVariable->getType()) { + case Types::T_CHAR: + $compilationContext->codePrinter->output( + sprintf('%s = %s;', $tempVariable->getName(), $symbolVariable->getName()) + ); + break; + default: + $variableCode = $compilationContext->backend->getVariableCode($symbolVariable); + $compilationContext->codePrinter->output( + sprintf( + '%s = (char) zephir_get_intval(%s);', + $tempVariable->getName(), + $variableCode + ) + ); + } + return new CompiledExpression('variable', $tempVariable->getName(), $expression); default: - throw new CompilerException('Cannot cast: '.$resolved->getType().' to '.$expression['left'], $expression); + throw new CompilerException( + sprintf('Cannot cast: %s to %s', + $resolved->getType(), + $expression['left'] + ), + $expression + ); } break; - case 'string': + case Types::T_STRING: switch ($resolved->getType()) { - case 'variable': + case Types::T_VARIABLE: $compilationContext->headersManager->add('kernel/operators'); $compilationContext->symbolTable->mustGrownStack(true); - $symbolVariable = $compilationContext->symbolTable->getTempVariable('string', $compilationContext); + + $symbolVariable = $compilationContext->symbolTable->getTempVariable( + 'string', + $compilationContext + ); + $symbolVariable->setMustInitNull(true); $symbolVariable->setIsInitialized(true, $compilationContext); $symbolVariable->increaseUses(); $symbol = $compilationContext->backend->getVariableCode($symbolVariable); - $resolvedVariable = $compilationContext->symbolTable->getVariableForRead($resolved->getCode(), $compilationContext); + $resolvedVariable = $compilationContext->symbolTable->getVariableForRead( + $resolved->getCode(), + $compilationContext + ); $resolvedCode = $compilationContext->backend->getVariableCode($resolvedVariable); - $compilationContext->codePrinter->output('zephir_get_strval('.$symbol.', '.$resolvedCode.');'); + + $compilationContext->codePrinter->output( + sprintf('zephir_get_strval(%s, %s);', $symbol, $resolvedCode) + ); + if ($symbolVariable->isTemporal()) { $symbolVariable->setIdle(true); } @@ -257,23 +357,41 @@ public function compile($expression, CompilationContext $compilationContext) return new CompiledExpression('variable', $symbolVariable->getName(), $expression); default: - throw new CompilerException('Cannot cast: '.$resolved->getType().' to '.$expression['left'], $expression); + throw new CompilerException( + sprintf('Cannot cast: %s to %s', + $resolved->getType(), + $expression['left'] + ), + $expression + ); } break; - case 'array': + case Types::T_ARRAY: switch ($resolved->getType()) { - case 'variable': + case Types::T_VARIABLE: $compilationContext->headersManager->add('kernel/operators'); $compilationContext->symbolTable->mustGrownStack(true); - $symbolVariable = $compilationContext->symbolTable->getTempVariable('array', $compilationContext); + + $symbolVariable = $compilationContext->symbolTable->getTempVariable( + 'array', + $compilationContext + ); + $symbolVariable->setMustInitNull(true); $symbolVariable->setIsInitialized(true, $compilationContext); $symbolVariable->increaseUses(); $symbol = $compilationContext->backend->getVariableCode($symbolVariable); - $resolvedVariable = $compilationContext->symbolTable->getVariableForRead($resolved->getCode(), $compilationContext); + $resolvedVariable = $compilationContext->symbolTable->getVariableForRead( + $resolved->getCode(), + $compilationContext + ); $resolvedCode = $compilationContext->backend->getVariableCode($resolvedVariable); - $compilationContext->codePrinter->output('zephir_get_arrval('.$symbol.', '.$resolvedCode.');'); + + $compilationContext->codePrinter->output( + sprintf('zephir_get_arrval(%s, %s);', $symbol, $resolvedCode) + ); + if ($symbolVariable->isTemporal()) { $symbolVariable->setIdle(true); } @@ -281,18 +399,24 @@ public function compile($expression, CompilationContext $compilationContext) return new CompiledExpression('variable', $symbolVariable->getName(), $expression); default: - throw new CompilerException('Cannot cast: '.$resolved->getType().' to '.$expression['left'], $expression); + throw new CompilerException( + sprintf('Cannot cast: %s to %s', + $resolved->getType(), + $expression['left'] + ), + $expression + ); } break; - case 'object': + case Types::T_OBJECT: switch ($resolved->getType()) { - case 'int': - case 'double': - case 'bool': - case 'null': - case 'string': - case 'array': + case Types::T_INT: + case Types::T_DOUBLE: + case Types::T_BOOL: + case Types::T_NULL: + case Types::T_STRING: + case Types::T_ARRAY: $compilationContext->headersManager->add('kernel/operators'); $compilationContext->symbolTable->mustGrownStack(true); $symbolVariable = $compilationContext->symbolTable->getTempVariable('variable', $compilationContext); @@ -311,7 +435,7 @@ public function compile($expression, CompilationContext $compilationContext) return new CompiledExpression('variable', $symbolVariable->getName(), $expression); - case 'variable': + case Types::T_VARIABLE: $compilationContext->headersManager->add('kernel/operators'); $symbolVariable = $compilationContext->symbolTable->getVariableForRead($resolved->getCode(), $compilationContext, $expression); $symbol = $compilationContext->backend->getVariableCode($symbolVariable); @@ -329,7 +453,13 @@ public function compile($expression, CompilationContext $compilationContext) break; default: - throw new CompilerException('Cannot cast: '.$resolved->getType().' to '.$expression['left'], $expression); + throw new CompilerException( + sprintf('Cannot cast: %s to %s', + $resolved->getType(), + $expression['left'] + ), + $expression + ); } } } diff --git a/test/cast.zep b/test/cast.zep index 6dd2725b1e..5369a078a2 100644 --- a/test/cast.zep +++ b/test/cast.zep @@ -7,7 +7,27 @@ namespace Test; class Cast { - /** To int cast */ + // To char cast + + /** + * @see https://github.com/phalcon/zephir/issues/1988 + */ + public function testCharCastFromChar() -> char + { + char a = 'a'; + return (char) a; + } + + /** + * @see https://github.com/phalcon/zephir/issues/1988 + */ + public function testCharCastFromVariableChar() -> char + { + var a = 'A'; + return (char) a; + } + + // To int cast public function testIntCastFromFloat() -> int { @@ -20,6 +40,15 @@ class Cast return (int) a; } + /** + * @see https://github.com/phalcon/zephir/issues/1988 + */ + public function testIntCastFromVariableChar() -> int + { + var a = 'a'; + return (int) a; + } + public function testIntCastFromBooleanTrue() -> int { return (int) true; @@ -113,7 +142,18 @@ class Cast return (int) a; } - /** To float cast */ + // To long cast + + /** + * @see https://github.com/phalcon/zephir/issues/1988 + */ + public function testLongCastFromVariableChar() -> long + { + char a = 'A'; + return (long) a; + } + + // To float cast public function testFloatCastFromFloat() -> float { @@ -201,7 +241,18 @@ class Cast return (float) a; } - /** To boolean cast */ + // To double cast + + /** + * @see https://github.com/phalcon/zephir/issues/1988 + */ + public function testDoubleCastFromVariableChar() -> double + { + char a = 'A'; + return (double) a; + } + + // To boolean cast public function testBooleanCastFromIntTrue1() -> boolean { @@ -243,7 +294,16 @@ class Cast return (boolean) a; } - /** To object cast */ + /** + * @see https://github.com/phalcon/zephir/issues/1988 + */ + public function testBooleanCastFromVariableChar() -> boolean + { + char a = 'A'; + return (boolean) a; + } + + // To object cast public function testObjectCastFromInt() -> var { @@ -312,58 +372,58 @@ class Cast return id; } - /** To array cast */ + // To array cast - public function testArrayCastFromVariableArray() + public function testArrayCastFromVariableArray() -> array { var uids = [1, "2", 3]; let uids = (array) uids; return uids; } - public function testArrayCastFromVariableTrue() + public function testArrayCastFromVariableTrue() -> array { var uids = true; let uids = (array) uids; return uids; } - public function testArrayCastFromVariableFalse() + public function testArrayCastFromVariableFalse() -> array { var uids = false; let uids = (array) uids; return uids; } - public function testArrayCastFromVariableNull() + public function testArrayCastFromVariableNull() -> array { var uids = null; let uids = (array) uids; return uids; } - public function testArrayCastFromVariableInteger() + public function testArrayCastFromVariableInteger() -> array { var uids = 1; let uids = (array) uids; return uids; } - public function testArrayCastFromVariableFloat() + public function testArrayCastFromVariableFloat() -> array { var uids = 1.1; let uids = (array) uids; return uids; } - public function testArrayCastFromVariableString() + public function testArrayCastFromVariableString() -> array { var uids = "aaa"; let uids = (array) uids; return uids; } - public function testArrayCastFromVariableStdClass() + public function testArrayCastFromVariableStdClass() -> array { var uids; let uids = new \StdClass; diff --git a/unit-tests/Extension/CastTest.php b/unit-tests/Extension/CastTest.php index 1496cd5fe1..34e30b8a0e 100644 --- a/unit-tests/Extension/CastTest.php +++ b/unit-tests/Extension/CastTest.php @@ -31,11 +31,25 @@ public function tearDown() $this->test = null; } + /** + * @see https://github.com/phalcon/zephir/issues/1988 + */ + public function testCharCast() + { + /* + * Variable types + */ + + $this->assertSame(97, $this->test->testCharCastFromChar()); + $this->assertSame(65, $this->test->testCharCastFromVariableChar()); + } + public function testIntCast() { /* * Value */ + $this->assertSame(5, $this->test->testIntCastFromFloat()); $this->assertSame(1, $this->test->testIntCastFromBooleanTrue()); $this->assertSame(0, $this->test->testIntCastFromBooleanFalse()); @@ -48,11 +62,15 @@ public function testIntCast() /* * Variable types */ + $this->assertSame(5, $this->test->testIntCastFromVariableFloat()); $this->assertSame(1, $this->test->testIntCastFromVariableBooleanTrue()); $this->assertSame(0, $this->test->testIntCastFromVariableBooleanFalse()); $this->assertSame(0, $this->test->testIntCastFromVariableNull()); + // https://github.com/phalcon/zephir/issues/1988 + $this->assertSame(97, $this->test->testIntCastFromVariableChar()); + $this->assertSame(0, $this->test->testIntCastFromVariableString()); $this->assertSame((int) 'test', $this->test->testIntCastFromParameterString('test')); $this->assertSame((int) '1', $this->test->testIntCastFromParameterString('1')); @@ -65,6 +83,16 @@ public function testIntCast() $this->assertSame(1, $this->test->testIntCastFromVariableStdClass()); } + public function testLongCast() + { + /* + * Variable types + */ + + // https://github.com/phalcon/zephir/issues/1988 + $this->assertSame(65, $this->test->testLongCastFromVariableChar()); + } + public function testFloatCast() { $this->assertSame(5.0, $this->test->testFloatCastFromFloat()); @@ -84,15 +112,37 @@ public function testFloatCast() $this->assertSame(1.0, $this->test->testFloatCastFromVariableStdClass()); } + public function testDoubleCast() + { + /* + * Variable types + */ + + // https://github.com/phalcon/zephir/issues/1988 + $this->assertSame(65, $this->test->testLongCastFromVariableChar()); + } + public function testBooleanCast() { + /* + * Value + */ + $this->assertTrue($this->test->testBooleanCastFromIntTrue1()); $this->assertTrue($this->test->testBooleanCastFromIntTrue2()); $this->assertFalse($this->test->testBooleanCastFromIntFalse()); + + /* + * Variable types + */ + $this->assertTrue($this->test->testBooleanCastFromObject()); $this->assertFalse($this->test->testBooleanCastFromEmptyArray()); $this->assertTrue($this->test->testBooleanCastFromArray()); $this->assertFalse($this->test->testBooleanCastFromNull()); + + // https://github.com/phalcon/zephir/issues/1988 + $this->assertTrue($this->test->testBooleanCastFromVariableChar()); } public function testObjectCast()