Coverage Report

Created: 2026-10-28 08:05

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/ghostpdl/psi/zvmem.c
Line
Count
Source
1
/* Copyright (C) 2001-2026 Artifex Software, Inc.
2
   All Rights Reserved.
3
4
   This software is provided AS-IS with no warranty, either express or
5
   implied.
6
7
   This software is distributed under license and may not be copied,
8
   modified or distributed except as expressly authorized under the terms
9
   of the license contained in the file LICENSE in this distribution.
10
11
   Refer to licensing information at http://www.artifex.com or contact
12
   Artifex Software, Inc.,  39 Mesa Street, Suite 108A, San Francisco,
13
   CA 94129, USA, for further information.
14
*/
15
16
17
/* "Virtual memory" operators */
18
#include "stat_.h" /* get system header early to avoid name clash on Cygwin */
19
#include "ghost.h"
20
#include "gsstruct.h"
21
#include "oper.h"
22
#include "estack.h"   /* for checking in restore */
23
#include "ialloc.h"
24
#include "idict.h"    /* ditto */
25
#include "iddict.h"
26
#include "igstate.h"
27
#include "isave.h"
28
#include "dstack.h"
29
#include "stream.h"   /* for files.h */
30
#include "files.h"    /* for e-stack processing */
31
#include "store.h"
32
#include "gsmatrix.h"   /* for gsstate.h */
33
#include "gsstate.h"
34
35
/* Define whether we validate memory before/after save/restore. */
36
/* Note that we only actually do this if DEBUG is set and -Z? is selected. */
37
static const bool I_VALIDATE_BEFORE_SAVE = true;
38
static const bool I_VALIDATE_AFTER_SAVE = true;
39
static const bool I_VALIDATE_BEFORE_RESTORE = true;
40
static const bool I_VALIDATE_AFTER_RESTORE = true;
41
42
gs_private_st_ptrs1(st_vm_save, vm_save_t, "savetype",
43
                    vm_save_enum_ptrs, vm_save_reloc_ptrs, gsave);
