Coverage Report

Created: 2026-06-08 06:46

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/mupdf/source/fitz/memory.c
Line
Count
Source
1
// Copyright (C) 2004-2026 Artifex Software, Inc.
2
//
3
// This file is part of MuPDF.
4
//
5
// MuPDF is free software: you can redistribute it and/or modify it under the
6
// terms of the GNU Affero General Public License as published by the Free
7
// Software Foundation, either version 3 of the License, or (at your option)
8
// any later version.
9
//
10
// MuPDF is distributed in the hope that it will be useful, but WITHOUT ANY
11
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
12
// FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
13
// details.
14
//
15
// You should have received a copy of the GNU Affero General Public License
16
// along with MuPDF. If not, see <https://www.gnu.org/licenses/agpl-3.0.en.html>
17
//
18
// Alternative licensing terms are available from the licensor.
19
// For commercial licensing, see <https://www.artifex.com/> or contact
20
// Artifex Software, Inc., 39 Mesa Street, Suite 108A, San Francisco,
21
// CA 94129, USA, for further information.
22
23
#include "mupdf/fitz.h"
24
25
#include <limits.h>
26
#include <string.h>
27
#include <stdlib.h>
28
#include <stdio.h>
29
#include <errno.h>
30
31
/* Enable FITZ_DEBUG_LOCKING_TIMES below if you want to check the times
32
 * for which locks are held too. */
33
#ifdef FITZ_DEBUG_LOCKING
34
#undef FITZ_DEBUG_LOCKING_TIMES
35
#endif
36
37
/*
38
 * The malloc family of functions will always try scavenging when they run out of memory.
39
 * They will only fail when scavenging cannot free up memory from caches in the fz_context.
40
 * All the functions will throw an exception when no memory can be allocated,
41
 * except the _no_throw family which instead silently returns NULL.
42
 */
