Coverage Report

Created: 2025-09-27 06:50

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/mysql-server/mysys/my_alloc.cc
Line
Count
Source
1
/* Copyright (c) 2000, 2025, Oracle and/or its affiliates.
2
   This program is free software; you can redistribute it and/or modify
3
   it under the terms of the GNU General Public License, version 2.0,
4
   as published by the Free Software Foundation.
5
6
   This program is designed to work with certain software (including
7
   but not limited to OpenSSL) that is licensed under separate terms,
8
   as designated in a particular file or component or in included license
9
   documentation.  The authors of MySQL hereby grant you an additional
10
   permission to link the program and your derivative works with the
11
   separately licensed software that they have either included with
12
   the program or referenced in the documentation.
13
14
   Without limiting anything contained in the foregoing, this file,
15
   which is part of C Driver for MySQL (Connector/C), is also subject to the
16
   Universal FOSS Exception, version 1.0, a copy of which can be found at
17
   http://oss.oracle.com/licenses/universal-foss-exception.
18
19
   This program is distributed in the hope that it will be useful,
20
   but WITHOUT ANY WARRANTY; without even the implied warranty of
21
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22
   GNU General Public License, version 2.0, for more details.
23
24
   You should have received a copy of the GNU General Public License
25
   along with this program; if not, write to the Free Software
26
   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA */
27
28
/**
29
 * @file mysys/my_alloc.cc
30
 * Implementation of MEM_ROOT.
31
 *
32
 * This file follows Google coding style.
33
 */
