| 1 | /***************************************************************************** |
| 2 | |
| 3 | Copyright (c) 1994, 2014, Oracle and/or its affiliates. All Rights Reserved. |
| 4 | Copyright (c) 2017, MariaDB Corporation. |
| 5 | |
| 6 | This program is free software; you can redistribute it and/or modify it under |
| 7 | the terms of the GNU General Public License as published by the Free Software |
| 8 | Foundation; version 2 of the License. |
| 9 | |
| 10 | This program is distributed in the hope that it will be useful, but WITHOUT |
| 11 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
| 12 | FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. |
| 13 | |
| 14 | You should have received a copy of the GNU General Public License along with |
| 15 | this program; if not, write to the Free Software Foundation, Inc., |
| 16 | 51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA |
| 17 | |
| 18 | *****************************************************************************/ |
| 19 | |
| 20 | /********************************************************************//** |
| 21 | @file include/mem0mem.ic |
| 22 | The memory management |
| 23 | |
| 24 | Created 6/8/1994 Heikki Tuuri |
| 25 | *************************************************************************/ |
| 26 | |
| 27 | #include "ut0new.h" |
| 28 | |
| 29 | #ifdef UNIV_DEBUG |
| 30 | # define mem_heap_create_block(heap, n, type, file_name, line) \ |
| 31 | mem_heap_create_block_func(heap, n, file_name, line, type) |
| 32 | # define mem_heap_create_at(N, file_name, line) \ |
| 33 | mem_heap_create_func(N, file_name, line, MEM_HEAP_DYNAMIC) |
| 34 | #else /* UNIV_DEBUG */ |
| 35 | # define mem_heap_create_block(heap, n, type, file_name, line) \ |
| 36 | mem_heap_create_block_func(heap, n, type) |
| 37 | # define mem_heap_create_at(N, file_name, line) \ |
| 38 | mem_heap_create_func(N, MEM_HEAP_DYNAMIC) |
| 39 | #endif /* UNIV_DEBUG */ |
| 40 | /***************************************************************//** |
| 41 | Creates a memory heap block where data can be allocated. |
| 42 | @return own: memory heap block, NULL if did not succeed (only possible |
| 43 | for MEM_HEAP_BTR_SEARCH type heaps) */ |
| 44 | mem_block_t* |
| 45 | mem_heap_create_block_func( |
| 46 | /*=======================*/ |
| 47 | mem_heap_t* heap, /*!< in: memory heap or NULL if first block |
| 48 | should be created */ |
| 49 | ulint n, /*!< in: number of bytes needed for user data */ |
| 50 | #ifdef UNIV_DEBUG |
| 51 | const char* file_name,/*!< in: file name where created */ |
| 52 | unsigned line, /*!< in: line where created */ |
| 53 | #endif /* UNIV_DEBUG */ |
| 54 | ulint type); /*!< in: type of heap: MEM_HEAP_DYNAMIC or |
| 55 | MEM_HEAP_BUFFER */ |
| 56 | |
| 57 | /******************************************************************//** |
| 58 | Frees a block from a memory heap. */ |
| 59 | void |
| 60 | mem_heap_block_free( |
| 61 | /*================*/ |
| 62 | mem_heap_t* heap, /*!< in: heap */ |
| 63 | mem_block_t* block); /*!< in: block to free */ |
| 64 | |
| 65 | /******************************************************************//** |
| 66 | Frees the free_block field from a memory heap. */ |
| 67 | void |
| 68 | mem_heap_free_block_free( |
| 69 | /*=====================*/ |
| 70 | mem_heap_t* heap); /*!< in: heap */ |
| 71 | |
| 72 | /***************************************************************//** |
| 73 | Adds a new block to a memory heap. |
| 74 | @param[in] heap memory heap |
| 75 | @param[in] n number of bytes needed |
| 76 | @return created block, NULL if did not succeed (only possible for |
| 77 | MEM_HEAP_BTR_SEARCH type heaps) */ |
| 78 | mem_block_t* |
| 79 | mem_heap_add_block( |
| 80 | mem_heap_t* heap, |
| 81 | ulint n); |
| 82 | |
| 83 | UNIV_INLINE |
| 84 | void |
| 85 | mem_block_set_len(mem_block_t* block, ulint len) |
| 86 | { |
| 87 | ut_ad(len > 0); |
| 88 | |
| 89 | block->len = len; |
| 90 | } |
| 91 | |
| 92 | UNIV_INLINE |
| 93 | ulint |
| 94 | mem_block_get_len(mem_block_t* block) |
| 95 | { |
| 96 | return(block->len); |
| 97 | } |
| 98 | |
| 99 | UNIV_INLINE |
| 100 | void |
| 101 | mem_block_set_type(mem_block_t* block, ulint type) |
| 102 | { |
| 103 | ut_ad((type == MEM_HEAP_DYNAMIC) || (type == MEM_HEAP_BUFFER) |
| 104 | || (type == MEM_HEAP_BUFFER + MEM_HEAP_BTR_SEARCH)); |
| 105 | |
| 106 | block->type = type; |
| 107 | } |
| 108 | |
| 109 | UNIV_INLINE |
| 110 | ulint |
| 111 | mem_block_get_type(mem_block_t* block) |
| 112 | { |
| 113 | return(block->type); |
| 114 | } |
| 115 | |
| 116 | UNIV_INLINE |
| 117 | void |
| 118 | mem_block_set_free(mem_block_t* block, ulint free) |
| 119 | { |
| 120 | ut_ad(free > 0); |
| 121 | ut_ad(free <= mem_block_get_len(block)); |
| 122 | |
| 123 | block->free = free; |
| 124 | } |
| 125 | |
| 126 | UNIV_INLINE |
| 127 | ulint |
| 128 | mem_block_get_free(mem_block_t* block) |
| 129 | { |
| 130 | return(block->free); |
| 131 | } |
| 132 | |
| 133 | UNIV_INLINE |
| 134 | void |
| 135 | mem_block_set_start(mem_block_t* block, ulint start) |
| 136 | { |
| 137 | ut_ad(start > 0); |
| 138 | |
| 139 | block->start = start; |
| 140 | } |
| 141 | |
| 142 | UNIV_INLINE |
| 143 | ulint |
| 144 | mem_block_get_start(mem_block_t* block) |
| 145 | { |
| 146 | return(block->start); |
| 147 | } |
| 148 | |
| 149 | /** Checks that an object is a memory heap block |
| 150 | @param[in] block Memory block to check. */ |
| 151 | UNIV_INLINE |
| 152 | void |
| 153 | mem_block_validate( |
| 154 | const mem_block_t* block) |
| 155 | { |
| 156 | ut_a(block->magic_n == MEM_BLOCK_MAGIC_N); |
| 157 | } |
| 158 | |
| 159 | /** Allocates and zero-fills n bytes of memory from a memory heap. |
| 160 | @param[in] heap memory heap |
| 161 | @param[in] n number of bytes; if the heap is allowed to grow into |
| 162 | the buffer pool, this must be <= MEM_MAX_ALLOC_IN_BUF |
| 163 | @return allocated, zero-filled storage */ |
| 164 | UNIV_INLINE |
| 165 | void* |
| 166 | mem_heap_zalloc( |
| 167 | mem_heap_t* heap, |
| 168 | ulint n) |
| 169 | { |
| 170 | ut_ad(heap); |
| 171 | ut_ad(!(heap->type & MEM_HEAP_BTR_SEARCH)); |
| 172 | return(memset(mem_heap_alloc(heap, n), 0, n)); |
| 173 | } |
| 174 | |
| 175 | /** Allocates n bytes of memory from a memory heap. |
| 176 | @param[in] heap memory heap |
| 177 | @param[in] n number of bytes; if the heap is allowed to grow into |
| 178 | the buffer pool, this must be <= MEM_MAX_ALLOC_IN_BUF |
| 179 | @return allocated storage, NULL if did not succeed (only possible for |
| 180 | MEM_HEAP_BTR_SEARCH type heaps) */ |
| 181 | UNIV_INLINE |
| 182 | void* |
| 183 | mem_heap_alloc( |
| 184 | mem_heap_t* heap, |
| 185 | ulint n) |
| 186 | { |
| 187 | mem_block_t* block; |
| 188 | void* buf; |
| 189 | ulint free; |
| 190 | |
| 191 | ut_d(mem_block_validate(heap)); |
| 192 | |
| 193 | block = UT_LIST_GET_LAST(heap->base); |
| 194 | |
| 195 | ut_ad(!(block->type & MEM_HEAP_BUFFER) || (n <= MEM_MAX_ALLOC_IN_BUF)); |
| 196 | |
| 197 | /* Check if there is enough space in block. If not, create a new |
| 198 | block to the heap */ |
| 199 | |
| 200 | if (mem_block_get_len(block) |
| 201 | < mem_block_get_free(block) + MEM_SPACE_NEEDED(n)) { |
| 202 | |
| 203 | block = mem_heap_add_block(heap, n); |
| 204 | |
| 205 | if (block == NULL) { |
| 206 | |
| 207 | return(NULL); |
| 208 | } |
| 209 | } |
| 210 | |
| 211 | free = mem_block_get_free(block); |
| 212 | |
| 213 | buf = (byte*) block + free; |
| 214 | |
| 215 | mem_block_set_free(block, free + MEM_SPACE_NEEDED(n)); |
| 216 | |
| 217 | UNIV_MEM_ALLOC(buf, n); |
| 218 | return(buf); |
| 219 | } |
| 220 | |
| 221 | /** Returns a pointer to the heap top. |
| 222 | @param[in] heap memory heap |
| 223 | @return pointer to the heap top */ |
| 224 | UNIV_INLINE |
| 225 | byte* |
| 226 | mem_heap_get_heap_top( |
| 227 | mem_heap_t* heap) |
| 228 | { |
| 229 | mem_block_t* block; |
| 230 | byte* buf; |
| 231 | |
| 232 | ut_d(mem_block_validate(heap)); |
| 233 | |
| 234 | block = UT_LIST_GET_LAST(heap->base); |
| 235 | |
| 236 | buf = (byte*) block + mem_block_get_free(block); |
| 237 | |
| 238 | return(buf); |
| 239 | } |
| 240 | |
| 241 | /** Frees the space in a memory heap exceeding the pointer given. |
| 242 | The pointer must have been acquired from mem_heap_get_heap_top. |
| 243 | The first memory block of the heap is not freed. |
| 244 | @param[in] heap heap from which to free |
| 245 | @param[in] old_top pointer to old top of heap */ |
| 246 | UNIV_INLINE |
| 247 | void |
| 248 | mem_heap_free_heap_top( |
| 249 | mem_heap_t* heap, |
| 250 | byte* old_top) |
| 251 | { |
| 252 | mem_block_t* block; |
| 253 | mem_block_t* prev_block; |
| 254 | |
| 255 | ut_d(mem_heap_validate(heap)); |
| 256 | |
| 257 | block = UT_LIST_GET_LAST(heap->base); |
| 258 | |
| 259 | while (block != NULL) { |
| 260 | if (((byte*) block + mem_block_get_free(block) >= old_top) |
| 261 | && ((byte*) block <= old_top)) { |
| 262 | /* Found the right block */ |
| 263 | |
| 264 | break; |
| 265 | } |
| 266 | |
| 267 | /* Store prev_block value before freeing the current block |
| 268 | (the current block will be erased in freeing) */ |
| 269 | |
| 270 | prev_block = UT_LIST_GET_PREV(list, block); |
| 271 | |
| 272 | mem_heap_block_free(heap, block); |
| 273 | |
| 274 | block = prev_block; |
| 275 | } |
| 276 | |
| 277 | ut_ad(block); |
| 278 | |
| 279 | /* Set the free field of block */ |
| 280 | mem_block_set_free(block, |
| 281 | ulint(old_top - reinterpret_cast<byte*>(block))); |
| 282 | |
| 283 | ut_ad(mem_block_get_start(block) <= mem_block_get_free(block)); |
| 284 | UNIV_MEM_FREE(old_top, (byte*) block + block->len - old_top); |
| 285 | |
| 286 | /* If free == start, we may free the block if it is not the first |
| 287 | one */ |
| 288 | |
| 289 | if ((heap != block) && (mem_block_get_free(block) |
| 290 | == mem_block_get_start(block))) { |
| 291 | mem_heap_block_free(heap, block); |
| 292 | } |
| 293 | } |
| 294 | |
| 295 | /** Empties a memory heap. |
| 296 | The first memory block of the heap is not freed. |
| 297 | @param[in] heap heap to empty */ |
| 298 | UNIV_INLINE |
| 299 | void |
| 300 | mem_heap_empty( |
| 301 | mem_heap_t* heap) |
| 302 | { |
| 303 | mem_heap_free_heap_top(heap, (byte*) heap + mem_block_get_start(heap)); |
| 304 | |
| 305 | if (heap->free_block) { |
| 306 | mem_heap_free_block_free(heap); |
| 307 | } |
| 308 | } |
| 309 | |
| 310 | /** Returns a pointer to the topmost element in a memory heap. |
| 311 | The size of the element must be given. |
| 312 | @param[in] heap memory heap |
| 313 | @param[in] n size of the topmost element |
| 314 | @return pointer to the topmost element */ |
| 315 | UNIV_INLINE |
| 316 | void* |
| 317 | mem_heap_get_top( |
| 318 | mem_heap_t* heap, |
| 319 | ulint n) |
| 320 | { |
| 321 | mem_block_t* block; |
| 322 | byte* buf; |
| 323 | |
| 324 | ut_d(mem_block_validate(heap)); |
| 325 | |
| 326 | block = UT_LIST_GET_LAST(heap->base); |
| 327 | |
| 328 | buf = (byte*) block + mem_block_get_free(block) - MEM_SPACE_NEEDED(n); |
| 329 | |
| 330 | return((void*) buf); |
| 331 | } |
| 332 | |
| 333 | /** Checks if a given chunk of memory is the topmost element stored in the |
| 334 | heap. If this is the case, then calling mem_heap_free_top() would free |
| 335 | that element from the heap. |
| 336 | @param[in] heap memory heap |
| 337 | @param[in] buf presumed topmost element |
| 338 | @param[in] buf_sz size of buf in bytes |
| 339 | @return true if topmost */ |
| 340 | UNIV_INLINE |
| 341 | bool |
| 342 | mem_heap_is_top( |
| 343 | mem_heap_t* heap, |
| 344 | const void* buf, |
| 345 | ulint buf_sz) |
| 346 | { |
| 347 | const byte* first_free_byte; |
| 348 | const byte* presumed_start_of_buf; |
| 349 | |
| 350 | ut_d(mem_block_validate(heap)); |
| 351 | |
| 352 | first_free_byte = mem_heap_get_heap_top(heap); |
| 353 | |
| 354 | presumed_start_of_buf = first_free_byte - MEM_SPACE_NEEDED(buf_sz); |
| 355 | |
| 356 | return(presumed_start_of_buf == buf); |
| 357 | } |
| 358 | |
| 359 | /*****************************************************************//** |
| 360 | Allocate a new chunk of memory from a memory heap, possibly discarding |
| 361 | the topmost element. If the memory chunk specified with (top, top_sz) |
| 362 | is the topmost element, then it will be discarded, otherwise it will |
| 363 | be left untouched and this function will be equivallent to |
| 364 | mem_heap_alloc(). |
| 365 | @return allocated storage, NULL if did not succeed (only possible for |
| 366 | MEM_HEAP_BTR_SEARCH type heaps) */ |
| 367 | UNIV_INLINE |
| 368 | void* |
| 369 | mem_heap_replace( |
| 370 | /*=============*/ |
| 371 | mem_heap_t* heap, /*!< in/out: memory heap */ |
| 372 | const void* top, /*!< in: chunk to discard if possible */ |
| 373 | ulint top_sz, /*!< in: size of top in bytes */ |
| 374 | ulint new_sz) /*!< in: desired size of the new chunk */ |
| 375 | { |
| 376 | if (mem_heap_is_top(heap, top, top_sz)) { |
| 377 | mem_heap_free_top(heap, top_sz); |
| 378 | } |
| 379 | |
| 380 | return(mem_heap_alloc(heap, new_sz)); |
| 381 | } |
| 382 | |
| 383 | /*****************************************************************//** |
| 384 | Allocate a new chunk of memory from a memory heap, possibly discarding |
| 385 | the topmost element and then copy the specified data to it. If the memory |
| 386 | chunk specified with (top, top_sz) is the topmost element, then it will be |
| 387 | discarded, otherwise it will be left untouched and this function will be |
| 388 | equivallent to mem_heap_dup(). |
| 389 | @return allocated storage, NULL if did not succeed (only possible for |
| 390 | MEM_HEAP_BTR_SEARCH type heaps) */ |
| 391 | UNIV_INLINE |
| 392 | void* |
| 393 | mem_heap_dup_replace( |
| 394 | /*=================*/ |
| 395 | mem_heap_t* heap, /*!< in/out: memory heap */ |
| 396 | const void* top, /*!< in: chunk to discard if possible */ |
| 397 | ulint top_sz, /*!< in: size of top in bytes */ |
| 398 | const void* data, /*!< in: new data to duplicate */ |
| 399 | ulint data_sz)/*!< in: size of data in bytes */ |
| 400 | { |
| 401 | void* p = mem_heap_replace(heap, top, top_sz, data_sz); |
| 402 | |
| 403 | memcpy(p, data, data_sz); |
| 404 | |
| 405 | return(p); |
| 406 | } |
| 407 | |
| 408 | /*****************************************************************//** |
| 409 | Allocate a new chunk of memory from a memory heap, possibly discarding |
| 410 | the topmost element and then copy the specified string to it. If the memory |
| 411 | chunk specified with (top, top_sz) is the topmost element, then it will be |
| 412 | discarded, otherwise it will be left untouched and this function will be |
| 413 | equivallent to mem_heap_strdup(). |
| 414 | @return allocated string, NULL if did not succeed (only possible for |
| 415 | MEM_HEAP_BTR_SEARCH type heaps) */ |
| 416 | UNIV_INLINE |
| 417 | char* |
| 418 | mem_heap_strdup_replace( |
| 419 | /*====================*/ |
| 420 | mem_heap_t* heap, /*!< in/out: memory heap */ |
| 421 | const void* top, /*!< in: chunk to discard if possible */ |
| 422 | ulint top_sz, /*!< in: size of top in bytes */ |
| 423 | const char* str) /*!< in: new data to duplicate */ |
| 424 | { |
| 425 | return(reinterpret_cast<char*>(mem_heap_dup_replace( |
| 426 | heap, top, top_sz, str, strlen(str) + 1))); |
| 427 | } |
| 428 | |
| 429 | /*****************************************************************//** |
| 430 | Frees the topmost element in a memory heap. The size of the element must be |
| 431 | given. */ |
| 432 | UNIV_INLINE |
| 433 | void |
| 434 | mem_heap_free_top( |
| 435 | /*==============*/ |
| 436 | mem_heap_t* heap, /*!< in: memory heap */ |
| 437 | ulint n) /*!< in: size of the topmost element */ |
| 438 | { |
| 439 | mem_block_t* block; |
| 440 | |
| 441 | ut_d(mem_block_validate(heap)); |
| 442 | |
| 443 | block = UT_LIST_GET_LAST(heap->base); |
| 444 | |
| 445 | /* Subtract the free field of block */ |
| 446 | mem_block_set_free(block, mem_block_get_free(block) |
| 447 | - MEM_SPACE_NEEDED(n)); |
| 448 | |
| 449 | /* If free == start, we may free the block if it is not the first |
| 450 | one */ |
| 451 | |
| 452 | if ((heap != block) && (mem_block_get_free(block) |
| 453 | == mem_block_get_start(block))) { |
| 454 | mem_heap_block_free(heap, block); |
| 455 | } else { |
| 456 | UNIV_MEM_FREE((byte*) block + mem_block_get_free(block), n); |
| 457 | } |
| 458 | } |
| 459 | |
| 460 | /** Creates a memory heap. |
| 461 | NOTE: Use the corresponding macros instead of this function. |
| 462 | A single user buffer of 'size' will fit in the block. |
| 463 | 0 creates a default size block. |
| 464 | @param[in] size Desired start block size. |
| 465 | @param[in] file_name File name where created |
| 466 | @param[in] line Line where created |
| 467 | @param[in] type Heap type |
| 468 | @return own: memory heap, NULL if did not succeed (only possible for |
| 469 | MEM_HEAP_BTR_SEARCH type heaps) */ |
| 470 | UNIV_INLINE |
| 471 | mem_heap_t* |
| 472 | mem_heap_create_func( |
| 473 | ulint size, |
| 474 | #ifdef UNIV_DEBUG |
| 475 | const char* file_name, |
| 476 | unsigned line, |
| 477 | #endif /* UNIV_DEBUG */ |
| 478 | ulint type) |
| 479 | { |
| 480 | mem_block_t* block; |
| 481 | |
| 482 | if (!size) { |
| 483 | size = MEM_BLOCK_START_SIZE; |
| 484 | } |
| 485 | |
| 486 | block = mem_heap_create_block(NULL, size, type, file_name, line); |
| 487 | |
| 488 | if (block == NULL) { |
| 489 | |
| 490 | return(NULL); |
| 491 | } |
| 492 | |
| 493 | /* The first block should not be in buffer pool, |
| 494 | because it might be relocated to resize buffer pool. */ |
| 495 | ut_ad(block->buf_block == NULL); |
| 496 | |
| 497 | UT_LIST_INIT(block->base, &mem_block_t::list); |
| 498 | |
| 499 | /* Add the created block itself as the first block in the list */ |
| 500 | UT_LIST_ADD_FIRST(block->base, block); |
| 501 | |
| 502 | return(block); |
| 503 | } |
| 504 | |
| 505 | /** Frees the space occupied by a memory heap. |
| 506 | NOTE: Use the corresponding macro instead of this function. |
| 507 | @param[in] heap Heap to be freed */ |
| 508 | UNIV_INLINE |
| 509 | void |
| 510 | mem_heap_free( |
| 511 | mem_heap_t* heap) |
| 512 | { |
| 513 | mem_block_t* block; |
| 514 | mem_block_t* prev_block; |
| 515 | |
| 516 | ut_d(mem_block_validate(heap)); |
| 517 | |
| 518 | block = UT_LIST_GET_LAST(heap->base); |
| 519 | |
| 520 | if (heap->free_block) { |
| 521 | mem_heap_free_block_free(heap); |
| 522 | } |
| 523 | |
| 524 | while (block != NULL) { |
| 525 | /* Store the contents of info before freeing current block |
| 526 | (it is erased in freeing) */ |
| 527 | |
| 528 | prev_block = UT_LIST_GET_PREV(list, block); |
| 529 | |
| 530 | mem_heap_block_free(heap, block); |
| 531 | |
| 532 | block = prev_block; |
| 533 | } |
| 534 | } |
| 535 | |
| 536 | /*****************************************************************//** |
| 537 | Returns the space in bytes occupied by a memory heap. */ |
| 538 | UNIV_INLINE |
| 539 | ulint |
| 540 | mem_heap_get_size( |
| 541 | /*==============*/ |
| 542 | mem_heap_t* heap) /*!< in: heap */ |
| 543 | { |
| 544 | ulint size = 0; |
| 545 | |
| 546 | ut_d(mem_block_validate(heap)); |
| 547 | |
| 548 | size = heap->total_size; |
| 549 | |
| 550 | if (heap->free_block) { |
| 551 | size += srv_page_size; |
| 552 | } |
| 553 | |
| 554 | return(size); |
| 555 | } |
| 556 | |
| 557 | /**********************************************************************//** |
| 558 | Duplicates a NUL-terminated string. |
| 559 | @return own: a copy of the string, must be deallocated with ut_free */ |
| 560 | UNIV_INLINE |
| 561 | char* |
| 562 | mem_strdup( |
| 563 | /*=======*/ |
| 564 | const char* str) /*!< in: string to be copied */ |
| 565 | { |
| 566 | ulint len = strlen(str) + 1; |
| 567 | return(static_cast<char*>(memcpy(ut_malloc_nokey(len), str, len))); |
| 568 | } |
| 569 | |
| 570 | /**********************************************************************//** |
| 571 | Makes a NUL-terminated copy of a nonterminated string. |
| 572 | @return own: a copy of the string, must be deallocated with ut_free */ |
| 573 | UNIV_INLINE |
| 574 | char* |
| 575 | mem_strdupl( |
| 576 | /*========*/ |
| 577 | const char* str, /*!< in: string to be copied */ |
| 578 | ulint len) /*!< in: length of str, in bytes */ |
| 579 | { |
| 580 | char* s = static_cast<char*>(ut_malloc_nokey(len + 1)); |
| 581 | s[len] = 0; |
| 582 | return(static_cast<char*>(memcpy(s, str, len))); |
| 583 | } |
| 584 | |