Skip to content

Commit f9ba0ef

Browse files
committed
Fix infinite hang when analysing src/wp-includes/user.php in wordpress-develop
1 parent 17d02e6 commit f9ba0ef

File tree

3 files changed

+126
-4
lines changed

3 files changed

+126
-4
lines changed

phpstan-baseline.neon

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,12 @@ parameters:
6666
count: 2
6767
path: src/Analyser/MutatingScope.php
6868

69+
-
70+
rawMessage: Doing instanceof PHPStan\Type\IntersectionType is error-prone and deprecated.
71+
identifier: phpstanApi.instanceofType
72+
count: 1
73+
path: src/Analyser/MutatingScope.php
74+
6975
-
7076
rawMessage: 'Parameter #2 $node of method PHPStan\BetterReflection\SourceLocator\Ast\Strategy\NodeToReflection::__invoke() expects PhpParser\Node\Expr\ArrowFunction|PhpParser\Node\Expr\Closure|PhpParser\Node\Expr\FuncCall|PhpParser\Node\Stmt\Class_|PhpParser\Node\Stmt\Const_|PhpParser\Node\Stmt\Enum_|PhpParser\Node\Stmt\Function_|PhpParser\Node\Stmt\Interface_|PhpParser\Node\Stmt\Trait_, PhpParser\Node\Stmt\ClassLike given.'
7177
identifier: argument.type

src/Analyser/MutatingScope.php

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,8 @@ class MutatingScope implements Scope, NodeCallbackInvoker
139139
public const KEEP_VOID_ATTRIBUTE_NAME = 'keepVoid';
140140
private const CONTAINS_SUPER_GLOBAL_ATTRIBUTE_NAME = 'containsSuperGlobal';
141141

142+
private const COMPLEX_UNION_TYPE_MEMBER_LIMIT = 8;
143+
142144
/** @var Type[] */
143145
private array $resolvedTypes = [];
144146

@@ -2743,10 +2745,12 @@ public function specifyExpressionType(Expr $expr, Type $type, Type $nativeType,
27432745
}
27442746

27452747
if ($dimType instanceof ConstantIntegerType || $dimType instanceof ConstantStringType) {
2746-
$varType = TypeCombinator::intersect(
2747-
$varType,
2748-
new HasOffsetValueType($dimType, $type),
2749-
);
2748+
if (!$this->isComplexUnionType($varType)) {
2749+
$varType = TypeCombinator::intersect(
2750+
$varType,
2751+
new HasOffsetValueType($dimType, $type),
2752+
);
2753+
}
27502754
}
27512755

27522756
$scope = $scope->specifyExpressionType(
@@ -3071,9 +3075,36 @@ private function setExpressionCertainty(Expr $expr, TrinaryLogic $certainty): se
30713075
);
30723076
}
30733077

