Skip to content

Commit d15eba3

Browse files
authored
Processing ArithmeticErrors with internal operators (fixed #77) (#82)
1 parent 93aeda6 commit d15eba3

File tree

3 files changed

+136
-0
lines changed

3 files changed

+136
-0
lines changed

src/Rules/ThrowsPhpDocRule.php

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
namespace Pepakriz\PHPStanExceptionRules\Rules;
44

5+
use ArithmeticError;
6+
use DivisionByZeroError;
57
use Iterator;
68
use IteratorAggregate;
79
use Pepakriz\PHPStanExceptionRules\CheckedExceptionService;
@@ -35,6 +37,7 @@
3537
use PHPStan\Reflection\ThrowableReflection;
3638
use PHPStan\Rules\Rule;
3739
use PHPStan\ShouldNotHappenException;
40+
use PHPStan\Type\NeverType;
3841
use PHPStan\Type\ObjectType;
3942
use PHPStan\Type\Type;
4043
use PHPStan\Type\TypeCombinator;
@@ -185,6 +188,22 @@ public function processNode(Node $node, Scope $scope): array
185188
return $this->processFuncCall($node, $scope);
186189
}
187190

191+
if ($node instanceof Expr\BinaryOp\Div || $node instanceof Expr\BinaryOp\Mod) {
192+
return $this->processDiv($node->right, $scope);
193+
}
194+
195+
if ($node instanceof Expr\AssignOp\Div || $node instanceof Expr\AssignOp\Mod) {
196+
return $this->processDiv($node->expr, $scope);
197+
}
198+
199+
if ($node instanceof Expr\BinaryOp\ShiftLeft || $node instanceof Expr\BinaryOp\ShiftRight) {
200+
return $this->processShift($node->right, $scope);
201+
}
202+
203+
if ($node instanceof Expr\AssignOp\ShiftLeft || $node instanceof Expr\AssignOp\ShiftRight) {
204+
return $this->processShift($node->expr, $scope);
205+
}
206+
188207
return [];
189208
}
190209

@@ -599,6 +618,53 @@ private function processFuncCall(FuncCall $node, Scope $scope): array
599618
return $this->processThrowsTypes($throwType);
600619
}
601620