43
44
static void *
45
do_scavenging_malloc(fz_context *ctx, size_t size)
46
4.59M
{
47
4.59M
  void *p;
48
4.59M
  int phase = 0;
49
50
4.59M
  fz_lock(ctx, FZ_LOCK_ALLOC);
51
4.59M
  do {
52
4.59M
    p = ctx->alloc.malloc(ctx->alloc.user, size);
53
4.59M
    if (p != NULL)
54
4.59M
    {
55
4.59M
      fz_unlock(ctx, FZ_LOCK_ALLOC);
56
4.59M
      return p;
57
4.59M
    }
58
4.59M
  }
59
#ifdef MEMENTO_SQUEEZEBUILD
60
  while (0);
61
  (void) phase;
62
#else
63
4.59M
  while (fz_store_scavenge(ctx, size, &phase));
64
0
#endif
65
0
  fz_unlock(ctx, FZ_LOCK_ALLOC);
66
67
0
  return NULL;
68
4.59M
}
69
70
static void *
71
do_scavenging_realloc(fz_context *ctx, void *p, size_t size)
72
720k
{
73
720k
  void *q;
74
720k
  int phase = 0;
75
76
720k
  fz_lock(ctx, FZ_LOCK_ALLOC);
77
720k
  do {
78
720k
    q = ctx->alloc.realloc(ctx->alloc.user, p, size);
79
720k
    if (q != NULL)
80
720k
    {
81
720k
      fz_unlock(ctx, FZ_LOCK_ALLOC);
82
720k
      return q;
83
720k
    }
84
720k
  }
85
#ifdef MEMENTO_SQUEEZEBUILD
86
  while (0);
87
  (void) phase;
88
#else
89
720k
  while (fz_store_scavenge(ctx, size, &phase));
90
0
#endif
91
0
  fz_unlock(ctx, FZ_LOCK_ALLOC);
92
93
0
  return NULL;
94
720k
}
95
96
void *
97
fz_malloc(fz_context *ctx, size_t size)
98
3.24M
{
99
3.24M
  void *p;
100
3.24M
  if (size == 0)
101
1.82k
    return NULL;
102
3.24M
  p = do_scavenging_malloc(ctx, size);
103
3.24M
  if (!p)
104
0
  {
105
0
    errno = ENOMEM;
106
0
    fz_throw(ctx, FZ_ERROR_SYSTEM, "malloc (%zu bytes) failed", size);
107
0
  }
108
3.24M
  return p;
109
3.24M
}
110
111
void *
112
fz_malloc_no_throw(fz_context *ctx, size_t size)
113
582k
{
114
582k
  if (size == 0)
115
0
    return NULL;
116
582k
  return do_scavenging_malloc(ctx, size);
117
582k
}
118
119
void *
120
fz_malloc_array_imp(fz_context *ctx, size_t nmemb, size_t size)
121
487k
{
122
487k
  size_t total;
123
487k
  if (fz_ckd_mul_size(&total, nmemb, size))
124
0
    fz_throw(ctx, FZ_ERROR_SYSTEM, "malloc array (%zu x %zu bytes) failed (overflow)", nmemb, size);
125
487k
  return fz_malloc(ctx, total);
126
487k
}
127
128
void *
129
fz_realloc_array_imp(fz_context *ctx, void *p, size_t nmemb, size_t size)
130
637k
{
131
637k
  size_t total;
132
637k
  if (fz_ckd_mul_size(&total, nmemb, size))
133
0
    fz_throw(ctx, FZ_ERROR_SYSTEM, "realloc array (%zu x %zu bytes) failed (overflow)", nmemb, size);
134
637k
  return fz_realloc(ctx, p, total);
135
637k
}
136
137
void *
138
fz_calloc(fz_context *ctx, size_t count, size_t size)
139
765k
{
140
765k
  void *p;
141
765k
  size_t total;
142
765k
  if (count == 0 || size == 0)
143
0
    return NULL;
144
765k
  if (fz_ckd_mul_size(&total, count, size))
145
0
    fz_throw(ctx, FZ_ERROR_SYSTEM, "calloc (%zu x %zu bytes) failed (overflow)", count, size);
146
765k
  p = do_scavenging_malloc(ctx, total);
147
765k
  if (!p)
148
0
  {
149
0
    errno = ENOMEM;
150
0
    fz_throw(ctx, FZ_ERROR_SYSTEM, "calloc (%zu x %zu bytes) failed", count, size);
151
0
  }
152
765k
  memset(p, 0, count*size);
153
765k
  return p;
154
765k
}
155
156
void *
157
fz_calloc_no_throw(fz_context *ctx, size_t count, size_t size)
158
4.58k
{
159
4.58k
  void *p;
160
4.58k
  size_t total;
161
4.58k
  if (count == 0 || size == 0)
162
0
    return NULL;
163
4.58k
  if (fz_ckd_mul_size(&total, count, size))
164
0
    return NULL;
165
4.58k
  p = do_scavenging_malloc(ctx, total);
166
4.58k
  if (p)
167
4.58k
    memset(p, 0, total);
168
4.58k
  return p;
169
4.58k
}
170
171
void *
172
fz_realloc(fz_context *ctx, void *p, size_t size)
173
707k
{
174
707k
  if (size == 0)
175
0
  {
176
0
    fz_free(ctx, p);
177
0
    return NULL;
178
0
  }
179
707k
  p = do_scavenging_realloc(ctx, p, size);
180
707k
  if (!p)
181
0
  {
182
0
    errno = ENOMEM;
183
0
    fz_throw(ctx, FZ_ERROR_SYSTEM, "realloc (%zu bytes) failed", size);
184
0
  }
185
707k
  return p;
186
707k
}
187
188
void *
189
fz_realloc_no_throw(fz_context *ctx, void *p, size_t size)
190
13.3k
{
191
13.3k
  if (size == 0)
192
0
  {
193
0
    fz_free(ctx, p);
194
0
    return NULL;
195
0
  }
196
13.3k
  return do_scavenging_realloc(ctx, p, size);
197
13.3k
}
198
199
void
200
fz_free(fz_context *ctx, void *p)
201
5.52M
{
202
5.52M
  if (p)
203
4.91M
  {
204
4.91M
    fz_lock(ctx, FZ_LOCK_ALLOC);
205
4.91M
    ctx->alloc.free(ctx->alloc.user, p);
206
4.91M
    fz_unlock(ctx, FZ_LOCK_ALLOC);
207
4.91M
  }
208
5.52M
}
209
210
/* align is assumed to be a power of 2. */
211
void *fz_malloc_aligned(fz_context *ctx, size_t size, int align)
212
0
{
213
0
  uint8_t *block;
214
0
  uint8_t *aligned;
215
216
0
  if (size == 0)
217
0
    return NULL;
218
219
0
  if (align >= 256)
220
0
    fz_throw(ctx, FZ_ERROR_ARGUMENT, "Alignment too large");
221
0
  if ((align & (align-1)) != 0)
222
0
    fz_throw(ctx, FZ_ERROR_ARGUMENT, "Alignment must be a power of 2");
223
224
0
  block = fz_malloc(ctx, size + align);
225
226
0
  aligned = (void *)((intptr_t)(block + align-1) & ~(align-1));
227
0
  if (aligned == block)
228
0
    aligned = block + align;
229
0
  memset(block, aligned-block, aligned-block);
230
231
0
  return aligned;
232
0
}
233
234
void fz_free_aligned(fz_context *ctx, void *ptr)
235
0
{
236
0
  uint8_t *block = ptr;
237
238
0
  if (ptr == NULL)
239
0
    return;
240
241
0
  block -= block[-1];
242
243
0
  fz_free(ctx, block);
244
0
}
245
246
char *
247
fz_strdup(fz_context *ctx, const char *s)
248
929
{
249
929
  size_t len = strlen(s) + 1;
250
929
  char *ns = fz_malloc(ctx, len);
251
929
  memcpy(ns, s, len);
252
929
  return ns;
253
929
}
254
255
fz_string *
256
fz_new_string(fz_context *ctx, const char *s)
257
0
{
258
0
  fz_string *str = fz_malloc_flexible(ctx, fz_string, str, strlen(s) + 1);
259
0
  str->refs = 1;
260
0
  strcpy(str->str, s);
261
0
  return str;
262
0
}
263
264
fz_string *fz_keep_string(fz_context *ctx, fz_string *str)
265
1.03k
{
266
1.03k
  return fz_keep_imp(ctx, str, &str->refs);
267
1.03k
}
268
269
void fz_drop_string(fz_context *ctx, fz_string *str)
270
1.03k
{
271
1.03k
  if (fz_drop_imp(ctx, str, &str->refs))
272
0
    fz_free(ctx, str);
273
1.03k
}
274
275
276
static void *
277
fz_malloc_default(void *opaque, size_t size)
278
0
{
279
0
  return malloc(size);
280
0
}
281
282
static void *
283
fz_realloc_default(void *opaque, void *old, size_t size)
284
0
{
285
0
  return realloc(old, size);
286
0
}
287
288
static void
289
fz_free_default(void *opaque, void *ptr)
290
0
{
291
0
  free(ptr);
292
0
}
293
294
fz_alloc_context fz_alloc_default =
295
{
296
  NULL,
297
  fz_malloc_default,
298
  fz_realloc_default,
299
  fz_free_default
300
};
301
302
static void
303
fz_lock_default(void *user, int lock)
304
25.3M
{
305
25.3M
}
306
307
static void
308
fz_unlock_default(void *user, int lock)
309
25.3M
{
310
25.3M
}
311
312
fz_locks_context fz_locks_default =
313
{
314
  NULL,
315
  fz_lock_default,
316
  fz_unlock_default
317
};
318
319
#ifdef FITZ_DEBUG_LOCKING
320
321
enum
322
{
323
  FZ_LOCK_DEBUG_CONTEXT_MAX = 100
324
};
325
326
fz_context *fz_lock_debug_contexts[FZ_LOCK_DEBUG_CONTEXT_MAX];
327
int fz_locks_debug[FZ_LOCK_DEBUG_CONTEXT_MAX][FZ_LOCK_MAX];
328
329
#ifdef FITZ_DEBUG_LOCKING_TIMES
330
331
int fz_debug_locking_inited = 0;
332
int fz_lock_program_start;
333
int fz_lock_time[FZ_LOCK_DEBUG_CONTEXT_MAX][FZ_LOCK_MAX] = { { 0 } };
334
int fz_lock_taken[FZ_LOCK_DEBUG_CONTEXT_MAX][FZ_LOCK_MAX] = { { 0 } };
335
336
/* We implement our own millisecond clock, as clock() cannot be trusted
337
 * when threads are involved. */
338
static int ms_clock(void)
339
{
340
#ifdef _WIN32
341
  return (int)GetTickCount();
342
#else
343
  struct timeval tp;
344
  gettimeofday(&tp, NULL);
345
  return (tp.tv_sec*1000) + (tp.tv_usec/1000);
346
#endif
347
}
348
349
static void dump_lock_times(void)
350
{
351
  int i, j;
352
  int prog_time = ms_clock() - fz_lock_program_start;
353
354
  for (j = 0; j < FZ_LOCK_MAX; j++)
355
  {
356
    int total = 0;
357
    for (i = 0; i < FZ_LOCK_DEBUG_CONTEXT_MAX; i++)
358
    {
359
      total += fz_lock_time[i][j];
360
    }
361
    printf("Lock %d held for %g seconds (%g%%)\n", j, total / 1000.0f, 100.0f*total/prog_time);
362
  }
363
  printf("Total program time %g seconds\n", prog_time / 1000.0f);
364
}
365
366
#endif
367
368
static int find_context(fz_context *ctx)
369
50.8M
{
370
50.8M
  int i;
371
372
315M
  for (i = 0; i < FZ_LOCK_DEBUG_CONTEXT_MAX; i++)
373
315M
  {
374
315M
    if (fz_lock_debug_contexts[i] == ctx)
375
50.8M
      return i;
376
264M
    if (fz_lock_debug_contexts[i] == NULL)
377
10
    {
378
10
      int gottit = 0;
379
      /* We've not locked on this context before, so use
380
       * this one for this new context. We might have other
381
       * threads trying here too though so, so claim it
382
       * atomically. No one has locked on this context
383
       * before, so we are safe to take the ALLOC lock. */
384
10
      ctx->locks.lock(ctx->locks.user, FZ_LOCK_ALLOC);
385
      /* If it's still free, then claim it as ours,
386
       * otherwise we'll keep hunting. */
387
10
      if (fz_lock_debug_contexts[i] == NULL)
388
10
      {
389
10
        gottit = 1;
390
10
        fz_lock_debug_contexts[i] = ctx;
391
#ifdef FITZ_DEBUG_LOCKING_TIMES
392
        if (fz_debug_locking_inited == 0)
393
        {
394
          fz_debug_locking_inited = 1;
395
          fz_lock_program_start = ms_clock();
396
          atexit(dump_lock_times);
397
        }
398
#endif
399
10
      }
400
10
      ctx->locks.unlock(ctx->locks.user, FZ_LOCK_ALLOC);
401
10
      if (gottit)
402
10
        return i;
403
10
    }
404
264M
  }
405
0
  return -1;
406
50.8M
}
407
408
void
409
fz_assert_lock_held(fz_context *ctx, int lock)
410
174k
{
411
174k
  int idx;
412
413
174k
  if (ctx->locks.lock != fz_lock_default)
414
0
    return;
415
416
174k
  idx = find_context(ctx);
417
174k
  if (idx < 0)
418
0
    return;
419
420
174k
  if (fz_locks_debug[idx][lock] == 0)
421
0
    fprintf(stderr, "Lock %d not held when expected\n", lock);
422
174k
}
423
424
void
425
fz_assert_lock_not_held(fz_context *ctx, int lock)
426
0
{
427
0
  int idx;
428
429
0
  if (ctx->locks.lock != fz_lock_default)
430
0
    return;
431
432
0
  idx = find_context(ctx);
433
0
  if (idx < 0)
434
0
    return;
435
436
0
  if (fz_locks_debug[idx][lock] != 0)
437
0
    fprintf(stderr, "Lock %d held when not expected\n", lock);
438
0
}
439
440
void fz_lock_debug_lock(fz_context *ctx, int lock)
441
25.3M
{
442
25.3M
  int i, idx;
443
444
25.3M
  if (ctx->locks.lock != fz_lock_default)
445
0
    return;
446
447
25.3M
  idx = find_context(ctx);
448
25.3M
  if (idx < 0)
449
0
    return;
450
451
25.3M
  if (fz_locks_debug[idx][lock] != 0)
452
0
  {
453
0
    fprintf(stderr, "Attempt to take lock %d when held already!\n", lock);
454
0
  }
455
28.3M
  for (i = lock-1; i >= 0; i--)
456
2.99M
  {
457
2.99M
    if (fz_locks_debug[idx][i] != 0)
458
0
    {
459
0
      fprintf(stderr, "Lock ordering violation: Attempt to take lock %d when %d held already!\n", lock, i);
460
0
    }
461
2.99M
  }
462
25.3M
  fz_locks_debug[idx][lock] = 1;
463
#ifdef FITZ_DEBUG_LOCKING_TIMES
464
  fz_lock_taken[idx][lock] = ms_clock();
465
#endif
466
25.3M
}
467
468
void fz_lock_debug_unlock(fz_context *ctx, int lock)
469
25.3M
{
470
25.3M
  int idx;
471
472
25.3M
  if (ctx->locks.lock != fz_lock_default)
473
0
    return;
474
475
25.3M
  idx = find_context(ctx);
476
25.3M
  if (idx < 0)
477
0
    return;
478
479
25.3M
  if (fz_locks_debug[idx][lock] == 0)
480
0
  {
481
0
    fprintf(stderr, "Attempt to release lock %d when not held!\n", lock);
482
0
  }
483
25.3M
  fz_locks_debug[idx][lock] = 0;
484
#ifdef FITZ_DEBUG_LOCKING_TIMES
485
  fz_lock_time[idx][lock] += ms_clock() - fz_lock_taken[idx][lock];
486
#endif
487
25.3M
}
488
489
#else
490
491
void
492
(fz_assert_lock_held)(fz_context *ctx, int lock)
493
{
494
}
495
496
void
497
(fz_assert_lock_not_held)(fz_context *ctx, int lock)
498
{
499
}
500
501
void (fz_lock_debug_lock)(fz_context *ctx, int lock)
502
{
503
}
504
505
void (fz_lock_debug_unlock)(fz_context *ctx, int lock)
506
{
507
}
508
509
#endif