Coverage Report

Created: 2026-05-19 06:33

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/nspr/pr/src/malloc/prmem.c
Line
Count
Source
1
/* This Source Code Form is subject to the terms of the Mozilla Public
2
 * License, v. 2.0. If a copy of the MPL was not distributed with this
3
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5
/*
6
** Thread safe versions of malloc, free, realloc, calloc and cfree.
7
*/
8
9
#include "primpl.h"
10
11
#ifdef _PR_ZONE_ALLOCATOR
12
13
/*
14
** The zone allocator code must use native mutexes and cannot
15
** use PRLocks because PR_NewLock calls PR_Calloc, resulting
16
** in cyclic dependency of initialization.
17
*/
18
19
#  include <string.h>
20
21
union memBlkHdrUn;
22
23
typedef struct MemoryZoneStr {
24
  union memBlkHdrUn* head; /* free list */
25
  pthread_mutex_t lock;
26
  size_t blockSize;    /* size of blocks on this free list */
27
  PRUint32 locked;     /* current state of lock */
28
  PRUint32 contention; /* counter: had to wait for lock */
29
  PRUint32 hits;       /* allocated from free list */
30
  PRUint32 misses;     /* had to call malloc */
31
  PRUint32 elements;   /* on free list */
32
} MemoryZone;
33
34
typedef union memBlkHdrUn {
35
  unsigned char filler[48]; /* fix the size of this beast */
36
  struct memBlkHdrStr {
37
    union memBlkHdrUn* next;
38
    MemoryZone* zone;
39
    size_t blockSize;
40
    size_t requestedSize;
41
    PRUint32 magic;
42
  } s;
43
} MemBlockHdr;
44
45
0
#  define MEM_ZONES 7
46
0
#  define THREAD_POOLS 11 /* prime number for modulus */
47
0
#  define ZONE_MAGIC 0x0BADC0DE
48
49
static MemoryZone zones[MEM_ZONES][THREAD_POOLS];
50
51
static PRBool use_zone_allocator = PR_FALSE;
52
53
static void pr_ZoneFree(void* ptr);
54
55
0
void _PR_DestroyZones(void) {
56
0
  int i, j;
57
58
0
  if (!use_zone_allocator) {
59
0
    return;
60
0
  }
61
62
0
  for (j = 0; j < THREAD_POOLS; j++) {
63
0
    for (i = 0; i < MEM_ZONES; i++) {
64
0
      MemoryZone* mz = &zones[i][j];
65
0
      pthread_mutex_destroy(&mz->lock);
66
0
      while (mz->head) {
67
0
        MemBlockHdr* hdr = mz->head;
68
0
        mz->head = hdr->s.next; /* unlink it */
69
0
        free(hdr);
70
0
        mz->elements--;
71
0
      }
72
0
    }
73
0
  }
74
0
  use_zone_allocator = PR_FALSE;
75
0
}
76
77
/*
78
** pr_FindSymbolInProg
79
**
80
** Find the specified data symbol in the program and return
81
** its address.
82
*/
83
84
#  ifdef HAVE_DLL
85
86
#    if defined(USE_DLFCN) && !defined(NO_DLOPEN_NULL)
87
88
#      include <dlfcn.h>
89
90
19
static void* pr_FindSymbolInProg(const char* name) {
91
19
  void* h;
92
19
  void* sym;
93
94
19
  h = dlopen(0, RTLD_LAZY);
95
19
  if (h == NULL) {
96
0
    return NULL;
97
0
  }
98
19
  sym = dlsym(h, name);
99
19
  (void)dlclose(h);
100
19
  return sym;
101
19
}
102
103
#    elif defined(USE_HPSHL)
104
105
#      include <dl.h>
106
107
static void* pr_FindSymbolInProg(const char* name) {
108
  shl_t h = NULL;
109
  void* sym;
110
111
  if (shl_findsym(&h, name, TYPE_DATA, &sym) == -1) {
112
    return NULL;
113
  }
114
  return sym;
115
}
116
117
#    elif defined(USE_MACH_DYLD) || defined(NO_DLOPEN_NULL)
118
119
static void* pr_FindSymbolInProg(const char* name) {
120
  /* FIXME: not implemented */
121
  return NULL;
122
}
123
124
#    else
125
126
#      error "The zone allocator is not supported on this platform"
127
128
#    endif
129
130
#  else /* !defined(HAVE_DLL) */
131
132
static void* pr_FindSymbolInProg(const char* name) {
133
  /* can't be implemented */
134
  return NULL;
135
}
136
137
#  endif /* HAVE_DLL */
138
139
19
void _PR_InitZones(void) {
140
19
  int i, j;
141
19
  char* envp;
142
19
  PRBool* sym;
143
144
19
  if ((sym = (PRBool*)pr_FindSymbolInProg("nspr_use_zone_allocator")) != NULL) {
145
0
    use_zone_allocator = *sym;
146
19
  } else if ((envp = getenv("NSPR_USE_ZONE_ALLOCATOR")) != NULL) {
147
0
    use_zone_allocator = (atoi(envp) == 1);
148
0
  }
149
150
19
  if (!use_zone_allocator) {
151
19
    return;
152
19
  }
153
154
0
  for (j = 0; j < THREAD_POOLS; j++) {
155
0
    for (i = 0; i < MEM_ZONES; i++) {
156
0
      MemoryZone* mz = &zones[i][j];
157
0
      int rv = pthread_mutex_init(&mz->lock, NULL);
158
0
      PR_ASSERT(0 == rv);
159
0
      if (rv != 0) {
160
0
        goto loser;
161
0
      }
162
0
      mz->blockSize = 16 << (2 * i);
163
0
    }
164
0
  }
165
0
  return;
166
167
0
loser:
168
0
  _PR_DestroyZones();
169
0
  return;
170
0
}
171
172
PR_IMPLEMENT(void)
173
0
PR_FPrintZoneStats(PRFileDesc* debug_out) {
174
0
  int i, j;
175
176
0
  for (j = 0; j < THREAD_POOLS; j++) {
177
0
    for (i = 0; i < MEM_ZONES; i++) {
178
0
      MemoryZone* mz = &zones[i][j];
179
0
      MemoryZone zone = *mz;
180
0
      if (zone.elements || zone.misses || zone.hits) {
181
0
        PR_fprintf(debug_out,
182
0
                   "pool: %d, zone: %d, size: %d, free: %d, hit: %d, miss: %d, "
183
0
                   "contend: %d\n",
184
0
                   j, i, zone.blockSize, zone.elements, zone.hits, zone.misses,
185
0
                   zone.contention);
186
0
      }
187
0
    }
188
0
  }
189
0
}
190
191
0
static void* pr_ZoneMalloc(PRUint32 size) {
192
0
  void* rv;
193
0
  unsigned int zone;
194
0
  size_t blockSize;
195
0
  MemBlockHdr *mb, *mt;
196
0
  MemoryZone* mz;
197
198
  /* Always allocate a non-zero amount of bytes */
199
0
  if (size < 1) {
200
0
    size = 1;
201
0
  }
202
0
  for (zone = 0, blockSize = 16; zone < MEM_ZONES; ++zone, blockSize <<= 2) {
203
0
    if (size <= blockSize) {
204
0
      break;
205
0
    }
206
0
  }
207
0
  if (zone < MEM_ZONES) {
208
0
    pthread_t me = pthread_self();
209
0
    unsigned int pool = (PRUptrdiff)me % THREAD_POOLS;
210
0
    PRUint32 wasLocked;
211
0
    mz = &zones[zone][pool];
212
0
    wasLocked = mz->locked;
213
0
    pthread_mutex_lock(&mz->lock);
214
0
    mz->locked = 1;
215
0
    if (wasLocked) {
216
0
      mz->contention++;
217
0
    }
218
0
    if (mz->head) {
219
0
      mb = mz->head;
220
0
      PR_ASSERT(mb->s.magic == ZONE_MAGIC);
221
0
      PR_ASSERT(mb->s.zone == mz);
222
0
      PR_ASSERT(mb->s.blockSize == blockSize);
223
0
      PR_ASSERT(mz->blockSize == blockSize);
224
225
0
      mt = (MemBlockHdr*)(((char*)(mb + 1)) + blockSize);
226
0
      PR_ASSERT(mt->s.magic == ZONE_MAGIC);
227
0
      PR_ASSERT(mt->s.zone == mz);
228
0
      PR_ASSERT(mt->s.blockSize == blockSize);
229
230
0
      mz->hits++;
231
0
      mz->elements--;
232
0
      mz->head = mb->s.next; /* take off free list */
233
0
      mz->locked = 0;
234
0
      pthread_mutex_unlock(&mz->lock);
235
236
0
      mt->s.next = mb->s.next = NULL;
237
0
      mt->s.requestedSize = mb->s.requestedSize = size;
238
239
0
      rv = (void*)(mb + 1);
240
0
      return rv;
241
0
    }
242
243
0
    mz->misses++;
244
0
    mz->locked = 0;
245
0
    pthread_mutex_unlock(&mz->lock);
246
247
0
    mb = (MemBlockHdr*)malloc(blockSize + 2 * (sizeof *mb));
248
0
    if (!mb) {
249
0
      PR_SetError(PR_OUT_OF_MEMORY_ERROR, 0);
250
0
      return NULL;
251
0
    }
252
0
    mb->s.next = NULL;
253
0
    mb->s.zone = mz;
254
0
    mb->s.magic = ZONE_MAGIC;
255
0
    mb->s.blockSize = blockSize;
256
0
    mb->s.requestedSize = size;
257
258
0
    mt = (MemBlockHdr*)(((char*)(mb + 1)) + blockSize);
259
0
    memcpy(mt, mb, sizeof *mb);
260
261
0
    rv = (void*)(mb + 1);
262
0
    return rv;
263
0
  }
264
265
  /* size was too big.  Create a block with no zone */
266
0
  blockSize = (size & 15) ? size + 16 - (size & 15) : size;
267
0
  mb = (MemBlockHdr*)malloc(blockSize + 2 * (sizeof *mb));
268
0
  if (!mb) {
269
0
    PR_SetError(PR_OUT_OF_MEMORY_ERROR, 0);
270
0
    return NULL;
271
0
  }
272
0
  mb->s.next = NULL;
273
0
  mb->s.zone = NULL;
274
0
  mb->s.magic = ZONE_MAGIC;
275
0
  mb->s.blockSize = blockSize;
276
0
  mb->s.requestedSize = size;
277
278
0
  mt = (MemBlockHdr*)(((char*)(mb + 1)) + blockSize);
279
0
  memcpy(mt, mb, sizeof *mb);
280
281
0
  rv = (void*)(mb + 1);
282
0
  return rv;
283
0
}
284
285
0
static void* pr_ZoneCalloc(PRUint32 nelem, PRUint32 elsize) {
286
0
  PRUint32 size = nelem * elsize;
287
0
  void* p = pr_ZoneMalloc(size);
288
0
  if (p) {
289
0
    memset(p, 0, size);
290
0
  }
291
0
  return p;
292
0
}
293
294
0
static void* pr_ZoneRealloc(void* oldptr, PRUint32 bytes) {
295
0
  void* rv;
296
0
  MemBlockHdr* mb;
297
0
  int ours;
298
0
  MemBlockHdr phony;
299
300
0
  if (!oldptr) {
301
0
    return pr_ZoneMalloc(bytes);
302
0
  }
303
0
  mb = (MemBlockHdr*)((char*)oldptr - (sizeof *mb));
304
0
  if (mb->s.magic != ZONE_MAGIC) {
305
    /* Maybe this just came from ordinary malloc */
306
0
#  ifdef DEBUG
307
0
    fprintf(stderr,
308
0
            "Warning: reallocing memory block %p from ordinary malloc\n",
309
0
            oldptr);
310
0
#  endif
311
    /*
312
     * We are going to realloc oldptr.  If realloc succeeds, the
313
     * original value of oldptr will point to freed memory.  So this
314
     * function must not fail after a successfull realloc call.  We
315
     * must perform any operation that may fail before the realloc
316
     * call.
317
     */
318
0
    rv = pr_ZoneMalloc(bytes); /* this may fail */
319
0
    if (!rv) {
320
0
      return rv;
321
0
    }
322
323
    /* We don't know how big it is.  But we can fix that. */
324
0
    oldptr = realloc(oldptr, bytes);
325
    /*
326
     * If realloc returns NULL, this function loses the original
327
     * value of oldptr.  This isn't a leak because the caller of
328
     * this function still has the original value of oldptr.
329
     */
330
0
    if (!oldptr) {
331
0
      if (bytes) {
332
0
        PR_SetError(PR_OUT_OF_MEMORY_ERROR, 0);
333
0
        pr_ZoneFree(rv);
334
0
        return oldptr;
335
0
      }
336
0
    }
337
0
    phony.s.requestedSize = bytes;
338
0
    mb = &phony;
339
0
    ours = 0;
340
0
  } else {
341
0
    size_t blockSize = mb->s.blockSize;
342
0
    MemBlockHdr* mt = (MemBlockHdr*)(((char*)(mb + 1)) + blockSize);
343
344
0
    PR_ASSERT(mt->s.magic == ZONE_MAGIC);
345
0
    PR_ASSERT(mt->s.zone == mb->s.zone);
346
0
    PR_ASSERT(mt->s.blockSize == blockSize);
347
348
0
    if (bytes <= blockSize) {
349
      /* The block is already big enough. */
350
0
      mt->s.requestedSize = mb->s.requestedSize = bytes;
351
0
      return oldptr;
352
0
    }
353
0
    ours = 1;
354
0
    rv = pr_ZoneMalloc(bytes);
355
0
    if (!rv) {
356
0
      return rv;
357
0
    }
358
0
  }
359
360
0
  if (oldptr && mb->s.requestedSize) {
361
0
    memcpy(rv, oldptr, mb->s.requestedSize);
362
0
  }
363
0
  if (ours) {
364
0
    pr_ZoneFree(oldptr);
365
0
  } else if (oldptr) {
366
0
    free(oldptr);
367
0
  }
368
0
  return rv;
369
0
}
370
371
0
static void pr_ZoneFree(void* ptr) {
372
0
  MemBlockHdr *mb, *mt;
373
0
  MemoryZone* mz;
374
0
  size_t blockSize;
375
0
  PRUint32 wasLocked;
376
377
0
  if (!ptr) {
378
0
    return;
379
0
  }
380
381
0
  mb = (MemBlockHdr*)((char*)ptr - (sizeof *mb));
382
383
0
  if (mb->s.magic != ZONE_MAGIC) {
384
    /* maybe this came from ordinary malloc */
385
0
#  ifdef DEBUG
386
0
    fprintf(stderr, "Warning: freeing memory block %p from ordinary malloc\n",
387
0
            ptr);
388
0
#  endif
389
0
    free(ptr);
390
0
    return;
391
0
  }
392
393
0
  blockSize = mb->s.blockSize;
394
0
  mz = mb->s.zone;
395
0
  mt = (MemBlockHdr*)(((char*)(mb + 1)) + blockSize);
396
0
  PR_ASSERT(mt->s.magic == ZONE_MAGIC);
397
0
  PR_ASSERT(mt->s.zone == mz);
398
0
  PR_ASSERT(mt->s.blockSize == blockSize);
399
0
  if (!mz) {
400
0
    PR_ASSERT(blockSize > 65536);
401
    /* This block was not in any zone.  Just free it. */
402
0
    free(mb);
403
0
    return;
404
0
  }
405
0
  PR_ASSERT(mz->blockSize == blockSize);
406
0
  wasLocked = mz->locked;
407
0
  pthread_mutex_lock(&mz->lock);
408
0
  mz->locked = 1;
409
0
  if (wasLocked) {
410
0
    mz->contention++;
411
0
  }
412
0
  mt->s.next = mb->s.next = mz->head; /* put on head of list */
413
0
  mz->head = mb;
414
0
  mz->elements++;
415
0
  mz->locked = 0;
416
0
  pthread_mutex_unlock(&mz->lock);
417
0
}
418
419
31.6M
PR_IMPLEMENT(void*) PR_Malloc(PRUint32 size) {
420
31.6M
  if (!_pr_initialized) {
421
0
    _PR_ImplicitInitialization();
422
0
  }
423
424
31.6M
  return use_zone_allocator ? pr_ZoneMalloc(size) : malloc(size);
425
31.6M
}
426
427
24.3M
PR_IMPLEMENT(void*) PR_Calloc(PRUint32 nelem, PRUint32 elsize) {
428
24.3M
  if (!_pr_initialized) {
429
1
    _PR_ImplicitInitialization();
430
1
  }
431
432
24.3M
  return use_zone_allocator ? pr_ZoneCalloc(nelem, elsize)
433
24.3M
                            : calloc(nelem, elsize);
434
24.3M
}
435
436
33.9k
PR_IMPLEMENT(void*) PR_Realloc(void* ptr, PRUint32 size) {
437
33.9k
  if (!_pr_initialized) {
438
0
    _PR_ImplicitInitialization();
439
0
  }
440
441
33.9k
  return use_zone_allocator ? pr_ZoneRealloc(ptr, size) : realloc(ptr, size);
442
33.9k
}
443
444
55.9M
PR_IMPLEMENT(void) PR_Free(void* ptr) {
445
55.9M
  if (use_zone_allocator) {
446
0
    pr_ZoneFree(ptr);
447
55.9M
  } else {
448
55.9M
    free(ptr);
449
55.9M
  }
450
55.9M
}
451
452
#else /* !defined(_PR_ZONE_ALLOCATOR) */
453
454
/*
455
** The PR_Malloc, PR_Calloc, PR_Realloc, and PR_Free functions simply
456
** call their libc equivalents now.  This may seem redundant, but it
457
** ensures that we are calling into the same runtime library.  On
458
** Win32, it is possible to have multiple runtime libraries (e.g.,
459
** objects compiled with /MD and /MDd) in the same process, and
460
** they maintain separate heaps, which cannot be mixed.
461
*/
462
PR_IMPLEMENT(void*) PR_Malloc(PRUint32 size) { return malloc(size); }
463
464
PR_IMPLEMENT(void*) PR_Calloc(PRUint32 nelem, PRUint32 elsize) {
465
  return calloc(nelem, elsize);
466
}
467
468
PR_IMPLEMENT(void*) PR_Realloc(void* ptr, PRUint32 size) {
469
  return realloc(ptr, size);
470
}
471
472
PR_IMPLEMENT(void) PR_Free(void* ptr) { free(ptr); }
473
474
#endif /* _PR_ZONE_ALLOCATOR */
475
476
/*
477
** Complexity alert!
478
**
479
** If malloc/calloc/free (etc.) were implemented to use pr lock's then
480
** the entry points could block when called if some other thread had the
481
** lock.
482
**
483
** Most of the time this isn't a problem. However, in the case that we
484
** are using the thread safe malloc code after PR_Init but before
485
** PR_AttachThread has been called (on a native thread that nspr has yet
486
** to be told about) we could get royally screwed if the lock was busy
487
** and we tried to context switch the thread away. In this scenario
488
**  PR_CURRENT_THREAD() == NULL
489
**
490
** To avoid this unfortunate case, we use the low level locking
491
** facilities for malloc protection instead of the slightly higher level
492
** locking. This makes malloc somewhat faster so maybe it's a good thing
493
** anyway.
494
*/
495
#ifdef _PR_OVERRIDE_MALLOC
496
497
/* Imports */
498
extern void* _PR_UnlockedMalloc(size_t size);
499
extern void* _PR_UnlockedMemalign(size_t alignment, size_t size);
500
extern void _PR_UnlockedFree(void* ptr);
501
extern void* _PR_UnlockedRealloc(void* ptr, size_t size);
502
extern void* _PR_UnlockedCalloc(size_t n, size_t elsize);
503
504
static PRBool _PR_malloc_initialised = PR_FALSE;
505
506
#  ifdef _PR_PTHREADS
507
static pthread_mutex_t _PR_MD_malloc_crustylock;
508
509
#    define _PR_Lock_Malloc()                                 \
510
      {                                                       \
511
        if (PR_TRUE == _PR_malloc_initialised) {              \
512
          PRStatus rv;                                        \
513
          rv = pthread_mutex_lock(&_PR_MD_malloc_crustylock); \
514
          PR_ASSERT(0 == rv);                                 \
515
        }
516
517
#    define _PR_Unlock_Malloc()                               \
518
      if (PR_TRUE == _PR_malloc_initialised) {                \
519
        PRStatus rv;                                          \
520
        rv = pthread_mutex_unlock(&_PR_MD_malloc_crustylock); \
521
        PR_ASSERT(0 == rv);                                   \
522
      }                                                       \
523
      }
