Coverage Report

Created: 2025-06-10 06:56

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