Skip to content

Commit 4e10ac9

Browse files
committed
Keep the function call stack in the other scope derivations
mergeWith(), enter/exitExpressionAssign(), set/unsetAllowedUndefinedExpression(), processFinallyScope(), processAlwaysIterableForeachScopeWithoutPollute(), generalizeWithVariableState(), withRecordedStatementDelta() and the Closure::bind()/call() derivations rebuilt the scope with an empty $inFunctionCallsStack. Inside a closure passed as an argument, any if/else, ternary, ??, isset(), loop or try/finally emptied Scope::getFunctionCallStack() for the rest of the closure. They now keep the stack; only entering a call, a class, a function or a namespace changes it.
1 parent fc673f2 commit 4e10ac9

5 files changed

Lines changed: 134 additions & 16 deletions

File tree

‎src/Analyser/MutatingScope.php‎

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1518,7 +1518,7 @@ public function withRecordedStatementDelta(self $recordedEntry, self $recordedEx
15181518
$conditionalExpressions,
15191519
[],
15201520
[],
1521-
[],
1521+
$this->inFunctionCallsStack,
15221522
$this->inFirstLevelStatement,
15231523
$recordedExit->afterExtractCall,
15241524
);
@@ -2334,6 +2334,7 @@ public function enterClosureBind(?Type $thisType, ?Type $nativeThisType, array $
23342334
$this->conditionalExpressions,
23352335
$scopeClasses,
23362336
$this->anonymousFunctionReflection,
2337+
inFunctionCallsStack: $this->inFunctionCallsStack,
23372338
templateArgumentFrame: $this->templateArgumentFrame,
23382339
templateArgumentConstraints: $this->templateArgumentConstraints,
23392340
);
@@ -2365,6 +2366,7 @@ public function restoreOriginalScopeAfterClosureBind(self $originalScope): self
23652366
$this->conditionalExpressions,
23662367
$originalScope->inClosureBindScopeClasses,
23672368
$this->anonymousFunctionReflection,
2369+
inFunctionCallsStack: $this->inFunctionCallsStack,
23682370
templateArgumentFrame: $this->templateArgumentFrame,
23692371
templateArgumentConstraints: $this->templateArgumentConstraints,
23702372
);
@@ -2436,6 +2438,7 @@ public function enterClosureCall(Type $thisType, Type $nativeThisType): self
24362438
$this->conditionalExpressions,
24372439
$thisType->getObjectClassNames(),
24382440
$this->anonymousFunctionReflection,
2441+
inFunctionCallsStack: $this->inFunctionCallsStack,
24392442
templateArgumentFrame: $this->templateArgumentFrame,
24402443
templateArgumentConstraints: $this->templateArgumentConstraints,
24412444
);
@@ -3044,7 +3047,7 @@ public function enterExpressionAssign(Expr $expr, bool $isPlainWrite = true): se
30443047
$this->isInFirstLevelStatement(),
30453048
$currentlyAssignedExpressions,
30463049
$this->currentlyAllowedUndefinedExpressions,
3047-
[],
3050+
$this->inFunctionCallsStack,
30483051
$this->afterExtractCall,
30493052
$this->parentScope,
30503053
$this->nativeTypesPromoted,
@@ -3075,7 +3078,7 @@ public function exitExpressionAssign(Expr $expr): self
30753078
$this->isInFirstLevelStatement(),
30763079
$currentlyAssignedExpressions,
30773080
$this->currentlyAllowedUndefinedExpressions,
3078-
[],
3081+
$this->inFunctionCallsStack,
30793082
$this->afterExtractCall,
30803083
$this->parentScope,
30813084
$this->nativeTypesPromoted,
@@ -3136,7 +3139,7 @@ public function setAllowedUndefinedExpression(Expr $expr): self
31363139
$this->isInFirstLevelStatement(),
31373140
$this->currentlyAssignedExpressions,
31383141
$currentlyAllowedUndefinedExpressions,
3139-
[],
3142+
$this->inFunctionCallsStack,
31403143
$this->afterExtractCall,
31413144
$this->parentScope,
31423145
$this->nativeTypesPromoted,
@@ -3167,7 +3170,7 @@ public function unsetAllowedUndefinedExpression(Expr $expr): self
31673170
$this->isInFirstLevelStatement(),
31683171
$this->currentlyAssignedExpressions,
31693172
$currentlyAllowedUndefinedExpressions,
3170-
[],
3173+
$this->inFunctionCallsStack,
31713174
$this->afterExtractCall,
31723175
$this->parentScope,
31733176
$this->nativeTypesPromoted,
@@ -4454,7 +4457,7 @@ private function mergeWithVariableState(?self $otherScope, bool $preserveVacuous
44544457
$conditionalExpressions,
44554458
[],
44564459
[],
4457-
[],
4460+
$this->inFunctionCallsStack,
44584461
$this->inFirstLevelStatement,
44594462
$this->afterExtractCall && $otherScope->afterExtractCall,
44604463
);
@@ -4733,7 +4736,7 @@ public function processFinallyScope(self $finallyScope, self $originalFinallySco
47334736
$this->inFirstLevelStatement,
47344737
[],
47354738
[],
4736-
[],
4739+
$this->inFunctionCallsStack,
47374740
$this->afterExtractCall,
47384741
$this->parentScope,
47394742
$this->nativeTypesPromoted,
@@ -4882,7 +4885,7 @@ public function processAlwaysIterableForeachScopeWithoutPollute(self $finalScope
48824885
$this->inFirstLevelStatement,
48834886
[],
48844887
[],
4885-
[],
4888+
$this->inFunctionCallsStack,
48864889
$this->afterExtractCall,
48874890
$this->parentScope,
48884891
$this->nativeTypesPromoted,
@@ -4947,7 +4950,7 @@ private function generalizeWithVariableState(self $otherScope, ?array $writableV
49474950
$this->inFirstLevelStatement,
49484951
[],
49494952
[],
4950-
[],
4953+
$this->inFunctionCallsStack,
49514954
$this->afterExtractCall,
49524955
$this->parentScope,
49534956
$this->nativeTypesPromoted,

‎tests/PHPStan/Rules/ScopeFunctionCallStackRuleTest.php‎

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,50 @@ public function testBug15377(): void
7979
'Bug15377\inTransaction',
8080
52,
8181
],
82+
[
83+
'Bug15377\inTransaction',
84+
62,
85+
],
86+
[
87+
'Bug15377\inTransaction',
88+
63,
89+
],
90+
[
91+
'Bug15377\inTransaction',
92+
64,
93+
],
94+
[
95+
'Bug15377\inTransaction',
96+
65,
97+
],
98+
[
99+
"Bug15377\\inTransaction\nBug15377\\takesTwo",
100+
66,
101+
],
102+
[
103+
'Bug15377\inTransaction',
104+
67,
105+
],
106+
[
107+
'Bug15377\inTransaction',
108+
68,
109+
],
110+
[
111+
'Bug15377\inTransaction',
112+
69,
113+
],
114+
[
115+
'Bug15377\inTransaction',
116+
70,
117+
],
118+
[
119+
'Bug15377\inTransaction',
120+
71,
121+
],
122+
[
123+
'Bug15377\inTransaction',
124+
72,
125+
],
82126
]);
83127
}
84128

