/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; |