1 /* Copyright (c) 2000, 2021, Oracle and/or its affiliates.
2 
3    This program is free software; you can redistribute it and/or modify
4    it under the terms of the GNU General Public License, version 2.0,
5    as published by the Free Software Foundation.
6 
7    This program is also distributed with certain software (including
8    but not limited to OpenSSL) that is licensed under separate terms,
9    as designated in a particular file or component or in included license
10    documentation.  The authors of MySQL hereby grant you an additional
11    permission to link the program and your derivative works with the
12    separately licensed software that they have included with MySQL.
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 /* Routines to handle mallocing of results which will be freed the same time */
29 
30 #include <my_global.h>
31 #include <my_sys.h>
32 #include <m_string.h>
33 #include "mysys_err.h"
34 
35 static inline my_bool is_mem_available(MEM_ROOT *mem_root, size_t size);
36 
37 /*
38   For instrumented code: don't preallocate memory in alloc_root().
39   This gives a lot more memory chunks, each with a red-zone around them.
40  */
41 #if !defined(HAVE_VALGRIND) && !defined(HAVE_ASAN)
42 #define PREALLOCATE_MEMORY_CHUNKS
43 #endif
44 
45 
46 /*
47   Initialize memory root
48 
49   SYNOPSIS
50     init_alloc_root()
51       mem_root       - memory root to initialize
52       block_size     - size of chunks (blocks) used for memory allocation
53                        (It is external size of chunk i.e. it should include
54                         memory required for internal structures, thus it
55                         should be no less than ALLOC_ROOT_MIN_BLOCK_SIZE)
56       pre_alloc_size - if non-0, then size of block that should be
57                        pre-allocated during memory root initialization.
58 
59   DESCRIPTION
60     This function prepares memory root for further use, sets initial size of
61     chunk for memory allocation and pre-allocates first block if specified.
62     Altough error can happen during execution of this function if
63     pre_alloc_size is non-0 it won't be reported. Instead it will be
64     reported as error in first alloc_root() on this memory root.
65 */
66 
init_alloc_root(PSI_memory_key key,MEM_ROOT * mem_root,size_t block_size,size_t pre_alloc_size MY_ATTRIBUTE ((unused)))67 void init_alloc_root(PSI_memory_key key,
68                      MEM_ROOT *mem_root, size_t block_size,
69 		     size_t pre_alloc_size MY_ATTRIBUTE((unused)))
70 {
71   DBUG_ENTER("init_alloc_root");
72   DBUG_PRINT("enter",("root: 0x%lx", (long) mem_root));
73 
74   mem_root->free= mem_root->used= mem_root->pre_alloc= 0;
75   mem_root->min_malloc= 32;
76   mem_root->block_size= block_size - ALLOC_ROOT_MIN_BLOCK_SIZE;
77   mem_root->error_handler= 0;
78   mem_root->block_num= 4;			/* We shift this with >>2 */
79   mem_root->first_block_usage= 0;
80   mem_root->m_psi_key= key;
81   mem_root->max_capacity= 0;
82   mem_root->allocated_size= 0;
83   mem_root->error_for_capacity_exceeded= FALSE;
84 
85 #if defined(PREALLOCATE_MEMORY_CHUNKS)
86   if (pre_alloc_size)
87   {
88     if ((mem_root->free= mem_root->pre_alloc=
89 	 (USED_MEM*) my_malloc(key,
90                                pre_alloc_size+ ALIGN_SIZE(sizeof(USED_MEM)),
91 			       MYF(0))))
92     {
93       mem_root->free->size= (uint)(pre_alloc_size+ALIGN_SIZE(sizeof(USED_MEM)));
94       mem_root->free->left= (uint)pre_alloc_size;
95       mem_root->free->next= 0;
96       mem_root->allocated_size+= pre_alloc_size+ ALIGN_SIZE(sizeof(USED_MEM));
97     }
98   }
99 #endif
100   DBUG_VOID_RETURN;
101 }
102 
103 /** This is a no-op unless the build is debug or for Valgrind. */
104 #define TRASH_MEM(X) TRASH(((char*)(X) + ((X)->size-(X)->left)), (X)->left)
105 
106 
107 /*
108   SYNOPSIS
109     reset_root_defaults()
110     mem_root        memory root to change defaults of
111     block_size      new value of block size. Must be greater or equal
112                     than ALLOC_ROOT_MIN_BLOCK_SIZE (this value is about
113                     68 bytes and depends on platform and compilation flags)
114     pre_alloc_size  new size of preallocated block. If not zero,
115                     must be equal to or greater than block size,
116                     otherwise means 'no prealloc'.
117   DESCRIPTION
118     Function aligns and assigns new value to block size; then it tries to
119     reuse one of existing blocks as prealloc block, or malloc new one of
120     requested size. If no blocks can be reused, all unused blocks are freed
121     before allocation.
122 */
123 
reset_root_defaults(MEM_ROOT * mem_root,size_t block_size,size_t pre_alloc_size MY_ATTRIBUTE ((unused)))124 void reset_root_defaults(MEM_ROOT *mem_root, size_t block_size,
125                          size_t pre_alloc_size MY_ATTRIBUTE((unused)))
126 {
127   assert(alloc_root_inited(mem_root));
128 
129   mem_root->block_size= block_size - ALLOC_ROOT_MIN_BLOCK_SIZE;
130 #if defined(PREALLOCATE_MEMORY_CHUNKS)
131   if (pre_alloc_size)
132   {
133     size_t size= pre_alloc_size + ALIGN_SIZE(sizeof(USED_MEM));
134     if (!mem_root->pre_alloc || mem_root->pre_alloc->size != size)
135     {
136       USED_MEM *mem, **prev= &mem_root->free;
137       /*
138         Free unused blocks, so that consequent calls
139         to reset_root_defaults won't eat away memory.
140       */
141       while (*prev)
142       {
143         mem= *prev;
144         if (mem->size == (uint)size)
145         {
146           /* We found a suitable block, no need to do anything else */
147           mem_root->pre_alloc= mem;
148           return;
149         }
150         if (mem->left + ALIGN_SIZE(sizeof(USED_MEM)) == mem->size)
151         {
152           /* remove block from the list and free it */
153           *prev= mem->next;
154           {
155             mem->left= mem->size;
156             mem_root->allocated_size-= mem->size;
157             TRASH_MEM(mem);
158             my_free(mem);
159           }
160         }
161         else
162           prev= &mem->next;
163       }
164       /* Allocate new prealloc block and add it to the end of free list */
165       if (is_mem_available(mem_root, size) &&
166           (mem= (USED_MEM *) my_malloc(mem_root->m_psi_key,
167                                        size, MYF(0))))
168       {
169         mem->size= (uint)size;
170         mem->left= (uint)pre_alloc_size;
171         mem->next= *prev;
172         *prev= mem_root->pre_alloc= mem;
173         mem_root->allocated_size+= size;
174       }
175       else
176       {
177         mem_root->pre_alloc= 0;
178       }
179     }
180   }
181   else
182 #endif
183     mem_root->pre_alloc= 0;
184 }
185 
186 
187 /**
188   Function allocates the requested memory in the mem_root specified.
189   If max_capacity is defined for the mem_root, it only allocates
190   if the requested size can be allocated without exceeding the limit.
191   However, when error_for_capacity_exceeded is set, an error is flagged
192   (see set_error_reporting), but allocation is still performed.
193 
194   @param mem_root           memory root to allocate memory from
195   @length                   size to be allocated
196 
197   @retval
198   void *                    Pointer to the memory thats been allocated
199   @retval
200   NULL                      Memory is not available.
201 */
202 
alloc_root(MEM_ROOT * mem_root,size_t length)203 void *alloc_root(MEM_ROOT *mem_root, size_t length)
204 {
205 #if !defined(PREALLOCATE_MEMORY_CHUNKS)
206   USED_MEM *next;
207   DBUG_ENTER("alloc_root");
208   DBUG_PRINT("enter",("root: 0x%lx", (long) mem_root));
209 
210   assert(alloc_root_inited(mem_root));
211 
212   DBUG_EXECUTE_IF("simulate_out_of_memory",
213                   {
214                     if (mem_root->error_handler)
215                       (*mem_root->error_handler)();
216                     DBUG_SET("-d,simulate_out_of_memory");
217                     DBUG_RETURN((void*) 0); /* purecov: inspected */
218                   });
219 
220   length+=ALIGN_SIZE(sizeof(USED_MEM));
221   if (!is_mem_available(mem_root, length))
222   {
223     if (mem_root->error_for_capacity_exceeded)
224       my_error(EE_CAPACITY_EXCEEDED, MYF(0),
225                (ulonglong) mem_root->max_capacity);
226     else
227       DBUG_RETURN(NULL);
228   }
229   if (!(next = (USED_MEM*) my_malloc(mem_root->m_psi_key,
230                                      length,MYF(MY_WME | ME_FATALERROR))))
231   {
232     if (mem_root->error_handler)
233       (*mem_root->error_handler)();
234     DBUG_RETURN((uchar*) 0);			/* purecov: inspected */
235   }
236   mem_root->allocated_size+= length;
237   next->next= mem_root->used;
238   next->size= (uint)length;
239   next->left= (uint)(length - ALIGN_SIZE(sizeof(USED_MEM)));
240   mem_root->used= next;
241   DBUG_PRINT("exit",("ptr: 0x%lx", (long) (((char*) next)+
242                                            ALIGN_SIZE(sizeof(USED_MEM)))));
243   DBUG_RETURN((uchar*) (((char*) next)+ALIGN_SIZE(sizeof(USED_MEM))));
244 #else
245   size_t get_size, block_size;
246   uchar* point;
247   USED_MEM *next= 0;
248   USED_MEM **prev;
249   DBUG_ENTER("alloc_root");
250   DBUG_PRINT("enter",("root: 0x%lx", (long) mem_root));
251   assert(alloc_root_inited(mem_root));
252 
253   DBUG_EXECUTE_IF("simulate_out_of_memory",
254                   {
255                     /* Avoid reusing an already allocated block */
256                     if (mem_root->error_handler)
257                       (*mem_root->error_handler)();
258                     DBUG_SET("-d,simulate_out_of_memory");
259                     DBUG_RETURN((void*) 0); /* purecov: inspected */
260                   });
261   length= ALIGN_SIZE(length);
262   if ((*(prev= &mem_root->free)) != NULL)
263   {
264     if ((*prev)->left < length &&
265 	mem_root->first_block_usage++ >= ALLOC_MAX_BLOCK_USAGE_BEFORE_DROP &&
266 	(*prev)->left < ALLOC_MAX_BLOCK_TO_DROP)
267     {
268       next= *prev;
269       *prev= next->next;			/* Remove block from list */
270       next->next= mem_root->used;
271       mem_root->used= next;
272       mem_root->first_block_usage= 0;
273     }
274     for (next= *prev ; next && next->left < length ; next= next->next)
275       prev= &next->next;
276   }
277   if (! next)
278   {						/* Time to alloc new block */
279     block_size= mem_root->block_size * (mem_root->block_num >> 2);
280     get_size= length+ALIGN_SIZE(sizeof(USED_MEM));
281     get_size= MY_MAX(get_size, block_size);
282 
283     if (!is_mem_available(mem_root, get_size))
284     {
285       if (mem_root->error_for_capacity_exceeded)
286         my_error(EE_CAPACITY_EXCEEDED, MYF(0),
287                  (ulonglong) mem_root->max_capacity);
288       else
289         DBUG_RETURN(NULL);
290     }
291     if (!(next = (USED_MEM*) my_malloc(mem_root->m_psi_key,
292                                        get_size,MYF(MY_WME | ME_FATALERROR))))
293     {
294       if (mem_root->error_handler)
295 	(*mem_root->error_handler)();
296       DBUG_RETURN((void*) 0);                      /* purecov: inspected */
297     }
298     mem_root->allocated_size+= get_size;
299     mem_root->block_num++;
300     next->next= *prev;
301     next->size= (uint)get_size;
302     next->left= (uint)(get_size-ALIGN_SIZE(sizeof(USED_MEM)));
303     *prev=next;
304   }
305 
306   point= (uchar*) ((char*) next+ (next->size-next->left));
307   /*TODO: next part may be unneded due to mem_root->first_block_usage counter*/
308   if ((next->left-= (uint)length) < mem_root->min_malloc)
309   {						/* Full block */
310     *prev= next->next;				/* Remove block from list */
311     next->next= mem_root->used;
312     mem_root->used= next;
313     mem_root->first_block_usage= 0;
314   }
315   DBUG_PRINT("exit",("ptr: 0x%lx", (ulong) point));
316   DBUG_RETURN((void*) point);
317 #endif
318 }
319 
320 
321 /*
322   Allocate many pointers at the same time.
323 
324   DESCRIPTION
325     ptr1, ptr2, etc all point into big allocated memory area.
326 
327   SYNOPSIS
328     multi_alloc_root()
329       root               Memory root
330       ptr1, length1      Multiple arguments terminated by a NULL pointer
331       ptr2, length2      ...
332       ...
333       NULL
334 
335   RETURN VALUE
336     A pointer to the beginning of the allocated memory block
337     in case of success or NULL if out of memory.
338 */
339 
multi_alloc_root(MEM_ROOT * root,...)340 void *multi_alloc_root(MEM_ROOT *root, ...)
341 {
342   va_list args;
343   char **ptr, *start, *res;
344   size_t tot_length, length;
345   DBUG_ENTER("multi_alloc_root");
346 
347   va_start(args, root);
348   tot_length= 0;
349   while ((ptr= va_arg(args, char **)))
350   {
351     length= va_arg(args, uint);
352     tot_length+= ALIGN_SIZE(length);
353   }
354   va_end(args);
355 
356   if (!(start= (char*) alloc_root(root, tot_length)))
357     DBUG_RETURN(0);                            /* purecov: inspected */
358 
359   va_start(args, root);
360   res= start;
361   while ((ptr= va_arg(args, char **)))
362   {
363     *ptr= res;
364     length= va_arg(args, uint);
365     res+= ALIGN_SIZE(length);
366   }
367   va_end(args);
368   DBUG_RETURN((void*) start);
369 }
370 
371 /* Mark all data in blocks free for reusage */
372 
mark_blocks_free(MEM_ROOT * root)373 static inline void mark_blocks_free(MEM_ROOT* root)
374 {
375   USED_MEM *next;
376   USED_MEM **last;
377 
378   /* iterate through (partially) free blocks, mark them free */
379   last= &root->free;
380   for (next= root->free; next; next= *(last= &next->next))
381   {
382     next->left= next->size - (uint)ALIGN_SIZE(sizeof(USED_MEM));
383     TRASH_MEM(next);
384   }
385 
386   /* Combine the free and the used list */
387   *last= next=root->used;
388 
389   /* now go through the used blocks and mark them free */
390   for (; next; next= next->next)
391   {
392     next->left= next->size - (uint)ALIGN_SIZE(sizeof(USED_MEM));
393     TRASH_MEM(next);
394   }
395 
396   /* Now everything is set; Indicate that nothing is used anymore */
397   root->used= 0;
398   root->first_block_usage= 0;
399 }
400 
claim_root(MEM_ROOT * root)401 void claim_root(MEM_ROOT *root)
402 {
403   USED_MEM *next,*old;
404   DBUG_ENTER("claim_root");
405   DBUG_PRINT("enter",("root: 0x%lx", (long) root));
406 
407   for (next=root->used; next ;)
408   {
409     old=next; next= next->next ;
410     my_claim(old);
411   }
412 
413   for (next=root->free ; next ;)
414   {
415     old=next; next= next->next;
416     my_claim(old);
417   }
418 
419   DBUG_VOID_RETURN;
420 }
421 
422 
423 /*
424   Deallocate everything used by alloc_root or just move
425   used blocks to free list if called with MY_USED_TO_FREE
426 
427   SYNOPSIS
428     free_root()
429       root		Memory root
430       MyFlags		Flags for what should be freed:
431 
432         MY_MARK_BLOCKS_FREED	Don't free blocks, just mark them free
433         MY_KEEP_PREALLOC	If this is not set, then free also the
434         		        preallocated block
435 
436   NOTES
437     One can call this function either with root block initialised with
438     init_alloc_root() or with a zero()-ed block.
439     It's also safe to call this multiple times with the same mem_root.
440 */
441 
free_root(MEM_ROOT * root,myf MyFlags)442 void free_root(MEM_ROOT *root, myf MyFlags)
443 {
444   USED_MEM *next,*old;
445   DBUG_ENTER("free_root");
446   DBUG_PRINT("enter",("root: 0x%lx  flags: %u", (long) root, (uint) MyFlags));
447 
448   if (MyFlags & MY_MARK_BLOCKS_FREE)
449   {
450     mark_blocks_free(root);
451     DBUG_VOID_RETURN;
452   }
453   if (!(MyFlags & MY_KEEP_PREALLOC))
454     root->pre_alloc=0;
455 
456   for (next=root->used; next ;)
457   {
458     old=next; next= next->next ;
459     if (old != root->pre_alloc)
460     {
461       old->left= old->size;
462       TRASH_MEM(old);
463       my_free(old);
464     }
465   }
466   for (next=root->free ; next ;)
467   {
468     old=next; next= next->next;
469     if (old != root->pre_alloc)
470     {
471       old->left= old->size;
472       TRASH_MEM(old);
473       my_free(old);
474     }
475   }
476   root->used=root->free=0;
477   if (root->pre_alloc)
478   {
479     root->free=root->pre_alloc;
480     root->free->left=root->pre_alloc->size-(uint)ALIGN_SIZE(sizeof(USED_MEM));
481     root->allocated_size= root->pre_alloc->size;
482     TRASH_MEM(root->pre_alloc);
483     root->free->next=0;
484   }
485   else
486     root->allocated_size= 0;
487   root->block_num= 4;
488   root->first_block_usage= 0;
489   DBUG_VOID_RETURN;
490 }
491 
492 
strdup_root(MEM_ROOT * root,const char * str)493 char *strdup_root(MEM_ROOT *root, const char *str)
494 {
495   return strmake_root(root, str, strlen(str));
496 }
497 
498 
strmake_root(MEM_ROOT * root,const char * str,size_t len)499 char *strmake_root(MEM_ROOT *root, const char *str, size_t len)
500 {
501   char *pos;
502   if ((pos=alloc_root(root,len+1)))
503   {
504     memcpy(pos,str,len);
505     pos[len]=0;
506   }
507   return pos;
508 }
509 
510 
memdup_root(MEM_ROOT * root,const void * str,size_t len)511 void *memdup_root(MEM_ROOT *root, const void *str, size_t len)
512 {
513   char *pos;
514   if ((pos=alloc_root(root,len)))
515     memcpy(pos,str,len);
516   return pos;
517 }
518 
519 /**
520   Check if the set max_capacity is exceeded if the requested
521   size is allocated
522 
523   @param mem_root           memory root to check for allocation
524   @param size               requested size to be allocated
525 
526   @retval
527   1 Memory is available
528   @retval
529   0 Memory is not available
530 */
is_mem_available(MEM_ROOT * mem_root,size_t size)531 static inline my_bool is_mem_available(MEM_ROOT *mem_root, size_t size)
532 {
533   if (mem_root->max_capacity)
534   {
535     if ((mem_root->allocated_size + size) > mem_root->max_capacity)
536       return 0;
537   }
538   return 1;
539 }
540 
541 /**
542   set max_capacity for this mem_root. Should be called after init_alloc_root.
543   If the max_capacity specified is less than what is already pre_alloced
544   in init_alloc_root, only the future allocations are affected.
545 
546   @param mem_root         memory root to set the max capacity
547   @param max_value        Maximum capacity this mem_root can hold
548 */
set_memroot_max_capacity(MEM_ROOT * mem_root,size_t max_value)549 void set_memroot_max_capacity(MEM_ROOT *mem_root, size_t max_value)
550 {
551   assert(alloc_root_inited(mem_root));
552   mem_root->max_capacity= max_value;
553 }
554 
555 /**
556   Enable/disable error reporting for exceeding max_capacity. If error
557   reporting is enabled, an error is flagged to indicate that the capacity
558   is exceeded. However allocation will still happen for the requested memory.
559 
560   @param mem_root        memory root
561   @param report_eroor    set to true if error should be reported
562                          else set to false
563 */
set_memroot_error_reporting(MEM_ROOT * mem_root,my_bool report_error)564 void set_memroot_error_reporting(MEM_ROOT *mem_root, my_bool report_error)
565 {
566   assert(alloc_root_inited(mem_root));
567   mem_root->error_for_capacity_exceeded= report_error;
568 }
569 
570