‎tests/PHPStan/Rules/ScopeFunctionCallStackWithParametersRuleTest.php‎

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,50 @@ public function testBug15377(): void
8181
'Bug15377\inTransaction ($callback)',
8282
52,
8383
],
84+
[
85+
'Bug15377\inTransaction ($callback)',
86+
62,
87+
],
88+
[
89+
'Bug15377\inTransaction ($callback)',
90+
63,
91+
],
92+
[
93+
'Bug15377\inTransaction ($callback)',
94+
64,
95+
],
96+
[
97+
'Bug15377\inTransaction ($callback)',
98+
65,
99+
],
100+
[
101+
"Bug15377\\inTransaction (\$callback)\nBug15377\\takesTwo (\$j)",
102+
66,
103+
],
104+
[
105+
'Bug15377\inTransaction ($callback)',
106+
67,
107+
],
108+
[
109+
'Bug15377\inTransaction ($callback)',
110+
68,
111+
],
112+
[
113+
'Bug15377\inTransaction ($callback)',
114+
69,
115+
],
116+
[
117+
'Bug15377\inTransaction ($callback)',
118+
70,
119+
],
120+
[
121+
'Bug15377\inTransaction ($callback)',
122+
71,
123+
],
124+
[
125+
'Bug15377\inTransaction ($callback)',
126+
72,
127+
],
84128
]);
85129
}
86130

