diff --git a/src/Analyser/ExprHandler/ArrayDimFetchHandler.php b/src/Analyser/ExprHandler/ArrayDimFetchHandler.php index 9a272b6134d..090c67316c7 100644 --- a/src/Analyser/ExprHandler/ArrayDimFetchHandler.php +++ b/src/Analyser/ExprHandler/ArrayDimFetchHandler.php @@ -15,9 +15,11 @@ use PHPStan\Analyser\ExpressionResultStorage; use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\ExprHandler\Helper\DefaultNarrowingHelper; +use PHPStan\Analyser\ExprHandler\Helper\IllegalOffsetTypeHelper; use PHPStan\Analyser\ExprHandler\Helper\MethodCallReturnTypeHelper; use PHPStan\Analyser\ExprHandler\Helper\MethodThrowPointHelper; use PHPStan\Analyser\Generics\TemplateArgumentFrame; +use PHPStan\Analyser\InternalThrowPoint; use PHPStan\Analyser\IssetabilityDescriptor; use PHPStan\Analyser\MutatingScope; use PHPStan\Analyser\NodeScopeResolver; @@ -29,6 +31,7 @@ use PHPStan\Dependency\Dependencies; use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Node\Expr\TypeExpr; +use PHPStan\Php\PhpVersion; use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\Turbo\ShadowedByTurboExtension; use PHPStan\Type\ErrorType; @@ -36,6 +39,7 @@ use PHPStan\Type\ObjectType; use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; +use TypeError; use function array_merge; use function is_string; @@ -52,6 +56,7 @@ public function __construct( private DefaultNarrowingHelper $defaultNarrowingHelper, private MethodThrowPointHelper $methodThrowPointHelper, private MethodCallReturnTypeHelper $methodCallReturnTypeHelper, + private PhpVersion $phpVersion, ) { } @@ -130,6 +135,17 @@ public function composeResult(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, $offsetGetCall = new MethodCall($expr->var, new Identifier('offsetGet'), [new Arg($expr->dim)], [TemplateArgumentFrame::SYNTHETIC_SITE_ATTRIBUTE => true]); } + if ( + $this->phpVersion->throwsTypeErrorForIllegalOffsets() + // only array and string offset reads throw TypeError for illegal offsets, + // reads on null and other scalars emit a warning, ArrayAccess objects go through offsetGet(); + // isset() and empty() of a string offset are false instead + && (!$varType->isArray()->no() || (!$context->isIssetTarget() && !$varType->isString()->no())) + && IllegalOffsetTypeHelper::mayOffsetThrowTypeError($dimResult->getType()) + ) { + $throwPoints[] = InternalThrowPoint::createExplicit($scope, new ObjectType(TypeError::class), $expr, false); + } + $result = $this->expressionResultFactory->create( $scope, beforeScope: $beforeScope, diff --git a/src/Analyser/ExprHandler/ArrayHandler.php b/src/Analyser/ExprHandler/ArrayHandler.php index ad28fe76aa0..85811676c7e 100644 --- a/src/Analyser/ExprHandler/ArrayHandler.php +++ b/src/Analyser/ExprHandler/ArrayHandler.php @@ -15,7 +15,9 @@ use PHPStan\Analyser\ExpressionResultFactory; use PHPStan\Analyser\ExpressionResultStorage; use PHPStan\Analyser\ExprHandler; +use PHPStan\Analyser\ExprHandler\Helper\IllegalOffsetTypeHelper; use PHPStan\Analyser\Generics\ClosureSignatureInference; +use PHPStan\Analyser\InternalThrowPoint; use PHPStan\Analyser\MutatingScope; use PHPStan\Analyser\NodeScopeResolver; use PHPStan\Analyser\SpecifiedTypes; @@ -27,6 +29,7 @@ use PHPStan\Node\LiteralArrayItem; use PHPStan\Node\LiteralArrayNode; use PHPStan\Node\Variable\VariableWrite; +use PHPStan\Php\PhpVersion; use PHPStan\Reflection\InitializerExprTypeResolver; use PHPStan\ShouldNotHappenException; use PHPStan\Turbo\ShadowedByTurboExtension; @@ -35,8 +38,10 @@ use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\IntegerType; use PHPStan\Type\MixedType; +use PHPStan\Type\ObjectType; use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; +use TypeError; use function array_key_exists; use function array_map; use function array_merge; @@ -57,6 +62,7 @@ final class ArrayHandler implements ExprHandler public function __construct( private InitializerExprTypeResolver $initializerExprTypeResolver, private ExpressionResultFactory $expressionResultFactory, + private PhpVersion $phpVersion, ) { } @@ -144,6 +150,15 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $impurePoints = array_merge($impurePoints, $valueResult->getImpurePoints()); $isAlwaysTerminating = $isAlwaysTerminating || $valueResult->isAlwaysTerminating(); $scope = $valueResult->getScope(); + if ( + $keyResult !== null + && $this->phpVersion->throwsTypeErrorForIllegalOffsets() + && IllegalOffsetTypeHelper::mayOffsetThrowTypeError($keyResult->getType()) + ) { + // an array or an object key throws TypeError once the value was evaluated + $throwPoints[] = InternalThrowPoint::createExplicit($scope, new ObjectType(TypeError::class), $arrayItem->key, false); + $variableFlows[] = VariableFlow::throwing(new ObjectType(TypeError::class), true); + } // the item's callback fires after its key and value were processed, // with the item's entry scope - callback-side asks answer from the // storage instead of re-walking the yet-unstored sub-expressions diff --git a/src/Analyser/ExprHandler/AssignHandler.php b/src/Analyser/ExprHandler/AssignHandler.php index 0a1f5f83e84..e93cda4fb57 100644 --- a/src/Analyser/ExprHandler/AssignHandler.php +++ b/src/Analyser/ExprHandler/AssignHandler.php @@ -33,6 +33,7 @@ use PHPStan\Analyser\ExprHandler; use PHPStan\Analyser\ExprHandler\Helper\DefaultNarrowingHelper; use PHPStan\Analyser\ExprHandler\Helper\IdenticalNarrowingHelper; +use PHPStan\Analyser\ExprHandler\Helper\IllegalOffsetTypeHelper; use PHPStan\Analyser\ExprHandler\Helper\MethodThrowPointHelper; use PHPStan\Analyser\ExprHandler\Helper\NonNullabilityHelper; use PHPStan\Analyser\ExprHandler\Helper\VirtualExprResultHelper; @@ -286,6 +287,8 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex VariableFlowBuilder::targetRead($expr->var, $storage, false), $assignedExprResult->getVariableFlow(), $valueFlowWrite !== null && $context->isValueConsumed() ? VariableFlow::inputs($valueFlowWrite->getId(), $context->getValueFlowTarget() !== null ? $context->getValueFlowTarget()->getId() : null) : null, + // an illegal offset throws before the write + count($target->getIllegalOffsetDimFetches()) > 0 ? VariableFlow::throwing(new ObjectType(TypeError::class), true) : null, VariableFlowBuilder::targetWrite($expr->var, VariableWrite::KIND_ASSIGN, $scope, $storage, $redundantType), ); if ($expr instanceof Assign && $expr->expr instanceof Expr\Array_ && self::hasArrayReference($expr->expr)) { @@ -852,6 +855,35 @@ private function doPrepareTarget( $nodeScopeResolver->callNodeCallback($nodeCallback, $dimFetch, $enterExpressionAssign ? $callbackScope->enterExpressionAssign($dimFetch) : $callbackScope, $storage); } + // the links whose offset may throw TypeError - PHP throws only after + // evaluating the assigned value, so applyWrite() creates their throw points + $illegalOffsetDimFetches = []; + if ($this->phpVersion->throwsTypeErrorForIllegalOffsets()) { + foreach ($offsetTypes as $i => [$offsetType, $offsetDimFetch]) { + // $arr[] = ... never throws for the offset + if ($offsetType === null) { + continue; + } + if (!IllegalOffsetTypeHelper::mayOffsetThrowTypeError($offsetType)) { + continue; + } + // writes to ArrayAccess objects go through offsetSet() whose throw points applyWrite() models; + // the container is the previous chain link - the root for the first dimension + $containerResult = $i === 0 ? $varResult : $deferredDimFetchResults[$i - 1][1]; + $containerType = $containerResult->getType(); + if ((new ObjectType(ArrayAccess::class))->isSuperTypeOf($containerType)->yes()) { + continue; + } + // `$lvalue ??= ...` throws reading an array or a string container, + // before the assigned value is evaluated + if ($mode->issetSemanticsForRead() && ($containerType->isArray()->yes() || $containerType->isString()->yes())) { + $throwPoints[] = InternalThrowPoint::createExplicit($scope, new ObjectType(TypeError::class), $offsetDimFetch, false); + continue; + } + $illegalOffsetDimFetches[] = $offsetDimFetch; + } + } + if ($mode->issetSemanticsForRead()) { // `$lvalue ??= ...` reads the chain with isset() semantics. The root // and dimensions were just walked, so each chain link's read is @@ -931,6 +963,7 @@ private function doPrepareTarget( targetReadResult: $targetReadResult, targetChainResults: $targetChainResults, dependencies: $targetDependencies, + illegalOffsetDimFetches: $illegalOffsetDimFetches, ); } @@ -1438,6 +1471,25 @@ public function applyWrite( $varType = new ConstantArrayType([], []); $varNativeType = new ConstantArrayType([], []); } + + // an illegal offset throws TypeError after the assigned value was + // evaluated and an undefined root was turned into an array + $illegalOffsetDimFetches = $target->getIllegalOffsetDimFetches(); + if (count($illegalOffsetDimFetches) > 0) { + $throwPointScope = $scope; + if (!$isImplicitArrayCreation->no() && $var instanceof Variable && is_string($var->name)) { + $throwPointScope = $throwPointScope->assignVariable( + $var->name, + TypeCombinator::union($varType, new ConstantArrayType([], [])), + TypeCombinator::union($varNativeType, new ConstantArrayType([], [])), + TrinaryLogic::createYes(), + [], + ); + } + foreach ($illegalOffsetDimFetches as $illegalOffsetDimFetch) { + $throwPoints[] = InternalThrowPoint::createExplicit($throwPointScope, new ObjectType(TypeError::class), $illegalOffsetDimFetch, false); + } + } $offsetValueType = $varType; $offsetNativeValueType = $varNativeType; diff --git a/src/Analyser/ExprHandler/AssignOpHandler.php b/src/Analyser/ExprHandler/AssignOpHandler.php index c7cc9c15257..c195792b62e 100644 --- a/src/Analyser/ExprHandler/AssignOpHandler.php +++ b/src/Analyser/ExprHandler/AssignOpHandler.php @@ -35,7 +35,9 @@ use PHPStan\Type\ObjectType; use PHPStan\Type\StaticTypeFactory; use PHPStan\Type\Type; +use TypeError; use function array_merge; +use function count; use function get_class; use function is_string; use function sprintf; @@ -296,6 +298,8 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $writeFlow = VariableFlow::sequence( $rhsResult->getVariableFlow(), $valueFlowWrite !== null && $context->isValueConsumed() ? VariableFlow::inputs($valueFlowWrite->getId(), $context->getValueFlowTarget() !== null ? $context->getValueFlowTarget()->getId() : null) : null, + // an illegal offset throws before the write + count($target->getIllegalOffsetDimFetches()) > 0 ? VariableFlow::throwing(new ObjectType(TypeError::class), true) : null, VariableFlowBuilder::targetWrite($expr->var, VariableWrite::KIND_READ_MODIFY_WRITE, $scope, $storage), ); $variableFlow = VariableFlow::sequence( diff --git a/src/Analyser/ExprHandler/EmptyHandler.php b/src/Analyser/ExprHandler/EmptyHandler.php index 05e4fa75fd6..f3006c4a448 100644 --- a/src/Analyser/ExprHandler/EmptyHandler.php +++ b/src/Analyser/ExprHandler/EmptyHandler.php @@ -51,7 +51,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $beforeScope = $scope; $nonNullabilityResult = $this->nonNullabilityHelper->ensureNonNullability($scope, $expr->expr); $scope = $nodeScopeResolver->lookForSetAllowedUndefinedExpressions($nonNullabilityResult->getScope(), $expr->expr); - $exprResult = $nodeScopeResolver->processExprNode($stmt, $expr->expr, $scope, $storage, $nodeCallback, $context->enterDeep()); + $exprResult = $nodeScopeResolver->processExprNode($stmt, $expr->expr, $scope, $storage, $nodeCallback, $context->enterDeep()->enterIssetTarget()); $scope = $exprResult->getScope(); $scope = $this->nonNullabilityHelper->revertNonNullability($scope, $nonNullabilityResult->getSpecifiedExpressions()); $scope = $nodeScopeResolver->lookForUnsetAllowedUndefinedExpressions($scope, $expr->expr); diff --git a/src/Analyser/ExprHandler/Helper/IllegalOffsetTypeHelper.php b/src/Analyser/ExprHandler/Helper/IllegalOffsetTypeHelper.php new file mode 100644 index 00000000000..b34fc06ce5a --- /dev/null +++ b/src/Analyser/ExprHandler/Helper/IllegalOffsetTypeHelper.php @@ -0,0 +1,22 @@ +isArray()->no() || !$offsetType->isObject()->no(); + } + +} diff --git a/src/Analyser/ExprHandler/IssetHandler.php b/src/Analyser/ExprHandler/IssetHandler.php index 122fffaa642..bca5f2e464a 100644 --- a/src/Analyser/ExprHandler/IssetHandler.php +++ b/src/Analyser/ExprHandler/IssetHandler.php @@ -74,7 +74,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex foreach ($expr->vars as $var) { $nonNullabilityResult = $this->nonNullabilityHelper->ensureNonNullability($scope, $var); $scope = $nodeScopeResolver->lookForSetAllowedUndefinedExpressions($nonNullabilityResult->getScope(), $var); - $varResult = $nodeScopeResolver->processExprNode($stmt, $var, $scope, $storage, $nodeCallback, $context->enterDeep()); + $varResult = $nodeScopeResolver->processExprNode($stmt, $var, $scope, $storage, $nodeCallback, $context->enterDeep()->enterIssetTarget()); $varResults[] = $varResult; $scope = $varResult->getScope(); $hasYield = $hasYield || $varResult->hasYield(); diff --git a/src/Analyser/ExpressionContext.php b/src/Analyser/ExpressionContext.php index 92a58672bfd..585a00a2ec9 100644 --- a/src/Analyser/ExpressionContext.php +++ b/src/Analyser/ExpressionContext.php @@ -29,6 +29,7 @@ private function __construct( private ?bool $valueConsumed = null, private ?Type $passedToType = null, private ?Type $nativePassedToType = null, + private bool $issetTarget = false, ) { } @@ -50,7 +51,7 @@ public static function createDeep(bool $resolveTemplateArguments = true): self */ public function enterDeep(): self { - if ($this->isDeep && $this->valueFlowTarget === null && !$this->arrayDimFetchRoot && !$this->unsetTarget && $this->passedToType === null && $this->nativePassedToType === null) { + if ($this->isDeep && $this->valueFlowTarget === null && !$this->arrayDimFetchRoot && !$this->unsetTarget && !$this->issetTarget && $this->passedToType === null && $this->nativePassedToType === null) { return $this; } @@ -78,7 +79,7 @@ public function enterDeepKeepingValueFlow(): self */ public function withoutValueFlow(): self { - if ($this->valueFlowTarget === null && !$this->arrayDimFetchRoot && !$this->unsetTarget && $this->passedToType === null && $this->nativePassedToType === null) { + if ($this->valueFlowTarget === null && !$this->arrayDimFetchRoot && !$this->unsetTarget && !$this->issetTarget && $this->passedToType === null && $this->nativePassedToType === null) { return $this; } @@ -136,7 +137,7 @@ public function withoutTemplateArgumentResolution(): self return $this; } - return new self($this->isDeep, $this->inAssignRightSideVariableName, $this->inAssignRightSideExpr, $this->inThrow, $this->inAssignRightSideType, $this->inAssignRightSideNativeType, false, $this->valueFlowTarget, $this->valueFlowDirect, $this->arrayDimFetchRoot, $this->unsetTarget, $this->valueConsumed); + return new self($this->isDeep, $this->inAssignRightSideVariableName, $this->inAssignRightSideExpr, $this->inThrow, $this->inAssignRightSideType, $this->inAssignRightSideNativeType, false, $this->valueFlowTarget, $this->valueFlowDirect, $this->arrayDimFetchRoot, $this->unsetTarget, $this->valueConsumed, issetTarget: $this->issetTarget); } public function enterThrow(): self @@ -243,4 +244,18 @@ public function isUnsetTarget(): bool return $this->unsetTarget; } + /** + * The expression is an isset() or empty() subject: an illegal offset of + * a string container makes it false instead of throwing. + */ + public function enterIssetTarget(): self + { + return new self($this->isDeep, $this->inAssignRightSideVariableName, $this->inAssignRightSideExpr, $this->inThrow, $this->inAssignRightSideType, $this->inAssignRightSideNativeType, $this->resolveTemplateArguments, issetTarget: true); + } + + public function isIssetTarget(): bool + { + return $this->issetTarget; + } + } diff --git a/src/Analyser/PreparedAssignTarget.php b/src/Analyser/PreparedAssignTarget.php index 40b111cb1b9..4d267ba8c57 100644 --- a/src/Analyser/PreparedAssignTarget.php +++ b/src/Analyser/PreparedAssignTarget.php @@ -42,6 +42,7 @@ final class PreparedAssignTarget * @param non-empty-list|null $existingOffsetTypes * @param non-empty-list|null $existingOffsetNativeTypes * @param ExpressionResult[] $targetChainResults + * @param list $illegalOffsetDimFetches */ public function __construct( private string $kind, @@ -71,6 +72,7 @@ public function __construct( private array $targetChainResults = [], private ?ExpressionResult $variableNameResult = null, private ?Dependencies $dependencies = null, + private array $illegalOffsetDimFetches = [], ) { } @@ -302,4 +304,16 @@ public function getVariableNameResult(): ?ExpressionResult return $this->variableNameResult; } + /** + * The links of an offset write whose offset may be an array or an object, + * which throws TypeError on PHP 8+ - after the assigned value was evaluated + * and an undefined root was turned into an array. + * + * @return list + */ + public function getIllegalOffsetDimFetches(): array + { + return $this->illegalOffsetDimFetches; + } + } diff --git a/src/Php/PhpVersion.php b/src/Php/PhpVersion.php index 6667a5916d9..f3f7a357530 100644 --- a/src/Php/PhpVersion.php +++ b/src/Php/PhpVersion.php @@ -188,6 +188,12 @@ public function throwsValueErrorForInternalFunctions(): bool return $this->versionId >= 80000; } + // see https://wiki.php.net/rfc/engine_warnings - "Illegal offset type" family + public function throwsTypeErrorForIllegalOffsets(): bool + { + return $this->versionId >= 80000; + } + public function supportsHhPrintfSpecifier(): bool { return $this->versionId >= 80000; diff --git a/src/Turbo/TurboExtensionEnabler.php b/src/Turbo/TurboExtensionEnabler.php index 88c83cac563..53c5548b81d 100644 --- a/src/Turbo/TurboExtensionEnabler.php +++ b/src/Turbo/TurboExtensionEnabler.php @@ -33,7 +33,7 @@ final class TurboExtensionEnabler { - public const EXPECTED_EXTENSION_VERSION = '28c29b8'; + public const EXPECTED_EXTENSION_VERSION = '33375c3'; private static bool $active = false; diff --git a/tests/PHPStan/Analyser/ExprHandler/Helper/IllegalOffsetTypeHelperTest.php b/tests/PHPStan/Analyser/ExprHandler/Helper/IllegalOffsetTypeHelperTest.php new file mode 100644 index 00000000000..ecea0a46fed --- /dev/null +++ b/tests/PHPStan/Analyser/ExprHandler/Helper/IllegalOffsetTypeHelperTest.php @@ -0,0 +1,57 @@ + + */ + public static function dataMayOffsetThrowTypeError(): iterable + { + yield [new IntegerType(), false]; + yield [new StringType(), false]; + yield [new ConstantStringType('foo'), false]; + yield [TypeCombinator::union(new FloatType(), new BooleanType(), new NullType()), false]; + yield [new ResourceType(), false]; + yield [new ArrayType(new MixedType(), new MixedType()), true]; + yield [new ObjectType(stdClass::class), true]; + yield [new ObjectType(Stringable::class), true]; + yield [new ObjectWithoutClassType(), true]; + yield [new MixedType(), true]; + yield [TypeCombinator::union(new IntegerType(), new ArrayType(new MixedType(), new MixedType())), true]; + yield [TypeCombinator::union(new IntegerType(), new ObjectType(stdClass::class)), true]; + yield [TypeCombinator::addNull(new ObjectType(stdClass::class)), true]; + } + + #[DataProvider('dataMayOffsetThrowTypeError')] + public function testMayOffsetThrowTypeError(Type $offsetType, bool $expected): void + { + $this->assertSame( + $expected, + IllegalOffsetTypeHelper::mayOffsetThrowTypeError($offsetType), + $offsetType->describe(VerbosityLevel::precise()), + ); + } + +} diff --git a/tests/PHPStan/Analyser/nsrt/throw-points/array-dim-fetch.php b/tests/PHPStan/Analyser/nsrt/throw-points/array-dim-fetch.php index 3a64f2c3a28..0d93e4c0293 100644 --- a/tests/PHPStan/Analyser/nsrt/throw-points/array-dim-fetch.php +++ b/tests/PHPStan/Analyser/nsrt/throw-points/array-dim-fetch.php @@ -4,18 +4,8 @@ use PHPStan\TrinaryLogic; use function PHPStan\Testing\assertVariableCertainty; -use function ThrowPoints\Helpers\doesntThrow; use function ThrowPoints\Helpers\maybeThrows; -function () { - try { - [][doesntThrow()]; - $foo = 1; - } finally { - assertVariableCertainty(TrinaryLogic::createYes(), $foo); - } -}; - function () { try { [][maybeThrows()]; diff --git a/tests/PHPStan/Analyser/nsrt/throw-points/array.php b/tests/PHPStan/Analyser/nsrt/throw-points/array.php index 97decfb2816..f932f12bc31 100644 --- a/tests/PHPStan/Analyser/nsrt/throw-points/array.php +++ b/tests/PHPStan/Analyser/nsrt/throw-points/array.php @@ -50,14 +50,6 @@ function () { } }; -function () { - try { - [doesntThrow() => 1, $foo = 1]; - } finally { - assertVariableCertainty(TrinaryLogic::createYes(), $foo); - } -}; - function () { try { [maybeThrows() => 1, $foo = 1]; diff --git a/tests/PHPStan/Analyser/nsrt/throw-points/assign-op.php b/tests/PHPStan/Analyser/nsrt/throw-points/assign-op.php index c419633fabf..7cf54b2bf9a 100644 --- a/tests/PHPStan/Analyser/nsrt/throw-points/assign-op.php +++ b/tests/PHPStan/Analyser/nsrt/throw-points/assign-op.php @@ -41,14 +41,6 @@ function () { } }; -function () { - try { - $foo[doesntThrow()] .= 0; - } finally { - assertVariableCertainty(TrinaryLogic::createYes(), $foo); - } -}; - function () { try { $foo[maybeThrows()] .= 0; diff --git a/tests/PHPStan/Analyser/nsrt/throw-points/assign.php b/tests/PHPStan/Analyser/nsrt/throw-points/assign.php index 13357533a43..055c05b9f1a 100644 --- a/tests/PHPStan/Analyser/nsrt/throw-points/assign.php +++ b/tests/PHPStan/Analyser/nsrt/throw-points/assign.php @@ -49,14 +49,6 @@ function () { } }; -function () { - try { - $foo[doesntThrow()] = 0; - } finally { - assertVariableCertainty(TrinaryLogic::createYes(), $foo); - } -}; - function () { try { $foo[maybeThrows()] = 0; @@ -153,14 +145,6 @@ function () { } }; -function () { - try { - [$foo[doesntThrow()]] = 1; - } finally { - assertVariableCertainty(TrinaryLogic::createYes(), $foo); - } -}; - function () { try { [$foo[maybeThrows()]] = 1; diff --git a/tests/PHPStan/Analyser/nsrt/throw-points/illegal-array-offset-php7.php b/tests/PHPStan/Analyser/nsrt/throw-points/illegal-array-offset-php7.php new file mode 100644 index 00000000000..b037b602284 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/throw-points/illegal-array-offset-php7.php @@ -0,0 +1,51 @@ + 1, $foo = 1]; + } finally { + assertVariableCertainty(TrinaryLogic::createYes(), $foo); + } +}; diff --git a/tests/PHPStan/Analyser/nsrt/throw-points/illegal-array-offset-php8.php b/tests/PHPStan/Analyser/nsrt/throw-points/illegal-array-offset-php8.php new file mode 100644 index 00000000000..f3bb1ba353e --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/throw-points/illegal-array-offset-php8.php @@ -0,0 +1,249 @@ += 8.0 + +namespace ThrowPoints\IllegalArrayOffset; + +use PHPStan\TrinaryLogic; +use function PHPStan\Testing\assertType; +use function PHPStan\Testing\assertVariableCertainty; +use function ThrowPoints\Helpers\doesntThrow; + +// doesntThrow() never throws by itself, but on PHP 8.0+ its mixed return value used +// as an array/string offset may be an array or an object, which throws TypeError. +// A write throws only after the assigned value was evaluated and an undefined +// container was turned into an array, so $foo is always assigned. A read throws +// before the statements after it, so $foo may be left unassigned there. + +function () { + try { + $foo[doesntThrow()] = 0; + } finally { + assertVariableCertainty(TrinaryLogic::createYes(), $foo); + } +}; + +function () { + try { + [$foo[doesntThrow()]] = 1; + } finally { + assertVariableCertainty(TrinaryLogic::createYes(), $foo); + } +}; + +function () { + try { + $foo[doesntThrow()] .= 0; + } finally { + assertVariableCertainty(TrinaryLogic::createYes(), $foo); + } +}; + +function () { + try { + [][doesntThrow()]; + $foo = 1; + } finally { + assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); + } +}; + +function () { + try { + [doesntThrow() => 1, $foo = 1]; + } finally { + assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); + } +}; + +/** + * @param mixed[] $array + * @param mixed $key + */ +function assignedValueIsEvaluatedFirst(array $array, $key): void +{ + try { + $array[$key] = $x = 1; + } catch (\TypeError $e) { + assertVariableCertainty(TrinaryLogic::createYes(), $x); + } + assertVariableCertainty(TrinaryLogic::createYes(), $x); +} + +/** + * @param mixed[] $array + * @param mixed $key + */ +function assignOpValueIsEvaluatedFirst(array $array, $key): void +{ + try { + $array[$key] .= $x = 'a'; + } catch (\TypeError $e) { + assertVariableCertainty(TrinaryLogic::createYes(), $x); + } + assertVariableCertainty(TrinaryLogic::createYes(), $x); +} + +/** + * @param mixed $key + */ +function undefinedContainerIsCreated($key): void +{ + try { + $foo[$key] = 1; + } catch (\TypeError $e) { + assertVariableCertainty(TrinaryLogic::createYes(), $foo); + assertType('array{}', $foo); + } + assertVariableCertainty(TrinaryLogic::createYes(), $foo); +} + +/** + * @param mixed $key + */ +function maybeUndefinedContainerIsCreated($key, bool $b): void +{ + if ($b) { + $foo = [1]; + } + try { + $foo[$key] = 1; + } catch (\TypeError $e) { + assertVariableCertainty(TrinaryLogic::createYes(), $foo); + } + assertVariableCertainty(TrinaryLogic::createYes(), $foo); +} + +/** + * @param mixed $key + */ +function undefinedNestedContainerIsCreated($key): void +{ + try { + $foo[1][$key] = 1; + } catch (\TypeError $e) { + assertVariableCertainty(TrinaryLogic::createYes(), $foo); + } + assertVariableCertainty(TrinaryLogic::createYes(), $foo); +} + +/** + * @param mixed[] $array + * @param mixed $key + */ +function coalesceAssignReadsFirst(array $array, $key): void +{ + try { + $array[$key] ??= $x = 1; + } catch (\TypeError $e) { + assertVariableCertainty(TrinaryLogic::createNo(), $x); + } + assertVariableCertainty(TrinaryLogic::createMaybe(), $x); +} + +/** + * @param mixed $key + */ +function coalesceAssignUndefinedContainer($key): void +{ + try { + $foo[$key] ??= 1; + } catch (\TypeError $e) { + assertVariableCertainty(TrinaryLogic::createYes(), $foo); + } + assertVariableCertainty(TrinaryLogic::createYes(), $foo); +} + +/** + * @param mixed[] $array + * @param mixed $key + */ +function readThrowsBeforeAssignment(array $array, $key): void +{ + try { + $y = $array[$key]; + } catch (\TypeError $e) { + assertVariableCertainty(TrinaryLogic::createNo(), $y); + } + assertVariableCertainty(TrinaryLogic::createMaybe(), $y); +} + +/** + * @param mixed $key + */ +function arrayLiteralValueIsEvaluatedFirst($key): void +{ + try { + $y = [$key => $x = 1]; + } catch (\TypeError $e) { + assertVariableCertainty(TrinaryLogic::createYes(), $x); + assertVariableCertainty(TrinaryLogic::createNo(), $y); + } + assertVariableCertainty(TrinaryLogic::createMaybe(), $y); +} + +function arrayLiteralObjectKey(\stdClass $key): void +{ + try { + $y = [$key => 1]; + } finally { + assertVariableCertainty(TrinaryLogic::createMaybe(), $y); + } +} + +function arrayLiteralIntKey(int $key): void +{ + try { + $y = [$key => 1]; + } finally { + assertVariableCertainty(TrinaryLogic::createYes(), $y); + } +} + +/** + * @param mixed $key + */ +function issetOfStringOffset(string $s, $key): void +{ + try { + $y = isset($s[$key]); + } finally { + assertVariableCertainty(TrinaryLogic::createYes(), $y); + } +} + +/** + * @param mixed $key + */ +function emptyOfStringOffset(string $s, $key): void +{ + try { + $y = empty($s[$key]); + } finally { + assertVariableCertainty(TrinaryLogic::createYes(), $y); + } +} + +/** + * @param mixed[]|string $container + * @param mixed $key + */ +function issetOfArrayOrStringOffset($container, $key): void +{ + try { + $y = isset($container[$key]); + } finally { + assertVariableCertainty(TrinaryLogic::createMaybe(), $y); + } +} + +/** + * @param mixed $key + */ +function issetOfNestedStringOffset(string $s, $key): void +{ + try { + // the inner link is read before isset() asks about the outer one + $y = isset($s[$key][0]); + } finally { + assertVariableCertainty(TrinaryLogic::createMaybe(), $y); + } +} diff --git a/tests/PHPStan/Levels/data/arrayOffsetAccess-4.json b/tests/PHPStan/Levels/data/arrayOffsetAccess-4.json index 3f91016ab34..eba3b85399b 100644 --- a/tests/PHPStan/Levels/data/arrayOffsetAccess-4.json +++ b/tests/PHPStan/Levels/data/arrayOffsetAccess-4.json @@ -14,26 +14,6 @@ "line": 18, "ignorable": true }, - { - "message": "Expression \"$a[$objectOrInt]\" on a separate line does not do anything.", - "line": 19, - "ignorable": true - }, - { - "message": "Expression \"$a[$objectOrNull]\" on a separate line does not do anything.", - "line": 20, - "ignorable": true - }, - { - "message": "Expression \"$a[$explicitlyMixed]\" on a separate line does not do anything.", - "line": 21, - "ignorable": true - }, - { - "message": "Expression \"$a[$implicitlyMixed]\" on a separate line does not do anything.", - "line": 22, - "ignorable": true - }, { "message": "Expression \"$arrayOrObject[42]\" on a separate line does not do anything.", "line": 24, @@ -49,26 +29,6 @@ "line": 27, "ignorable": true }, - { - "message": "Expression \"$arrayOrObject[$objectOrInt]\" on a separate line does not do anything.", - "line": 28, - "ignorable": true - }, - { - "message": "Expression \"$arrayOrObject[$objectOrNull]\" on a separate line does not do anything.", - "line": 29, - "ignorable": true - }, - { - "message": "Expression \"$arrayOrObject[$explicitlyMixed]\" on a separate line does not do anything.", - "line": 30, - "ignorable": true - }, - { - "message": "Expression \"$arrayOrObject[$implicitlyMixed]\" on a separate line does not do anything.", - "line": 31, - "ignorable": true - }, { "message": "Expression \"$explicitlyMixed[42]\" on a separate line does not do anything.", "line": 33, @@ -84,26 +44,6 @@ "line": 36, "ignorable": true }, - { - "message": "Expression \"$explicitlyMixed[$objectOrInt]\" on a separate line does not do anything.", - "line": 37, - "ignorable": true - }, - { - "message": "Expression \"$explicitlyMixed[$objectOrNull]\" on a separate line does not do anything.", - "line": 38, - "ignorable": true - }, - { - "message": "Expression \"$explicitlyMixed[$explicitlyMixed]\" on a separate line does not do anything.", - "line": 39, - "ignorable": true - }, - { - "message": "Expression \"$explicitlyMixed[$implicitlyMixed]\" on a separate line does not do anything.", - "line": 40, - "ignorable": true - }, { "message": "Expression \"$implicitlyMixed[42]\" on a separate line does not do anything.", "line": 42, @@ -118,25 +58,5 @@ "message": "Expression \"$implicitlyMixed[$intOrNull]\" on a separate line does not do anything.", "line": 45, "ignorable": true - }, - { - "message": "Expression \"$implicitlyMixed[$objectOrInt]\" on a separate line does not do anything.", - "line": 46, - "ignorable": true - }, - { - "message": "Expression \"$implicitlyMixed[$objectOrNull]\" on a separate line does not do anything.", - "line": 47, - "ignorable": true - }, - { - "message": "Expression \"$implicitlyMixed[$explicitlyMixed]\" on a separate line does not do anything.", - "line": 48, - "ignorable": true - }, - { - "message": "Expression \"$implicitlyMixed[$implicitlyMixed]\" on a separate line does not do anything.", - "line": 49, - "ignorable": true } -] +] \ No newline at end of file diff --git a/tests/PHPStan/Rules/DeadCode/UnusedVariableRuleTest.php b/tests/PHPStan/Rules/DeadCode/UnusedVariableRuleTest.php index 2ceca6991c7..22b13f2bde0 100644 --- a/tests/PHPStan/Rules/DeadCode/UnusedVariableRuleTest.php +++ b/tests/PHPStan/Rules/DeadCode/UnusedVariableRuleTest.php @@ -28,6 +28,17 @@ public function testThrowableCatchAfterDocumentedException(): void $this->analyse([__DIR__ . '/data/unused-variable-throwable-catch.php'], []); } + #[RequiresPhp('>= 8.0.0')] + public function testIllegalOffsetThrowReachesCatch(): void + { + $this->analyse([__DIR__ . '/data/unused-variable-illegal-offset.php'], [ + [ + 'Value assigned to variable $x is never read before being overwritten.', + 74, + ], + ]); + } + public function testOverridingThrowsReachesCatch(): void { $this->analyse([__DIR__ . '/data/unused-variable-overriding-throws.php'], [ diff --git a/tests/PHPStan/Rules/DeadCode/data/unused-variable-illegal-offset.php b/tests/PHPStan/Rules/DeadCode/data/unused-variable-illegal-offset.php new file mode 100644 index 00000000000..8fe64aaf22a --- /dev/null +++ b/tests/PHPStan/Rules/DeadCode/data/unused-variable-illegal-offset.php @@ -0,0 +1,85 @@ += 8.0 + +declare(strict_types = 1); + +namespace UnusedVariableIllegalOffset; + +use TypeError; + +/** + * @param mixed[] $array + * @param mixed $key + * @return mixed[] + */ +function write(array $array, $key): array +{ + $x = 1; + try { + $array[$key] = 1; + $x = 2; + } catch (TypeError) { + echo $x; + return []; + } + + echo $x; + return $array; +} + +/** + * @param mixed[] $array + * @param mixed $key + * @return mixed[] + */ +function assignOp(array $array, $key): array +{ + $x = 1; + try { + $array[$key] .= 'a'; + $x = 2; + } catch (TypeError) { + echo $x; + return []; + } + + echo $x; + return $array; +} + +/** + * @param mixed $key + * @return mixed[] + */ +function literal($key): array +{ + $x = 1; + try { + $array = [$key => 1]; + $x = 2; + } catch (TypeError) { + echo $x; + return []; + } + + echo $x; + return $array; +} + +/** + * @param mixed[] $array + * @return mixed[] + */ +function intKey(array $array, int $key): array +{ + $x = 1; + try { + $array[$key] = 1; + $x = 2; + } catch (TypeError) { + echo $x; + return []; + } + + echo $x; + return $array; +} diff --git a/tests/PHPStan/Rules/Exceptions/CatchWithUnthrownExceptionRuleTest.php b/tests/PHPStan/Rules/Exceptions/CatchWithUnthrownExceptionRuleTest.php index 43633d859c5..84be4d17281 100644 --- a/tests/PHPStan/Rules/Exceptions/CatchWithUnthrownExceptionRuleTest.php +++ b/tests/PHPStan/Rules/Exceptions/CatchWithUnthrownExceptionRuleTest.php @@ -734,6 +734,45 @@ public function testBug9146(): void ]); } + #[RequiresPhp('>= 8.0.0')] + public function testBug6970(): void + { + $this->analyse([__DIR__ . '/data/bug-6970.php'], [ + [ + 'Dead catch - TypeError is never thrown in the try block.', + 96, + ], + [ + 'Dead catch - TypeError is never thrown in the try block.', + 109, + ], + [ + 'Dead catch - TypeError is never thrown in the try block.', + 147, + ], + [ + 'Dead catch - TypeError is never thrown in the try block.', + 198, + ], + [ + 'Dead catch - TypeError is never thrown in the try block.', + 286, + ], + [ + 'Dead catch - TypeError is never thrown in the try block.', + 329, + ], + [ + 'Dead catch - TypeError is never thrown in the try block.', + 341, + ], + [ + 'Dead catch - TypeError is never thrown in the try block.', + 353, + ], + ]); + } + #[RequiresPhp('>= 8.3.0')] public function testPr5105(): void { diff --git a/tests/PHPStan/Rules/Exceptions/data/bug-6970.php b/tests/PHPStan/Rules/Exceptions/data/bug-6970.php new file mode 100644 index 00000000000..fcd6e0cd339 --- /dev/null +++ b/tests/PHPStan/Rules/Exceptions/data/bug-6970.php @@ -0,0 +1,413 @@ += 8.0 + +namespace Bug6970; + +use ArrayAccess; +use Generator; +use TypeError; +use UnexpectedValueException; +use function count; + +/** + * @template K + * @template T + * @template L + * @template U + * + * @param iterable $stream + * @param callable(T, K): Generator $fn + * + * @return Generator + */ +function scollect(iterable $stream, callable $fn): Generator +{ + foreach ($stream as $key => $value) { + yield from $fn($value, $key); + } +} + +/** + * @param mixed[] $array + * + * @return mixed[] + */ +function collectWithKeys(array $array, callable $fn): array +{ + $stream = scollect($array, $fn); + + $values = []; + $counter = 0; + + foreach ($stream as $key => $value) { + try { + $values[$key] = $value; + } catch (TypeError) { + throw new UnexpectedValueException('The key yielded in the callable is not compatible with the type "array-key".'); + } + + ++$counter; + } + + if ($counter !== count($values)) { + throw new UnexpectedValueException( + 'Data loss occurred because of duplicated keys. Use `collect()` if you do not care about ' . + 'the yielded keys, or use `scollect()` if you need to support duplicated keys (as arrays cannot).', + ); + } + + return $values; +} + +class MoreCases +{ + + /** + * @param mixed[] $array + * @param mixed $key + */ + public function writeMixedKey(array $array, $key): void + { + try { + $array[$key] = 1; + } catch (TypeError) { // not dead + echo 'caught'; + } + } + + /** + * @param mixed[] $array + */ + public function writeObjectKey(array $array, \stdClass $key): void + { + try { + $array[$key] = 1; + } catch (TypeError) { // not dead + echo 'caught'; + } + } + + /** + * @param mixed[] $array + */ + public function writeIntKey(array $array, int $key): void + { + try { + $array[$key] = 1; + } catch (TypeError) { // error: Dead catch - TypeError is never thrown in the try block. + echo 'caught'; + } + } + + /** + * @param mixed[] $array + * @param mixed $value + */ + public function append(array $array, $value): void + { + try { + $array[] = $value; + } catch (TypeError) { // error: Dead catch - TypeError is never thrown in the try block. + echo 'caught'; + } + } + + /** + * @param mixed[] $array + * @param mixed $key + */ + public function assignOpMixedKey(array $array, $key): void + { + try { + $array[$key] .= 'x'; + } catch (TypeError) { // not dead + echo 'caught'; + } + } + + /** + * @param mixed[] $array + * @param mixed $key + */ + public function readMixedKey(array $array, $key): void + { + try { + $x = $array[$key]; + } catch (TypeError) { // not dead + echo 'caught'; + } + } + + /** + * @param mixed[] $array + */ + public function readIntKey(array $array, int $key): void + { + try { + $x = $array[$key]; + } catch (TypeError) { // error: Dead catch - TypeError is never thrown in the try block. + echo 'caught'; + } + } + + /** + * @param mixed[] $array + * @param mixed $key + */ + public function readCoalesceMixedKey(array $array, $key): void + { + try { + $x = $array[$key] ?? null; + } catch (TypeError) { // not dead + echo 'caught'; + } + } + + /** + * @param mixed[] $array + * @param mixed $key + */ + public function issetMixedKey(array $array, $key): void + { + try { + $x = isset($array[$key]); + } catch (TypeError) { // not dead + echo 'caught'; + } + } + + /** + * @param mixed[] $array + * @param mixed $key + */ + public function unsetMixedKey(array $array, $key): void + { + try { + unset($array[$key]); + } catch (TypeError) { // not dead + echo 'caught'; + } + } + + /** + * @param mixed[] $array + */ + public function unsetIntKey(array $array, int $key): void + { + try { + unset($array[$key]); + } catch (TypeError) { // error: Dead catch - TypeError is never thrown in the try block. + echo 'caught'; + } + } + + /** + * @param mixed $key + */ + public function stringOffsetWriteMixedKey(string $s, $key): void + { + try { + $s[$key] = 'x'; + } catch (TypeError) { // not dead + echo 'caught'; + } + } + + /** + * @param mixed $key + */ + public function stringOffsetReadMixedKey(string $s, $key): void + { + try { + $x = $s[$key]; + } catch (TypeError) { // not dead + echo 'caught'; + } + } + + /** + * @param mixed[] $array + * @param int|mixed[] $key + */ + public function writeArrayOrIntKey(array $array, $key): void + { + try { + $array[$key] = 1; + } catch (TypeError) { // not dead - key may be an array + echo 'caught'; + } + } + + /** + * @param mixed[] $array + * @param int|\stdClass $key + */ + public function writeObjectOrIntKey(array $array, $key): void + { + try { + $array[$key] = 1; + } catch (TypeError) { // not dead - key may be an object + echo 'caught'; + } + } + + /** + * @param mixed[]|int $arrayOrInt + * @param mixed $key + */ + public function readArrayOrIntVar($arrayOrInt, $key): void + { + try { + $x = $arrayOrInt[$key]; + } catch (TypeError) { // not dead - reading an array offset with an illegal key throws + echo 'caught'; + } + } + + /** + * @param string|int $stringOrInt + * @param mixed $key + */ + public function readStringOrIntVar($stringOrInt, $key): void + { + try { + $x = $stringOrInt[$key]; + } catch (TypeError) { // not dead - reading a string offset with an illegal key throws + echo 'caught'; + } + } + + /** + * @param mixed $key + */ + public function writeConcreteArrayAccessKey(ConcreteArrayAccess $container, $key): void + { + try { + $container[$key] = 1; + } catch (TypeError) { // error: dead - ArrayAccess offsetSet() handles the offset, so no TypeError from the illegal offset + echo 'caught'; + } + } + + /** + * @param mixed[]|ConcreteArrayAccess $container + * @param mixed $key + */ + public function writeMaybeArrayAccessKey($container, $key): void + { + try { + $container[$key] = 1; + } catch (TypeError) { // not dead - container may be a plain array, whose illegal offset throws + echo 'caught'; + } + } + + public function arrayLiteralObjectKey(\stdClass $key): void + { + try { + $x = [$key => 1]; + } catch (TypeError) { // not dead + echo 'caught'; + } + } + + /** + * @param mixed $key + */ + public function arrayLiteralMixedKey($key): void + { + try { + $x = [$key => 1]; + } catch (TypeError) { // not dead + echo 'caught'; + } + } + + public function arrayLiteralIntKey(int $key): void + { + try { + $x = [$key => 1]; + } catch (TypeError) { // error: Dead catch - TypeError is never thrown in the try block. + echo 'caught'; + } + } + + /** + * @param mixed $key + */ + public function issetStringOffsetMixedKey(string $s, $key): void + { + try { + $x = isset($s[$key]); + } catch (TypeError) { // error: Dead catch - isset() of an illegal string offset is false + echo 'caught'; + } + } + + /** + * @param mixed $key + */ + public function emptyStringOffsetMixedKey(string $s, $key): void + { + try { + $x = empty($s[$key]); + } catch (TypeError) { // error: Dead catch - empty() of an illegal string offset is true + echo 'caught'; + } + } + + /** + * @param mixed[]|string $container + * @param mixed $key + */ + public function issetArrayOrStringOffsetMixedKey($container, $key): void + { + try { + $x = isset($container[$key]); + } catch (TypeError) { // not dead - container may be an array + echo 'caught'; + } + } + + /** + * @param mixed $key + */ + public function issetNestedStringOffsetMixedKey(string $s, $key): void + { + try { + $x = isset($s[$key][0]); + } catch (TypeError) { // not dead - the inner offset is read + echo 'caught'; + } + } + +} + +/** + * @implements ArrayAccess + */ +final class ConcreteArrayAccess implements ArrayAccess +{ + + public function offsetExists($offset): bool + { + return true; + } + + public function offsetGet($offset): mixed + { + return null; + } + + /** + * @throws \RuntimeException + */ + public function offsetSet($offset, $value): void + { + throw new \RuntimeException(); + } + + public function offsetUnset($offset): void + { + } + +} diff --git a/turbo-ext/src/AnalyserValues.h b/turbo-ext/src/AnalyserValues.h index a707c6470df..d755bb2cee9 100644 --- a/turbo-ext/src/AnalyserValues.h +++ b/turbo-ext/src/AnalyserValues.h @@ -471,6 +471,7 @@ PT_AV_PREPARED_ASSIGN_TARGET_REQUIRED(target_read_result, targetReadResult, "get PT_AV_PREPARED_ASSIGN_TARGET(target_chain_results, targetChainResults, "gettargetchainresults") PT_AV_PREPARED_ASSIGN_TARGET(variable_name_result, variableNameResult, "getvariablenameresult") PT_AV_PREPARED_ASSIGN_TARGET(dependencies, dependencies, "getdependencies") +PT_AV_PREPARED_ASSIGN_TARGET(illegal_offset_dim_fetches, illegalOffsetDimFetches, "getillegaloffsetdimfetches") #undef PT_AV_PREPARED_ASSIGN_TARGET #undef PT_AV_PREPARED_ASSIGN_TARGET_REQUIRED diff --git a/turbo-ext/src/ArrayDimFetchHandler.cpp b/turbo-ext/src/ArrayDimFetchHandler.cpp index f7809d67b15..fed12fe5108 100644 --- a/turbo-ext/src/ArrayDimFetchHandler.cpp +++ b/turbo-ext/src/ArrayDimFetchHandler.cpp @@ -16,9 +16,10 @@ * NodeScopeResolver, MutatingScope, ExpressionResult, ExpressionContext, * VariableFlow(Builder), IssetabilityDescriptor, DefaultNarrowingHelper, * MethodThrowPointHelper, MethodCallReturnTypeHelper, the method reflections, - * VariableWriteOffset, TypeCombinator and the Type kernel are called through - * their direct entries; ParametersAcceptorSelector through its - * CallHandlerSupport.h helper. + * VariableWriteOffset, TypeCombinator, IllegalOffsetTypeHelper, + * InternalThrowPoint and the Type kernel are called through their direct + * entries; ParametersAcceptorSelector through its CallHandlerSupport.h helper; + * PhpVersion through PhpVersionAccess.cpp. */ #include "support.h" @@ -53,6 +54,7 @@ zval *variableName(zval *variable) { return nodeProperty(pt_adfh_variable_name_s /* the permanent interned literals (module startup) */ zend_string *pt_adfh_offset_get = nullptr; zend_string *pt_adfh_array_access = nullptr; +zend_string *pt_adfh_type_error = nullptr; zend_string *pt_adfh_synthetic_site_attribute = nullptr; /* $type->$op() as a PT_TRI_* value (a TrinaryLogic or a result object); -1 = @@ -102,12 +104,13 @@ class ArrayDimFetchHandler explicit ArrayDimFetchHandler(zend_object *self) : self(self) {} /* the constructor body: the promoted properties */ - void construct(zval *expressionResultFactory, zval *defaultNarrowingHelper, zval *methodThrowPointHelper, zval *methodCallReturnTypeHelper) const + void construct(zval *expressionResultFactory, zval *defaultNarrowingHelper, zval *methodThrowPointHelper, zval *methodCallReturnTypeHelper, zval *phpVersion) const { pt_write_slot(self, slots::expressionResultFactory, expressionResultFactory); pt_write_slot(self, slots::defaultNarrowingHelper, defaultNarrowingHelper); pt_write_slot(self, slots::methodThrowPointHelper, methodThrowPointHelper); pt_write_slot(self, slots::methodCallReturnTypeHelper, methodCallReturnTypeHelper); + pt_write_slot(self, slots::phpVersion, phpVersion); } /* Mirrors supports(); false = pending exception */ @@ -260,6 +263,39 @@ class ArrayDimFetchHandler } } + bool throwsTypeErrorForIllegalOffsets; + if (UNEXPECTED(!pt_php_version_answer(OBJ_PROP_NUM(self, slots::phpVersion), PT_PHP_VERSION_THROWS_TYPE_ERROR_FOR_ILLEGAL_OFFSETS, throwsTypeErrorForIllegalOffsets))) return zv::Val(); + if (throwsTypeErrorForIllegalOffsets) { + // only array and string offset reads throw TypeError for illegal offsets, + // reads on null and other scalars emit a warning, ArrayAccess objects go through offsetGet(); + // isset() and empty() of a string offset are false instead + bool mayBeArrayOrString = varIsArray != PT_TRI_NO; + if (!mayBeArrayOrString) { + bool issetTarget; + if (UNEXPECTED(!pt_expression_context_is_isset_target(context, issetTarget))) return zv::Val(); + if (!issetTarget) { + zend_long varIsString = typeOpTrinary(varType.raw(), PT_OP_IS_STRING, 0, NULL); + if (UNEXPECTED(varIsString < 0)) return zv::Val(); + mayBeArrayOrString = varIsString != PT_TRI_NO; + } + } + if (mayBeArrayOrString) { + zv::Val dimType = pt_expression_result_get_type(dimResult); + if (UNEXPECTED(dimType.isUndef())) return zv::Val(); + bool mayThrow; + if (UNEXPECTED(!pt_illegal_offset_type_helper_may_offset_throw_type_error(dimType.raw(), mayThrow))) return zv::Val(); + if (mayThrow) { + zval className; + ZVAL_STR(&className, pt_adfh_type_error); + zv::Val typeError = pt_type_new_object_type(&className); + if (UNEXPECTED(typeError.isUndef())) return zv::Val(); + zv::Val throwPoint = pt_internal_throw_point_create_explicit(scope.raw(), typeError.raw(), expr, false); + if (UNEXPECTED(throwPoint.isUndef())) return zv::Val(); + appendTo(throwPoints, std::move(throwPoint)); + } + } + } + zv::Val variableFlow; { zv::Val varFlow = pt_expression_result_variable_flow(varResult); @@ -522,6 +558,7 @@ PT_MINIT_REGISTRATION(pt_register_array_dim_fetch_handler) { pt_adfh_offset_get = zend_string_init_interned(PT_LC("offsetGet"), 1); pt_adfh_array_access = zend_string_init_interned(PT_LC("ArrayAccess"), 1); + pt_adfh_type_error = zend_string_init_interned(PT_LC("TypeError"), 1); pt_adfh_synthetic_site_attribute = zend_string_init_interned(PT_LC("templateArgumentSyntheticSite"), 1); reg::Class cls("PHPStan\\Analyser\\ExprHandler\\ArrayDimFetchHandler"); @@ -531,9 +568,9 @@ PT_MINIT_REGISTRATION(pt_register_array_dim_fetch_handler) /* the real parameter class names: the DI container autowires the * service by reflecting the constructor */ cls.method(sigs::__construct, [](INTERNAL_FUNCTION_PARAMETERS) { - zval *expressionResultFactory, *defaultNarrowingHelper, *methodThrowPointHelper, *methodCallReturnTypeHelper; - if (!zp::parse(execute_data, expressionResultFactory, defaultNarrowingHelper, methodThrowPointHelper, methodCallReturnTypeHelper)) RETURN_THROWS(); - ArrayDimFetchHandler(Z_OBJ_P(ZEND_THIS)).construct(expressionResultFactory, defaultNarrowingHelper, methodThrowPointHelper, methodCallReturnTypeHelper); + zval *expressionResultFactory, *defaultNarrowingHelper, *methodThrowPointHelper, *methodCallReturnTypeHelper, *phpVersion; + if (!zp::parse(execute_data, expressionResultFactory, defaultNarrowingHelper, methodThrowPointHelper, methodCallReturnTypeHelper, phpVersion)) RETURN_THROWS(); + ArrayDimFetchHandler(Z_OBJ_P(ZEND_THIS)).construct(expressionResultFactory, defaultNarrowingHelper, methodThrowPointHelper, methodCallReturnTypeHelper, phpVersion); }); cls.method<&ArrayDimFetchHandler::supports, zp::Obj>(sigs::supports); diff --git a/turbo-ext/src/ArrayHandler.cpp b/turbo-ext/src/ArrayHandler.cpp index 7211f9d1d85..07d32da82c2 100644 --- a/turbo-ext/src/ArrayHandler.cpp +++ b/turbo-ext/src/ArrayHandler.cpp @@ -13,8 +13,10 @@ * SpecifiedTypes::emptySpecifyCallback(), as in the twin. * * NodeScopeResolver, MutatingScope, ExpressionResult, ExpressionContext, - * VariableFlow(Builder), VariableWriteOffset, TypeCombinator and the Type - * kernel are called through their direct entries; the VariableWrite value + * VariableFlow(Builder), VariableWriteOffset, TypeCombinator, + * IllegalOffsetTypeHelper, InternalThrowPoint and the Type kernel are called + * through their direct entries, PhpVersion through PhpVersionAccess.cpp; the + * VariableWrite value * target through its slots (VariableFlow.cpp's pt_variable_write_slots_of()), * InitializerExprTypeResolver::getArrayType() through its direct entry; the * LiteralArrayItem / LiteralArrayNode / VariableWrite virtual @@ -88,8 +90,17 @@ zv::Val variableWriteGetter(zval *write, uint32_t pt_variable_write_slots::*memb /* {{{ small value helpers */ -/* the permanent interned 'is_callable' (module startup) */ +/* the permanent interned 'is_callable' / 'TypeError' (module startup) */ zend_string *pt_arh_is_callable = nullptr; +zend_string *pt_arh_type_error = nullptr; + +/* new ObjectType(TypeError::class) */ +zv::Val newTypeErrorType() +{ + zval className; + ZVAL_STR(&className, pt_arh_type_error); + return pt_type_new_object_type(&className); +} /* a PHP `$x !== null` read of a $nextIndex zval */ inline bool isSet(const zval *value) @@ -140,10 +151,11 @@ class ArrayHandler explicit ArrayHandler(zend_object *self) : self(self) {} /* the constructor body: the promoted properties */ - void construct(zval *initializerExprTypeResolver, zval *expressionResultFactory) const + void construct(zval *initializerExprTypeResolver, zval *expressionResultFactory, zval *phpVersion) const { pt_write_slot(self, slots::initializerExprTypeResolver, initializerExprTypeResolver); pt_write_slot(self, slots::expressionResultFactory, expressionResultFactory); + pt_write_slot(self, slots::phpVersion, phpVersion); } /* Mirrors supports(); false = pending exception */ @@ -343,6 +355,31 @@ class ArrayHandler return zv::Val(); } if (UNEXPECTED(!storeItemResult(itemResults, arrayItem, heldValue.raw(), valueResult.raw(), variableFlows, hasYield, throwPoints, impurePoints, isAlwaysTerminating, scope, hold))) return zv::Val(); + if (!keyResult.isUndef()) { + bool throwsTypeErrorForIllegalOffsets; + if (UNEXPECTED(!pt_php_version_answer(OBJ_PROP_NUM(self, slots::phpVersion), PT_PHP_VERSION_THROWS_TYPE_ERROR_FOR_ILLEGAL_OFFSETS, throwsTypeErrorForIllegalOffsets))) return zv::Val(); + if (throwsTypeErrorForIllegalOffsets) { + zv::Val keyResultType = pt_expression_result_get_type(keyResult.raw()); + if (UNEXPECTED(keyResultType.isUndef())) return zv::Val(); + bool mayThrow; + if (UNEXPECTED(!pt_illegal_offset_type_helper_may_offset_throw_type_error(keyResultType.raw(), mayThrow))) return zv::Val(); + if (mayThrow) { + // an array or an object key throws TypeError once the value was evaluated + zv::Val typeError = newTypeErrorType(); + if (UNEXPECTED(typeError.isUndef())) return zv::Val(); + zval *throwingKey = itemKey(arrayItem); + if (UNEXPECTED(throwingKey == NULL)) return zv::Val(); + zv::Val throwPoint = pt_internal_throw_point_create_explicit(scope.raw(), typeError.raw(), throwingKey, false); + if (UNEXPECTED(throwPoint.isUndef())) return zv::Val(); + appendTo(throwPoints, std::move(throwPoint)); + zv::Val flowType = newTypeErrorType(); + if (UNEXPECTED(flowType.isUndef())) return zv::Val(); + zv::Val throwFlow = pt_variable_flow_throwing(flowType.raw(), true, false); + if (UNEXPECTED(throwFlow.isUndef())) return zv::Val(); + variableFlows.push(std::move(throwFlow)); + } + } + } // the item's callback fires after its key and value were processed, // with the item's entry scope @@ -732,6 +769,7 @@ using phpstanturbo::ArrayHandler; PT_MINIT_REGISTRATION(pt_register_array_handler) { pt_arh_is_callable = zend_string_init_interned(PT_LC("is_callable"), 1); + pt_arh_type_error = zend_string_init_interned(PT_LC("TypeError"), 1); reg::Class cls("PHPStan\\Analyser\\ExprHandler\\ArrayHandler"); ptdecl::ArrayHandler::declareClass(cls); @@ -740,9 +778,9 @@ PT_MINIT_REGISTRATION(pt_register_array_handler) /* the real parameter class names: the DI container autowires the * service by reflecting the constructor */ cls.method(sigs::__construct, [](INTERNAL_FUNCTION_PARAMETERS) { - zval *initializerExprTypeResolver, *expressionResultFactory; - if (!zp::parse(execute_data, initializerExprTypeResolver, expressionResultFactory)) RETURN_THROWS(); - ArrayHandler(Z_OBJ_P(ZEND_THIS)).construct(initializerExprTypeResolver, expressionResultFactory); + zval *initializerExprTypeResolver, *expressionResultFactory, *phpVersion; + if (!zp::parse(execute_data, initializerExprTypeResolver, expressionResultFactory, phpVersion)) RETURN_THROWS(); + ArrayHandler(Z_OBJ_P(ZEND_THIS)).construct(initializerExprTypeResolver, expressionResultFactory, phpVersion); }); cls.method<&ArrayHandler::supports, zp::Obj>(sigs::supports); diff --git a/turbo-ext/src/AssignHandler.cpp b/turbo-ext/src/AssignHandler.cpp index f5a81e3b4bc..567d3d0751a 100644 --- a/turbo-ext/src/AssignHandler.cpp +++ b/turbo-ext/src/AssignHandler.cpp @@ -651,6 +651,18 @@ zv::Val vehCreateTypeExprResult(zval *helper, zval *scope, zval *expr) return pt_php_version_supports_property_hooks(phpVersion, out); } +/* $phpVersion->throwsTypeErrorForIllegalOffsets(); false = pending exception */ +[[nodiscard]] bool phpVersionThrowsTypeErrorForIllegalOffsets(zval *phpVersion, bool &out) +{ + return pt_php_version_answer(phpVersion, PT_PHP_VERSION_THROWS_TYPE_ERROR_FOR_ILLEGAL_OFFSETS, out); +} + +/* IllegalOffsetTypeHelper::mayOffsetThrowTypeError($offsetType); false = pending exception */ +[[nodiscard]] bool iothMayOffsetThrowTypeError(zval *offsetType, bool &out) +{ + return pt_illegal_offset_type_helper_may_offset_throw_type_error(offsetType, out); +} + /* VirtualAssignNodeCallback::create($nodeCallback) */ zv::Val vancCreate(zval *nodeCallback) { @@ -760,6 +772,7 @@ struct PreparedTarget zval *targetChainResults = NULL; zval *variableNameResult = NULL; zval *dependencies = NULL; + zval *illegalOffsetDimFetches = NULL; }; /* new PreparedAssignTarget(...) */ @@ -774,9 +787,9 @@ zv::Val preparedAssignTargetNew(const PreparedTarget &t) zv::Args argv{ kind.raw(), t.var, t.assignedExpr, t.beforeScope, t.scope, t.enterExpressionAssign, t.isAssignOp, t.hasYield, t.throwPoints, t.impurePoints, t.isAlwaysTerminating, orNull(t.rootVar), orNull(t.varResult), orNull(t.dimFetchStack), orNull(t.assignedPropertyExpr), orNull(t.offsetTypes), orNull(t.offsetNativeTypes), orNull(t.existingOffsetTypes), orNull(t.existingOffsetNativeTypes), orNull(t.offsetSetTargetResult), orNull(t.objectResult), orNull(t.propertyName), orNull(t.propertyHolderType), orNull(t.targetReadResult), t.targetChainResults != NULL ? t.targetChainResults : &emptyArray, orNull(t.variableNameResult), - orNull(t.dependencies), + orNull(t.dependencies), t.illegalOffsetDimFetches != NULL ? t.illegalOffsetDimFetches : &emptyArray, }; - return pt_prepared_assign_target_new(27, argv); + return pt_prepared_assign_target_new(28, argv); } /* the getters: the borrowed slots of the native class (its private @@ -1249,9 +1262,15 @@ class AssignHandler AH_SET(inputsFlow, pt_variable_flow_inputs(zval_get_long(writeId.raw()), targetId.raw())); } } + // an illegal offset throws before the write + zv::Val illegalOffsetThrowFlow = ahNull(); + if (zend_hash_num_elements(Z_ARRVAL_P(AH_TARGET(target.raw(), illegalOffsetDimFetches))) > 0) { + AH_VAL(typeError, newObjectType(PT_LC("TypeError"))); + AH_SET(illegalOffsetThrowFlow, pt_variable_flow_throwing(typeError.raw(), true, false)); + } AH_VAL(targetWriteFlow, pt_variable_flow_builder_target_write(exprVar, PT_AH_KIND_ASSIGN, scope.raw(), storage, redundantType.raw())); - zv::Args flows{targetReadFlow.raw(), assignedFlow.raw(), inputsFlow.raw(), targetWriteFlow.raw()}; - AH_SET(variableFlow, pt_variable_flow_sequence(4, flows)); + zv::Args flows{targetReadFlow.raw(), assignedFlow.raw(), inputsFlow.raw(), illegalOffsetThrowFlow.raw(), targetWriteFlow.raw()}; + AH_SET(variableFlow, pt_variable_flow_sequence(5, flows)); } if (isAssign && ahIs(exprExpr, PT_CLASS_ARRAY_EXPR) && hasArrayReference(exprExpr)) { AH_VAL(escape, pt_variable_flow_builder_escape_root(exprVar)); @@ -1543,6 +1562,52 @@ class AssignHandler AH_OK(nsrCallNodeCallback(nsr, nodeCallback, dimFetch, callbackScope.raw(), storage)); } + // the links whose offset may throw TypeError - PHP throws only after + // evaluating the assigned value, so applyWrite() creates their throw points + zv::Val illegalOffsetDimFetches = ahEmptyArray(); + bool throwsTypeErrorForIllegalOffsets; + AH_OK(phpVersionThrowsTypeErrorForIllegalOffsets(prop(slots::phpVersion), throwsTypeErrorForIllegalOffsets)); + if (throwsTypeErrorForIllegalOffsets) { + HashTable *links = Z_ARRVAL_P(deferredDimFetchResults.raw()); + for (auto entry : zv::TableRef(Z_ARRVAL_P(offsetTypes.raw()))) { + zend_ulong i = entry.indexKey(); + HashTable *pair = Z_ARRVAL_P(entry.value().raw()); + zval *offsetType = zend_hash_index_find(pair, 0); + // $arr[] = ... never throws for the offset + if (Z_TYPE_P(offsetType) == IS_NULL) continue; + bool mayThrow; + AH_OK(iothMayOffsetThrowTypeError(offsetType, mayThrow)); + if (!mayThrow) continue; + // writes to ArrayAccess objects go through offsetSet() whose throw points applyWrite() models; + // the container is the previous chain link - the root for the first dimension + zval *containerResult = i == 0 ? varResult.raw() : zend_hash_index_find(Z_ARRVAL_P(zend_hash_index_find(links, i - 1)), 1); + AH_VAL(containerType, pt_expression_result_get_type(containerResult)); + AH_VAL(arrayAccess, newObjectType(PT_LC("ArrayAccess"))); + zend_long isArrayAccess = ahTypeOpTri(arrayAccess.raw(), PT_OP_IS_SUPER_TYPE_OF, 1, containerType.raw()); + AH_OK(isArrayAccess >= 0); + if (isArrayAccess == PT_TRI_YES) continue; + // `$lvalue ??= ...` throws reading an array or a string container, + // before the assigned value is evaluated + if (walkModeIssetSemanticsForRead(mode)) { + zend_long containerIsArray = ahTypeOpTri(containerType.raw(), PT_OP_IS_ARRAY, 0, NULL); + AH_OK(containerIsArray >= 0); + bool readThrows = containerIsArray == PT_TRI_YES; + if (!readThrows) { + zend_long containerIsString = ahTypeOpTri(containerType.raw(), PT_OP_IS_STRING, 0, NULL); + AH_OK(containerIsString >= 0); + readThrows = containerIsString == PT_TRI_YES; + } + if (readThrows) { + AH_VAL(typeError, newObjectType(PT_LC("TypeError"))); + AH_VAL(throwPoint, itpCreateExplicit(scope.raw(), typeError.raw(), zend_hash_index_find(pair, 1), false)); + ahPush(throwPoints, std::move(throwPoint)); + continue; + } + } + ahPushRef(illegalOffsetDimFetches, zend_hash_index_find(pair, 1)); + } + } + HashTable *deferred = Z_ARRVAL_P(deferredDimFetchResults.raw()); uint32_t deferredCount = zend_hash_num_elements(deferred); if (walkModeIssetSemanticsForRead(mode)) { @@ -1623,6 +1688,7 @@ class AssignHandler t.targetReadResult = targetReadResult.raw(); t.targetChainResults = targetChainResults.raw(); t.dependencies = targetDependencies.raw(); + t.illegalOffsetDimFetches = illegalOffsetDimFetches.raw(); return preparedAssignTargetNew(t); } @@ -3052,6 +3118,29 @@ class AssignHandler AH_SETB(varType, newEmptyConstantArrayType()); AH_SETB(varNativeType, newEmptyConstantArrayType()); } + + // an illegal offset throws TypeError after the assigned value was + // evaluated and an undefined root was turned into an array + zval *illegalOffsetDimFetches = AH_TARGET(target, illegalOffsetDimFetches); + if (zend_hash_num_elements(Z_ARRVAL_P(illegalOffsetDimFetches)) > 0) { + zv::Val throwPointScope = zv::Val::copyOf(scope.ref()); + zend_string *rootName = isImplicitArrayCreation != PT_TRI_NO ? ahStringVariableName(var) : NULL; + if (rootName != NULL) { + AH_VALB(emptyArray, newEmptyConstantArrayType()); + zv::Args unionArgs{varType.raw(), emptyArray.raw()}; + AH_VALB(rootType, pt_type_combinator_union(2, unionArgs)); + AH_VALB(nativeEmptyArray, newEmptyConstantArrayType()); + zv::Args nativeUnionArgs{varNativeType.raw(), nativeEmptyArray.raw()}; + AH_VALB(rootNativeType, pt_type_combinator_union(2, nativeUnionArgs)); + AH_SETB(throwPointScope, pt_mutating_scope_assign_variable(Z_OBJ_P(throwPointScope.raw()), rootName, rootType.raw(), rootNativeType.raw(), ahTrinary(PT_TRI_YES))); + } + for (auto entry : zv::TableRef(Z_ARRVAL_P(illegalOffsetDimFetches))) { + AH_VALB(typeError, newObjectType(PT_LC("TypeError"))); + AH_VALB(throwPoint, itpCreateExplicit(throwPointScope.raw(), typeError.raw(), entry.value().raw(), false)); + ahPush(throwPoints, std::move(throwPoint)); + } + } + zval *offsetValueType = varType.raw(); zval *offsetNativeValueType = varNativeType.raw(); diff --git a/turbo-ext/src/AssignOpHandler.cpp b/turbo-ext/src/AssignOpHandler.cpp index a596827c683..d688d3f4ef1 100644 --- a/turbo-ext/src/AssignOpHandler.cpp +++ b/turbo-ext/src/AssignOpHandler.cpp @@ -553,11 +553,21 @@ class AssignOpHandler AOH_SET(inputsFlow, pt_variable_flow_inputs(zval_get_long(writeId.raw()), targetId.raw())); } } + // an illegal offset throws before the write + zv::Val illegalOffsetThrowFlow = zv::Val::null(); + { + AOH_VAL(illegalOffsetDimFetches, targetRead(&pt_prepared_assign_target_illegal_offset_dim_fetches, target.raw())); + if (zend_hash_num_elements(Z_ARRVAL_P(illegalOffsetDimFetches.raw())) > 0) { + zv::Val className = zv::Val::string(PT_LC("TypeError")); + AOH_VAL(typeError, pt_type_new_object_type(className.raw())); + AOH_SET(illegalOffsetThrowFlow, pt_variable_flow_throwing(typeError.raw(), true, false)); + } + } zval null = {}; ZVAL_NULL(&null); AOH_VAL(targetWriteFlow, pt_variable_flow_builder_target_write(exprVar, PT_AOH_KIND_READ_MODIFY_WRITE, scope.raw(), storage, &null)); - zv::Args flows{rhsFlow.raw(), inputsFlow.raw(), targetWriteFlow.raw()}; - AOH_SET(writeFlow, pt_variable_flow_sequence(3, flows)); + zv::Args flows{rhsFlow.raw(), inputsFlow.raw(), illegalOffsetThrowFlow.raw(), targetWriteFlow.raw()}; + AOH_SET(writeFlow, pt_variable_flow_sequence(4, flows)); } zv::Val variableFlow; { diff --git a/turbo-ext/src/EmptyHandler.cpp b/turbo-ext/src/EmptyHandler.cpp index 93737a69819..38a72c08c41 100644 --- a/turbo-ext/src/EmptyHandler.cpp +++ b/turbo-ext/src/EmptyHandler.cpp @@ -190,9 +190,11 @@ class EmptyHandler { zv::Val deepContext = pt_expression_context_enter_deep(context); if (UNEXPECTED(deepContext.isUndef())) return zv::Val(); + zv::Val subjectContext = pt_expression_context_enter_isset_target(deepContext.raw()); + if (UNEXPECTED(subjectContext.isUndef())) return zv::Val(); subject = exprExpr(expr); if (UNEXPECTED(subject == NULL)) return zv::Val(); - exprResult = pt_node_scope_resolver_process_expr_node(nodeScopeResolver, stmt, subject, scope.raw(), storage, nodeCallback, deepContext.raw()); + exprResult = pt_node_scope_resolver_process_expr_node(nodeScopeResolver, stmt, subject, scope.raw(), storage, nodeCallback, subjectContext.raw()); if (UNEXPECTED(exprResult.isUndef())) return zv::Val(); } { diff --git a/turbo-ext/src/ExpressionContext.cpp b/turbo-ext/src/ExpressionContext.cpp index ad240e233f9..403f6486db1 100644 --- a/turbo-ext/src/ExpressionContext.cpp +++ b/turbo-ext/src/ExpressionContext.cpp @@ -4,7 +4,7 @@ * * A final value class with a private constructor: instances come only from * the static factories and the enter*() / with*() derivations. The state - * lives in the twin's fourteen promoted property slots (generated + * lives in the twin's fifteen promoted property slots (generated * declarations), so the standard object handlers do GC/free/clone — * enterPassedToType()'s `clone $this` is the engine's clone handler. * @@ -53,6 +53,7 @@ class ExpressionContext int valueConsumed = -1; zval *passedToType = NULL; zval *nativePassedToType = NULL; + bool issetTarget = false; }; /* the constructor body: the promoted properties */ @@ -80,6 +81,7 @@ class ExpressionContext zval_ptr_dtor(&old); write(slots::passedToType, f.passedToType); write(slots::nativePassedToType, f.nativePassedToType); + writeBool(slots::issetTarget, f.issetTarget); } /* Mirrors createTopLevel(). */ @@ -107,7 +109,7 @@ class ExpressionContext /* Mirrors enterDeep(). */ zv::Val enterDeep() const { - if (boolSlot(slots::isDeep) && isNull(slots::valueFlowTarget) && !boolSlot(slots::arrayDimFetchRoot) && !boolSlot(slots::unsetTarget) && isNull(slots::passedToType) && isNull(slots::nativePassedToType)) { + if (boolSlot(slots::isDeep) && isNull(slots::valueFlowTarget) && !boolSlot(slots::arrayDimFetchRoot) && !boolSlot(slots::unsetTarget) && !boolSlot(slots::issetTarget) && isNull(slots::passedToType) && isNull(slots::nativePassedToType)) { return thisValue(); } @@ -131,7 +133,7 @@ class ExpressionContext /* Mirrors withoutValueFlow(). */ zv::Val withoutValueFlow() const { - if (isNull(slots::valueFlowTarget) && !boolSlot(slots::arrayDimFetchRoot) && !boolSlot(slots::unsetTarget) && isNull(slots::passedToType) && isNull(slots::nativePassedToType)) { + if (isNull(slots::valueFlowTarget) && !boolSlot(slots::arrayDimFetchRoot) && !boolSlot(slots::unsetTarget) && !boolSlot(slots::issetTarget) && isNull(slots::passedToType) && isNull(slots::nativePassedToType)) { return thisValue(); } @@ -198,6 +200,7 @@ class ExpressionContext f.unsetTarget = boolSlot(slots::unsetTarget); zval *valueConsumed = slot(slots::valueConsumed); f.valueConsumed = Z_TYPE_P(valueConsumed) == IS_NULL ? -1 : (Z_TYPE_P(valueConsumed) == IS_TRUE ? 1 : 0); + f.issetTarget = boolSlot(slots::issetTarget); return newSelf(f); } @@ -287,6 +290,16 @@ class ExpressionContext bool isUnsetTarget() const { return boolSlot(slots::unsetTarget); } + /* Mirrors enterIssetTarget(). */ + zv::Val enterIssetTarget() const + { + Fields f = carried(); + f.issetTarget = true; + return newSelf(f); + } + + bool isIssetTarget() const { return boolSlot(slots::issetTarget); } + private: zend_object *self; @@ -446,6 +459,12 @@ zv::Val pt_expression_context_enter_unset_target(zval *context) return pt_type_call(Z_OBJ_P(context), PT_LC("enterunsettarget"), 0, NULL); } +zv::Val pt_expression_context_enter_isset_target(zval *context) +{ + if (isNative(context)) return ExpressionContext(Z_OBJ_P(context)).enterIssetTarget(); + return pt_type_call(Z_OBJ_P(context), PT_LC("enterissettarget"), 0, NULL); +} + zv::Val pt_expression_context_enter_passed_to_type(zval *context, zval *type, zval *nativeType) { if (isNative(context)) return ExpressionContext(Z_OBJ_P(context)).enterPassedToType(type, nativeType); @@ -540,6 +559,15 @@ bool pt_expression_context_is_unset_target(zval *context, bool &out) return boolMethod(context, PT_LC("isunsettarget"), out); } +bool pt_expression_context_is_isset_target(zval *context, bool &out) +{ + if (isNative(context)) { + out = ExpressionContext(Z_OBJ_P(context)).isIssetTarget(); + return true; + } + return boolMethod(context, PT_LC("isissettarget"), out); +} + zv::Val pt_expression_context_get_passed_to_type(zval *context) { if (isNative(context)) return ExpressionContext(Z_OBJ_P(context)).getPassedToType(); @@ -602,7 +630,7 @@ PT_MINIT_REGISTRATION(pt_register_expression_context) zval *expr; bool valueConsumedIsNull = true; bool valueConsumed = false; - ZEND_PARSE_PARAMETERS_START(3, 14) + ZEND_PARSE_PARAMETERS_START(3, 15) Z_PARAM_BOOL(f.isDeep) Z_PARAM_STR_OR_NULL(variableName) Z_PARAM_OBJECT_OR_NULL(expr) @@ -618,6 +646,7 @@ PT_MINIT_REGISTRATION(pt_register_expression_context) Z_PARAM_BOOL_OR_NULL(valueConsumed, valueConsumedIsNull) Z_PARAM_OBJECT_OR_NULL(f.passedToType) Z_PARAM_OBJECT_OR_NULL(f.nativePassedToType) + Z_PARAM_BOOL(f.issetTarget) ZEND_PARSE_PARAMETERS_END(); zval name; if (variableName != NULL) { @@ -716,6 +745,13 @@ PT_MINIT_REGISTRATION(pt_register_expression_context) RETURN_BOOL(ExpressionContext(Z_OBJ_P(ZEND_THIS)).isUnsetTarget()); }); + cls.method<&ExpressionContext::enterIssetTarget>(sigs::enterIssetTarget); + + cls.method(sigs::isIssetTarget, [](INTERNAL_FUNCTION_PARAMETERS) { + ZEND_PARSE_PARAMETERS_NONE(); + RETURN_BOOL(ExpressionContext(Z_OBJ_P(ZEND_THIS)).isIssetTarget()); + }); + cls.shadow(&pt_ce_expression_context); } diff --git a/turbo-ext/src/IllegalOffsetTypeHelper.cpp b/turbo-ext/src/IllegalOffsetTypeHelper.cpp new file mode 100644 index 00000000000..ce559155da0 --- /dev/null +++ b/turbo-ext/src/IllegalOffsetTypeHelper.cpp @@ -0,0 +1,68 @@ +/* + * PHPStanTurbo\IllegalOffsetTypeHelper — native implementation of + * PHPStan\Analyser\ExprHandler\Helper\IllegalOffsetTypeHelper. + * + * mayOffsetThrowTypeError() is exported as + * pt_illegal_offset_type_helper_may_offset_throw_type_error() for the native + * ArrayDimFetchHandler and AssignHandler, which ask it for every offset they + * read or write. + */ + +#include "support.h" +#include "generated/IllegalOffsetTypeHelper.h" + +namespace sigs = ptdecl::IllegalOffsetTypeHelper::sig; +#include "zv.h" +#include "TypeTraits.h" +#include "TypeOps.h" + +static zend_class_entry *pt_ce_illegal_offset_type_helper = nullptr; + +namespace phpstanturbo { + +/* Mirrors PHPStan\Analyser\ExprHandler\Helper\IllegalOffsetTypeHelper. */ +class IllegalOffsetTypeHelper +{ +public: + /* Mirrors mayOffsetThrowTypeError(); false = pending exception */ + [[nodiscard]] static bool mayOffsetThrowTypeError(zval *offsetType, bool &out) + { + // !$offsetType->isArray()->no() || !$offsetType->isObject()->no() + zend_long isArray = pt_type_op_trinary(Z_OBJ_P(offsetType), PT_OP_IS_ARRAY, 0, NULL); + if (UNEXPECTED(isArray < 0)) return false; + if (isArray != PT_TRI_NO) { + out = true; + return true; + } + zend_long isObject = pt_type_call_trinary(Z_OBJ_P(offsetType), PT_LC("isobject"), 0, NULL); + if (UNEXPECTED(isObject < 0)) return false; + out = isObject != PT_TRI_NO; + return true; + } +}; + +} // namespace phpstanturbo + +using phpstanturbo::IllegalOffsetTypeHelper; + +bool pt_illegal_offset_type_helper_may_offset_throw_type_error(zval *offsetType, bool &out) +{ + return IllegalOffsetTypeHelper::mayOffsetThrowTypeError(offsetType, out); +} + +/* {{{ engine ABI glue: parameter parsing + registration */ + +#include "reg.h" + +PT_MINIT_REGISTRATION(pt_register_illegal_offset_type_helper) +{ + reg::Class cls("PHPStan\\Analyser\\ExprHandler\\Helper\\IllegalOffsetTypeHelper"); + ptdecl::IllegalOffsetTypeHelper::declareClass(cls); + ptdecl::IllegalOffsetTypeHelper::declareProperties(cls); + + cls.method<&IllegalOffsetTypeHelper::mayOffsetThrowTypeError, zp::Obj>(sigs::mayOffsetThrowTypeError); + + cls.shadow(&pt_ce_illegal_offset_type_helper); +} + +/* }}} */ diff --git a/turbo-ext/src/IssetHandler.cpp b/turbo-ext/src/IssetHandler.cpp index 5085a8854fa..55fc343dc92 100644 --- a/turbo-ext/src/IssetHandler.cpp +++ b/turbo-ext/src/IssetHandler.cpp @@ -229,7 +229,9 @@ class IssetHandler { zv::Val deepContext = pt_expression_context_enter_deep(context); if (UNEXPECTED(deepContext.isUndef())) return zv::Val(); - varResult = pt_node_scope_resolver_process_expr_node(nodeScopeResolver, stmt, var, scope.raw(), storage, nodeCallback, deepContext.raw()); + zv::Val subjectContext = pt_expression_context_enter_isset_target(deepContext.raw()); + if (UNEXPECTED(subjectContext.isUndef())) return zv::Val(); + varResult = pt_node_scope_resolver_process_expr_node(nodeScopeResolver, stmt, var, scope.raw(), storage, nodeCallback, subjectContext.raw()); if (UNEXPECTED(varResult.isUndef())) return zv::Val(); } varResults.push(zv::Ref(varResult.raw())); diff --git a/turbo-ext/src/PhpVersionAccess.cpp b/turbo-ext/src/PhpVersionAccess.cpp index af23c45a53d..e1589219b95 100644 --- a/turbo-ext/src/PhpVersionAccess.cpp +++ b/turbo-ext/src/PhpVersionAccess.cpp @@ -39,6 +39,7 @@ const VersionQuery pt_pva_queries[PT_PHP_VERSION_QUERY_COUNT] = { /* PT_PHP_VERSION_SUPPORTS_ASYMMETRIC_VISIBILITY */ {PT_LC("supportsasymmetricvisibility"), "supportsAsymmetricVisibility", 80400, false}, /* PT_PHP_VERSION_SUPPORTS_ENUMS */ {PT_LC("supportsenums"), "supportsEnums", 80100, false}, /* PT_PHP_VERSION_SUPPORTS_PROPERTY_HOOKS */ {PT_LC("supportspropertyhooks"), "supportsPropertyHooks", 80400, false}, + /* PT_PHP_VERSION_THROWS_TYPE_ERROR_FOR_ILLEGAL_OFFSETS */ {PT_LC("throwstypeerrorforillegaloffsets"), "throwsTypeErrorForIllegalOffsets", 80000, false}, }; pt_method_site pt_pva_query_sites[PT_PHP_VERSION_QUERY_COUNT]; diff --git a/turbo-ext/src/PreparedAssignTarget.cpp b/turbo-ext/src/PreparedAssignTarget.cpp index da7bd36296a..65a28d46ce4 100644 --- a/turbo-ext/src/PreparedAssignTarget.cpp +++ b/turbo-ext/src/PreparedAssignTarget.cpp @@ -4,7 +4,7 @@ * * The pre-value half of an assignment AssignHandler::prepareTarget() * captures and applyWrite() reads back (~590K getter calls per - * self-analysis). State lives in the twin's 27 promoted property slots, in + * self-analysis). State lives in the twin's 28 promoted property slots, in * its order; the getters of the kind-specific parts throw the twin's * ShouldNotHappenException when the slot is null. Native creators use * pt_prepared_assign_target_new() with the constructor's positional @@ -22,7 +22,7 @@ namespace sigs = ptdecl::PreparedAssignTarget::sig; zend_class_entry *pt_ce_prepared_assign_target = nullptr; /* the constructor's positional parameter count and the required ones */ -#define PT_PAT_ARG_COUNT 27 +#define PT_PAT_ARG_COUNT 28 #define PT_PAT_REQUIRED_ARG_COUNT 11 namespace phpstanturbo { @@ -33,9 +33,9 @@ class PreparedAssignTarget public: explicit PreparedAssignTarget(zend_object *self) : self(self) {} - /* the constructor: argv holds the 27 positional arguments in the twin's + /* the constructor: argv holds the 28 positional arguments in the twin's * order, an UNDEF slot for an omitted optional one (its default: null, - * [] for $targetChainResults) */ + * [] for $targetChainResults and $illegalOffsetDimFetches) */ void construct(zval *argv) const { for (uint32_t i = 0; i < PT_PAT_ARG_COUNT; i++) { @@ -43,7 +43,7 @@ class PreparedAssignTarget if (Z_TYPE(argv[i]) != IS_UNDEF) { pt_write_slot(self, i, &argv[i]); } else { - if (i == slots::targetChainResults) { + if (i == slots::targetChainResults || i == slots::illegalOffsetDimFetches) { ZVAL_EMPTY_ARRAY(&value); } else { ZVAL_NULL(&value); @@ -89,6 +89,7 @@ class PreparedAssignTarget zv::Val getTargetReadResult() const { return readRequired(slots::targetReadResult, "targetReadResult"); } zv::Val getTargetChainResults() const { return read(slots::targetChainResults, "targetChainResults"); } zv::Val getVariableNameResult() const { return read(slots::variableNameResult, "variableNameResult"); } + zv::Val getIllegalOffsetDimFetches() const { return read(slots::illegalOffsetDimFetches, "illegalOffsetDimFetches"); } private: zend_object *self; @@ -187,6 +188,7 @@ PT_MINIT_REGISTRATION(pt_register_prepared_assign_target) Z_PARAM_ARRAY(objects[24]) Z_PARAM_OBJECT_OR_NULL(objects[25]) Z_PARAM_OBJECT_OR_NULL(objects[26]) + Z_PARAM_ARRAY(objects[27]) ZEND_PARSE_PARAMETERS_END(); zval scalar; ZVAL_STR(&scalar, kindStr); @@ -266,6 +268,8 @@ PT_MINIT_REGISTRATION(pt_register_prepared_assign_target) cls.method<&PreparedAssignTarget::getVariableNameResult>(sigs::getVariableNameResult); + cls.method<&PreparedAssignTarget::getIllegalOffsetDimFetches>(sigs::getIllegalOffsetDimFetches); + cls.shadow(&pt_ce_prepared_assign_target); } diff --git a/turbo-ext/src/generated/ArrayDimFetchHandler.h b/turbo-ext/src/generated/ArrayDimFetchHandler.h index c59941abbb4..6fc3cd60be7 100644 --- a/turbo-ext/src/generated/ArrayDimFetchHandler.h +++ b/turbo-ext/src/generated/ArrayDimFetchHandler.h @@ -14,6 +14,7 @@ inline constexpr uint32_t expressionResultFactory = 0; inline constexpr uint32_t defaultNarrowingHelper = 1; inline constexpr uint32_t methodThrowPointHelper = 2; inline constexpr uint32_t methodCallReturnTypeHelper = 3; +inline constexpr uint32_t phpVersion = 4; } // namespace slot inline void declareClass(reg::Class &cls) @@ -29,6 +30,7 @@ inline void declareProperties(reg::Class &cls) cls.property("defaultNarrowingHelper", ZEND_ACC_PRIVATE, reg::PropertyKind::Typed, 0, "PHPStan\\Analyser\\ExprHandler\\Helper\\DefaultNarrowingHelper"); cls.property("methodThrowPointHelper", ZEND_ACC_PRIVATE, reg::PropertyKind::Typed, 0, "PHPStan\\Analyser\\ExprHandler\\Helper\\MethodThrowPointHelper"); cls.property("methodCallReturnTypeHelper", ZEND_ACC_PRIVATE, reg::PropertyKind::Typed, 0, "PHPStan\\Analyser\\ExprHandler\\Helper\\MethodCallReturnTypeHelper"); + cls.property("phpVersion", ZEND_ACC_PRIVATE, reg::PropertyKind::Typed, 0, "PHPStan\\Php\\PhpVersion"); } /* the string and parameter tables the signatures below index into (see reg::Sig) */ @@ -42,70 +44,73 @@ inline constexpr char strings[] = "PHPStan\\Analyser\\ExprHandler\\Helper\\MethodThrowPointHelper\0" /* 170 */ "methodCallReturnTypeHelper\0" /* 229 */ "PHPStan\\Analyser\\ExprHandler\\Helper\\MethodCallReturnTypeHelper\0" /* 256 */ - "__construct\0" /* 319 */ - "expr\0" /* 331 */ - "PhpParser\\Node\\Expr\0" /* 336 */ - "\0" /* 356 */ - "supports\0" /* 357 */ - "nodeScopeResolver\0" /* 366 */ - "PHPStan\\Analyser\\NodeScopeResolver\0" /* 384 */ - "stmt\0" /* 419 */ - "PhpParser\\Node\\Stmt\0" /* 424 */ - "scope\0" /* 444 */ - "PHPStan\\Analyser\\MutatingScope\0" /* 450 */ - "storage\0" /* 481 */ - "PHPStan\\Analyser\\ExpressionResultStorage\0" /* 489 */ - "nodeCallback\0" /* 530 */ - "context\0" /* 543 */ - "PHPStan\\Analyser\\ExpressionContext\0" /* 551 */ - "PHPStan\\Analyser\\ExpressionResult\0" /* 586 */ - "processExpr\0" /* 620 */ - "PhpParser\\Node\\Expr\\ArrayDimFetch\0" /* 632 */ - "dimResult\0" /* 666 */ - "varResult\0" /* 676 */ - "beforeScope\0" /* 686 */ - "composeResult\0" /* 698 */ - "PHPStan\\Analyser\\VariableFlow\0" /* 712 */ - "offsetRead"; /* 742 */ + "phpVersion\0" /* 319 */ + "PHPStan\\Php\\PhpVersion\0" /* 330 */ + "__construct\0" /* 353 */ + "expr\0" /* 365 */ + "PhpParser\\Node\\Expr\0" /* 370 */ + "\0" /* 390 */ + "supports\0" /* 391 */ + "nodeScopeResolver\0" /* 400 */ + "PHPStan\\Analyser\\NodeScopeResolver\0" /* 418 */ + "stmt\0" /* 453 */ + "PhpParser\\Node\\Stmt\0" /* 458 */ + "scope\0" /* 478 */ + "PHPStan\\Analyser\\MutatingScope\0" /* 484 */ + "storage\0" /* 515 */ + "PHPStan\\Analyser\\ExpressionResultStorage\0" /* 523 */ + "nodeCallback\0" /* 564 */ + "context\0" /* 577 */ + "PHPStan\\Analyser\\ExpressionContext\0" /* 585 */ + "PHPStan\\Analyser\\ExpressionResult\0" /* 620 */ + "processExpr\0" /* 654 */ + "PhpParser\\Node\\Expr\\ArrayDimFetch\0" /* 666 */ + "dimResult\0" /* 700 */ + "varResult\0" /* 710 */ + "beforeScope\0" /* 720 */ + "composeResult\0" /* 732 */ + "PHPStan\\Analyser\\VariableFlow\0" /* 746 */ + "offsetRead"; /* 776 */ inline constexpr reg::PackedArg args[] = { reg::packed(0, 0, 24), /* __construct $expressionResultFactory */ reg::packed(65, 0, 88), /* __construct $defaultNarrowingHelper */ reg::packed(147, 0, 170), /* __construct $methodThrowPointHelper */ reg::packed(229, 0, 256), /* __construct $methodCallReturnTypeHelper */ - reg::packed(331, 0, 336), /* supports $expr */ - reg::packed(356, MAY_BE_BOOL), /* supports return */ - reg::packed(366, 0, 384), /* processExpr $nodeScopeResolver */ - reg::packed(419, 0, 424), /* processExpr $stmt */ - reg::packed(331, 0, 336), /* processExpr $expr */ - reg::packed(444, 0, 450), /* processExpr $scope */ - reg::packed(481, 0, 489), /* processExpr $storage */ - reg::packed(530, MAY_BE_CALLABLE), /* processExpr $nodeCallback */ - reg::packed(543, 0, 551), /* processExpr $context */ - reg::packed(356, 0, 586), /* processExpr return */ - reg::packed(366, 0, 384), /* composeResult $nodeScopeResolver */ - reg::packed(419, 0, 424), /* composeResult $stmt */ - reg::packed(331, 0, 632), /* composeResult $expr */ - reg::packed(666, MAY_BE_NULL, 586), /* composeResult $dimResult */ - reg::packed(676, 0, 586), /* composeResult $varResult */ - reg::packed(481, 0, 489), /* composeResult $storage */ - reg::packed(543, 0, 551), /* composeResult $context */ - reg::packed(686, 0, 450), /* composeResult $beforeScope */ - reg::packed(356, 0, 586), /* composeResult return */ - reg::packed(331, 0, 632), /* offsetRead $expr */ - reg::packed(666, MAY_BE_NULL, 586), /* offsetRead $dimResult */ - reg::packed(543, 0, 551), /* offsetRead $context */ - reg::packed(356, MAY_BE_NULL, 712), /* offsetRead return */ + reg::packed(319, 0, 330), /* __construct $phpVersion */ + reg::packed(365, 0, 370), /* supports $expr */ + reg::packed(390, MAY_BE_BOOL), /* supports return */ + reg::packed(400, 0, 418), /* processExpr $nodeScopeResolver */ + reg::packed(453, 0, 458), /* processExpr $stmt */ + reg::packed(365, 0, 370), /* processExpr $expr */ + reg::packed(478, 0, 484), /* processExpr $scope */ + reg::packed(515, 0, 523), /* processExpr $storage */ + reg::packed(564, MAY_BE_CALLABLE), /* processExpr $nodeCallback */ + reg::packed(577, 0, 585), /* processExpr $context */ + reg::packed(390, 0, 620), /* processExpr return */ + reg::packed(400, 0, 418), /* composeResult $nodeScopeResolver */ + reg::packed(453, 0, 458), /* composeResult $stmt */ + reg::packed(365, 0, 666), /* composeResult $expr */ + reg::packed(700, MAY_BE_NULL, 620), /* composeResult $dimResult */ + reg::packed(710, 0, 620), /* composeResult $varResult */ + reg::packed(515, 0, 523), /* composeResult $storage */ + reg::packed(577, 0, 585), /* composeResult $context */ + reg::packed(720, 0, 484), /* composeResult $beforeScope */ + reg::packed(390, 0, 620), /* composeResult return */ + reg::packed(365, 0, 666), /* offsetRead $expr */ + reg::packed(700, MAY_BE_NULL, 620), /* offsetRead $dimResult */ + reg::packed(577, 0, 585), /* offsetRead $context */ + reg::packed(390, MAY_BE_NULL, 746), /* offsetRead return */ }; using Sig = reg::Sig; } // namespace sigtab /* the signatures of the methods the class declares itself (a used trait's are in the trait's header) */ namespace sig { -inline constexpr sigtab::Sig __construct = { { 319 /* __construct */, 4, 0, 4, reg::NoArg, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig supports = { { 357 /* supports */, 1, 4, 1, 5, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig processExpr = { { 620 /* processExpr */, 7, 6, 7, 13, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig composeResult = { { 698 /* composeResult */, 8, 14, 8, 22, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig offsetRead = { { 742 /* offsetRead */, 3, 23, 3, 26, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig __construct = { { 353 /* __construct */, 5, 0, 5, reg::NoArg, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig supports = { { 391 /* supports */, 1, 5, 1, 6, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig processExpr = { { 654 /* processExpr */, 7, 7, 7, 14, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig composeResult = { { 732 /* composeResult */, 8, 15, 8, 23, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig offsetRead = { { 776 /* offsetRead */, 3, 24, 3, 27, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; } // namespace sig } // namespace ptdecl::ArrayDimFetchHandler diff --git a/turbo-ext/src/generated/ArrayHandler.h b/turbo-ext/src/generated/ArrayHandler.h index 57586518ce9..25e907c26e1 100644 --- a/turbo-ext/src/generated/ArrayHandler.h +++ b/turbo-ext/src/generated/ArrayHandler.h @@ -12,6 +12,7 @@ namespace ptdecl::ArrayHandler { namespace slot { inline constexpr uint32_t initializerExprTypeResolver = 0; inline constexpr uint32_t expressionResultFactory = 1; +inline constexpr uint32_t phpVersion = 2; } // namespace slot inline void declareClass(reg::Class &cls) @@ -25,6 +26,7 @@ inline void declareProperties(reg::Class &cls) { cls.property("initializerExprTypeResolver", ZEND_ACC_PRIVATE, reg::PropertyKind::Typed, 0, "PHPStan\\Reflection\\InitializerExprTypeResolver"); cls.property("expressionResultFactory", ZEND_ACC_PRIVATE, reg::PropertyKind::Typed, 0, "PHPStan\\Analyser\\ExpressionResultFactory"); + cls.property("phpVersion", ZEND_ACC_PRIVATE, reg::PropertyKind::Typed, 0, "PHPStan\\Php\\PhpVersion"); } /* the string and parameter tables the signatures below index into (see reg::Sig) */ @@ -34,76 +36,79 @@ inline constexpr char strings[] = "PHPStan\\Reflection\\InitializerExprTypeResolver\0" /* 28 */ "expressionResultFactory\0" /* 75 */ "PHPStan\\Analyser\\ExpressionResultFactory\0" /* 99 */ - "__construct\0" /* 140 */ - "expr\0" /* 152 */ - "PhpParser\\Node\\Expr\0" /* 157 */ - "\0" /* 177 */ - "supports\0" /* 178 */ - "nodeScopeResolver\0" /* 187 */ - "PHPStan\\Analyser\\NodeScopeResolver\0" /* 205 */ - "stmt\0" /* 240 */ - "PhpParser\\Node\\Stmt\0" /* 245 */ - "scope\0" /* 265 */ - "PHPStan\\Analyser\\MutatingScope\0" /* 271 */ - "storage\0" /* 302 */ - "PHPStan\\Analyser\\ExpressionResultStorage\0" /* 310 */ - "nodeCallback\0" /* 351 */ - "context\0" /* 364 */ - "PHPStan\\Analyser\\ExpressionContext\0" /* 372 */ - "PHPStan\\Analyser\\ExpressionResult\0" /* 407 */ - "processExpr\0" /* 441 */ - "PhpParser\\Node\\Expr\\Array_\0" /* 453 */ - "itemResults\0" /* 480 */ - "result\0" /* 492 */ - "PHPStan\\Dependency\\Dependencies\0" /* 499 */ - "getCallableDependencies\0" /* 531 */ - "collectAbsorbedItems\0" /* 555 */ - "type\0" /* 576 */ - "PHPStan\\Type\\Type\0" /* 581 */ - "getExpectedArrayType\0" /* 599 */ - "arrayType\0" /* 620 */ - "keyType\0" /* 630 */ - "getExpectedValueType"; /* 638 */ + "phpVersion\0" /* 140 */ + "PHPStan\\Php\\PhpVersion\0" /* 151 */ + "__construct\0" /* 174 */ + "expr\0" /* 186 */ + "PhpParser\\Node\\Expr\0" /* 191 */ + "\0" /* 211 */ + "supports\0" /* 212 */ + "nodeScopeResolver\0" /* 221 */ + "PHPStan\\Analyser\\NodeScopeResolver\0" /* 239 */ + "stmt\0" /* 274 */ + "PhpParser\\Node\\Stmt\0" /* 279 */ + "scope\0" /* 299 */ + "PHPStan\\Analyser\\MutatingScope\0" /* 305 */ + "storage\0" /* 336 */ + "PHPStan\\Analyser\\ExpressionResultStorage\0" /* 344 */ + "nodeCallback\0" /* 385 */ + "context\0" /* 398 */ + "PHPStan\\Analyser\\ExpressionContext\0" /* 406 */ + "PHPStan\\Analyser\\ExpressionResult\0" /* 441 */ + "processExpr\0" /* 475 */ + "PhpParser\\Node\\Expr\\Array_\0" /* 487 */ + "itemResults\0" /* 514 */ + "result\0" /* 526 */ + "PHPStan\\Dependency\\Dependencies\0" /* 533 */ + "getCallableDependencies\0" /* 565 */ + "collectAbsorbedItems\0" /* 589 */ + "type\0" /* 610 */ + "PHPStan\\Type\\Type\0" /* 615 */ + "getExpectedArrayType\0" /* 633 */ + "arrayType\0" /* 654 */ + "keyType\0" /* 664 */ + "getExpectedValueType"; /* 672 */ inline constexpr reg::PackedArg args[] = { reg::packed(0, 0, 28), /* __construct $initializerExprTypeResolver */ reg::packed(75, 0, 99), /* __construct $expressionResultFactory */ - reg::packed(152, 0, 157), /* supports $expr */ - reg::packed(177, MAY_BE_BOOL), /* supports return */ - reg::packed(187, 0, 205), /* processExpr $nodeScopeResolver */ - reg::packed(240, 0, 245), /* processExpr $stmt */ - reg::packed(152, 0, 157), /* processExpr $expr */ - reg::packed(265, 0, 271), /* processExpr $scope */ - reg::packed(302, 0, 310), /* processExpr $storage */ - reg::packed(351, MAY_BE_CALLABLE), /* processExpr $nodeCallback */ - reg::packed(364, 0, 372), /* processExpr $context */ - reg::packed(177, 0, 407), /* processExpr return */ - reg::packed(265, 0, 271), /* getCallableDependencies $scope */ - reg::packed(152, 0, 453), /* getCallableDependencies $expr */ - reg::packed(480, MAY_BE_ARRAY), /* getCallableDependencies $itemResults */ - reg::packed(492, 0, 407), /* getCallableDependencies $result */ - reg::packed(177, MAY_BE_NULL, 499), /* getCallableDependencies return */ - reg::packed(152, 0, 453), /* collectAbsorbedItems $expr */ - reg::packed(480, MAY_BE_ARRAY), /* collectAbsorbedItems $itemResults */ - reg::packed(265, 0, 271), /* collectAbsorbedItems $scope */ - reg::packed(177, 0, 271), /* collectAbsorbedItems return */ - reg::packed(576, MAY_BE_NULL, 581), /* getExpectedArrayType $type */ - reg::packed(177, MAY_BE_NULL, 581), /* getExpectedArrayType return */ - reg::packed(620, MAY_BE_NULL, 581), /* getExpectedValueType $arrayType */ - reg::packed(630, 0, 581), /* getExpectedValueType $keyType */ - reg::packed(177, MAY_BE_NULL, 581), /* getExpectedValueType return */ + reg::packed(140, 0, 151), /* __construct $phpVersion */ + reg::packed(186, 0, 191), /* supports $expr */ + reg::packed(211, MAY_BE_BOOL), /* supports return */ + reg::packed(221, 0, 239), /* processExpr $nodeScopeResolver */ + reg::packed(274, 0, 279), /* processExpr $stmt */ + reg::packed(186, 0, 191), /* processExpr $expr */ + reg::packed(299, 0, 305), /* processExpr $scope */ + reg::packed(336, 0, 344), /* processExpr $storage */ + reg::packed(385, MAY_BE_CALLABLE), /* processExpr $nodeCallback */ + reg::packed(398, 0, 406), /* processExpr $context */ + reg::packed(211, 0, 441), /* processExpr return */ + reg::packed(299, 0, 305), /* getCallableDependencies $scope */ + reg::packed(186, 0, 487), /* getCallableDependencies $expr */ + reg::packed(514, MAY_BE_ARRAY), /* getCallableDependencies $itemResults */ + reg::packed(526, 0, 441), /* getCallableDependencies $result */ + reg::packed(211, MAY_BE_NULL, 533), /* getCallableDependencies return */ + reg::packed(186, 0, 487), /* collectAbsorbedItems $expr */ + reg::packed(514, MAY_BE_ARRAY), /* collectAbsorbedItems $itemResults */ + reg::packed(299, 0, 305), /* collectAbsorbedItems $scope */ + reg::packed(211, 0, 305), /* collectAbsorbedItems return */ + reg::packed(610, MAY_BE_NULL, 615), /* getExpectedArrayType $type */ + reg::packed(211, MAY_BE_NULL, 615), /* getExpectedArrayType return */ + reg::packed(654, MAY_BE_NULL, 615), /* getExpectedValueType $arrayType */ + reg::packed(664, 0, 615), /* getExpectedValueType $keyType */ + reg::packed(211, MAY_BE_NULL, 615), /* getExpectedValueType return */ }; using Sig = reg::Sig; } // namespace sigtab /* the signatures of the methods the class declares itself (a used trait's are in the trait's header) */ namespace sig { -inline constexpr sigtab::Sig __construct = { { 140 /* __construct */, 2, 0, 2, reg::NoArg, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig supports = { { 178 /* supports */, 1, 2, 1, 3, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig processExpr = { { 441 /* processExpr */, 7, 4, 7, 11, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getCallableDependencies = { { 531 /* getCallableDependencies */, 4, 12, 4, 16, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig collectAbsorbedItems = { { 555 /* collectAbsorbedItems */, 3, 17, 3, 20, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig getExpectedArrayType = { { 599 /* getExpectedArrayType */, 1, 21, 1, 22, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig getExpectedValueType = { { 638 /* getExpectedValueType */, 2, 23, 2, 25, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig __construct = { { 174 /* __construct */, 3, 0, 3, reg::NoArg, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig supports = { { 212 /* supports */, 1, 3, 1, 4, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig processExpr = { { 475 /* processExpr */, 7, 5, 7, 12, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getCallableDependencies = { { 565 /* getCallableDependencies */, 4, 13, 4, 17, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig collectAbsorbedItems = { { 589 /* collectAbsorbedItems */, 3, 18, 3, 21, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig getExpectedArrayType = { { 633 /* getExpectedArrayType */, 1, 22, 1, 23, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig getExpectedValueType = { { 672 /* getExpectedValueType */, 2, 24, 2, 26, ZEND_ACC_PRIVATE } }; } // namespace sig } // namespace ptdecl::ArrayHandler diff --git a/turbo-ext/src/generated/ExpressionContext.h b/turbo-ext/src/generated/ExpressionContext.h index 83bbb8b3a4b..465421c335e 100644 --- a/turbo-ext/src/generated/ExpressionContext.h +++ b/turbo-ext/src/generated/ExpressionContext.h @@ -24,6 +24,7 @@ inline constexpr uint32_t unsetTarget = 10; inline constexpr uint32_t valueConsumed = 11; inline constexpr uint32_t passedToType = 12; inline constexpr uint32_t nativePassedToType = 13; +inline constexpr uint32_t issetTarget = 14; } // namespace slot inline void declareClass(reg::Class &cls) @@ -48,6 +49,7 @@ inline void declareProperties(reg::Class &cls) cls.property("valueConsumed", ZEND_ACC_PRIVATE, reg::PropertyKind::Typed, MAY_BE_NULL | MAY_BE_BOOL); cls.property("passedToType", ZEND_ACC_PRIVATE, reg::PropertyKind::Typed, MAY_BE_NULL, "PHPStan\\Type\\Type"); cls.property("nativePassedToType", ZEND_ACC_PRIVATE, reg::PropertyKind::Typed, MAY_BE_NULL, "PHPStan\\Type\\Type"); + cls.property("issetTarget", ZEND_ACC_PRIVATE, reg::PropertyKind::Typed, MAY_BE_BOOL); } /* the string and parameter tables the signatures below index into (see reg::Sig) */ @@ -73,44 +75,47 @@ inline constexpr char strings[] = "valueConsumed\0" /* 294 */ "passedToType\0" /* 308 */ "nativePassedToType\0" /* 321 */ - "__construct\0" /* 340 */ - "\0" /* 352 */ - "PHPStan\\Analyser\\ExpressionContext\0" /* 353 */ - "createTopLevel\0" /* 388 */ - "createDeep\0" /* 403 */ - "enterDeep\0" /* 414 */ - "enterDeepKeepingValueFlow\0" /* 424 */ - "withoutValueFlow\0" /* 450 */ - "enterMatchArm\0" /* 467 */ - "isValueConsumed\0" /* 481 */ - "type\0" /* 497 */ - "nativeType\0" /* 502 */ - "enterPassedToType\0" /* 513 */ - "getPassedToType\0" /* 531 */ - "getNativePassedToType\0" /* 547 */ - "shouldResolveTemplateArguments\0" /* 569 */ - "withoutTemplateArgumentResolution\0" /* 600 */ - "enterThrow\0" /* 634 */ - "isInThrow\0" /* 645 */ - "variableName\0" /* 655 */ - "expr\0" /* 668 */ - "enterRightSideAssign\0" /* 673 */ - "getInAssignRightSideVariableName\0" /* 694 */ - "getInAssignRightSideExpr\0" /* 727 */ - "acceptor\0" /* 752 */ - "PHPStan\\Reflection\\ParametersAcceptor\0" /* 761 */ - "enterAssignRightSideCallArgs\0" /* 799 */ - "getInAssignRightSideType\0" /* 828 */ - "getInAssignRightSideNativeType\0" /* 853 */ - "target\0" /* 884 */ - "direct\0" /* 891 */ - "enterValueFlow\0" /* 898 */ - "getValueFlowTarget\0" /* 913 */ - "isValueFlowDirect\0" /* 932 */ - "enterArrayDimFetchRoot\0" /* 950 */ - "isArrayDimFetchRoot\0" /* 973 */ - "enterUnsetTarget\0" /* 993 */ - "isUnsetTarget"; /* 1010 */ + "issetTarget\0" /* 340 */ + "__construct\0" /* 352 */ + "\0" /* 364 */ + "PHPStan\\Analyser\\ExpressionContext\0" /* 365 */ + "createTopLevel\0" /* 400 */ + "createDeep\0" /* 415 */ + "enterDeep\0" /* 426 */ + "enterDeepKeepingValueFlow\0" /* 436 */ + "withoutValueFlow\0" /* 462 */ + "enterMatchArm\0" /* 479 */ + "isValueConsumed\0" /* 493 */ + "type\0" /* 509 */ + "nativeType\0" /* 514 */ + "enterPassedToType\0" /* 525 */ + "getPassedToType\0" /* 543 */ + "getNativePassedToType\0" /* 559 */ + "shouldResolveTemplateArguments\0" /* 581 */ + "withoutTemplateArgumentResolution\0" /* 612 */ + "enterThrow\0" /* 646 */ + "isInThrow\0" /* 657 */ + "variableName\0" /* 667 */ + "expr\0" /* 680 */ + "enterRightSideAssign\0" /* 685 */ + "getInAssignRightSideVariableName\0" /* 706 */ + "getInAssignRightSideExpr\0" /* 739 */ + "acceptor\0" /* 764 */ + "PHPStan\\Reflection\\ParametersAcceptor\0" /* 773 */ + "enterAssignRightSideCallArgs\0" /* 811 */ + "getInAssignRightSideType\0" /* 840 */ + "getInAssignRightSideNativeType\0" /* 865 */ + "target\0" /* 896 */ + "direct\0" /* 903 */ + "enterValueFlow\0" /* 910 */ + "getValueFlowTarget\0" /* 925 */ + "isValueFlowDirect\0" /* 944 */ + "enterArrayDimFetchRoot\0" /* 962 */ + "isArrayDimFetchRoot\0" /* 985 */ + "enterUnsetTarget\0" /* 1005 */ + "isUnsetTarget\0" /* 1022 */ + "enterIssetTarget\0" /* 1036 */ + "isIssetTarget"; /* 1053 */ inline constexpr reg::PackedArg args[] = { reg::packed(0, MAY_BE_BOOL), /* __construct $isDeep */ reg::packed(7, MAY_BE_NULL | MAY_BE_STRING), /* __construct $inAssignRightSideVariableName */ @@ -126,78 +131,83 @@ inline constexpr reg::PackedArg args[] = { reg::packed(294, MAY_BE_NULL | MAY_BE_BOOL, reg::NoString, false, false, 133), /* __construct $valueConsumed */ reg::packed(308, MAY_BE_NULL, 115, false, false, 133), /* __construct $passedToType */ reg::packed(321, MAY_BE_NULL, 115, false, false, 133), /* __construct $nativePassedToType */ + reg::packed(340, MAY_BE_BOOL, reg::NoString, false, false, 87), /* __construct $issetTarget */ reg::packed(166, MAY_BE_BOOL, reg::NoString, false, false, 191), /* createTopLevel $resolveTemplateArguments */ - reg::packed(352, 0, 353), /* createTopLevel return */ + reg::packed(364, 0, 365), /* createTopLevel return */ reg::packed(166, MAY_BE_BOOL, reg::NoString, false, false, 191), /* createDeep $resolveTemplateArguments */ - reg::packed(352, 0, 353), /* createDeep return */ - reg::packed(352, 0, 353), /* enterDeep return */ - reg::packed(352, 0, 353), /* enterDeepKeepingValueFlow return */ - reg::packed(352, 0, 353), /* withoutValueFlow return */ - reg::packed(352, 0, 353), /* enterMatchArm return */ - reg::packed(352, MAY_BE_BOOL), /* isValueConsumed return */ - reg::packed(497, MAY_BE_NULL, 115), /* enterPassedToType $type */ - reg::packed(502, MAY_BE_NULL, 115), /* enterPassedToType $nativeType */ - reg::packed(352, 0, 353), /* enterPassedToType return */ - reg::packed(352, MAY_BE_NULL, 115), /* getPassedToType return */ - reg::packed(352, MAY_BE_NULL, 115), /* getNativePassedToType return */ - reg::packed(352, MAY_BE_BOOL), /* isDeep return */ - reg::packed(352, MAY_BE_BOOL), /* shouldResolveTemplateArguments return */ - reg::packed(352, 0, 353), /* withoutTemplateArgumentResolution return */ - reg::packed(352, 0, 353), /* enterThrow return */ - reg::packed(352, MAY_BE_BOOL), /* isInThrow return */ - reg::packed(655, MAY_BE_STRING), /* enterRightSideAssign $variableName */ - reg::packed(668, 0, 59), /* enterRightSideAssign $expr */ - reg::packed(352, 0, 353), /* enterRightSideAssign return */ - reg::packed(352, MAY_BE_NULL | MAY_BE_STRING), /* getInAssignRightSideVariableName return */ - reg::packed(352, MAY_BE_NULL, 59), /* getInAssignRightSideExpr return */ - reg::packed(752, 0, 761), /* enterAssignRightSideCallArgs $acceptor */ - reg::packed(352, 0, 353), /* enterAssignRightSideCallArgs return */ - reg::packed(352, MAY_BE_NULL, 115), /* getInAssignRightSideType return */ - reg::packed(352, MAY_BE_NULL, 115), /* getInAssignRightSideNativeType return */ - reg::packed(884, 0, 212), /* enterValueFlow $target */ - reg::packed(891, MAY_BE_BOOL), /* enterValueFlow $direct */ - reg::packed(352, 0, 353), /* enterValueFlow return */ - reg::packed(352, MAY_BE_NULL, 212), /* getValueFlowTarget return */ - reg::packed(352, MAY_BE_BOOL), /* isValueFlowDirect return */ - reg::packed(352, 0, 353), /* enterArrayDimFetchRoot return */ - reg::packed(352, MAY_BE_BOOL), /* isArrayDimFetchRoot return */ - reg::packed(352, 0, 353), /* enterUnsetTarget return */ - reg::packed(352, MAY_BE_BOOL), /* isUnsetTarget return */ + reg::packed(364, 0, 365), /* createDeep return */ + reg::packed(364, 0, 365), /* enterDeep return */ + reg::packed(364, 0, 365), /* enterDeepKeepingValueFlow return */ + reg::packed(364, 0, 365), /* withoutValueFlow return */ + reg::packed(364, 0, 365), /* enterMatchArm return */ + reg::packed(364, MAY_BE_BOOL), /* isValueConsumed return */ + reg::packed(509, MAY_BE_NULL, 115), /* enterPassedToType $type */ + reg::packed(514, MAY_BE_NULL, 115), /* enterPassedToType $nativeType */ + reg::packed(364, 0, 365), /* enterPassedToType return */ + reg::packed(364, MAY_BE_NULL, 115), /* getPassedToType return */ + reg::packed(364, MAY_BE_NULL, 115), /* getNativePassedToType return */ + reg::packed(364, MAY_BE_BOOL), /* isDeep return */ + reg::packed(364, MAY_BE_BOOL), /* shouldResolveTemplateArguments return */ + reg::packed(364, 0, 365), /* withoutTemplateArgumentResolution return */ + reg::packed(364, 0, 365), /* enterThrow return */ + reg::packed(364, MAY_BE_BOOL), /* isInThrow return */ + reg::packed(667, MAY_BE_STRING), /* enterRightSideAssign $variableName */ + reg::packed(680, 0, 59), /* enterRightSideAssign $expr */ + reg::packed(364, 0, 365), /* enterRightSideAssign return */ + reg::packed(364, MAY_BE_NULL | MAY_BE_STRING), /* getInAssignRightSideVariableName return */ + reg::packed(364, MAY_BE_NULL, 59), /* getInAssignRightSideExpr return */ + reg::packed(764, 0, 773), /* enterAssignRightSideCallArgs $acceptor */ + reg::packed(364, 0, 365), /* enterAssignRightSideCallArgs return */ + reg::packed(364, MAY_BE_NULL, 115), /* getInAssignRightSideType return */ + reg::packed(364, MAY_BE_NULL, 115), /* getInAssignRightSideNativeType return */ + reg::packed(896, 0, 212), /* enterValueFlow $target */ + reg::packed(903, MAY_BE_BOOL), /* enterValueFlow $direct */ + reg::packed(364, 0, 365), /* enterValueFlow return */ + reg::packed(364, MAY_BE_NULL, 212), /* getValueFlowTarget return */ + reg::packed(364, MAY_BE_BOOL), /* isValueFlowDirect return */ + reg::packed(364, 0, 365), /* enterArrayDimFetchRoot return */ + reg::packed(364, MAY_BE_BOOL), /* isArrayDimFetchRoot return */ + reg::packed(364, 0, 365), /* enterUnsetTarget return */ + reg::packed(364, MAY_BE_BOOL), /* isUnsetTarget return */ + reg::packed(364, 0, 365), /* enterIssetTarget return */ + reg::packed(364, MAY_BE_BOOL), /* isIssetTarget return */ }; using Sig = reg::Sig; } // namespace sigtab /* the signatures of the methods the class declares itself (a used trait's are in the trait's header) */ namespace sig { -inline constexpr sigtab::Sig __construct = { { 340 /* __construct */, 3, 0, 14, reg::NoArg, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig createTopLevel = { { 388 /* createTopLevel */, 0, 14, 1, 15, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig createDeep = { { 403 /* createDeep */, 0, 16, 1, 17, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig enterDeep = { { 414 /* enterDeep */, 0, 18, 0, 18, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig enterDeepKeepingValueFlow = { { 424 /* enterDeepKeepingValueFlow */, 0, 19, 0, 19, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig withoutValueFlow = { { 450 /* withoutValueFlow */, 0, 20, 0, 20, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig enterMatchArm = { { 467 /* enterMatchArm */, 0, 21, 0, 21, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig isValueConsumed = { { 481 /* isValueConsumed */, 0, 22, 0, 22, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig enterPassedToType = { { 513 /* enterPassedToType */, 2, 23, 2, 25, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getPassedToType = { { 531 /* getPassedToType */, 0, 26, 0, 26, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getNativePassedToType = { { 547 /* getNativePassedToType */, 0, 27, 0, 27, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig isDeep = { { 0 /* isDeep */, 0, 28, 0, 28, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig shouldResolveTemplateArguments = { { 569 /* shouldResolveTemplateArguments */, 0, 29, 0, 29, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig withoutTemplateArgumentResolution = { { 600 /* withoutTemplateArgumentResolution */, 0, 30, 0, 30, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig enterThrow = { { 634 /* enterThrow */, 0, 31, 0, 31, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig isInThrow = { { 645 /* isInThrow */, 0, 32, 0, 32, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig enterRightSideAssign = { { 673 /* enterRightSideAssign */, 2, 33, 2, 35, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getInAssignRightSideVariableName = { { 694 /* getInAssignRightSideVariableName */, 0, 36, 0, 36, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getInAssignRightSideExpr = { { 727 /* getInAssignRightSideExpr */, 0, 37, 0, 37, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig enterAssignRightSideCallArgs = { { 799 /* enterAssignRightSideCallArgs */, 1, 38, 1, 39, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getInAssignRightSideType = { { 828 /* getInAssignRightSideType */, 0, 40, 0, 40, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getInAssignRightSideNativeType = { { 853 /* getInAssignRightSideNativeType */, 0, 41, 0, 41, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig enterValueFlow = { { 898 /* enterValueFlow */, 2, 42, 2, 44, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getValueFlowTarget = { { 913 /* getValueFlowTarget */, 0, 45, 0, 45, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig isValueFlowDirect = { { 932 /* isValueFlowDirect */, 0, 46, 0, 46, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig enterArrayDimFetchRoot = { { 950 /* enterArrayDimFetchRoot */, 0, 47, 0, 47, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig isArrayDimFetchRoot = { { 973 /* isArrayDimFetchRoot */, 0, 48, 0, 48, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig enterUnsetTarget = { { 993 /* enterUnsetTarget */, 0, 49, 0, 49, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig isUnsetTarget = { { 1010 /* isUnsetTarget */, 0, 50, 0, 50, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig __construct = { { 352 /* __construct */, 3, 0, 15, reg::NoArg, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig createTopLevel = { { 400 /* createTopLevel */, 0, 15, 1, 16, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig createDeep = { { 415 /* createDeep */, 0, 17, 1, 18, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig enterDeep = { { 426 /* enterDeep */, 0, 19, 0, 19, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig enterDeepKeepingValueFlow = { { 436 /* enterDeepKeepingValueFlow */, 0, 20, 0, 20, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig withoutValueFlow = { { 462 /* withoutValueFlow */, 0, 21, 0, 21, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig enterMatchArm = { { 479 /* enterMatchArm */, 0, 22, 0, 22, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isValueConsumed = { { 493 /* isValueConsumed */, 0, 23, 0, 23, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig enterPassedToType = { { 525 /* enterPassedToType */, 2, 24, 2, 26, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getPassedToType = { { 543 /* getPassedToType */, 0, 27, 0, 27, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getNativePassedToType = { { 559 /* getNativePassedToType */, 0, 28, 0, 28, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isDeep = { { 0 /* isDeep */, 0, 29, 0, 29, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig shouldResolveTemplateArguments = { { 581 /* shouldResolveTemplateArguments */, 0, 30, 0, 30, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig withoutTemplateArgumentResolution = { { 612 /* withoutTemplateArgumentResolution */, 0, 31, 0, 31, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig enterThrow = { { 646 /* enterThrow */, 0, 32, 0, 32, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isInThrow = { { 657 /* isInThrow */, 0, 33, 0, 33, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig enterRightSideAssign = { { 685 /* enterRightSideAssign */, 2, 34, 2, 36, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getInAssignRightSideVariableName = { { 706 /* getInAssignRightSideVariableName */, 0, 37, 0, 37, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getInAssignRightSideExpr = { { 739 /* getInAssignRightSideExpr */, 0, 38, 0, 38, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig enterAssignRightSideCallArgs = { { 811 /* enterAssignRightSideCallArgs */, 1, 39, 1, 40, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getInAssignRightSideType = { { 840 /* getInAssignRightSideType */, 0, 41, 0, 41, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getInAssignRightSideNativeType = { { 865 /* getInAssignRightSideNativeType */, 0, 42, 0, 42, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig enterValueFlow = { { 910 /* enterValueFlow */, 2, 43, 2, 45, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getValueFlowTarget = { { 925 /* getValueFlowTarget */, 0, 46, 0, 46, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isValueFlowDirect = { { 944 /* isValueFlowDirect */, 0, 47, 0, 47, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig enterArrayDimFetchRoot = { { 962 /* enterArrayDimFetchRoot */, 0, 48, 0, 48, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isArrayDimFetchRoot = { { 985 /* isArrayDimFetchRoot */, 0, 49, 0, 49, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig enterUnsetTarget = { { 1005 /* enterUnsetTarget */, 0, 50, 0, 50, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isUnsetTarget = { { 1022 /* isUnsetTarget */, 0, 51, 0, 51, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig enterIssetTarget = { { 1036 /* enterIssetTarget */, 0, 52, 0, 52, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isIssetTarget = { { 1053 /* isIssetTarget */, 0, 53, 0, 53, ZEND_ACC_PUBLIC } }; } // namespace sig } // namespace ptdecl::ExpressionContext diff --git a/turbo-ext/src/generated/IllegalOffsetTypeHelper.h b/turbo-ext/src/generated/IllegalOffsetTypeHelper.h new file mode 100644 index 00000000000..b29eb074a7a --- /dev/null +++ b/turbo-ext/src/generated/IllegalOffsetTypeHelper.h @@ -0,0 +1,43 @@ +/* Generated by turbo-ext/bin/generate-declarations.php from + * src/Analyser/ExprHandler/Helper/IllegalOffsetTypeHelper.php — do not edit. */ + +#ifndef PHPSTANTURBO_GENERATED_ILLEGAL_OFFSET_TYPE_HELPER_H +#define PHPSTANTURBO_GENERATED_ILLEGAL_OFFSET_TYPE_HELPER_H + +#include "../reg.h" + +namespace ptdecl::IllegalOffsetTypeHelper { + +inline void declareClass(reg::Class &cls) +{ + cls.final(); +} + +/* the properties the class declares itself, in declaration order (a used trait's come from its registrar) */ +inline void declareProperties(reg::Class &cls) +{ + (void) cls; +} + +/* the string and parameter tables the signatures below index into (see reg::Sig) */ +namespace sigtab { +inline constexpr char strings[] = + "offsetType\0" /* 0 */ + "PHPStan\\Type\\Type\0" /* 11 */ + "\0" /* 29 */ + "mayOffsetThrowTypeError"; /* 30 */ +inline constexpr reg::PackedArg args[] = { + reg::packed(0, 0, 11), /* mayOffsetThrowTypeError $offsetType */ + reg::packed(29, MAY_BE_BOOL), /* mayOffsetThrowTypeError return */ +}; +using Sig = reg::Sig; +} // namespace sigtab + +/* the signatures of the methods the class declares itself (a used trait's are in the trait's header) */ +namespace sig { +inline constexpr sigtab::Sig mayOffsetThrowTypeError = { { 30 /* mayOffsetThrowTypeError */, 1, 0, 1, 1, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; +} // namespace sig + +} // namespace ptdecl::IllegalOffsetTypeHelper + +#endif diff --git a/turbo-ext/src/generated/PreparedAssignTarget.h b/turbo-ext/src/generated/PreparedAssignTarget.h index ba82bba0377..30d51bad5c7 100644 --- a/turbo-ext/src/generated/PreparedAssignTarget.h +++ b/turbo-ext/src/generated/PreparedAssignTarget.h @@ -37,6 +37,7 @@ inline constexpr uint32_t targetReadResult = 23; inline constexpr uint32_t targetChainResults = 24; inline constexpr uint32_t variableNameResult = 25; inline constexpr uint32_t dependencies = 26; +inline constexpr uint32_t illegalOffsetDimFetches = 27; } // namespace slot inline void declareClass(reg::Class &cls) @@ -74,6 +75,7 @@ inline void declareProperties(reg::Class &cls) cls.property("targetChainResults", ZEND_ACC_PRIVATE, reg::PropertyKind::Typed, MAY_BE_ARRAY); cls.property("variableNameResult", ZEND_ACC_PRIVATE, reg::PropertyKind::Typed, MAY_BE_NULL, "PHPStan\\Analyser\\ExpressionResult"); cls.property("dependencies", ZEND_ACC_PRIVATE, reg::PropertyKind::Typed, MAY_BE_NULL, "PHPStan\\Dependency\\Dependencies"); + cls.property("illegalOffsetDimFetches", ZEND_ACC_PRIVATE, reg::PropertyKind::Typed, MAY_BE_ARRAY); } /* the string and parameter tables the signatures below index into (see reg::Sig) */ @@ -113,31 +115,33 @@ inline constexpr char strings[] = "variableNameResult\0" /* 470 */ "dependencies\0" /* 489 */ "PHPStan\\Dependency\\Dependencies\0" /* 502 */ - "__construct\0" /* 534 */ - "\0" /* 546 */ - "getDependencies\0" /* 547 */ - "getKind\0" /* 563 */ - "getVar\0" /* 571 */ - "getAssignedExpr\0" /* 578 */ - "getBeforeScope\0" /* 594 */ - "getScope\0" /* 609 */ - "getThrowPoints\0" /* 618 */ - "getImpurePoints\0" /* 633 */ - "getRootVar\0" /* 649 */ - "getVarResult\0" /* 660 */ - "getDimFetchStack\0" /* 673 */ - "getAssignedPropertyExpr\0" /* 690 */ - "getOffsetTypes\0" /* 714 */ - "getOffsetNativeTypes\0" /* 729 */ - "getExistingOffsetTypes\0" /* 750 */ - "getExistingOffsetNativeTypes\0" /* 773 */ - "getOffsetSetTargetResult\0" /* 802 */ - "getObjectResult\0" /* 827 */ - "getPropertyName\0" /* 843 */ - "getPropertyHolderType\0" /* 859 */ - "getTargetReadResult\0" /* 881 */ - "getTargetChainResults\0" /* 901 */ - "getVariableNameResult"; /* 923 */ + "illegalOffsetDimFetches\0" /* 534 */ + "__construct\0" /* 558 */ + "\0" /* 570 */ + "getDependencies\0" /* 571 */ + "getKind\0" /* 587 */ + "getVar\0" /* 595 */ + "getAssignedExpr\0" /* 602 */ + "getBeforeScope\0" /* 618 */ + "getScope\0" /* 633 */ + "getThrowPoints\0" /* 642 */ + "getImpurePoints\0" /* 657 */ + "getRootVar\0" /* 673 */ + "getVarResult\0" /* 684 */ + "getDimFetchStack\0" /* 697 */ + "getAssignedPropertyExpr\0" /* 714 */ + "getOffsetTypes\0" /* 738 */ + "getOffsetNativeTypes\0" /* 753 */ + "getExistingOffsetTypes\0" /* 774 */ + "getExistingOffsetNativeTypes\0" /* 797 */ + "getOffsetSetTargetResult\0" /* 826 */ + "getObjectResult\0" /* 851 */ + "getPropertyName\0" /* 867 */ + "getPropertyHolderType\0" /* 883 */ + "getTargetReadResult\0" /* 905 */ + "getTargetChainResults\0" /* 925 */ + "getVariableNameResult\0" /* 947 */ + "getIllegalOffsetDimFetches"; /* 969 */ inline constexpr reg::PackedArg args[] = { reg::packed(0, MAY_BE_STRING), /* __construct $kind */ reg::packed(5, 0, 9), /* __construct $var */ @@ -166,67 +170,70 @@ inline constexpr reg::PackedArg args[] = { reg::packed(448, MAY_BE_ARRAY, reg::NoString, false, false, 467), /* __construct $targetChainResults */ reg::packed(470, MAY_BE_NULL, 201, false, false, 186), /* __construct $variableNameResult */ reg::packed(489, MAY_BE_NULL, 502, false, false, 186), /* __construct $dependencies */ - reg::packed(546, MAY_BE_NULL, 502), /* getDependencies return */ - reg::packed(546, MAY_BE_STRING), /* getKind return */ - reg::packed(546, 0, 9), /* getVar return */ - reg::packed(546, 0, 9), /* getAssignedExpr return */ - reg::packed(546, 0, 54), /* getBeforeScope return */ - reg::packed(546, 0, 54), /* getScope return */ - reg::packed(546, MAY_BE_BOOL), /* enterExpressionAssign return */ - reg::packed(546, MAY_BE_BOOL), /* isAssignOp return */ - reg::packed(546, MAY_BE_BOOL), /* hasYield return */ - reg::packed(546, MAY_BE_ARRAY), /* getThrowPoints return */ - reg::packed(546, MAY_BE_ARRAY), /* getImpurePoints return */ - reg::packed(546, MAY_BE_BOOL), /* isAlwaysTerminating return */ - reg::packed(546, 0, 9), /* getRootVar return */ - reg::packed(546, 0, 201), /* getVarResult return */ - reg::packed(546, MAY_BE_ARRAY), /* getDimFetchStack return */ - reg::packed(546, 0, 9), /* getAssignedPropertyExpr return */ - reg::packed(546, MAY_BE_ARRAY), /* getOffsetTypes return */ - reg::packed(546, MAY_BE_ARRAY), /* getOffsetNativeTypes return */ - reg::packed(546, MAY_BE_ARRAY), /* getExistingOffsetTypes return */ - reg::packed(546, MAY_BE_ARRAY), /* getExistingOffsetNativeTypes return */ - reg::packed(546, 0, 201), /* getOffsetSetTargetResult return */ - reg::packed(546, 0, 201), /* getObjectResult return */ - reg::packed(546, MAY_BE_NULL | MAY_BE_STRING), /* getPropertyName return */ - reg::packed(546, 0, 413), /* getPropertyHolderType return */ - reg::packed(546, 0, 201), /* getTargetReadResult return */ - reg::packed(546, MAY_BE_ARRAY), /* getTargetChainResults return */ - reg::packed(546, MAY_BE_NULL, 201), /* getVariableNameResult return */ + reg::packed(534, MAY_BE_ARRAY, reg::NoString, false, false, 467), /* __construct $illegalOffsetDimFetches */ + reg::packed(570, MAY_BE_NULL, 502), /* getDependencies return */ + reg::packed(570, MAY_BE_STRING), /* getKind return */ + reg::packed(570, 0, 9), /* getVar return */ + reg::packed(570, 0, 9), /* getAssignedExpr return */ + reg::packed(570, 0, 54), /* getBeforeScope return */ + reg::packed(570, 0, 54), /* getScope return */ + reg::packed(570, MAY_BE_BOOL), /* enterExpressionAssign return */ + reg::packed(570, MAY_BE_BOOL), /* isAssignOp return */ + reg::packed(570, MAY_BE_BOOL), /* hasYield return */ + reg::packed(570, MAY_BE_ARRAY), /* getThrowPoints return */ + reg::packed(570, MAY_BE_ARRAY), /* getImpurePoints return */ + reg::packed(570, MAY_BE_BOOL), /* isAlwaysTerminating return */ + reg::packed(570, 0, 9), /* getRootVar return */ + reg::packed(570, 0, 201), /* getVarResult return */ + reg::packed(570, MAY_BE_ARRAY), /* getDimFetchStack return */ + reg::packed(570, 0, 9), /* getAssignedPropertyExpr return */ + reg::packed(570, MAY_BE_ARRAY), /* getOffsetTypes return */ + reg::packed(570, MAY_BE_ARRAY), /* getOffsetNativeTypes return */ + reg::packed(570, MAY_BE_ARRAY), /* getExistingOffsetTypes return */ + reg::packed(570, MAY_BE_ARRAY), /* getExistingOffsetNativeTypes return */ + reg::packed(570, 0, 201), /* getOffsetSetTargetResult return */ + reg::packed(570, 0, 201), /* getObjectResult return */ + reg::packed(570, MAY_BE_NULL | MAY_BE_STRING), /* getPropertyName return */ + reg::packed(570, 0, 413), /* getPropertyHolderType return */ + reg::packed(570, 0, 201), /* getTargetReadResult return */ + reg::packed(570, MAY_BE_ARRAY), /* getTargetChainResults return */ + reg::packed(570, MAY_BE_NULL, 201), /* getVariableNameResult return */ + reg::packed(570, MAY_BE_ARRAY), /* getIllegalOffsetDimFetches return */ }; using Sig = reg::Sig; } // namespace sigtab /* the signatures of the methods the class declares itself (a used trait's are in the trait's header) */ namespace sig { -inline constexpr sigtab::Sig __construct = { { 534 /* __construct */, 11, 0, 27, reg::NoArg, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getDependencies = { { 547 /* getDependencies */, 0, 27, 0, 27, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getKind = { { 563 /* getKind */, 0, 28, 0, 28, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getVar = { { 571 /* getVar */, 0, 29, 0, 29, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getAssignedExpr = { { 578 /* getAssignedExpr */, 0, 30, 0, 30, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getBeforeScope = { { 594 /* getBeforeScope */, 0, 31, 0, 31, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getScope = { { 609 /* getScope */, 0, 32, 0, 32, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig enterExpressionAssign = { { 91 /* enterExpressionAssign */, 0, 33, 0, 33, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig isAssignOp = { { 113 /* isAssignOp */, 0, 34, 0, 34, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig hasYield = { { 124 /* hasYield */, 0, 35, 0, 35, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getThrowPoints = { { 618 /* getThrowPoints */, 0, 36, 0, 36, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getImpurePoints = { { 633 /* getImpurePoints */, 0, 37, 0, 37, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig isAlwaysTerminating = { { 158 /* isAlwaysTerminating */, 0, 38, 0, 38, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getRootVar = { { 649 /* getRootVar */, 0, 39, 0, 39, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getVarResult = { { 660 /* getVarResult */, 0, 40, 0, 40, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getDimFetchStack = { { 673 /* getDimFetchStack */, 0, 41, 0, 41, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getAssignedPropertyExpr = { { 690 /* getAssignedPropertyExpr */, 0, 42, 0, 42, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getOffsetTypes = { { 714 /* getOffsetTypes */, 0, 43, 0, 43, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getOffsetNativeTypes = { { 729 /* getOffsetNativeTypes */, 0, 44, 0, 44, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getExistingOffsetTypes = { { 750 /* getExistingOffsetTypes */, 0, 45, 0, 45, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getExistingOffsetNativeTypes = { { 773 /* getExistingOffsetNativeTypes */, 0, 46, 0, 46, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getOffsetSetTargetResult = { { 802 /* getOffsetSetTargetResult */, 0, 47, 0, 47, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getObjectResult = { { 827 /* getObjectResult */, 0, 48, 0, 48, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getPropertyName = { { 843 /* getPropertyName */, 0, 49, 0, 49, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getPropertyHolderType = { { 859 /* getPropertyHolderType */, 0, 50, 0, 50, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getTargetReadResult = { { 881 /* getTargetReadResult */, 0, 51, 0, 51, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getTargetChainResults = { { 901 /* getTargetChainResults */, 0, 52, 0, 52, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getVariableNameResult = { { 923 /* getVariableNameResult */, 0, 53, 0, 53, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig __construct = { { 558 /* __construct */, 11, 0, 28, reg::NoArg, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getDependencies = { { 571 /* getDependencies */, 0, 28, 0, 28, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getKind = { { 587 /* getKind */, 0, 29, 0, 29, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getVar = { { 595 /* getVar */, 0, 30, 0, 30, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getAssignedExpr = { { 602 /* getAssignedExpr */, 0, 31, 0, 31, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getBeforeScope = { { 618 /* getBeforeScope */, 0, 32, 0, 32, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getScope = { { 633 /* getScope */, 0, 33, 0, 33, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig enterExpressionAssign = { { 91 /* enterExpressionAssign */, 0, 34, 0, 34, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isAssignOp = { { 113 /* isAssignOp */, 0, 35, 0, 35, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig hasYield = { { 124 /* hasYield */, 0, 36, 0, 36, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getThrowPoints = { { 642 /* getThrowPoints */, 0, 37, 0, 37, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getImpurePoints = { { 657 /* getImpurePoints */, 0, 38, 0, 38, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isAlwaysTerminating = { { 158 /* isAlwaysTerminating */, 0, 39, 0, 39, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getRootVar = { { 673 /* getRootVar */, 0, 40, 0, 40, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getVarResult = { { 684 /* getVarResult */, 0, 41, 0, 41, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getDimFetchStack = { { 697 /* getDimFetchStack */, 0, 42, 0, 42, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getAssignedPropertyExpr = { { 714 /* getAssignedPropertyExpr */, 0, 43, 0, 43, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getOffsetTypes = { { 738 /* getOffsetTypes */, 0, 44, 0, 44, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getOffsetNativeTypes = { { 753 /* getOffsetNativeTypes */, 0, 45, 0, 45, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getExistingOffsetTypes = { { 774 /* getExistingOffsetTypes */, 0, 46, 0, 46, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getExistingOffsetNativeTypes = { { 797 /* getExistingOffsetNativeTypes */, 0, 47, 0, 47, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getOffsetSetTargetResult = { { 826 /* getOffsetSetTargetResult */, 0, 48, 0, 48, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getObjectResult = { { 851 /* getObjectResult */, 0, 49, 0, 49, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getPropertyName = { { 867 /* getPropertyName */, 0, 50, 0, 50, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getPropertyHolderType = { { 883 /* getPropertyHolderType */, 0, 51, 0, 51, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getTargetReadResult = { { 905 /* getTargetReadResult */, 0, 52, 0, 52, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getTargetChainResults = { { 925 /* getTargetChainResults */, 0, 53, 0, 53, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getVariableNameResult = { { 947 /* getVariableNameResult */, 0, 54, 0, 54, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getIllegalOffsetDimFetches = { { 969 /* getIllegalOffsetDimFetches */, 0, 55, 0, 55, ZEND_ACC_PUBLIC } }; } // namespace sig } // namespace ptdecl::PreparedAssignTarget diff --git a/turbo-ext/src/support.h b/turbo-ext/src/support.h index 6980dfcbf72..b1e76b43b62 100644 --- a/turbo-ext/src/support.h +++ b/turbo-ext/src/support.h @@ -1819,7 +1819,8 @@ zv::Val pt_expression_context_create_top_level(bool resolveTemplateArguments = t zv::Val pt_expression_context_create_deep(bool resolveTemplateArguments = true); /* $context->enterDeep() / enterDeepKeepingValueFlow() / withoutValueFlow() / * enterMatchArm() / withoutTemplateArgumentResolution() / enterThrow() / - * enterArrayDimFetchRoot() / enterUnsetTarget() / enterPassedToType($type, + * enterArrayDimFetchRoot() / enterUnsetTarget() / enterIssetTarget() / + * enterPassedToType($type, * $nativeType) / enterRightSideAssign($variableName, $expr) / * enterAssignRightSideCallArgs($acceptor) / enterValueFlow($target, $direct) * — the native bodies for a native context, the methods of anything else @@ -1832,13 +1833,14 @@ zv::Val pt_expression_context_without_template_argument_resolution(zval *context zv::Val pt_expression_context_enter_throw(zval *context); zv::Val pt_expression_context_enter_array_dim_fetch_root(zval *context); zv::Val pt_expression_context_enter_unset_target(zval *context); +zv::Val pt_expression_context_enter_isset_target(zval *context); zv::Val pt_expression_context_enter_passed_to_type(zval *context, zval *type, zval *nativeType); zv::Val pt_expression_context_enter_right_side_assign(zval *context, zend_string *variableName, zval *expr); zv::Val pt_expression_context_enter_assign_right_side_call_args(zval *context, zval *acceptor); zv::Val pt_expression_context_enter_value_flow(zval *context, zval *target, bool direct); /* $context->isDeep() / isValueConsumed() / shouldResolveTemplateArguments() / * isInThrow() / isValueFlowDirect() / isArrayDimFetchRoot() / - * isUnsetTarget(); false = pending exception */ + * isUnsetTarget() / isIssetTarget(); false = pending exception */ [[nodiscard]] bool pt_expression_context_is_deep(zval *context, bool &out); [[nodiscard]] bool pt_expression_context_is_value_consumed(zval *context, bool &out); [[nodiscard]] bool pt_expression_context_should_resolve_template_arguments(zval *context, bool &out); @@ -1846,6 +1848,7 @@ zv::Val pt_expression_context_enter_value_flow(zval *context, zval *target, bool [[nodiscard]] bool pt_expression_context_is_value_flow_direct(zval *context, bool &out); [[nodiscard]] bool pt_expression_context_is_array_dim_fetch_root(zval *context, bool &out); [[nodiscard]] bool pt_expression_context_is_unset_target(zval *context, bool &out); +[[nodiscard]] bool pt_expression_context_is_isset_target(zval *context, bool &out); /* $context->getPassedToType() / getNativePassedToType() / * getInAssignRightSideVariableName() / getInAssignRightSideExpr() / * getInAssignRightSideType() / getInAssignRightSideNativeType() / @@ -2098,7 +2101,8 @@ extern zend_class_entry *pt_ce_prepared_assign_target; /* new PreparedAssignTarget(...$argv): the constructor's positional * arguments in the twin's order (at least the 11 required ones), an * omitted or UNDEF optional one taking its default (the last one, - * $dependencies, is ptdecl::PreparedAssignTarget::slot::dependencies) */ + * $illegalOffsetDimFetches, is + * ptdecl::PreparedAssignTarget::slot::illegalOffsetDimFetches) */ zv::Val pt_prepared_assign_target_new(uint32_t argc, zval *argv); /* }}} */ @@ -3101,6 +3105,10 @@ zv::Val pt_variable_write_offset_from_type(zval *dimType); * for the shadowing class, the method otherwise ($dimResult NULL or IS_NULL * for null); UNDEF = pending exception */ zv::Val pt_array_dim_fetch_handler_compose_result(zval *handler, zval *nodeScopeResolver, zval *stmt, zval *expr, zval *dimResult, zval *varResult, zval *storage, zval *context, zval *beforeScope); +/* IllegalOffsetTypeHelper.cpp — + * IllegalOffsetTypeHelper::mayOffsetThrowTypeError($offsetType) (borrowed); + * false = pending exception */ +[[nodiscard]] bool pt_illegal_offset_type_helper_may_offset_throw_type_error(zval *offsetType, bool &out); /* }}} */ @@ -4389,6 +4397,7 @@ enum pt_php_version_query PT_PHP_VERSION_SUPPORTS_ASYMMETRIC_VISIBILITY, PT_PHP_VERSION_SUPPORTS_ENUMS, PT_PHP_VERSION_SUPPORTS_PROPERTY_HOOKS, + PT_PHP_VERSION_THROWS_TYPE_ERROR_FOR_ILLEGAL_OFFSETS, PT_PHP_VERSION_QUERY_COUNT }; /* $phpVersion->() / ->getVersionId(): the $versionId slot of exactly diff --git a/turbo-ext/tests/initializer-escape.php b/turbo-ext/tests/initializer-escape.php index f206d7ab248..34125be8d3c 100644 --- a/turbo-ext/tests/initializer-escape.php +++ b/turbo-ext/tests/initializer-escape.php @@ -91,7 +91,7 @@ public function resolveConcatType($left, $right) { return $this->inner->resolveC 'UnaryMinusHandler' => static fn ($storage) => (new \PHPStanTurbo\UnaryMinusHandler($ieKeeping, $ieFactory, $ieDefault))->processExpr($ieNodeScopeResolver, $ieStmt, new \PhpParser\Node\Expr\UnaryMinus(new \PhpParser\Node\Expr\Variable('a')), $ieScope, $storage, new \PHPStan\Analyser\NoopNodeCallback(), \PHPStan\Analyser\ExpressionContext::createDeep()), 'UnaryPlusHandler' => static fn ($storage) => (new \PHPStanTurbo\UnaryPlusHandler($ieKeeping, $ieFactory, $ieDefault))->processExpr($ieNodeScopeResolver, $ieStmt, new \PhpParser\Node\Expr\UnaryPlus(new \PhpParser\Node\Expr\Variable('n')), $ieScope, $storage, new \PHPStan\Analyser\NoopNodeCallback(), \PHPStan\Analyser\ExpressionContext::createDeep()), 'BitwiseNotHandler' => static fn ($storage) => (new \PHPStanTurbo\BitwiseNotHandler($ieKeeping, $ieFactory, $ieDefault))->processExpr($ieNodeScopeResolver, $ieStmt, new \PhpParser\Node\Expr\BitwiseNot(new \PhpParser\Node\Expr\Variable('a')), $ieScope, $storage, new \PHPStan\Analyser\NoopNodeCallback(), \PHPStan\Analyser\ExpressionContext::createDeep()), - 'ArrayHandler' => static fn ($storage) => (new \PHPStanTurbo\ArrayHandler($ieKeeping, $ieFactory))->processExpr($ieNodeScopeResolver, $ieStmt, new \PhpParser\Node\Expr\Array_([new \PhpParser\Node\ArrayItem(new \PhpParser\Node\Expr\Variable('n')), new \PhpParser\Node\ArrayItem(new \PhpParser\Node\Scalar\Int_(5))]), $ieScope, $storage, new \PHPStan\Analyser\NoopNodeCallback(), \PHPStan\Analyser\ExpressionContext::createDeep()), + 'ArrayHandler' => static fn ($storage) => (new \PHPStanTurbo\ArrayHandler($ieKeeping, $ieFactory, $ieContainer->getByType(\PHPStan\Php\PhpVersion::class)))->processExpr($ieNodeScopeResolver, $ieStmt, new \PhpParser\Node\Expr\Array_([new \PhpParser\Node\ArrayItem(new \PhpParser\Node\Expr\Variable('n')), new \PhpParser\Node\ArrayItem(new \PhpParser\Node\Scalar\Int_(5))]), $ieScope, $storage, new \PHPStan\Analyser\NoopNodeCallback(), \PHPStan\Analyser\ExpressionContext::createDeep()), 'ClassConstFetchHandler' => static fn ($storage) => (new \PHPStanTurbo\ClassConstFetchHandler($ieKeeping, $ieFactory, $ieDefault, $ieContainer->getByType(\PHPStan\Reflection\ReflectionProvider::class)))->processExpr($ieNodeScopeResolver, $ieStmt, new \PhpParser\Node\Expr\ClassConstFetch(new \PhpParser\Node\Expr\Variable('n'), 'FOO'), $ieScope, $storage, new \PHPStan\Analyser\NoopNodeCallback(), \PHPStan\Analyser\ExpressionContext::createDeep()), 'BinaryOpHandler' => static fn ($storage) => (new \PHPStanTurbo\BinaryOpHandler($ieKeeping, $ieContainer->getByType(\PHPStan\Analyser\RicherScopeGetTypeHelper::class), $ieContainer->getByType(\PHPStan\Php\PhpVersion::class), $ieContainer->getByType(\PHPStan\Analyser\ExprHandler\Helper\ImplicitToStringCallHelper::class), $ieContainer->getByType(\PHPStan\Node\Printer\ExprPrinter::class), $ieIdentical, $ieContainer->getByType(\PHPStan\Analyser\ExprHandler\Helper\CountNarrowingHelper::class), $ieFactory, $ieDefault))->processExpr($ieNodeScopeResolver, $ieStmt, new \PhpParser\Node\Expr\BinaryOp\Minus(new \PhpParser\Node\Expr\Variable('a'), new \PhpParser\Node\Scalar\Int_(3)), $ieScope, $storage, new \PHPStan\Analyser\NoopNodeCallback(), \PHPStan\Analyser\ExpressionContext::createDeep()), ]; diff --git a/turbo-ext/tests/smoke.php b/turbo-ext/tests/smoke.php index 9df729e25f5..74a621b20d9 100644 --- a/turbo-ext/tests/smoke.php +++ b/turbo-ext/tests/smoke.php @@ -2687,6 +2687,46 @@ enum SmokeTurboBacked: string check($vehResults['php'] === $vehResults['native'], 'VolatileExpressionHelper parity: ' . json_encode($vehResults['php']) . ' vs ' . json_encode($vehResults['native'])); check($vehResults['php'][0][0] === true && $vehResults['php'][2][0] === true && $vehResults['php'][4][0] === true && $vehResults['php'][3][0] === false, 'VolatileExpressionHelper: the fixture exercises removals and no-ops'); +// ---- IllegalOffsetTypeHelper ---- +// every flavour an offset type comes in, built from the PHP type classes and +// from the native ones (the helper only asks the type, so both sides must +// answer the same for either) +$covered[\PHPStan\Analyser\ExprHandler\Helper\IllegalOffsetTypeHelper::class] = true; +$iothTypes = static function (bool $nativeTypes) use ($shadowedClasses): array { + $t = static fn (string $class, ...$args) => new ($nativeTypes ? $shadowedClasses[$class]['turboClass'] : $class)(...$args); + $object = $t(\PHPStan\Type\ObjectType::class, \stdClass::class); + $array = $t(\PHPStan\Type\ArrayType::class, $t(\PHPStan\Type\MixedType::class), $t(\PHPStan\Type\MixedType::class)); + return [ + 'int' => $t(\PHPStan\Type\IntegerType::class), + 'string' => $t(\PHPStan\Type\StringType::class), + 'float' => $t(\PHPStan\Type\FloatType::class), + 'bool' => $t(\PHPStan\Type\BooleanType::class), + 'true' => $t(\PHPStan\Type\Constant\ConstantBooleanType::class, true), + 'null' => $t(\PHPStan\Type\NullType::class), + 'resource' => $t(\PHPStan\Type\ResourceType::class), + 'never' => $t(\PHPStan\Type\NeverType::class), + 'mixed' => $t(\PHPStan\Type\MixedType::class), + 'object' => $object, + 'object without class' => $t(\PHPStan\Type\ObjectWithoutClassType::class), + 'array' => $array, + 'array{}' => $t(\PHPStan\Type\Constant\ConstantArrayType::class, [], []), + 'int|object' => $t(\PHPStan\Type\UnionType::class, [$t(\PHPStan\Type\IntegerType::class), $object]), + 'array|null' => $t(\PHPStan\Type\UnionType::class, [$array, $t(\PHPStan\Type\NullType::class)]), + 'int|string' => $t(\PHPStan\Type\UnionType::class, [$t(\PHPStan\Type\IntegerType::class), $t(\PHPStan\Type\StringType::class)]), + 'benevolent int|object' => $t(\PHPStan\Type\BenevolentUnionType::class, [$t(\PHPStan\Type\IntegerType::class), $object]), + ]; +}; +$iothAnswers = []; +foreach (['php types' => false, 'native types' => true] as $typesSide => $nativeTypes) { + foreach ($iothTypes($nativeTypes) as $label => $type) { + $php = \PHPStan\Analyser\ExprHandler\Helper\IllegalOffsetTypeHelper::mayOffsetThrowTypeError($type); + $native = \PHPStanTurbo\IllegalOffsetTypeHelper::mayOffsetThrowTypeError($type); + check($php === $native, "IllegalOffsetTypeHelper::mayOffsetThrowTypeError($label, $typesSide): " . var_export($php, true) . ' vs ' . var_export($native, true)); + $iothAnswers[] = $php; + } +} +check(in_array(true, $iothAnswers, true) && in_array(false, $iothAnswers, true), 'IllegalOffsetTypeHelper: the fixture exercises both answers'); + // ---- VariableFlow ---- // The factories build each side's flow classes over shared nodes and writes // (the native flow classes are a separate hierarchy under the prefix); flows