Skip to content

Commit 48d4823

Browse files
authored
Read the updated value of consumed prefix mutations (#6692)
1 parent f9a9159 commit 48d4823

8 files changed

Lines changed: 66 additions & 12 deletions

File tree

‎src/Analyser/ExprHandler/PreDecHandler.php‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,11 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex
9090
$assignedScope,
9191
beforeScope: $scope,
9292
expr: $expr,
93-
variableFlow: VariableFlow::sequence($varResult->getVariableFlow(), $valueFlowWrite !== null && $context->isValueConsumed() ? VariableFlow::inputs($valueFlowWrite->getId(), $context->getValueFlowTarget() !== null ? $context->getValueFlowTarget()->getId() : null) : null, VariableFlowBuilder::targetWrite($expr->var, VariableWrite::KIND_PRE_DEC, $assignedScope, $storage)),
93+
variableFlow: VariableFlow::sequence(
94+
$varResult->getVariableFlow(),
95+
VariableFlowBuilder::targetWrite($expr->var, VariableWrite::KIND_PRE_DEC, $assignedScope, $storage),
96+
$valueFlowWrite !== null && $context->isValueConsumed() ? VariableFlowBuilder::targetRead($expr->var, $storage, true, $context->getValueFlowTarget() !== null ? $context->getValueFlowTarget()->getId() : null) : null,
97+
),
9498
hasYield: $varResult->hasYield(),
9599
isAlwaysTerminating: $varResult->isAlwaysTerminating(),
96100
throwPoints: $varResult->getThrowPoints(),

‎src/Analyser/ExprHandler/PreIncHandler.php‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,11 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex
9090
$assignedScope,
9191
beforeScope: $scope,
9292
expr: $expr,
93-
variableFlow: VariableFlow::sequence($varResult->getVariableFlow(), $valueFlowWrite !== null && $context->isValueConsumed() ? VariableFlow::inputs($valueFlowWrite->getId(), $context->getValueFlowTarget() !== null ? $context->getValueFlowTarget()->getId() : null) : null, VariableFlowBuilder::targetWrite($expr->var, VariableWrite::KIND_PRE_INC, $assignedScope, $storage)),
93+
variableFlow: VariableFlow::sequence(
94+
$varResult->getVariableFlow(),
95+
VariableFlowBuilder::targetWrite($expr->var, VariableWrite::KIND_PRE_INC, $assignedScope, $storage),
96+
$valueFlowWrite !== null && $context->isValueConsumed() ? VariableFlowBuilder::targetRead($expr->var, $storage, true, $context->getValueFlowTarget() !== null ? $context->getValueFlowTarget()->getId() : null) : null,
97+
),
9498
hasYield: $varResult->hasYield(),
9599
isAlwaysTerminating: $varResult->isAlwaysTerminating(),
96100
throwPoints: $varResult->getThrowPoints(),

‎src/Turbo/TurboExtensionEnabler.php‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
final class TurboExtensionEnabler
3434
{
3535

36-
public const EXPECTED_EXTENSION_VERSION = '12acf37';
36+
public const EXPECTED_EXTENSION_VERSION = '9f63407';
3737

3838
private static bool $active = false;
3939

‎tests/PHPStan/Rules/DeadCode/UnusedVariableRuleTest.php‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@ protected function getRule(): Rule
1818
return new UnusedVariableRule(self::getContainer()->getByType(ExprPrinter::class));
1919
}
2020

21+
public function testBug15411(): void
22+
{
23+
$this->analyse([__DIR__ . '/data/bug-15411.php'], []);
24+
}
25+
2126
public function testThrowableCatchAfterDocumentedException(): void
2227
{
2328
$this->analyse([__DIR__ . '/data/unused-variable-throwable-catch.php'], []);
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Bug15411;
4+
5+
function nextCookieValue(int $count): string
6+
{
7+
return (string) ++$count;
8+
}
9+
10+
echo nextCookieValue(1);
11+
12+
function previousCookieValue(int $count): string
13+
{
14+
return (string) --$count;
15+
}
16+
17+
echo previousCookieValue(1);
18+
19+
function nextAssignedValue(int $count): int
20+
{
21+
$value = ++$count;
22+
return $value;
23+
}
24+
25+
function previousAssignedValue(int $count): int
26+
{
27+
$value = --$count;
28+
return $value;
29+
}
30+
31+
/** @param array{count: int} $counts */
32+
function nextOffsetValue(array $counts): int
33+
{
34+
return ++$counts['count'];
35+
}
36+
37+
/** @param array{count: int} $counts */
38+
function previousOffsetValue(array $counts): int
39+
{
40+
return --$counts['count'];
41+
}

‎turbo-ext/src/PreDecHandler.cpp‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ class PreDecHandler
102102
zv::Val assignedScope = ptse::scopeOf(assignResult.raw());
103103
if (UNEXPECTED(assignedScope.isUndef())) return zv::Val();
104104

105-
zv::Val variableFlow = ptse::incDecFlow(child.variableFlow.raw(), valueFlowWrite.raw(), context, var, pt_pdh_kind, assignedScope.raw(), storage);
105+
zv::Val variableFlow = ptse::incDecFlow(child.variableFlow.raw(), valueFlowWrite.raw(), context, var, pt_pdh_kind, assignedScope.raw(), storage, true);
106106
if (UNEXPECTED(variableFlow.isUndef())) return zv::Val();
107107
zv::Val dependenciesHold;
108108
zval *dependencies = pt_expression_result_dependencies(varResult.raw(), dependenciesHold);

‎turbo-ext/src/PreIncHandler.cpp‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ class PreIncHandler
102102
zv::Val assignedScope = ptse::scopeOf(assignResult.raw());
103103
if (UNEXPECTED(assignedScope.isUndef())) return zv::Val();
104104

105-
zv::Val variableFlow = ptse::incDecFlow(child.variableFlow.raw(), valueFlowWrite.raw(), context, var, pt_pih_kind, assignedScope.raw(), storage);
105+
zv::Val variableFlow = ptse::incDecFlow(child.variableFlow.raw(), valueFlowWrite.raw(), context, var, pt_pih_kind, assignedScope.raw(), storage, true);
106106
if (UNEXPECTED(variableFlow.isUndef())) return zv::Val();
107107
zv::Val dependenciesHold;
108108
zval *dependencies = pt_expression_result_dependencies(varResult.raw(), dependenciesHold);

‎turbo-ext/src/SimpleExprHandlers.h‎

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -132,11 +132,9 @@ inline zv::Val valueFlowContext(zval *context, zval *valueFlowWrite)
132132
return pt_expression_context_enter_value_flow(deep.raw(), valueFlowWrite, false);
133133
}
134134

135-
/* VariableFlow::sequence($varResult->getVariableFlow(), $valueFlowWrite !==
136-
* null && $context->isValueConsumed() ? VariableFlow::inputs($valueFlowWrite->getId(),
137-
* $context->getValueFlowTarget() !== null ? $context->getValueFlowTarget()->getId() : null)
138-
* : null, VariableFlowBuilder::targetWrite($var, $kind, $assignedScope, $storage)) */
139-
inline zv::Val incDecFlow(zval *varFlow, zval *valueFlowWrite, zval *context, zval *var, zend_long kind, zval *assignedScope, zval *storage)
135+
/* Post-inc/dec consumes the inputs of the write; pre-inc/dec consumes the
136+
* newly written value, so its target read must follow the target write. */
137+
inline zv::Val incDecFlow(zval *varFlow, zval *valueFlowWrite, zval *context, zval *var, zend_long kind, zval *assignedScope, zval *storage, bool readsNewValue = false)
140138
{
141139
zv::Val inputsFlow = zv::Val::null();
142140
if (Z_TYPE_P(valueFlowWrite) != IS_NULL) {
@@ -154,13 +152,15 @@ inline zv::Val incDecFlow(zval *varFlow, zval *valueFlowWrite, zval *context, zv
154152
targetId = variableWriteId(target.raw());
155153
if (UNEXPECTED(targetId.isUndef())) return zv::Val();
156154
}
157-
inputsFlow = pt_variable_flow_inputs(zval_get_long(writeId.raw()), targetId.raw());
155+
inputsFlow = readsNewValue
156+
? pt_variable_flow_builder_target_read(var, storage, true, targetId.raw())
157+
: pt_variable_flow_inputs(zval_get_long(writeId.raw()), targetId.raw());
158158
if (UNEXPECTED(inputsFlow.isUndef())) return zv::Val();
159159
}
160160
}
161161
zv::Val targetWriteFlow = pt_variable_flow_builder_target_write(var, kind, assignedScope, storage, NULL);
162162
if (UNEXPECTED(targetWriteFlow.isUndef())) return zv::Val();
163-
zv::Args flows{varFlow, inputsFlow.raw(), targetWriteFlow.raw()};
163+
zv::Args flows{varFlow, readsNewValue ? targetWriteFlow.raw() : inputsFlow.raw(), readsNewValue ? inputsFlow.raw() : targetWriteFlow.raw()};
164164
return pt_variable_flow_sequence(3, flows);
165165
}
166166

0 commit comments

Comments
 (0)