44
45
/* Clean up the stacks and validate storage. */
46
void
47
ivalidate_clean_spaces(i_ctx_t *i_ctx_p)
48
2.31M
{
49
2.31M
    if (gs_debug_c('?')) {
50
0
        ref_stack_cleanup(&d_stack);
51
0
        ref_stack_cleanup(&e_stack);
52
0
        ref_stack_cleanup(&o_stack);
53
0
        ivalidate_spaces();
54
0
    }
55
2.31M
}
56
57
/* - save <save> */
58
int
59
zsave(i_ctx_t *i_ctx_p)
60
1.00M
{
61
1.00M
    os_ptr op = osp;
62
1.00M
    uint space = icurrent_space;
63
1.00M
    vm_save_t *vmsave;
64
1.00M
    ulong sid;
65
1.00M
    int code;
66
1.00M
    gs_gstate *prev;
67
68
1.00M
    if (I_VALIDATE_BEFORE_SAVE)
69
1.00M
        ivalidate_clean_spaces(i_ctx_p);
70
1.00M
    ialloc_set_space(idmemory, avm_local);
71
1.00M
    vmsave = ialloc_struct(vm_save_t, &st_vm_save, "zsave");
72
1.00M
    ialloc_set_space(idmemory, space);
73
1.00M
    if (vmsave == 0)
74
0
        return_error(gs_error_VMerror);
75
1.00M
    vmsave->gsave = NULL; /* Ensure constructed enough to destroy safely */
76
1.00M
    code = alloc_save_state(idmemory, vmsave, &sid);
77
78
1.00M
    if (code < 0 || sid == 0) {
79
1
        ifree_object(vmsave, "zsave");
80
1
        if (code < 0)
81
1
            return code;
82
0
        else
83
0
            return_error(gs_error_VMerror);
84
1
    }
85
1.00M
    if_debug2m('u', imemory, "[u]vmsave "PRI_INTPTR", id = %lu\n",
86
1.00M
               (intptr_t) vmsave, (ulong) sid);
87
1.00M
    code = gs_gsave_for_save(igs, &prev);
88
1.00M
    if (code < 0) {
89
2
        alloc_save_t *asave;
90
2
        int code2;
91
        /* dorestore() pops the restore operand off the stack,
92
           despite dorestore() actually having the save state
93
           passed to it as a C function parameter. So push a
94
           sacrificial object.
95
         */
96
2
        push(1);
97
2
        make_null(op);
98
        /* We use dorestore() to discard the save state we
99
           created above.
100
         */
101
2
        asave = alloc_find_save(idmemory, sid);
102
2
        code2 = dorestore(i_ctx_p, asave);
103
2
        if (code2 < 0) /* shouldn't happen! */
104
0
            return_error(gs_error_Fatal);
105
2
        return code;
106
2
    }
107
1.00M
    vmsave->gsave = prev;
108
1.00M
    push(1);
109
1.00M
    make_tav(op, t_save, 0, saveid, sid);
110
1.00M
    if (I_VALIDATE_AFTER_SAVE)
111
1.00M
        ivalidate_clean_spaces(i_ctx_p);
112
1.00M
    return 0;
113
1.00M
}
114
115
/* <save> restore - */
116
static int restore_check_operand(i_ctx_t *i_ctx_p, alloc_save_t **, gs_dual_memory_t *);
117
static int restore_check_stack(const i_ctx_t *i_ctx_p, const ref_stack_t *, const alloc_save_t *, bool);
118
static void restore_fix_stack(i_ctx_t *i_ctx_p, ref_stack_t *, const alloc_save_t *, bool);
119
120
/* Do as many up front checks of the save object as we reasonably can */
121
int
122
restore_check_save(i_ctx_t *i_ctx_p, alloc_save_t **asave)
123
192k
{
124
192k
    int code = restore_check_operand(i_ctx_p, asave, idmemory);
125
126
192k
    if (code < 0)
127
78
        return code;
128
192k
    if_debug2m('u', imemory, "[u]vmrestore "PRI_INTPTR", id = %lu\n",
129
192k
               (intptr_t) alloc_save_client_data(*asave),
130
192k
               (ulong) osp->value.saveid);
131
192k
    if (I_VALIDATE_BEFORE_RESTORE)
132
192k
        ivalidate_clean_spaces(i_ctx_p);
133
    /* Check the contents of the stacks. */
134
192k
    osp--;
135
192k
    {
136
192k
        int code;
137
138
192k
        if ((code = restore_check_stack(i_ctx_p, &o_stack, *asave, false)) < 0 ||
139
192k
            (code = restore_check_stack(i_ctx_p, &e_stack, *asave, true)) < 0 ||
140
192k
            (code = restore_check_stack(i_ctx_p, &d_stack, *asave, false)) < 0
141
192k
            ) {
142
31
            osp++;
143
31
            return code;
144
31
        }
145
192k
    }
146
192k
    osp++;
147
192k
    return 0;
148
192k
}
149
150
/* the semantics of restore differ slightly between Level 1 and
151
   Level 2 and later - the latter includes restoring the device
152
   state (whilst Level 1 didn't have "page devices" as such).
153
   Hence we have two restore operators - one here (Level 1)
154
   and one in zdevice2.c (Level 2+). For that reason, the
155
   operand checking and guts of the restore operation are
156
   separated so both implementations can use them to best
157
   effect.
158
 */