621+
/**
622+
* @return string[]
623+
*/
624+
private function processDiv(Expr $divisor, Scope $scope): array
625+
{
626+
$divisionByZero = false;
627+
$divisorType = $scope->getType($divisor);
628+
foreach (TypeUtils::getConstantScalars($divisorType) as $constantScalarType) {
629+
if ($constantScalarType->getValue() === 0) {
630+
$divisionByZero = true;
631+
}
632+
633+
$divisorType = TypeCombinator::remove($divisorType, $constantScalarType);
634+
}
635+
636+
if (!$divisorType instanceof NeverType) {
637+
return $this->processThrowsTypes(new ObjectType(ArithmeticError::class));
638+
}
639+
640+
if ($divisionByZero) {
641+
return $this->processThrowsTypes(new ObjectType(DivisionByZeroError::class));
642+
}
643+
644+
return [];
645+
}
646+
647+
/**
648+
* @return string[]
649+
*/
650+
private function processShift(Expr $value, Scope $scope): array
651+
{
652+
$valueType = $scope->getType($value);
653+
foreach (TypeUtils::getConstantScalars($valueType) as $constantScalarType) {
654+
if ($constantScalarType->getValue() < 0) {
655+
return $this->processThrowsTypes(new ObjectType(ArithmeticError::class));
656+
}
657+
658+
$valueType = TypeCombinator::remove($valueType, $constantScalarType);
659+
}
660+
661+
if (!$valueType instanceof NeverType) {
662+
return $this->processThrowsTypes(new ObjectType(ArithmeticError::class));
663+
}
664+
665+
return [];
666+
}
667+
602668
/**
603669
* @param Name|Expr|ClassLike $class
604670
* @param string[] $methods

tests/src/Rules/PhpInternalsTest.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,4 +62,9 @@ public function testPhpInternalFunctionsPhp73(): void
6262
$this->analyse(__DIR__ . '/data/throws-php-internal-functions-php7.3.php');
6363
}
6464

65+
public function testPhpInternalOperators(): void
66+
{
67+
$this->analyse(__DIR__ . '/data/throws-php-internal-operators.php');
68+
}
69+
6570
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Pepakriz\PHPStanExceptionRules\Rules\PhpInternalOperators;
4+
5+
use function rand;
6+
7+
class Example
8+
{
9+
10+
public function test(): void
11+
{
12+
$a = 5;
13+
/** @var int $b */
14+
$b = rand(0, 99999);
15+
16+
echo $a / 1;
17+
echo $a / 0; // error: Missing @throws DivisionByZeroError annotation
18+
echo $a / (rand(0, 1) === 0 ? 0 : 1); // error: Missing @throws DivisionByZeroError annotation
19+
echo $a / $b; // error: Missing @throws ArithmeticError annotation
20+
echo (rand(0, 1) === 0 ? 20 : 10) / (rand(0, 1) === 0 ? 5 : 10);
21+
22+
echo $a /= 1;
23+
echo $a /= 0; // error: Missing @throws DivisionByZeroError annotation
24+
echo $a /= (rand(0, 1) === 0 ? 0 : 1); // error: Missing @throws DivisionByZeroError annotation
25+
echo $a /= $b; // error: Missing @throws ArithmeticError annotation
26+
echo $a /= (rand(0, 1) === 0 ? 5 : 10);
27+
28+
echo $a % 1;
29+
echo $a % 0; // error: Missing @throws DivisionByZeroError annotation
30+
echo $a % (rand(0, 1) === 0 ? 0 : 1); // error: Missing @throws DivisionByZeroError annotation
31+
echo $a % $b; // error: Missing @throws ArithmeticError annotation
32+
echo (rand(0, 1) === 0 ? 20 : 10) % (rand(0, 1) === 0 ? 5 : 10);
33+
34+
echo $a %= 1;
35+
echo $a %= 0; // error: Missing @throws DivisionByZeroError annotation
36+
echo $a %= (rand(0, 1) === 0 ? 0 : 1); // error: Missing @throws DivisionByZeroError annotation
37+
echo $a %= $b; // error: Missing @throws ArithmeticError annotation
38+
echo $a %= (rand(0, 1) === 0 ? 5 : 10);
39+
40+
echo $a << 0;
41+
echo $a << -3; // error: Missing @throws ArithmeticError annotation
42+
echo $a << (rand(0, 1) === 0 ? 0 : -1); // error: Missing @throws ArithmeticError annotation
43+
echo $a << $b; // error: Missing @throws ArithmeticError annotation
44+
echo $a << (rand(0, 1) === 0 ? 0 : 1);
45+
46+
echo $a <<= 0;
47+
echo $a <<= -3; // error: Missing @throws ArithmeticError annotation
48+
echo $a <<= (rand(0, 1) === 0 ? 0 : -1); // error: Missing @throws ArithmeticError annotation
49+
echo $a <<= $b; // error: Missing @throws ArithmeticError annotation
50+
echo $a <<= (rand(0, 1) === 0 ? 0 : 1);
51+
52+
echo $a >> 0;
53+
echo $a >> -3; // error: Missing @throws ArithmeticError annotation
54+
echo $a >> (rand(0, 1) === 0 ? 0 : -1); // error: Missing @throws ArithmeticError annotation
55+
echo $a >> $b; // error: Missing @throws ArithmeticError annotation
56+
echo $a >> (rand(0, 1) === 0 ? 0 : 1);
57+
58+
echo $a >>= 0;
59+
echo $a >>= -3; // error: Missing @throws ArithmeticError annotation
60+
echo $a >>= (rand(0, 1) === 0 ? 0 : -1); // error: Missing @throws ArithmeticError annotation
61+
echo $a >>= $b; // error: Missing @throws ArithmeticError annotation
62+
echo $a >>= (rand(0, 1) === 0 ? 0 : 1);
63+
}
64+
65+
}

0 commit comments

Comments
 (0)