-
Notifications
You must be signed in to change notification settings - Fork 523
Limit int ranges when narrowing arrays via count()
#3902
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
581ec91
to
29d5146
Compare
This pull request has been marked as ready for review. |
src/Analyser/TypeSpecifier.php
Outdated
&& $sizeType->getMin() <= ConstantArrayTypeBuilder::ARRAY_COUNT_LIMIT | ||
&& ($sizeType->getMax() === null || $sizeType->getMax() - $sizeType->getMin() <= ConstantArrayTypeBuilder::ARRAY_COUNT_LIMIT) | ||
&& $arrayType->getKeyType()->isSuperTypeOf(IntegerRangeType::fromInterval(0, $sizeType->getMin() - 1))->yes() | ||
&& ($sizeType->getMax() === null || $arrayType->getKeyType()->isSuperTypeOf(IntegerRangeType::fromInterval(0, $sizeType->getMax() - 1))->yes()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This isn't what I'd expect. You should first calculate the expected array size and then compare it with ConstantArrayTypeBuilder::ARRAY_COUNT_LIMIT.
What you're doing is that you're first comparing min with the limit, and then (max - min) with the limit. Theoretically you can end up with a much larger array than the limit.
Thanks.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the code here that creates the arrays, is a bit complex and dynamic. and it was like that already before I did my changes. but I'll try to see what I can do
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just want to count how many time exactly is setOffsetValueType being called, and that number has to be under the limit.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sure, I understand. I checked again and slightly changed things. the max - min was not useful, I agree. I think the current state is OK, but let me know please.
what the code for int ranges basically does is 3 things
- use the min value to create non-optional offsets in a constant array. this is what makes it create a huuge constant array in the reproducer. and min decides how often setOffsetValueType() is being called.
- use the max value to then continue starting from min up until max to create optional offsets. here we get then up until max setOffsetValueType() calls in summary. so this should also be under the limit.
- use an existing constant array, start with min, building a list until it finds an offset that does not exist. this is a bit weird for me still, but at least not relevant to limits I'd say, since it does not create new offsets
what do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
summary: min and max should be under the limit IMO. checking max would be enough if it is set, but the if is already complex enough, so currently both are checked
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
writing this down made me realize it can be a bit simpler. pushed again 🙈
src/Analyser/TypeSpecifier.php
Outdated
$isList->yes() | ||
|| $isConstantArray->yes() && $arrayType->getKeyType()->isSuperTypeOf(IntegerRangeType::fromInterval(0, $sizeType->getMin() - 1))->yes() | ||
) | ||
&& ($sizeType->getMax() ?? $sizeType->getMin()) < ConstantArrayTypeBuilder::ARRAY_COUNT_LIMIT |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think that ($sizeType->getMax() ?? $sizeType->getMin())
is the final size of the array created here.
Please refactor this section. You can prepare the arguments for setOffsetValueType
in a single array beforehand, count the number of elements, and then proceed with creating the constant array only if it's below ConstantArrayTypeBuilder::ARRAY_COUNT_LIMIT
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I still think it does, I tried to describe the logic here #3902 (comment)
but you can also check
phpstan-src/src/Analyser/TypeSpecifier.php
Lines 1108 to 1133 in dfd423f
// turn optional offsets non-optional | |
$valueTypesBuilder = ConstantArrayTypeBuilder::createEmpty(); | |
for ($i = 0; $i < $sizeType->getMin(); $i++) { | |
$offsetType = new ConstantIntegerType($i); | |
$valueTypesBuilder->setOffsetValueType($offsetType, $arrayType->getOffsetValueType($offsetType)); | |
} | |
if ($sizeType->getMax() !== null) { | |
for ($i = $sizeType->getMin(); $i < $sizeType->getMax(); $i++) { | |
$offsetType = new ConstantIntegerType($i); | |
$valueTypesBuilder->setOffsetValueType($offsetType, $arrayType->getOffsetValueType($offsetType), true); | |
} | |
} elseif ($arrayType->isConstantArray()->yes()) { | |
for ($i = $sizeType->getMin();; $i++) { | |
$offsetType = new ConstantIntegerType($i); | |
$hasOffset = $arrayType->hasOffsetValueType($offsetType); | |
if ($hasOffset->no()) { | |
break; | |
} | |
$valueTypesBuilder->setOffsetValueType($offsetType, $arrayType->getOffsetValueType($offsetType), !$hasOffset->yes()); | |
} | |
} else { | |
$resultTypes[] = TypeCombinator::intersect($arrayType, new NonEmptyArrayType()); | |
continue; | |
} | |
$resultTypes[] = $valueTypesBuilder->getArray(); |
case (for loop) 1 and 2 combined call setOffsetValueType()
up to max
times. case 3 is special but limited by an existing constant array which should make it safe.
do you disagree? I don't even know how to refactor this right now tbh, I think the "max if set, otherwise min" is the simplest way of dealing with this.
Check the 2nd commit - this is what I meant. |
Thank you. |
Oh. Sorry for not getting you and thx for adapting it yourself. Will check it later in detail. |
Tbh, I still think my solution is enough and was simpler of course. But yes, yours is slightly safer I'd say 😊 |
Fixes phpstan/phpstan#12787
for const ints a limit existed already.