34
35
#include <sys/types.h>
36
#include <algorithm>
37
#include <cstdarg>
38
#include <cstring>
39
#include <iterator>
40
41
#include "my_alloc.h"
42
#include "my_compiler.h"
43
#include "my_dbug.h"
44
#include "my_inttypes.h"
45
#include "my_pointer_arithmetic.h"
46
#include "my_sys.h"
47
#include "mysql/service_mysql_alloc.h"
48
#include "mysys_err.h"
49
#include "template_utils.h"
50
51
// For instrumented code: Always use malloc(); never reuse a chunk.
52
// This gives a lot more memory chunks, each with a red-zone around them.
53
#if defined(HAVE_VALGRIND) || defined(HAVE_ASAN)
54
static constexpr bool MEM_ROOT_SINGLE_CHUNKS = true;
55
#else
56
static constexpr bool MEM_ROOT_SINGLE_CHUNKS = false;
57
#endif
58
59
MEM_ROOT::Block *MEM_ROOT::AllocBlock(size_t wanted_length,
60
79.3k
                                      size_t minimum_length) {
61
79.3k
  DBUG_TRACE;
62
63
79.3k
  size_t length = std::max(wanted_length, ALIGN_SIZE(minimum_length));
64
79.3k
  if (m_max_capacity != 0) {
65
0
    size_t bytes_left;
66
0
    if (m_allocated_size > m_max_capacity) {
67
0
      bytes_left = 0;
68
0
    } else {
69
0
      bytes_left = m_max_capacity - m_allocated_size;
70
0
    }
71
0
    if (length > bytes_left) {
72
0
      if (m_error_for_capacity_exceeded) {
73
0
        my_error(EE_CAPACITY_EXCEEDED, MYF(0),
74
0
                 static_cast<ulonglong>(m_max_capacity));
75
        // NOTE: No early return; we will abort the query at the next safe
76
        // point. We also don't go down to minimum_length, as this will give a
77
        // new block on every subsequent Alloc() (of which there might be
78
        // many, since we don't know when the next safe point will be).
79
0
      } else if (minimum_length <= bytes_left) {
80
        // Make one final chunk with all that we have left.
81
0
        length = bytes_left;
82
0
      } else {
83
        // We don't have enough memory left to satisfy minimum_length.
84
0
        return nullptr;
85
0
      }
86
0
    }
87
0
  }
88
89
79.3k
  const size_t bytes_to_alloc = length + ALIGN_SIZE(sizeof(Block));
90
79.3k
  auto *new_block = static_cast<Block *>(
91
79.3k
      my_malloc(m_psi_key, bytes_to_alloc, MYF(MY_WME | ME_FATALERROR)));
92
79.3k
  if (new_block == nullptr) {
93
0
    if (m_error_handler) (m_error_handler)();
94
0
    return nullptr;
95
0
  }
96
79.3k
  TRASH(new_block, bytes_to_alloc);
97
79.3k
  new_block->end = pointer_cast<char *>(new_block) + bytes_to_alloc;
98
99
79.3k
  m_allocated_size += length;
100
101
  // Make the default block size 50% larger next time.
102
  // This ensures O(1) total mallocs (assuming Clear() is not called).
103
79.3k
  if (!MEM_ROOT_SINGLE_CHUNKS) {
104
0
    m_block_size += m_block_size / 2;
105
0
  }
106
79.3k
  return new_block;
107
79.3k
}
108
109
79.3k
void *MEM_ROOT::AllocSlow(size_t length) {
110
79.3k
  DBUG_TRACE;
111
79.3k
  DBUG_PRINT("enter", ("root: %p", this));
112
113
  // We need to allocate a new block to satisfy this allocation;
114
  // otherwise, the fast path in Alloc() would not have sent us here.
115
  // We plan to allocate a block of <block_size> bytes; see if that
116
  // would be enough or not.
117
79.3k
  if (length >= m_block_size || MEM_ROOT_SINGLE_CHUNKS) {
118
    // The next block we'd allocate would _not_ be big enough
119
    // (or we're in Valgrind/ASAN mode, and want everything in single chunks).
120
    // Allocate an entirely new block, not disturbing anything;
121
    // since the new block isn't going to be used for the next allocation
122
    // anyway, we can just as well keep the previous one.
123
79.3k
    Block *new_block =
124
79.3k
        AllocBlock(/*wanted_length=*/length, /*minimum_length=*/length);
125
79.3k
    if (new_block == nullptr) return nullptr;
126
127
79.3k
    if (m_current_block == nullptr) {
128
      // This is the only block, so it has to be the current block, too.
129
      // However, it will be full, so we won't be allocating from it
130
      // unless ClearForReuse() is called.
131
4.84k
      new_block->prev = nullptr;
132
4.84k
      m_current_block = new_block;
133
4.84k
      m_current_free_end = new_block->end;
134
4.84k
      m_current_free_start = m_current_free_end;
135
74.5k
    } else {
136
      // Insert the new block in the second-to-last position.
137
74.5k
      new_block->prev = m_current_block->prev;
138
74.5k
      m_current_block->prev = new_block;
139
74.5k
    }
140
141
79.3k
    return pointer_cast<char *>(new_block) + ALIGN_SIZE(sizeof(*new_block));
142
79.3k
  }
143
  // The normal case: Throw away the current block, allocate a new block,
144
  // and use that to satisfy the new allocation.
145
0
  if (ForceNewBlock(/*minimum_length=*/length)) {
146
0
    return nullptr;
147
0
  }
148
0
  char *new_mem = m_current_free_start;
149
0
  m_current_free_start += length;
150
0
  return new_mem;
151
0
}
152
153
0
bool MEM_ROOT::ForceNewBlock(size_t minimum_length) {
154
0
  if (MEM_ROOT_SINGLE_CHUNKS) {
155
0
    assert(m_block_size == m_orig_block_size);
156
0
  }
157
0
  Block *new_block = AllocBlock(/*wanted_length=*/ALIGN_SIZE(m_block_size),
158
0
                                minimum_length);  // Will modify block_size.
159
0
  if (new_block == nullptr) return true;
160
161
0
  new_block->prev = m_current_block;
162
0
  m_current_block = new_block;
163
164
0
  char *new_mem =
165
0
      pointer_cast<char *>(new_block) + ALIGN_SIZE(sizeof(*new_block));
166
0
  m_current_free_start = new_mem;
167
0
  m_current_free_end = new_block->end;
168
0
  return false;
169
0
}
170
171
4.84k
void MEM_ROOT::Clear() {
172
4.84k
  DBUG_TRACE;
173
4.84k
  DBUG_PRINT("enter", ("root: %p", this));
174
175
  // Already cleared, or memset() to zero, so just ignore.
176
4.84k
  if (m_current_block == nullptr) return;
177
178
4.84k
  Block *start = m_current_block;
179
180
4.84k
  m_current_block = nullptr;
181
4.84k
  m_block_size = m_orig_block_size;
182
4.84k
  m_current_free_start = &s_dummy_target;
183
4.84k
  m_current_free_end = &s_dummy_target;
184
4.84k
  m_allocated_size = 0;
185
186
4.84k
  FreeBlocks(start);
187
4.84k
}
188
189
0
void MEM_ROOT::ClearForReuse() {
190
0
  DBUG_TRACE;
191
192
0
  if (MEM_ROOT_SINGLE_CHUNKS) {
193
0
    Clear();
194
0
    return;
195
0
  }
196
197
  // Already cleared, or memset() to zero, so just ignore.
198
0
  if (m_current_block == nullptr) return;
199
200
  // Keep the last block, which is usually the biggest one.
201
0
  m_current_free_start = pointer_cast<char *>(m_current_block) +
202
0
                         ALIGN_SIZE(sizeof(*m_current_block));
203
0
  Block *start = m_current_block->prev;
204
0
  m_current_block->prev = nullptr;
205
0
  m_allocated_size = m_current_free_end - m_current_free_start;
206
0
  TRASH(m_current_free_start, m_allocated_size);
207
208
0
  FreeBlocks(start);
209
0
}
210
211
4.84k
void MEM_ROOT::FreeBlocks(Block *start) {
212
  // The MEM_ROOT might be allocated on itself, so make sure we don't
213
  // touch it after we've started freeing.
214
84.2k
  for (Block *block = start; block != nullptr;) {
215
79.3k
    Block *prev = block->prev;
216
79.3k
    TRASH(block, std::distance(pointer_cast<char *>(block), block->end));
217
79.3k
    my_free(block);
218
79.3k
    block = prev;
219
79.3k
  }
220
4.84k
}
221
222
0
void MEM_ROOT::Claim(bool claim) {
223
0
  DBUG_TRACE;
224
0
  DBUG_PRINT("enter", ("root: %p", this));
225
226
0
  for (Block *block = m_current_block; block != nullptr; block = block->prev) {
227
0
    my_claim(block, claim);
228
0
  }
229
0
}
230
231
/*
232
 * Allocate many pointers at the same time.
233
 *
234
 * DESCRIPTION
235
 *   ptr1, ptr2, etc all point into big allocated memory area.
236
 *
237
 * SYNOPSIS
238
 *   multi_alloc_root()
239
 *     root               Memory root
240
 *     ptr1, length1      Multiple arguments terminated by a NULL pointer
241
 *     ptr2, length2      ...
242
 *     ...
243
 *     NULL
244
 *
245
 * RETURN VALUE
246
 *   A pointer to the beginning of the allocated memory block
247
 *   in case of success or NULL if out of memory.
248
 */