159
int
160
dorestore(i_ctx_t *i_ctx_p, alloc_save_t *asave)
161
108k
{
162
108k
    os_ptr op = osp;
163
108k
    bool last;
164
108k
    vm_save_t *vmsave;
165
108k
    int code;
166
167
108k
    check_op(1);
168
108k
    osp--;
169
170
    /* Reset l_new in all stack entries if the new save level is zero. */
171
    /* Also do some special fixing on the e-stack. */
172
108k
    restore_fix_stack(i_ctx_p, &o_stack, asave, false);
173
108k
    restore_fix_stack(i_ctx_p, &e_stack, asave, true);
174
108k
    restore_fix_stack(i_ctx_p, &d_stack, asave, false);
175
    /* Iteratively restore the state of memory, */
176
    /* also doing a grestoreall at each step. */
177
108k
    do {
178
108k
        vmsave = alloc_save_client_data(alloc_save_current(idmemory));
179
        /* Restore the graphics state. */
180
        /* The only time vmsave->gsave should be NULL is if we are
181
           cleaning up after a VMerror during a save operation.
182
         */
183
108k
        if (vmsave->gsave != NULL)
184
108k
            gs_grestoreall_for_restore(igs, vmsave->gsave);
185
        /*
186
         * If alloc_save_space decided to do a second save, the vmsave
187
         * object was allocated one save level less deep than the
188
         * current level, so ifree_object won't actually free it;
189
         * however, it points to a gsave object that definitely
190
         * *has* been freed.  In order not to trip up the garbage
191
         * collector, we clear the gsave pointer now.
192
         */
193
108k
        vmsave->gsave = 0;
194
        /* Now it's safe to restore the state of memory. */
195
108k
        code = alloc_restore_step_in(idmemory, asave);
196
108k
        if (code < 0)
197
0
            return code;
198
108k
        last = code;
199
108k
    }
200
108k
    while (!last);
201
108k
    {
202
108k
        uint space = icurrent_space;
203
204
108k
        ialloc_set_space(idmemory, avm_local);
205
108k
        ifree_object(vmsave, "zrestore");
206
108k
        ialloc_set_space(idmemory, space);
207
108k
    }
208
108k
    dict_set_top();   /* reload dict stack cache */
209
108k
    if (I_VALIDATE_AFTER_RESTORE)
210
108k
        ivalidate_clean_spaces(i_ctx_p);
211
    /* If the i_ctx_p LockFilePermissions is true, but the userparams */
212
    /* we just restored is false, we need to make sure that we do not */
213
    /* cause an 'invalidaccess' in setuserparams. Temporarily set     */
214
    /* LockFilePermissions false until the gs_lev2.ps can do a        */
215
    /* setuserparams from the restored userparam dictionary.          */
216
    /* NOTE: This is safe to do here, since the restore has           */
217
    /* successfully completed - this should never come before any     */
218
    /* operation that can trigger an error                            */
219
108k
    i_ctx_p->LockFilePermissions = false;
220
221
    /* If there are fonts in the GlobalFontDirectory that the references in
222
       in the LocalFontDirectory for have been restored away, we need to
223
       copy them back across.
224
       _ONLY_ names that don't already exist in LocalFontDirectory.
225
     */
226
108k
    if ((code = idict_copy_new(&i_ctx_p->GlobalFontDirectory, &i_ctx_p->LocalFontDirectory)) < 0)
227
0
        return code;
228
229
    /* For safety, we also need to "rebind" the name FontDirectory
230
       to either the global or local one, depending on the current mode
231
     */
232
108k
    if (ialloc_space(idmemory) != avm_local) {
233
0
        code = idict_put_string(systemdict, "FontDirectory", &i_ctx_p->GlobalFontDirectory);
234
0
    }
235
108k
    else {
236
        /* Hack the VM flag of systemdict so we write the local VM object
237
           to the global systemdict.
238
         */
239
108k
        r_set_space(systemdict, avm_local);
240
108k
        code = idict_put_string(systemdict, "FontDirectory", &i_ctx_p->LocalFontDirectory);
241
108k
        r_set_space(systemdict, avm_global);
242
108k
    }
243
244
108k
    return 0;
245
108k
}
246
247
int
248
zrestore(i_ctx_t *i_ctx_p)
249
0
{
250
0
    alloc_save_t *asave;
251
0
    int code = restore_check_save(i_ctx_p, &asave);
252
0
    if (code < 0)
253
0
        return code;
254
255
0
    return dorestore(i_ctx_p, asave);
256
0
}
257
258
/* Check the operand of a restore. */
259
static int
260
restore_check_operand(i_ctx_t *i_ctx_p, alloc_save_t ** pasave,
261
                      gs_dual_memory_t *idmem)
262
192k
{
263
192k
    os_ptr op = osp;
264
192k
    vm_save_t *vmsave;
265
192k
    ulong sid;
266
192k
    alloc_save_t *asave;
267
268
192k
    check_op(1);
269
192k
    *pasave = NULL;
270
192k
    check_type(*op, t_save);
271
192k
    vmsave = r_ptr(op, vm_save_t);
272
192k
    if (vmsave == 0)    /* invalidated save */
273
0
        return_error(gs_error_invalidrestore);
274
192k
    sid = op->value.saveid;
275
192k
    asave = alloc_find_save(idmem, sid);
276
192k
    if (asave == 0)
277
0
        return_error(gs_error_invalidrestore);
278
192k
    *pasave = asave;
279
192k
    return 0;
280
192k
}
281
282
/* Check a stack to make sure all its elements are older than a save. */
283
static int
284
restore_check_stack(const i_ctx_t *i_ctx_p, const ref_stack_t * pstack,
285
                    const alloc_save_t * asave, bool is_estack)
286
577k
{
287
577k
    ref_stack_enum_t rsenum;
288
289
577k
    ref_stack_enum_begin(&rsenum, pstack);
290
577k
    do {
291
577k
        const ref *stkp = rsenum.ptr;
292
577k
        uint size = rsenum.size;
293
294
14.2M
        for (; size; stkp++, size--) {
295
13.6M
            const void *ptr;
296
297
13.6M
            switch (r_type(stkp)) {
298
1.60k
                case t_array:
299
                    /*
300
                     * Zero-length arrays are a special case: see the
301
                     * t_*array case (label rr:) in igc.c:gc_trace.
302
                     */
303
1.60k
                    if (r_size(stkp) == 0) {
304
                        /*stkp->value.refs = (void *)0;*/
305
292
                        continue;
306
292
                    }
307
1.31k
                    ptr = stkp->value.refs;
308
1.31k
                    break;
309
738k
                case t_dictionary:
310
738k
                    ptr = stkp->value.pdict;
311
738k
                    break;
312
758k
                case t_file:
313
                    /* Don't check executable or closed literal */
314
                    /* files on the e-stack. */
315
758k
                    {
316
758k
                        stream *s;
317
318
758k
                        if (is_estack &&
319
444k
                            (r_has_attr(stkp, a_executable) ||
320
223k
                             file_is_invalid(s, stkp))
321
758k
                            )
322
410k
                            continue;
323
758k
                    }
324
347k
                    ptr = stkp->value.pfile;
325
347k
                    break;
326
261k
                case t_name:
327
                    /* Names are special because of how they are allocated. */
328
261k
                    if (alloc_name_is_since_save((const gs_memory_t *)pstack->memory,
329
261k
                                                 stkp, asave))
330
0
                        return_error(gs_error_invalidrestore);
331
261k
                    continue;
332
261k
                case t_string:
333
                    /* Don't check empty executable strings */
334
                    /* on the e-stack. */
335
156k
                    if (r_size(stkp) == 0 &&
336
146
                        r_has_attr(stkp, a_executable) && is_estack
337
156k
                        )
338
0
                        continue;
339
156k
                    ptr = stkp->value.bytes;
340
156k
                    break;
341
1.32M
                case t_mixedarray:
342
1.59M
                case t_shortarray:
343
                    /* See the t_array case above. */
344
1.59M
                    if (r_size(stkp) == 0) {
345
                        /*stkp->value.packed = (void *)0;*/
346
0
                        continue;
347
0
                    }
348
1.59M
                    ptr = stkp->value.packed;
349
1.59M
                    break;
350
0
                case t_device:
351
0
                    ptr = stkp->value.pdevice;
352
0
                    break;
353
0
                case t_fontID:
354
0
                case t_struct:
355
0
                case t_astruct:
356
0
                case t_pdfctx:
357
0
                    ptr = stkp->value.pstruct;
358
0
                    break;
359
1.13M
                case t_save:
360
                    /* See the comment in isave.h regarding the following. */
361
1.13M
                    if (i_ctx_p->language_level <= 2)
362
0
                        continue;
363
1.13M
                    ptr = alloc_find_save(&gs_imemory, stkp->value.saveid);
364
                    /*
365
                     * Invalid save objects aren't supposed to be possible
366
                     * in LL3, but just in case....
367
                     */
368
1.13M
                    if (ptr == 0)
369
0
                        return_error(gs_error_invalidrestore);
370
1.13M
                    if (ptr == asave)
371
0
                        continue;
372
1.13M
                    break;
373
8.97M
                default:
374
8.97M
                    continue;
375
13.6M
            }
376
3.97M
            if (alloc_is_since_save(ptr, asave))
377
31
                return_error(gs_error_invalidrestore);
378
3.97M
        }
379
577k
    } while (ref_stack_enum_next(&rsenum));
380
577k
    return 0;   /* OK */
381
577k
}
382
/*
383
 * If the new save level is zero, fix up the contents of a stack
384
 * by clearing the l_new bit in all the entries (since we can't tolerate
385
 * values with l_new set if the save level is zero).
386
 * Also, in any case, fix up the e-stack by replacing empty executable
387
 * strings and closed executable files that are newer than the save
388
 * with canonical ones that aren't.
389
 *
390
 * Note that this procedure is only called if restore_check_stack succeeded.
391
 */