‎tests/PHPStan/Rules/data/bug-15377.php‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,3 +51,23 @@ function d(): void
5151
{
5252
inTransaction(function (): void { try { var_dump(name()); } catch (\RuntimeException $e) { throw $e; } });
5353
}
54+
55+
final class Item { public ?string $name = null; public function label(): string { return ''; } }
56+
57+
/**
58+
* @param list<Item> $items
59+
*/
60+
function e(?int $n, bool $b, array $items): void
61+
{
62+
inTransaction(function () use ($n): void { $x = $n ?? 1; throw new \Exception(); });
63+
inTransaction(function () use ($n): void { if (isset($n)) { $n++; } throw new \Exception(); });
64+
inTransaction(function () use ($b): void { if ($b) { $y = 1; } else { $y = 2; } throw new \Exception(); });
65+
inTransaction(function () use ($b): void { $z = $b ? 1 : 2; throw new \Exception(); });
66+
inTransaction(function () use ($b): void { takesTwo($b ? 1 : 2, throw new \Exception()); });
67+
inTransaction(function () use ($items): void { foreach ($items as $item) { $item->name = 'x'; } throw new \Exception(); });
68+
inTransaction(function () use ($items): void { foreach ($items as $i => $item) { if ($item->name !== null) { break; } $label = $items[$i]->label(); } throw new \Exception(); });
69+
inTransaction(function () use ($b): void { while ($b) { $b = false; } throw new \Exception(); });
70+
inTransaction(function (): void { try { var_dump(name()); } finally { $f = 1; } throw new \Exception(); });
71+
inTransaction(function (): void { try { var_dump(name(), name()); } catch (\RuntimeException $e) { throw $e; } });
72+
inTransaction(function (): void { $c = \Closure::bind(static function (): void {}, null, null); throw new \Exception(); });
73+
}

‎turbo-ext/src/MutatingScope.cpp‎

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3049,7 +3049,8 @@ class MutatingScope
30493049
ZVAL_COPY_VALUE(&args[2], conditionalExpressions.raw());
30503050
ZVAL_EMPTY_ARRAY(&args[3]);
30513051
ZVAL_EMPTY_ARRAY(&args[4]);
3052-
ZVAL_EMPTY_ARRAY(&args[5]);
3052+
if (UNEXPECTED(!requireSlot(PT_MS_PROP_IN_FUNCTION_CALLS_STACK, "inFunctionCallsStack"))) return zv::Val();
3053+
ZVAL_COPY_VALUE(&args[5], slot(PT_MS_PROP_IN_FUNCTION_CALLS_STACK).raw());
30533054
ZVAL_BOOL(&args[6], slotBool(PT_MS_PROP_IN_FIRST_LEVEL_STATEMENT));
30543055
ZVAL_BOOL(&args[7], recordedExit.slotBool(PT_MS_PROP_AFTER_EXTRACT_CALL));
30553056
return thisDuplicateWith(args);
@@ -4764,7 +4765,8 @@ class MutatingScope
47644765
a.setBool(CreateArgs::IN_FIRST_LEVEL_STATEMENT, true);
47654766
a.setEmptyArray(CreateArgs::CURRENTLY_ASSIGNED_EXPRESSIONS);
47664767
a.setEmptyArray(CreateArgs::CURRENTLY_ALLOWED_UNDEFINED_EXPRESSIONS);
4767-
a.setEmptyArray(CreateArgs::IN_FUNCTION_CALLS_STACK);
4768+
if (UNEXPECTED(!requireSlot(PT_MS_PROP_IN_FUNCTION_CALLS_STACK, "inFunctionCallsStack"))) return zv::Val();
4769+
a.set(CreateArgs::IN_FUNCTION_CALLS_STACK, slot(PT_MS_PROP_IN_FUNCTION_CALLS_STACK));
47684770
a.setBool(CreateArgs::AFTER_EXTRACT_CALL, false);
47694771
a.setNull(CreateArgs::PARENT_SCOPE);
47704772
a.setBool(CreateArgs::NATIVE_TYPES_PROMOTED, false);
@@ -6044,7 +6046,8 @@ class MutatingScope
60446046
if (UNEXPECTED(!fillDispatched(a, true, true))) return zv::Val();
60456047
a.setOwned(CreateArgs::CURRENTLY_ASSIGNED_EXPRESSIONS, std::move(currentlyAssignedExpressions));
60466048
a.setOwned(CreateArgs::CURRENTLY_ALLOWED_UNDEFINED_EXPRESSIONS, std::move(currentlyAllowedUndefinedExpressions));
6047-
a.setEmptyArray(CreateArgs::IN_FUNCTION_CALLS_STACK);
6049+
if (UNEXPECTED(!requireSlot(PT_MS_PROP_IN_FUNCTION_CALLS_STACK, "inFunctionCallsStack"))) return zv::Val();
6050+
a.set(CreateArgs::IN_FUNCTION_CALLS_STACK, slot(PT_MS_PROP_IN_FUNCTION_CALLS_STACK));
60486051
zv::Val scope = scopeFactoryCreate(a);
60496052
if (UNEXPECTED(scope.isUndef())) return zv::Val();
60506053
if (UNEXPECTED(!assignResolvedTypes(scope))) return zv::Val();
@@ -8438,13 +8441,14 @@ class MutatingScope
84388441
return zv::Val();
84398442
}
84408443

