Declare Throwable as the unserialize() throw type unless allowed_classes provably forbids all classes - #6629
Open
phpstan-bot wants to merge 1 commit into
Open
phpstan-bot wants to merge 1 commit into
phpstan-bot wants to merge 1 commit into
Conversation
…_classes` provably forbids all classes - UnserializeFunctionThrowTypeExtension now returns Throwable whenever unserialize() may instantiate classes (no options, non-constant options, named/unpacked args, or allowed_classes not provably false/[]), since autoloaders, __wakeup(), __unserialize() and Serializable::unserialize() can throw anything. Previously only TypeError (or the stub's TypeError|ValueError) was declared, so catching Exception (or ValueError) around unserialize() was reported as a dead catch. - The same applies on PHP 7, which previously always got a void throw type. - When no class is allowed, valid options still produce no throw point and invalid options on PHP 8 keep the stub's TypeError|ValueError. - Probed json_encode() with JsonSerializable (jsonSerialize() can also throw); not changed here because a single Type cannot express JsonException together with arbitrary user exceptions without losing the checked JsonException.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
unserialize()runs user code: autoloaders,__wakeup(),__unserialize()andSerializable::unserialize(). Any of these can throw any exception. The throw type extension only declaredTypeError, so acatch (Exception $e)aroundunserialize($s)was reported asDead catch - Exception is never thrown in the try block.With this change, the extension returns
Throwablewhenever the call may instantiate classes.Changes
src/Type/Php/UnserializeFunctionThrowTypeExtension.php:Throwablewhen there are no options, when the options are not a constant array, when arguments are named or unpacked, or whenallowed_classesis not provablyfalse/[].void. Invalid options give the stub'sTypeError|ValueErroron PHP 8 andvoidon PHP 7.void. It now getsThrowabletoo when classes are allowed, because magic methods can throw there as well.serialize()already has an implicit throw point.sprintf()/printf()do not accept objects in PHPStan's signatures.json_encode()of aJsonSerializablecan also throw fromjsonSerialize(). I left it unchanged: returningThrowablewould swallow the checkedJsonExceptionforJSON_THROW_ON_ERROR, and a dynamic throw type extension can only return a singleType.Root cause
The extension treated
unserialize()as if its only possible exceptions were those thrown by the engine: option validation and typed-property mismatches. It ignored the user code thatunserialize()runs for every object it restores. So every catch of a non-TypeErrorexception looked dead, includingValueErrorcatches when classes were allowed.Test
tests/PHPStan/Rules/Exceptions/data/bug-15329.php: the reproducer from the issue. Before the fix it reported a dead catch; now it reports nothing.tests/PHPStan/Rules/Exceptions/data/unserialize-throw-type.php: updated expectations. Catches around calls that may instantiate classes are no longer reported. New cases cover:allowed_classes => false(ValueErroris thrown on PHP 8 and is dead on PHP 7)catch (Exception)withallowed_classes => [](still dead)catch (Exception)withallowed_classes => true(not dead)Fixes phpstan/phpstan#15329
🤖 Generated with Claude Code