Skip to content

Commit 7f5582e

Browse files
committed
new static() is safe only with final constructor or in final class (level 0)
1 parent 74b38e8 commit 7f5582e

14 files changed

Lines changed: 193 additions & 10 deletions

conf/config.level0.neon

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ rules:
1919
- PHPStan\Rules\Classes\ExistingClassInClassExtendsRule
2020
- PHPStan\Rules\Classes\ExistingClassInTraitUseRule
2121
- PHPStan\Rules\Classes\InstantiationRule
22+
- PHPStan\Rules\Classes\NewStaticRule
2223
- PHPStan\Rules\Functions\CallToFunctionParametersRule
2324
- PHPStan\Rules\Functions\ExistingClassesInArrowFunctionTypehintsRule
2425
- PHPStan\Rules\Functions\ExistingClassesInClosureTypehintsRule
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Rules\Classes;
4+
5+
use PhpParser\Node;
6+
use PHPStan\Analyser\Scope;
7+
use PHPStan\Reflection\Php\PhpMethodReflection;
8+
use PHPStan\Rules\Rule;
9+
10+
class NewStaticRule implements Rule
11+
{
12+
13+
public function getNodeType(): string
14+
{
15+
return Node\Expr\New_::class;
16+
}
17+
18+
/**
19+
* @param \PhpParser\Node\Expr\New_ $node
20+
* @param \PHPStan\Analyser\Scope $scope
21+
* @return string[]
22+
*/
23+
public function processNode(Node $node, Scope $scope): array
24+
{
25+
if (!$node->class instanceof Node\Name) {
26+
return [];
27+
}
28+
29+
if (!$scope->isInClass()) {
30+
return [];
31+
}
32+
33+
if (strtolower($node->class->toString()) !== 'static') {
34+
return [];
35+
}
36+
37+
$classReflection = $scope->getClassReflection();
38+
if ($classReflection->isFinal()) {
39+
return [];
40+
}
41+
42+
$messages = [
43+
"Unsafe usage of new static().\n💡 Consider making the class or the constructor final.",
44+
];
45+
if (!$classReflection->hasConstructor()) {
46+
return $messages;
47+
}
48+
49+
$constructor = $classReflection->getConstructor();
50+
if ($constructor->getPrototype()->getDeclaringClass()->isInterface()) {
51+
return [];
52+
}
53+
54+
$isFinal = $constructor instanceof PhpMethodReflection && $constructor->isFinal()->yes();
55+
if (!$isFinal) {
56+
return $messages;
57+
}
58+
59+
return [];
60+
}
61+
62+
}

src/Type/ArrayType.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -300,7 +300,7 @@ public function traverse(callable $cb): Type
300300
$itemType = $cb($this->itemType);
301301

302302
if ($keyType !== $this->keyType || $itemType !== $this->itemType) {
303-
return new static($keyType, $itemType);
303+
return new self($keyType, $itemType);
304304
}
305305

306306
return $this;

src/Type/BooleanType.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ public function setOffsetValueType(?Type $offsetType, Type $valueType): Type
9292
*/
9393
public static function __set_state(array $properties): Type
9494
{
95-
return new static();
95+
return new self();
9696
}
9797

9898
}

src/Type/CallableType.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -263,7 +263,7 @@ public function traverse(callable $cb): Type
263263
);
264264
}, $this->getParameters());
265265

266-
return new static(
266+
return new self(
267267
$parameters,
268268
$cb($this->getReturnType()),
269269
$this->isVariadic()

src/Type/ClosureType.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -294,7 +294,7 @@ public function getReturnType(): Type
294294

295295
public function traverse(callable $cb): Type
296296
{
297-
return new static(
297+
return new self(
298298
array_map(static function (NativeParameterReflection $param) use ($cb): NativeParameterReflection {
299299
$defaultValue = $param->getDefaultValue();
300300
return new NativeParameterReflection(

src/Type/Generic/TemplateMixedType.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ public function subtract(Type $type): Type
152152
$type = TypeCombinator::union($this->getSubtractedType(), $type);
153153
}
154154

155-
return new static(
155+
return new self(
156156
$this->scope,
157157
$this->strategy,
158158
$this->name,

src/Type/IterableType.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ public function traverse(callable $cb): Type
200200
$itemType = $cb($this->itemType);
201201

202202
if ($keyType !== $this->keyType || $itemType !== $this->itemType) {
203-
return new static($keyType, $itemType);
203+
return new self($keyType, $itemType);
204204
}
205205

206206
return $this;

src/Type/MixedType.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,7 @@ public function subtract(Type $type): Type
257257
$type = TypeCombinator::union($this->subtractedType, $type);
258258
}
259259

260-
return new static($this->isExplicitMixed, $type);
260+
return new self($this->isExplicitMixed, $type);
261261
}
262262

263263
public function getTypeWithoutSubtractedType(): Type

src/Type/ObjectType.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -719,7 +719,7 @@ public function traverse(callable $cb): Type
719719
$subtractedType = $this->subtractedType !== null ? $cb($this->subtractedType) : null;
720720

721721
if ($subtractedType !== $this->subtractedType) {
722-
return new static(
722+
return new self(
723723
$this->className,
724724
$subtractedType
725725
);

0 commit comments

Comments
 (0)