Skip to content

Report new on an expression whose type is not string|object via new.nonObject#5866

Open
phpstan-bot wants to merge 1 commit into
phpstan:2.2.xfrom
phpstan-bot:create-pull-request/patch-9u2kw5i
Open

Report new on an expression whose type is not string|object via new.nonObject#5866
phpstan-bot wants to merge 1 commit into
phpstan:2.2.xfrom
phpstan-bot:create-pull-request/patch-9u2kw5i

Conversation

@phpstan-bot

Copy link
Copy Markdown
Collaborator

Summary

PHPStan did not detect when the new operator is used with an expression whose type can never be a valid class name. For example new $class where $class is int was silently accepted, even though it always fails at runtime. This PR makes InstantiationRule report such cases with a new new.nonObject error, mirroring the existing checks for the analogous dynamic-class-name constructs.

Changes

  • src/Rules/Classes/InstantiationRule.php
    • Injected RuleLevelHelper and added checkClassNameExprType(), run for new $expr (when the class is an expression).
    • It uses findTypeToCheck() against the accepted type string|object (new UnionType([new StringType(), new ObjectWithoutClassType()])) and reports Cannot instantiate class using <type>. with identifier new.nonObject when the type is not a supertype-compatible string/object.
    • ErrorType results (mixed/never/unknown class) are intentionally skipped so the existing class.notFound ("Instantiated class X not found.") path keeps working for object values of unknown classes.
  • src/Type/Php/Base64DecodeDynamicFunctionReturnTypeExtension.php
    • Constant-fold base64_decode() when the input is a constant string, returning the decoded ConstantStringType (or false in strict mode for invalid base64). The decode always runs in strict mode internally; for a non-strict call on invalid input the extension falls back to the generic string rather than guessing the lenient result.
  • Test updates:
    • New tests/PHPStan/Rules/Classes/data/instantiation-non-object.php + testInstantiationWithNonObjectType() in InstantiationRuleTest.
    • Updated both InstantiationRule test factories (InstantiationRuleTest, ForbiddenNameCheckExtensionRuleTest) for the new constructor argument.
    • Extended tests/PHPStan/Analyser/nsrt/base64_decode.php with constant-input cases and adjusted the constant '' expectations in tests/PHPStan/Analyser/nsrt/functions.php.

Root cause

InstantiationRule::getClassNames() only produced class names from constant strings and object types; for any other expression type it returned an empty list, so no error was ever emitted for new $wrongType. The fix adds an explicit type check for the expression form of new, using the same string|object acceptance criterion that the parallel rules already use.

Parallel-construct audit (dynamic class name in a class-name context): static method call (staticMethod.nonObject), static property access (staticProperty.nonObject), class constant fetch (classConstant.nonObject) and instanceof (instanceof.invalidExprType) already report wrong dynamic class types. new was the single missing member of that family, now covered.

The base64_decode() constant-folding is required because PHPStan's own source instantiates a base64-obfuscated adapter class name (new $enumAdapter(...) in NodeScopeResolver and BetterReflectionProvider). Without folding, that value is string|false, which the new rule correctly flags. Folding resolves it to the precise class-string, which both satisfies the new rule and lets the surrounding ClassReflectionFactory::create() call stay well-typed — i.e. teaching inference rather than rewriting the source.

Test

  • InstantiationRuleTest::testInstantiationWithNonObjectType() asserts new.nonObject is reported for new $int, new $float, new $bool, new $intOrString and new $array, and that valid forms (string, class-string, object, an instance) produce no error. Verified the data file produces no errors before the rule change.
  • tests/PHPStan/Analyser/nsrt/base64_decode.php asserts the new constant-folded return types ('Hello world', false, string).
  • Full suite green (make tests: 17383 tests) and make phpstan shows no new errors (only pre-existing shipmonk.deadMethod findings unrelated to this change).

Fixes phpstan/phpstan#4922

…new.nonObject`

- InstantiationRule now checks the type of a dynamic `new $expr` class
  expression with RuleLevelHelper and reports `new.nonObject` when the type
  cannot be a `string` or `object` (e.g. `new 123`, `new $intOrString`).
- ErrorType (mixed/never/unknown class) is left to the existing checks so
  the "Instantiated class X not found." message is preserved.
- Constant-fold `base64_decode()` on constant string input in
  Base64DecodeDynamicFunctionReturnTypeExtension so the obfuscated
  `new $enumAdapter(...)` call sites in PHPStan's own source resolve to a
  concrete class-string instead of `string|false`.
- Probed the parallel dynamic-class-name constructs (static method call,
  static property access, class constant fetch, `instanceof`): all already
  report a `*.nonObject` / `instanceof.invalidExprType` error, so `new` was
  the only missing instance of the family.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Detect wrong type used with new

2 participants