249
250
0
void *multi_alloc_root(MEM_ROOT *root, ...) {
251
0
  va_list args;
252
0
  char **ptr, *start, *res;
253
0
  size_t tot_length, length;
254
0
  DBUG_TRACE;
255
256
0
  va_start(args, root);
257
0
  tot_length = 0;
258
0
  while ((ptr = va_arg(args, char **))) {
259
0
    length = va_arg(args, uint);
260
0
    tot_length += ALIGN_SIZE(length);
261
0
  }
262
0
  va_end(args);
263
264
0
  if (!(start = static_cast<char *>(root->Alloc(tot_length))))
265
0
    return nullptr; /* purecov: inspected */
266
267
0
  va_start(args, root);
268
0
  res = start;
269
0
  while ((ptr = va_arg(args, char **))) {
270
0
    *ptr = res;
271
0
    length = va_arg(args, uint);
272
0
    res += ALIGN_SIZE(length);
273
0
  }
274
0
  va_end(args);
275
0
  return (void *)start;
276
0
}
277
278
0
char *strdup_root(MEM_ROOT *root, const char *str) {
279
0
  return strmake_root(root, str, strlen(str));
280
0
}
281
282
0
char *safe_strdup_root(MEM_ROOT *root, const char *str) {
283
0
  return str ? strdup_root(root, str) : nullptr;
284
0
}
285
286
70.4k
char *strmake_root(MEM_ROOT *root, const char *str, size_t len) {
287
70.4k
  char *pos;
288
70.4k
  if ((pos = static_cast<char *>(root->Alloc(len + 1)))) {
289
70.4k
    if (len > 0) memcpy(pos, str, len);
290
70.4k
    pos[len] = 0;
291
70.4k
  }
292
70.4k
  return pos;
293
70.4k
}
294
295
0
void *memdup_root(MEM_ROOT *root, const void *str, size_t len) {
296
0
  char *pos;
297
0
  if ((pos = static_cast<char *>(root->Alloc(len)))) {
298
0
    memcpy(pos, str, len);
299
0
  }
300
0
  return pos;
301
0
}
302
303
char MEM_ROOT::s_dummy_target;