8444+
if (UNEXPECTED(!requireSlot(PT_MS_PROP_IN_FUNCTION_CALLS_STACK, "inFunctionCallsStack"))) return zv::Val();
84418445
return scopeWithTables(
84428446
Z_ARRVAL_P(mergedTypes),
84438447
Z_ARRVAL_P(mergedNativeTypes),
84448448
Z_ARRVAL_P(conditionalExpressions.raw()),
84458449
(HashTable *) &zend_empty_array,
84468450
(HashTable *) &zend_empty_array,
8447-
(HashTable *) &zend_empty_array,
8451+
Z_ARRVAL_P(slot(PT_MS_PROP_IN_FUNCTION_CALLS_STACK).raw()),
84488452
slotBool(PT_MS_PROP_IN_FIRST_LEVEL_STATEMENT),
84498453
slotBool(PT_MS_PROP_AFTER_EXTRACT_CALL) && theirAfterExtractCall);
84508454
}
@@ -8798,7 +8802,8 @@ class MutatingScope
87988802
PT_MS_ARG_CREATE(a, CreateArgs::CONDITIONAL_EXPRESSIONS, pt_scope_ops_intersect_conditional_expressions(Z_ARRVAL_P(slot(PT_MS_PROP_CONDITIONAL_EXPRESSIONS).raw()), finallyConditional));
87998803
a.setEmptyArray(CreateArgs::CURRENTLY_ASSIGNED_EXPRESSIONS);
88008804
a.setEmptyArray(CreateArgs::CURRENTLY_ALLOWED_UNDEFINED_EXPRESSIONS);
8801-
a.setEmptyArray(CreateArgs::IN_FUNCTION_CALLS_STACK);
8805+
if (UNEXPECTED(!requireSlot(PT_MS_PROP_IN_FUNCTION_CALLS_STACK, "inFunctionCallsStack"))) return zv::Val();
8806+
a.set(CreateArgs::IN_FUNCTION_CALLS_STACK, slot(PT_MS_PROP_IN_FUNCTION_CALLS_STACK));
88028807
return scopeFactoryCreate(a);
88038808
}
88048809

@@ -8995,7 +9000,8 @@ class MutatingScope
89959000
a.setBool(CreateArgs::IN_FIRST_LEVEL_STATEMENT, slotBool(PT_MS_PROP_IN_FIRST_LEVEL_STATEMENT));
89969001
a.setEmptyArray(CreateArgs::CURRENTLY_ASSIGNED_EXPRESSIONS);
89979002
a.setEmptyArray(CreateArgs::CURRENTLY_ALLOWED_UNDEFINED_EXPRESSIONS);
8998-
a.setEmptyArray(CreateArgs::IN_FUNCTION_CALLS_STACK);
9003+
if (UNEXPECTED(!requireSlot(PT_MS_PROP_IN_FUNCTION_CALLS_STACK, "inFunctionCallsStack"))) return zv::Val();
9004+
a.set(CreateArgs::IN_FUNCTION_CALLS_STACK, slot(PT_MS_PROP_IN_FUNCTION_CALLS_STACK));
89999005
return scopeFactoryCreate(a);
90009006
}
90019007

@@ -9105,7 +9111,8 @@ class MutatingScope
91059111
a.setBool(CreateArgs::IN_FIRST_LEVEL_STATEMENT, slotBool(PT_MS_PROP_IN_FIRST_LEVEL_STATEMENT));
91069112
a.setEmptyArray(CreateArgs::CURRENTLY_ASSIGNED_EXPRESSIONS);
91079113
a.setEmptyArray(CreateArgs::CURRENTLY_ALLOWED_UNDEFINED_EXPRESSIONS);
9108-
a.setEmptyArray(CreateArgs::IN_FUNCTION_CALLS_STACK);
9114+
if (UNEXPECTED(!requireSlot(PT_MS_PROP_IN_FUNCTION_CALLS_STACK, "inFunctionCallsStack"))) return zv::Val();
9115+
a.set(CreateArgs::IN_FUNCTION_CALLS_STACK, slot(PT_MS_PROP_IN_FUNCTION_CALLS_STACK));
91099116
return scopeFactoryCreate(a);
91109117
}
91119118

0 commit comments

Comments
 (0)