524
#  else /* _PR_PTHREADS */
525
static _MDLock _PR_MD_malloc_crustylock;
526
527
#    define _PR_Lock_Malloc()                                 \
528
      {                                                       \
529
        PRIntn _is;                                           \
530
        if (PR_TRUE == _PR_malloc_initialised) {              \
531
          if (_PR_MD_CURRENT_THREAD() &&                      \
532
              !_PR_IS_NATIVE_THREAD(_PR_MD_CURRENT_THREAD())) \
533
            _PR_INTSOFF(_is);                                 \
534
          _PR_MD_LOCK(&_PR_MD_malloc_crustylock);             \
535
        }
536
537
#    define _PR_Unlock_Malloc()                             \
538
      if (PR_TRUE == _PR_malloc_initialised) {              \
539
        _PR_MD_UNLOCK(&_PR_MD_malloc_crustylock);           \
540
        if (_PR_MD_CURRENT_THREAD() &&                      \
541
            !_PR_IS_NATIVE_THREAD(_PR_MD_CURRENT_THREAD())) \
542
          _PR_INTSON(_is);                                  \
543
      }                                                     \
544
      }
545
#  endif /* _PR_PTHREADS */
546
547
PR_IMPLEMENT(PRStatus) _PR_MallocInit(void) {
548
  PRStatus rv = PR_SUCCESS;
549
550
  if (PR_TRUE == _PR_malloc_initialised) {
551
    return PR_SUCCESS;
552
  }
553
554
#  ifdef _PR_PTHREADS
555
  {
556
    int status;
557
    pthread_mutexattr_t mattr;
558
559
    status = _PT_PTHREAD_MUTEXATTR_INIT(&mattr);
560
    PR_ASSERT(0 == status);
561
    status = _PT_PTHREAD_MUTEX_INIT(_PR_MD_malloc_crustylock, mattr);
562
    PR_ASSERT(0 == status);
563
    status = _PT_PTHREAD_MUTEXATTR_DESTROY(&mattr);
564
    PR_ASSERT(0 == status);
565
  }
566
#  else  /* _PR_PTHREADS */
567
  _MD_NEW_LOCK(&_PR_MD_malloc_crustylock);
568
#  endif /* _PR_PTHREADS */
569
570
  if (PR_SUCCESS == rv) {
571
    _PR_malloc_initialised = PR_TRUE;
572
  }
573
574
  return rv;
575
}
576
577
void* malloc(size_t size) {
578
  void* p;
579
  _PR_Lock_Malloc();
580
  p = _PR_UnlockedMalloc(size);
581
  _PR_Unlock_Malloc();
582
  return p;
583
}
584
585
void free(void* ptr) {
586
  _PR_Lock_Malloc();
587
  _PR_UnlockedFree(ptr);
588
  _PR_Unlock_Malloc();
589
}
590
591
void* realloc(void* ptr, size_t size) {
592
  void* p;
593
  _PR_Lock_Malloc();
594
  p = _PR_UnlockedRealloc(ptr, size);
595
  _PR_Unlock_Malloc();
596
  return p;
597
}
598
599
void* calloc(size_t n, size_t elsize) {
600
  void* p;
601
  _PR_Lock_Malloc();
602
  p = _PR_UnlockedCalloc(n, elsize);
603
  _PR_Unlock_Malloc();
604
  return p;
605
}
606
607
void cfree(void* p) {
608
  _PR_Lock_Malloc();
609
  _PR_UnlockedFree(p);
610
  _PR_Unlock_Malloc();
611
}
612
613
void _PR_InitMem(void) {
614
  PRStatus rv;
615
  rv = _PR_MallocInit();
616
  PR_ASSERT(PR_SUCCESS == rv);
617
}
618
619
#endif /* _PR_OVERRIDE_MALLOC */