392
static void
393
restore_fix_stack(i_ctx_t *i_ctx_p, ref_stack_t * pstack,
394
                  const alloc_save_t * asave, bool is_estack)
395
324k
{
396
324k
    ref_stack_enum_t rsenum;
397
398
324k
    ref_stack_enum_begin(&rsenum, pstack);
399
325k
    do {
400
325k
        ref *stkp = rsenum.ptr;
401
325k
        uint size = rsenum.size;
402
403
8.19M
        for (; size; stkp++, size--) {
404
7.87M
            r_clear_attrs(stkp, l_new);    /* always do it, no harm */
405
7.87M
            if (is_estack) {
406
5.85M
                ref ofile;
407
408
5.85M
                ref_assign(&ofile, stkp);
409
5.85M
                switch (r_type(stkp)) {
410
0
                    case t_string:
411
0
                        if (r_size(stkp) == 0 &&
412
0
                            alloc_is_since_save(stkp->value.bytes,
413
0
                                                asave)
414
0
                            ) {
415
0
                            make_empty_const_string(stkp,
416
0
                                                    avm_foreign);
417
0
                            break;
418
0
                        }
419
0
                        continue;
420
247k
                    case t_file:
421
247k
                        if (alloc_is_since_save(stkp->value.pfile,
422
247k
                                                asave)
423
247k
                            ) {
424
0
                            make_invalid_file(i_ctx_p, stkp);
425
0
                            break;
426
0
                        }
427
247k
                        continue;
428
5.60M
                    default:
429
5.60M
                        continue;
430
5.85M
                }
431
0
                r_copy_attrs(stkp, a_all | a_executable,
432
0
                             &ofile);
433
0
            }
434
7.87M
        }
435
325k
    } while (ref_stack_enum_next(&rsenum));
436
324k
}
437
438
/* - vmstatus <save_level> <vm_used> <vm_maximum> */
439
static int
440
zvmstatus(i_ctx_t *i_ctx_p)
441
1.92M
{
442
1.92M
    os_ptr op = osp;
443
1.92M
    gs_memory_status_t mstat, dstat;
444
445
1.92M
    gs_memory_status(imemory, &mstat);
446
1.92M
    if (imemory == imemory_global) {
447
1.12M
        gs_memory_status_t sstat;
448
449
1.12M
        gs_memory_status(imemory_system, &sstat);
450
1.12M
        mstat.allocated += sstat.allocated;
451
1.12M
        mstat.used += sstat.used;
452
1.12M
    }
453
1.92M
    gs_memory_status(imemory->non_gc_memory, &dstat);
454
1.92M
    push(3);
455
1.92M
    make_int(op - 2, imemory_save_level(iimemory_local));
456
1.92M
    make_int(op - 1, mstat.used);
457
1.92M
    make_int(op, mstat.allocated + dstat.allocated - dstat.used);
458
1.92M
    return 0;
459
1.92M
}
460
461
/* ------ Initialization procedure ------ */
462
463
const op_def zvmem_op_defs[] =
464
{
465
    {"1restore", zrestore},
466
    {"0save", zsave},
467
    {"0vmstatus", zvmstatus},
468
    op_def_end(0)
469
};