mirror of git://gcc.gnu.org/git/gcc.git
Fix overflows in std::pmr::unsynchonized_pool_resource
* src/c++17/memory_resource.cc (bitset::full()): Handle edge case for _M_next_word maximum value. (bitset::get_first_unset(), bitset::set(size_type)): Use update_next_word() to update _M_next_word. (bitset::update_next_word()): New function, avoiding wraparound of unsigned _M_next_word member. (bitset::max_word_index()): New function. (chunk::chunk(void*, uint32_t, void*, size_t)): Add assertion. (chunk::max_bytes_per_chunk()): New function. (pool::replenish(memory_resource*, const pool_options&)): Prevent _M_blocks_per_chunk from exceeding max_blocks_per_chunk or from causing chunk::max_bytes_per_chunk() to be exceeded. * testsuite/20_util/unsynchronized_pool_resource/allocate-max-chunks.cc: New test. From-SVN: r266087
This commit is contained in:
parent
874e50cbd5
commit
6bdd58f73a
|
|
@ -1,3 +1,20 @@
|
||||||
|
2018-11-13 Jonathan Wakely <jwakely@redhat.com>
|
||||||
|
|
||||||
|
* src/c++17/memory_resource.cc (bitset::full()): Handle edge case
|
||||||
|
for _M_next_word maximum value.
|
||||||
|
(bitset::get_first_unset(), bitset::set(size_type)): Use
|
||||||
|
update_next_word() to update _M_next_word.
|
||||||
|
(bitset::update_next_word()): New function, avoiding wraparound of
|
||||||
|
unsigned _M_next_word member.
|
||||||
|
(bitset::max_word_index()): New function.
|
||||||
|
(chunk::chunk(void*, uint32_t, void*, size_t)): Add assertion.
|
||||||
|
(chunk::max_bytes_per_chunk()): New function.
|
||||||
|
(pool::replenish(memory_resource*, const pool_options&)): Prevent
|
||||||
|
_M_blocks_per_chunk from exceeding max_blocks_per_chunk or from
|
||||||
|
causing chunk::max_bytes_per_chunk() to be exceeded.
|
||||||
|
* testsuite/20_util/unsynchronized_pool_resource/allocate-max-chunks.cc:
|
||||||
|
New test.
|
||||||
|
|
||||||
2018-11-12 Jason Merrill <jason@redhat.com>
|
2018-11-12 Jason Merrill <jason@redhat.com>
|
||||||
|
|
||||||
* libsupc++/new (std::destroying_delete_t): New.
|
* libsupc++/new (std::destroying_delete_t): New.
|
||||||
|
|
|
||||||
|
|
@ -290,7 +290,18 @@ namespace pmr
|
||||||
}
|
}
|
||||||
|
|
||||||
// True if all bits are set
|
// True if all bits are set
|
||||||
bool full() const noexcept { return _M_next_word >= nwords(); }
|
bool full() const noexcept
|
||||||
|
{
|
||||||
|
if (_M_next_word >= nwords())
|
||||||
|
return true;
|
||||||
|
// For a bitset with size() > (max_blocks_per_chunk() - 64) we will
|
||||||
|
// have nwords() == (max_word_index() + 1) and so _M_next_word will
|
||||||
|
// never be equal to nwords().
|
||||||
|
// In that case, check if the last word is full:
|
||||||
|
if (_M_next_word == max_word_index())
|
||||||
|
return _M_words[_M_next_word] == word(-1);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// True if size() != 0 and no bits are set.
|
// True if size() != 0 and no bits are set.
|
||||||
bool empty() const noexcept
|
bool empty() const noexcept
|
||||||
|
|
@ -343,11 +354,7 @@ namespace pmr
|
||||||
const word bit = word(1) << n;
|
const word bit = word(1) << n;
|
||||||
_M_words[i] |= bit;
|
_M_words[i] |= bit;
|
||||||
if (i == _M_next_word)
|
if (i == _M_next_word)
|
||||||
{
|
update_next_word();
|
||||||
while (_M_words[_M_next_word] == word(-1)
|
|
||||||
&& ++_M_next_word != nwords())
|
|
||||||
{ }
|
|
||||||
}
|
|
||||||
return (i * bits_per_word) + n;
|
return (i * bits_per_word) + n;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -361,11 +368,7 @@ namespace pmr
|
||||||
const word bit = word(1) << (n % bits_per_word);
|
const word bit = word(1) << (n % bits_per_word);
|
||||||
_M_words[wd] |= bit;
|
_M_words[wd] |= bit;
|
||||||
if (wd == _M_next_word)
|
if (wd == _M_next_word)
|
||||||
{
|
update_next_word();
|
||||||
while (_M_words[_M_next_word] == word(-1)
|
|
||||||
&& ++_M_next_word != nwords())
|
|
||||||
{ }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void clear(size_type n) noexcept
|
void clear(size_type n) noexcept
|
||||||
|
|
@ -378,6 +381,18 @@ namespace pmr
|
||||||
_M_next_word = wd;
|
_M_next_word = wd;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update _M_next_word to refer to the next word with an unset bit.
|
||||||
|
// The size of the _M_next_word bit-field means it cannot represent
|
||||||
|
// the maximum possible nwords() value. To avoid wraparound to zero
|
||||||
|
// this function saturates _M_next_word at max_word_index().
|
||||||
|
void update_next_word() noexcept
|
||||||
|
{
|
||||||
|
size_t next = _M_next_word;
|
||||||
|
while (_M_words[next] == word(-1) && ++next < nwords())
|
||||||
|
{ }
|
||||||
|
_M_next_word = std::min(next, max_word_index());
|
||||||
|
}
|
||||||
|
|
||||||
void swap(bitset& b) noexcept
|
void swap(bitset& b) noexcept
|
||||||
{
|
{
|
||||||
std::swap(_M_words, b._M_words);
|
std::swap(_M_words, b._M_words);
|
||||||
|
|
@ -396,6 +411,10 @@ namespace pmr
|
||||||
static constexpr size_t max_blocks_per_chunk() noexcept
|
static constexpr size_t max_blocks_per_chunk() noexcept
|
||||||
{ return (1ull << _S_size_digits) - 1; }
|
{ return (1ull << _S_size_digits) - 1; }
|
||||||
|
|
||||||
|
// Maximum value that can be stored in bitset::_M_next_word member (8191).
|
||||||
|
static constexpr size_t max_word_index() noexcept
|
||||||
|
{ return (max_blocks_per_chunk() + bits_per_word - 1) / bits_per_word; }
|
||||||
|
|
||||||
word* data() const noexcept { return _M_words; }
|
word* data() const noexcept { return _M_words; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
@ -425,7 +444,7 @@ namespace pmr
|
||||||
: bitset(words, n),
|
: bitset(words, n),
|
||||||
_M_bytes(bytes),
|
_M_bytes(bytes),
|
||||||
_M_p(static_cast<std::byte*>(p))
|
_M_p(static_cast<std::byte*>(p))
|
||||||
{ }
|
{ __glibcxx_assert(bytes <= chunk::max_bytes_per_chunk()); }
|
||||||
|
|
||||||
chunk(chunk&& c) noexcept
|
chunk(chunk&& c) noexcept
|
||||||
: bitset(std::move(c)), _M_bytes(c._M_bytes), _M_p(c._M_p)
|
: bitset(std::move(c)), _M_bytes(c._M_bytes), _M_p(c._M_p)
|
||||||
|
|
@ -451,6 +470,9 @@ namespace pmr
|
||||||
// Number of blocks in this chunk
|
// Number of blocks in this chunk
|
||||||
using bitset::size;
|
using bitset::size;
|
||||||
|
|
||||||
|
static constexpr uint32_t max_bytes_per_chunk() noexcept
|
||||||
|
{ return numeric_limits<decltype(_M_bytes)>::max(); }
|
||||||
|
|
||||||
// Determine if block with address p and size block_size
|
// Determine if block with address p and size block_size
|
||||||
// is contained within this chunk.
|
// is contained within this chunk.
|
||||||
bool owns(void* p, size_t block_size)
|
bool owns(void* p, size_t block_size)
|
||||||
|
|
@ -639,8 +661,7 @@ namespace pmr
|
||||||
void replenish(memory_resource* __r, const pool_options& __opts)
|
void replenish(memory_resource* __r, const pool_options& __opts)
|
||||||
{
|
{
|
||||||
using word = chunk::word;
|
using word = chunk::word;
|
||||||
const size_t __blocks
|
const size_t __blocks = _M_blocks_per_chunk;
|
||||||
= std::min<size_t>(__opts.max_blocks_per_chunk, _M_blocks_per_chunk);
|
|
||||||
const auto __bits = chunk::bits_per_word;
|
const auto __bits = chunk::bits_per_word;
|
||||||
const size_t __words = (__blocks + __bits - 1) / __bits;
|
const size_t __words = (__blocks + __bits - 1) / __bits;
|
||||||
const size_t __block_size = block_size();
|
const size_t __block_size = block_size();
|
||||||
|
|
@ -658,7 +679,16 @@ namespace pmr
|
||||||
__r->deallocate(__p, __bytes, __alignment);
|
__r->deallocate(__p, __bytes, __alignment);
|
||||||
}
|
}
|
||||||
if (_M_blocks_per_chunk < __opts.max_blocks_per_chunk)
|
if (_M_blocks_per_chunk < __opts.max_blocks_per_chunk)
|
||||||
_M_blocks_per_chunk *= 2;
|
{
|
||||||
|
const size_t max_blocks
|
||||||
|
= (chunk::max_bytes_per_chunk() - sizeof(word))
|
||||||
|
/ (__block_size + 0.125);
|
||||||
|
_M_blocks_per_chunk = std::min({
|
||||||
|
max_blocks,
|
||||||
|
__opts.max_blocks_per_chunk,
|
||||||
|
(size_t)_M_blocks_per_chunk * 2
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void release(memory_resource* __r)
|
void release(memory_resource* __r)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,88 @@
|
||||||
|
// Copyright (C) 2018 Free Software Foundation, Inc.
|
||||||
|
//
|
||||||
|
// This file is part of the GNU ISO C++ Library. This library is free
|
||||||
|
// software; you can redistribute it and/or modify it under the
|
||||||
|
// terms of the GNU General Public License as published by the
|
||||||
|
// Free Software Foundation; either version 3, or (at your option)
|
||||||
|
// any later version.
|
||||||
|
|
||||||
|
// This library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License along
|
||||||
|
// with this library; see the file COPYING3. If not see
|
||||||
|
// <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
// { dg-options "-std=gnu++17" }
|
||||||
|
// { dg-do run { target c++17 } }
|
||||||
|
|
||||||
|
#include <memory_resource>
|
||||||
|
#include <testsuite_hooks.h>
|
||||||
|
|
||||||
|
struct custom_mr : std::pmr::memory_resource
|
||||||
|
{
|
||||||
|
custom_mr(std::size_t max) : max(max) { }
|
||||||
|
|
||||||
|
bool reached_max = false;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::size_t max;
|
||||||
|
std::size_t count = 0;
|
||||||
|
|
||||||
|
void* do_allocate(std::size_t b, std::size_t a)
|
||||||
|
{
|
||||||
|
if (b >= max)
|
||||||
|
reached_max = true;
|
||||||
|
count += b;
|
||||||
|
if (count > (18 * 1024 * 1024))
|
||||||
|
// Something went wrong, should not need to allocate this much.
|
||||||
|
throw std::bad_alloc();
|
||||||
|
return std::pmr::new_delete_resource()->allocate(b, a);
|
||||||
|
}
|
||||||
|
|
||||||
|
void do_deallocate(void* p, std::size_t b, std::size_t a)
|
||||||
|
{ std::pmr::new_delete_resource()->deallocate(p, b, a); }
|
||||||
|
|
||||||
|
bool do_is_equal(const memory_resource& r) const noexcept
|
||||||
|
{ return false; }
|
||||||
|
};
|
||||||
|
|
||||||
|
void
|
||||||
|
test01()
|
||||||
|
{
|
||||||
|
// Only going to allocate blocks of this size:
|
||||||
|
const std::size_t block_size = 8;
|
||||||
|
std::pmr::pool_options opts{};
|
||||||
|
// Use maximum allowed number of blocks per chunk:
|
||||||
|
opts.max_blocks_per_chunk = (std::size_t)-1;
|
||||||
|
opts.largest_required_pool_block = block_size;
|
||||||
|
{
|
||||||
|
std::pmr::unsynchronized_pool_resource r(opts);
|
||||||
|
// Get the real max_blocks_per_chunk that will be used:
|
||||||
|
opts = r.options();
|
||||||
|
// Sanity test in case chunk::max_blocks_per_chunk() changes,
|
||||||
|
// as that could make this test take much longer to run:
|
||||||
|
VERIFY( opts.max_blocks_per_chunk <= (1 << 19) );
|
||||||
|
}
|
||||||
|
custom_mr c(block_size * opts.max_blocks_per_chunk);
|
||||||
|
std::pmr::unsynchronized_pool_resource r(opts, &c);
|
||||||
|
// Keep allocating from the pool until reaching the maximum chunk size:
|
||||||
|
while (!c.reached_max)
|
||||||
|
(void) r.allocate(block_size, 1);
|
||||||
|
c.reached_max = false;
|
||||||
|
// Now fill that maximally-sized chunk
|
||||||
|
// (this used to go into an infinite loop ):
|
||||||
|
for (std::size_t i = 0; i < opts.max_blocks_per_chunk; ++i)
|
||||||
|
(void) r.allocate(block_size, 1);
|
||||||
|
// Should have filled the maximally-sized chunk and allocated another
|
||||||
|
// maximally-sized chunk from upstream:
|
||||||
|
VERIFY( c.reached_max );
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
main()
|
||||||
|
{
|
||||||
|
test01();
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue