Skip to content
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

Fix the behavior of vector<bool, HugeAlloc> whose size overflows uint32_t #3342

Merged
merged 7 commits into from
Jan 22, 2023

Conversation

frederick-vs-ja
Copy link
Contributor

@frederick-vs-ja frederick-vs-ja commented Jan 12, 2023

On 32-bit platforms, the following program currently behaves incorrectly with MSVC STL - assertions fail (but pass on 64-bit platforms). Godbolt link.

#include <algorithm>
#include <cassert>
#include <cstddef>
#include <cstdint>
#include <memory>
#include <vector>

template <class T>
struct huge_allocator {
    huge_allocator() = default;
    template <class U>
    constexpr huge_allocator(const huge_allocator<U>) noexcept {}

    using value_type      = T;
    using size_type       = std::uint64_t;
    using difference_type = std::int64_t;

    T* allocate(std::uint64_t n) {
        return std::allocator<T>{}.allocate(static_cast<std::size_t>(n));
    }

    void deallocate(T* p, std::uint64_t n) {
        std::allocator<T>{}.deallocate(p, static_cast<std::size_t>(n));
    }
};

int main() {
    constexpr std::uint64_t small_bit_length = 0x7000'4321ULL;
    constexpr std::uint64_t large_bit_length = 0x1'2000'4321ULL; // overflows uint32_t
    constexpr auto large_bit_diff            = static_cast<std::int64_t>(large_bit_length);

    std::vector<bool, huge_allocator<bool>> v(small_bit_length);
    v.resize(large_bit_length);
    assert(v.end() - v.begin() == large_bit_diff);

    v.back() = true;
    assert(std::find(v.begin(), v.end(), true) - v.begin() == large_bit_diff - 1);

    v[small_bit_length]                    = true;
    v[large_bit_length - small_bit_length] = true;
    assert(std::count(v.begin(), v.end(), false) == large_bit_diff - 3);
}

The cause of this issue is that multiplication of _VBITS (of type int after lvalue-to-rvalue conversion) and differences of pointers (of type ptrdiff_t) may overflow int32_t. IMO it's still reasonable for a 32-bit program to allocate ~600MiB storage for a dynamic bitset, so this issue probably needs fix.

This PR casts _VBITS to difference_type/_Difference_type (which is int64_t when using huge_allocator) before multiplication in order to fix the issue.

@frederick-vs-ja frederick-vs-ja requested a review from a team as a code owner January 12, 2023 04:29
@CaseyCarter CaseyCarter added the bug Something isn't working label Jan 12, 2023
stl/inc/vector Outdated Show resolved Hide resolved
And avoid duplicating _VBITS.
Copy link
Contributor

@strega-nil-ms strega-nil-ms left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I certainly like the bugfix here; however, I'm not sure I love that we have this new _VBITS_DIFF; it doesn't seem that unreasonable to just cast _VBITS to difference_type in the specific places it's required. I'm still an approve, though.

@StephanTLavavej StephanTLavavej self-assigned this Jan 18, 2023

void test_huge_vector_bool() {
constexpr uint64_t small_bit_length = 0x7000'4321ULL;
constexpr uint64_t large_bit_length = 0x1'2000'4321ULL; // overflows uint32_t
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am somewhat concerned about the test reliability implications of consuming 604 MB at runtime. That said, this is less than the compiler memory consumption of certain ranges tests, so I have no concrete objection to this.

@@ -2393,7 +2395,7 @@ public:

#if _ITERATOR_DEBUG_LEVEL != 0
_CONSTEXPR20 _Difference_type _Total_off(const _Mycont* _Cont) const noexcept {
return static_cast<_Difference_type>(_VBITS * (_Myptr - _Cont->_Myvec.data()) + _Myoff);
return static_cast<_Difference_type>(_VBITS_DIFF * (_Myptr - _Cont->_Myvec.data()) + _Myoff);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No change requested: I searched for all mentions of _VBITS and it appears that all affected locations are indeed being updated. 😻

@StephanTLavavej
Copy link
Member

Thanks! I fixed a comment typo and rewrapped it, and changed the test allocator's converting constructor to take a const reference as is traditional.

@StephanTLavavej StephanTLavavej removed their assignment Jan 20, 2023
@StephanTLavavej StephanTLavavej self-assigned this Jan 21, 2023
@StephanTLavavej
Copy link
Member

I'm mirroring this to the MSVC-internal repo - please notify me if any further changes are pushed.

@StephanTLavavej StephanTLavavej merged commit c2b4352 into microsoft:main Jan 22, 2023
@StephanTLavavej
Copy link
Member

5 billion thanks for fixing this bug! 😹 🐞 🛠️

@frederick-vs-ja frederick-vs-ja deleted the huge-vector-bool branch January 22, 2023 06:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants