Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 10 additions & 7 deletions src/Analyser/ExprHandler/ArrayHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@
use PhpParser\Node\Expr;
use PhpParser\Node\Expr\Array_;
use PhpParser\Node\Expr\ArrowFunction;
use PhpParser\Node\Expr\ClassConstFetch;
use PhpParser\Node\Expr\Closure;
use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Identifier;
use PhpParser\Node\Name\FullyQualified;
use PhpParser\Node\Stmt;
use PHPStan\Analyser\ExpressionContext;
Expand Down Expand Up @@ -219,17 +221,18 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex
*/
private function getCallableDependencies(MutatingScope $scope, Array_ $expr, array $itemResults, ExpressionResult $result): ?Dependencies
{
if (count($expr->items) !== 2 || !isset($expr->items[0])) {
if (count($expr->items) !== 2 || !isset($expr->items[0], $expr->items[1])) {
return null;
}

if ($scope->getFunctionCallStack() === []) {
return null;
}

// a class constant, property default or enum case value is not called where it is
// declared - testing it would reflect whatever class its first item happens to name
if (
$scope->isInClass()
&& $scope->getFunction() === null
&& !$scope->isInAnonymousFunction()
&& $scope->getFunctionCallStack() === []
!$expr->items[0]->value instanceof ClassConstFetch
|| !$expr->items[0]->value->name instanceof Identifier
|| $expr->items[0]->value->name->toLowerString() !== 'class'
) {
return null;
}
Expand Down
32 changes: 31 additions & 1 deletion tests/PHPStan/Analyser/data/bug-15292.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,37 @@ public function isAllowed(string $item): bool
return in_array($item, self::ALLOWED_MODALITIES, true)
|| in_array($item, $this->allowedProperty, true)
|| in_array($item, Modality::ALLOWED, true)
|| in_array('Bug15292\BigContainer::string_ident', [$item], true);
|| in_array('Bug15292\BigContainer::string_ident', [$item], true)
|| in_array($item, ['Bug15292\BigContainer', 'packstation'], true);
}

public function checkMethodBody(string $item): bool
{
$modes = ['Bug15292\BigContainer', 'packstation'];
return in_array($item, $modes, true);
}

/**
* @param list<string> $modes
*/
public function checkParamDefault(array $modes = ['Bug15292\BigContainer', 'packstation']): bool
{
return count($modes) > 0;
}

/**
* @return list<string>
*/
public function getModes(): array
{
return ['Bug15292\BigContainer', 'packstation'];
}

}

function checkFunctionBody(): void
{
$modes = ['Bug15292\BigContainer', 'packstation'];
$fn = static fn (): array => ['Bug15292\BigContainer', 'packstation'];
}

36 changes: 19 additions & 17 deletions turbo-ext/src/ArrayHandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -466,24 +466,14 @@ class ArrayHandler
if (first == NULL) return zv::Val::null();
ZVAL_DEREF(first);
if (Z_TYPE_P(first) == IS_NULL) return zv::Val::null();
zval *second = zend_hash_index_find(Z_ARRVAL_P(items), 1);
if (second == NULL) return zv::Val::null();
ZVAL_DEREF(second);
if (Z_TYPE_P(second) == IS_NULL) return zv::Val::null();

// a class constant, property default or enum case value is not called where it is
// declared - testing it would reflect whatever class its first item happens to name
bool inClass;
if (UNEXPECTED(!pt_mutating_scope_is_in_class(Z_OBJ_P(scope), inClass))) return zv::Val();
if (inClass) {
zv::Val function = pt_mutating_scope_get_function(Z_OBJ_P(scope));
if (UNEXPECTED(function.isUndef())) return zv::Val();
if (function.isNull()) {
bool inAnonymousFunction;
if (UNEXPECTED(!pt_mutating_scope_is_in_anonymous_function(Z_OBJ_P(scope), inAnonymousFunction))) return zv::Val();
if (!inAnonymousFunction) {
zv::Val functionCallStack = pt_type_call(Z_OBJ_P(scope), PT_LC("getfunctioncallstack"), 0, NULL);
if (UNEXPECTED(functionCallStack.isUndef())) return zv::Val();
if (Z_TYPE_P(functionCallStack.raw()) == IS_ARRAY && zend_hash_num_elements(Z_ARRVAL_P(functionCallStack.raw())) == 0) return zv::Val::null();
}
}
}
zv::Val functionCallStack = pt_type_call(Z_OBJ_P(scope), PT_LC("getfunctioncallstack"), 0, NULL);
if (UNEXPECTED(functionCallStack.isUndef())) return zv::Val();
if (Z_TYPE_P(functionCallStack.raw()) == IS_ARRAY && zend_hash_num_elements(Z_ARRVAL_P(functionCallStack.raw())) == 0) return zv::Val::null();

if (UNEXPECTED(Z_TYPE_P(first) != IS_OBJECT)) {
zend_throw_error(NULL, "Attempt to read property \"value\" on %s", zend_zval_value_name(first));
Expand All @@ -495,6 +485,18 @@ class ArrayHandler
zend_type_error("spl_object_id(): Argument #1 ($object) must be of type object, %s given", zend_zval_value_name(value));
return zv::Val();
}

zend_class_entry *classConstFetchCe = pt_class(PT_CLASS_CLASS_CONST_FETCH);
if (UNEXPECTED(classConstFetchCe == NULL)) return zv::Val();
if (!instanceof_function(Z_OBJCE_P(value), classConstFetchCe)) return zv::Val::null();
static pt_property_site nameSite, identifierNameSite;
zval *name = nodeProperty(nameSite, value, PT_LC("name"));
if (name == NULL || Z_TYPE_P(name) != IS_OBJECT) return zv::Val::null();
zend_class_entry *identifierCe = pt_class(PT_CLASS_IDENTIFIER);
if (UNEXPECTED(identifierCe == NULL)) return zv::Val();
if (!instanceof_function(Z_OBJCE_P(name), identifierCe)) return zv::Val::null();
zval *idName = nodeProperty(identifierNameSite, name, PT_LC("name"));
if (idName == NULL || Z_TYPE_P(idName) != IS_STRING || !zend_string_equals_literal_ci(Z_STR_P(idName), "class")) return zv::Val::null();
zval *firstItemResult = zend_hash_index_find(Z_ARRVAL_P(itemResults), Z_OBJ_HANDLE_P(value));
if (firstItemResult == NULL || Z_TYPE_P(firstItemResult) == IS_NULL) return zv::Val::null();
zv::Val firstItemType = pt_expression_result_get_type(firstItemResult);
Expand Down
Loading