3078+
/**
3079+
* Returns true when the type is a large union with non-trivial
3080+
* (IntersectionType) members — a sign of HasOffsetValueType
3081+
* combinatorial growth from array|object offset access patterns.
3082+
* Operating on such types is expensive and should be skipped.
3083+
*/
3084+
private function isComplexUnionType(Type $type): bool
3085+
{
3086+
if (!$type instanceof UnionType) {
3087+
return false;
3088+
}
3089+
$types = $type->getTypes();
3090+
if (count($types) <= self::COMPLEX_UNION_TYPE_MEMBER_LIMIT) {
3091+
return false;
3092+
}
3093+
foreach ($types as $member) {
3094+
if ($member instanceof IntersectionType) {
3095+
return true;
3096+
}
3097+
}
3098+
return false;
3099+
}
3100+
30743101
public function addTypeToExpression(Expr $expr, Type $type): self
30753102
{
30763103
$originalExprType = $this->getType($expr);
3104+
if ($this->isComplexUnionType($originalExprType)) {
3105+
return $this;
3106+
}
3107+
30773108
$nativeType = $this->getNativeType($expr);
30783109

30793110
if ($originalExprType->equals($nativeType)) {
@@ -3100,6 +3131,10 @@ public function removeTypeFromExpression(Expr $expr, Type $typeToRemove): self
31003131
return $this;
31013132
}
31023133

3134+
if ($this->isComplexUnionType($exprType)) {
3135+
return $this;
3136+
}
3137+
31033138
return $this->specifyExpressionType(
31043139
$expr,
31053140
TypeCombinator::remove($exprType, $typeToRemove),
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
<?php
2+
3+
namespace BenchComplexUnion;
4+
5+
class WP_User {
6+
/** @return array<string, mixed> */
7+
public function to_array(): array { return []; }
8+
}
9+
10+
/** @var array<string, list<callable>> */
11+
$hooks = [];
12+
13+
/** @return mixed */
14+
function apply_filters(string $hook, mixed $value, mixed ...$args): mixed {
15+
global $hooks;
16+
if (isset($hooks[$hook])) { foreach ($hooks[$hook] as $cb) { $value = $cb($value); } }
17+
return $value;
18+
}
19+
20+
function do_action(string $hook, mixed ...$args): void {
21+
global $hooks;
22+
if (isset($hooks[$hook])) { foreach ($hooks[$hook] as $cb) { $cb(...$args); } }
23+
}
24+
25+
/** @param array|object $data */
26+
function insert($data): int {
27+
if ($data instanceof \stdClass) { $data = get_object_vars($data); }
28+
elseif ($data instanceof WP_User) { $data = $data->to_array(); }
29+
30+
if (!empty($data["ID"])) {
31+
$id = (int)$data["ID"];
32+
$update = true;
33+
$old_1 = $data["old_1"] ?? "";
34+
$old_2 = $data["old_2"] ?? "";
35+
$old_3 = $data["old_3"] ?? "";
36+
$old_4 = $data["old_4"] ?? "";
37+
$old_5 = $data["old_5"] ?? "";
38+
$old_6 = $data["old_6"] ?? "";
39+
$old_7 = $data["old_7"] ?? "";
40+
$old_8 = $data["old_8"] ?? "";
41+
$old_9 = $data["old_9"] ?? "";
42+
$old_10 = $data["old_10"] ?? "";
43+
} else {
44+
$id = 0;
45+
$update = false;
46+
$old_1 = "";
47+
$old_2 = "";
48+
$old_3 = "";
49+
$old_4 = "";
50+
$old_5 = "";
51+
$old_6 = "";
52+
$old_7 = "";
53+
$old_8 = "";
54+
$old_9 = "";
55+
$old_10 = "";
56+
}
57+
58+
$meta_1 = apply_filters("pre_f1", empty($data["f1"]) ? "" : $data["f1"]);
59+
$meta_2 = apply_filters("pre_f2", empty($data["f2"]) ? "" : $data["f2"]);
60+
$meta_3 = apply_filters("pre_f3", empty($data["f3"]) ? "" : $data["f3"]);
61+
$meta_4 = apply_filters("pre_f4", empty($data["f4"]) ? "" : $data["f4"]);
62+
$meta_5 = apply_filters("pre_f5", empty($data["f5"]) ? "" : $data["f5"]);
63+
$meta_6 = apply_filters("pre_f6", empty($data["f6"]) ? "" : $data["f6"]);
64+
$meta_7 = apply_filters("pre_f7", empty($data["f7"]) ? "" : $data["f7"]);
65+
$meta_8 = apply_filters("pre_f8", empty($data["f8"]) ? "" : $data["f8"]);
66+
$meta_9 = apply_filters("pre_f9", empty($data["f9"]) ? "" : $data["f9"]);
67+
$meta_10 = apply_filters("pre_f10", empty($data["f10"]) ? "" : $data["f10"]);
68+
$meta_11 = apply_filters("pre_f11", empty($data["f11"]) ? "" : $data["f11"]);
69+
$meta_12 = apply_filters("pre_f12", empty($data["f12"]) ? "" : $data["f12"]);
70+
$meta_13 = apply_filters("pre_f13", empty($data["f13"]) ? "" : $data["f13"]);
71+
$meta_14 = apply_filters("pre_f14", empty($data["f14"]) ? "" : $data["f14"]);
72+
$meta_15 = apply_filters("pre_f15", empty($data["f15"]) ? "" : $data["f15"]);
73+
$meta_16 = apply_filters("pre_f16", empty($data["f16"]) ? "" : $data["f16"]);
74+
$meta_17 = apply_filters("pre_f17", empty($data["f17"]) ? "" : $data["f17"]);
75+
$meta_18 = apply_filters("pre_f18", empty($data["f18"]) ? "" : $data["f18"]);
76+
$meta_19 = apply_filters("pre_f19", empty($data["f19"]) ? "" : $data["f19"]);
77+
$meta_20 = apply_filters("pre_f20", empty($data["f20"]) ? "" : $data["f20"]);
78+
79+
do_action("after_insert", $id, $data);
80+
return $id;
81+
}

0 commit comments

Comments
 (0)