diff --git a/src/Analyser/ExprHandler/PreDecHandler.php b/src/Analyser/ExprHandler/PreDecHandler.php index efaf6a7b44d..b7bc7515ffa 100644 --- a/src/Analyser/ExprHandler/PreDecHandler.php +++ b/src/Analyser/ExprHandler/PreDecHandler.php @@ -90,7 +90,11 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $assignedScope, beforeScope: $scope, expr: $expr, - 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)), + variableFlow: VariableFlow::sequence( + $varResult->getVariableFlow(), + VariableFlowBuilder::targetWrite($expr->var, VariableWrite::KIND_PRE_DEC, $assignedScope, $storage), + $valueFlowWrite !== null && $context->isValueConsumed() ? VariableFlowBuilder::targetRead($expr->var, $storage, true, $context->getValueFlowTarget() !== null ? $context->getValueFlowTarget()->getId() : null) : null, + ), hasYield: $varResult->hasYield(), isAlwaysTerminating: $varResult->isAlwaysTerminating(), throwPoints: $varResult->getThrowPoints(), diff --git a/src/Analyser/ExprHandler/PreIncHandler.php b/src/Analyser/ExprHandler/PreIncHandler.php index 9d7e02bf780..854627dc21c 100644 --- a/src/Analyser/ExprHandler/PreIncHandler.php +++ b/src/Analyser/ExprHandler/PreIncHandler.php @@ -90,7 +90,11 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $assignedScope, beforeScope: $scope, expr: $expr, - 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)), + variableFlow: VariableFlow::sequence( + $varResult->getVariableFlow(), + VariableFlowBuilder::targetWrite($expr->var, VariableWrite::KIND_PRE_INC, $assignedScope, $storage), + $valueFlowWrite !== null && $context->isValueConsumed() ? VariableFlowBuilder::targetRead($expr->var, $storage, true, $context->getValueFlowTarget() !== null ? $context->getValueFlowTarget()->getId() : null) : null, + ), hasYield: $varResult->hasYield(), isAlwaysTerminating: $varResult->isAlwaysTerminating(), throwPoints: $varResult->getThrowPoints(), diff --git a/src/Turbo/TurboExtensionEnabler.php b/src/Turbo/TurboExtensionEnabler.php index 03b1e80d4d7..b61ebc4e69a 100644 --- a/src/Turbo/TurboExtensionEnabler.php +++ b/src/Turbo/TurboExtensionEnabler.php @@ -33,7 +33,7 @@ final class TurboExtensionEnabler { - public const EXPECTED_EXTENSION_VERSION = '12acf37'; + public const EXPECTED_EXTENSION_VERSION = '9f63407'; private static bool $active = false; diff --git a/tests/PHPStan/Rules/DeadCode/UnusedVariableRuleTest.php b/tests/PHPStan/Rules/DeadCode/UnusedVariableRuleTest.php index a16a5965174..2ceca6991c7 100644 --- a/tests/PHPStan/Rules/DeadCode/UnusedVariableRuleTest.php +++ b/tests/PHPStan/Rules/DeadCode/UnusedVariableRuleTest.php @@ -18,6 +18,11 @@ protected function getRule(): Rule return new UnusedVariableRule(self::getContainer()->getByType(ExprPrinter::class)); } + public function testBug15411(): void + { + $this->analyse([__DIR__ . '/data/bug-15411.php'], []); + } + public function testThrowableCatchAfterDocumentedException(): void { $this->analyse([__DIR__ . '/data/unused-variable-throwable-catch.php'], []); diff --git a/tests/PHPStan/Rules/DeadCode/data/bug-15411.php b/tests/PHPStan/Rules/DeadCode/data/bug-15411.php new file mode 100644 index 00000000000..380edf7633a --- /dev/null +++ b/tests/PHPStan/Rules/DeadCode/data/bug-15411.php @@ -0,0 +1,41 @@ +getVariableFlow(), $valueFlowWrite !== - * null && $context->isValueConsumed() ? VariableFlow::inputs($valueFlowWrite->getId(), - * $context->getValueFlowTarget() !== null ? $context->getValueFlowTarget()->getId() : null) - * : null, VariableFlowBuilder::targetWrite($var, $kind, $assignedScope, $storage)) */ -inline zv::Val incDecFlow(zval *varFlow, zval *valueFlowWrite, zval *context, zval *var, zend_long kind, zval *assignedScope, zval *storage) +/* Post-inc/dec consumes the inputs of the write; pre-inc/dec consumes the + * newly written value, so its target read must follow the target write. */ +inline zv::Val incDecFlow(zval *varFlow, zval *valueFlowWrite, zval *context, zval *var, zend_long kind, zval *assignedScope, zval *storage, bool readsNewValue = false) { zv::Val inputsFlow = zv::Val::null(); if (Z_TYPE_P(valueFlowWrite) != IS_NULL) { @@ -154,13 +152,15 @@ inline zv::Val incDecFlow(zval *varFlow, zval *valueFlowWrite, zval *context, zv targetId = variableWriteId(target.raw()); if (UNEXPECTED(targetId.isUndef())) return zv::Val(); } - inputsFlow = pt_variable_flow_inputs(zval_get_long(writeId.raw()), targetId.raw()); + inputsFlow = readsNewValue + ? pt_variable_flow_builder_target_read(var, storage, true, targetId.raw()) + : pt_variable_flow_inputs(zval_get_long(writeId.raw()), targetId.raw()); if (UNEXPECTED(inputsFlow.isUndef())) return zv::Val(); } } zv::Val targetWriteFlow = pt_variable_flow_builder_target_write(var, kind, assignedScope, storage, NULL); if (UNEXPECTED(targetWriteFlow.isUndef())) return zv::Val(); - zv::Args flows{varFlow, inputsFlow.raw(), targetWriteFlow.raw()}; + zv::Args flows{varFlow, readsNewValue ? targetWriteFlow.raw() : inputsFlow.raw(), readsNewValue ? inputsFlow.raw() : targetWriteFlow.raw()}; return pt_variable_flow_sequence(3, flows); }