When an array reached a size that would require >4GB allocation, the argument to realloc would overflow during multiplication, resulting in a very small allocation and a subsequent out of bounds access.
This change fixes that and also adjusts capacity calculation so that it doesn't overflow 32-bit range until it's basically impossible not to.
This is not perfect - in particular, on 32-bit systems there's a risk of size_t overflow that remains, however because we grow in 1.5x increments, realistically an attempt to grow a 2GB allocation to the next increment would fail before that. We can also technically overflow capacity even after the adjustment, but that requires 3+B elements which effectively means an .obj file on the scale of hundreds of gigabytes, at which point maybe a streaming parser would be more practical.
When an array reached a size that would require >4GB allocation, the argument to realloc would overflow during multiplication, resulting in a very small allocation and a subsequent out of bounds access.
This change fixes that and also adjusts capacity calculation so that it doesn't overflow 32-bit range until it's basically impossible not to.
This is not perfect - in particular, on 32-bit systems there's a risk of size_t overflow that remains, however because we grow in 1.5x increments, realistically an attempt to grow a 2GB allocation to the next increment would fail before that. We can also technically overflow capacity even after the adjustment, but that requires 3+B elements which effectively means an .obj file on the scale of hundreds of gigabytes, at which point maybe a streaming parser would be more practical.