/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 | 2.68M | { |
48 | 2.68M | 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 | 2.68M | } |
55 | | |
56 | | /* - save <save> */ |
57 | | int |
58 | | zsave(i_ctx_t *i_ctx_p) |
59 | 1.18M | { |
60 | 1.18M | os_ptr op = osp; |
61 | 1.18M | uint space = icurrent_space; |
62 | 1.18M | vm_save_t *vmsave; |
63 | 1.18M | ulong sid; |
64 | 1.18M | int code; |
65 | 1.18M | gs_gstate *prev; |
66 | | |
67 | 1.18M | if (I_VALIDATE_BEFORE_SAVE) |
68 | 1.18M | ivalidate_clean_spaces(i_ctx_p); |
69 | 1.18M | ialloc_set_space(idmemory, avm_local); |
70 | 1.18M | vmsave = ialloc_struct(vm_save_t, &st_vm_save, "zsave"); |
71 | 1.18M | ialloc_set_space(idmemory, space); |
72 | 1.18M | if (vmsave == 0) |
73 | 0 | return_error(gs_error_VMerror); |
74 | 1.18M | vmsave->gsave = NULL; /* Ensure constructed enough to destroy safely */ |
75 | 1.18M | code = alloc_save_state(idmemory, vmsave, &sid); |
76 | | |
77 | 1.18M | 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 | 1.18M | if_debug2m('u', imemory, "[u]vmsave "PRI_INTPTR", id = %lu\n", |
85 | 1.18M | (intptr_t) vmsave, (ulong) sid); |
86 | 1.18M | code = gs_gsave_for_save(igs, &prev); |
87 | 1.18M | if (code < 0) { |
88 | 2 | alloc_save_t *asave; |
89 | 2 | 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 | 2 | push(1); |
96 | 2 | make_null(op); |
97 | | /* We use dorestore() to discard the save state we |
98 | | created above. |
99 | | */ |
100 | 2 | asave = alloc_find_save(idmemory, sid); |
101 | 2 | code2 = dorestore(i_ctx_p, asave); |
102 | 2 | if (code2 < 0) /* shouldn't happen! */ |
103 | 0 | return_error(gs_error_Fatal); |
104 | 2 | return code; |
105 | 2 | } |
106 | 1.18M | vmsave->gsave = prev; |
107 | 1.18M | push(1); |
108 | 1.18M | make_tav(op, t_save, 0, saveid, sid); |
109 | 1.18M | if (I_VALIDATE_AFTER_SAVE) |
110 | 1.18M | ivalidate_clean_spaces(i_ctx_p); |
111 | 1.18M | return 0; |
112 | 1.18M | } |
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 | 203k | { |
123 | 203k | int code = restore_check_operand(i_ctx_p, asave, idmemory); |
124 | | |
125 | 203k | if (code < 0) |
126 | 103 | return code; |
127 | 203k | if_debug2m('u', imemory, "[u]vmrestore "PRI_INTPTR", id = %lu\n", |
128 | 203k | (intptr_t) alloc_save_client_data(*asave), |
129 | 203k | (ulong) osp->value.saveid); |
130 | 203k | if (I_VALIDATE_BEFORE_RESTORE) |
131 | 203k | ivalidate_clean_spaces(i_ctx_p); |
132 | | /* Check the contents of the stacks. */ |
133 | 203k | osp--; |
134 | 203k | { |
135 | 203k | int code; |
136 | | |
137 | 203k | if ((code = restore_check_stack(i_ctx_p, &o_stack, *asave, false)) < 0 || |
138 | 203k | (code = restore_check_stack(i_ctx_p, &e_stack, *asave, true)) < 0 || |
139 | 203k | (code = restore_check_stack(i_ctx_p, &d_stack, *asave, false)) < 0 |
140 | 203k | ) { |
141 | 27 | osp++; |
142 | 27 | return code; |
143 | 27 | } |
144 | 203k | } |
145 | 203k | osp++; |
146 | 203k | return 0; |
147 | 203k | } |
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 | 113k | { |
161 | 113k | os_ptr op = osp; |
162 | 113k | bool last; |
163 | 113k | vm_save_t *vmsave; |
164 | 113k | int code; |
165 | | |
166 | 113k | check_op(1); |
167 | 113k | 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 | 113k | restore_fix_stack(i_ctx_p, &o_stack, asave, false); |
172 | 113k | restore_fix_stack(i_ctx_p, &e_stack, asave, true); |
173 | 113k | 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 | 114k | do { |
177 | 114k | 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 | 114k | if (vmsave->gsave != NULL) |
183 | 114k | 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 | 114k | vmsave->gsave = 0; |
193 | | /* Now it's safe to restore the state of memory. */ |
194 | 114k | code = alloc_restore_step_in(idmemory, asave); |
195 | 114k | if (code < 0) |
196 | 0 | return code; |
197 | 114k | last = code; |
198 | 114k | } |
199 | 114k | while (!last); |
200 | 113k | { |
201 | 113k | uint space = icurrent_space; |
202 | | |
203 | 113k | ialloc_set_space(idmemory, avm_local); |
204 | 113k | ifree_object(vmsave, "zrestore"); |
205 | 113k | ialloc_set_space(idmemory, space); |
206 | 113k | } |
207 | 113k | dict_set_top(); /* reload dict stack cache */ |
208 | 113k | if (I_VALIDATE_AFTER_RESTORE) |
209 | 113k | 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 | 113k | i_ctx_p->LockFilePermissions = false; |
219 | 113k | return 0; |
220 | 113k | } |
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 | 203k | { |
238 | 203k | os_ptr op = osp; |
239 | 203k | vm_save_t *vmsave; |
240 | 203k | ulong sid; |
241 | 203k | alloc_save_t *asave; |
242 | | |
243 | 203k | check_op(1); |
244 | 203k | *pasave = NULL; |
245 | 203k | check_type(*op, t_save); |
246 | 203k | vmsave = r_ptr(op, vm_save_t); |
247 | 203k | if (vmsave == 0) /* invalidated save */ |
248 | 0 | return_error(gs_error_invalidrestore); |
249 | 203k | sid = op->value.saveid; |
250 | 203k | asave = alloc_find_save(idmem, sid); |
251 | 203k | if (asave == 0) |
252 | 0 | return_error(gs_error_invalidrestore); |
253 | 203k | *pasave = asave; |
254 | 203k | return 0; |
255 | 203k | } |
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 | 609k | { |
262 | 609k | ref_stack_enum_t rsenum; |
263 | | |
264 | 609k | ref_stack_enum_begin(&rsenum, pstack); |
265 | 610k | do { |
266 | 610k | const ref *stkp = rsenum.ptr; |
267 | 610k | uint size = rsenum.size; |
268 | | |
269 | 17.4M | for (; size; stkp++, size--) { |
270 | 16.8M | const void *ptr; |
271 | | |
272 | 16.8M | switch (r_type(stkp)) { |
273 | 1.48k | 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 | 1.48k | if (r_size(stkp) == 0) { |
279 | | /*stkp->value.refs = (void *)0;*/ |
280 | 472 | continue; |
281 | 472 | } |
282 | 1.01k | ptr = stkp->value.refs; |
283 | 1.01k | break; |
284 | 781k | case t_dictionary: |
285 | 781k | ptr = stkp->value.pdict; |
286 | 781k | break; |
287 | 811k | case t_file: |
288 | | /* Don't check executable or closed literal */ |
289 | | /* files on the e-stack. */ |
290 | 811k | { |
291 | 811k | stream *s; |
292 | | |
293 | 811k | if (is_estack && |
294 | 811k | (r_has_attr(stkp, a_executable) || |
295 | 478k | file_is_invalid(s, stkp)) |
296 | 811k | ) |
297 | 442k | continue; |
298 | 811k | } |
299 | 368k | ptr = stkp->value.pfile; |
300 | 368k | break; |
301 | 296k | case t_name: |
302 | | /* Names are special because of how they are allocated. */ |
303 | 296k | if (alloc_name_is_since_save((const gs_memory_t *)pstack->memory, |
304 | 296k | stkp, asave)) |
305 | 0 | return_error(gs_error_invalidrestore); |
306 | 296k | continue; |
307 | 296k | case t_string: |
308 | | /* Don't check empty executable strings */ |
309 | | /* on the e-stack. */ |
310 | 167k | if (r_size(stkp) == 0 && |
311 | 167k | r_has_attr(stkp, a_executable) && is_estack |
312 | 167k | ) |
313 | 0 | continue; |
314 | 167k | ptr = stkp->value.bytes; |
315 | 167k | break; |
316 | 1.69M | case t_mixedarray: |
317 | 1.98M | case t_shortarray: |
318 | | /* See the t_array case above. */ |
319 | 1.98M | if (r_size(stkp) == 0) { |
320 | | /*stkp->value.packed = (void *)0;*/ |
321 | 0 | continue; |
322 | 0 | } |
323 | 1.98M | ptr = stkp->value.packed; |
324 | 1.98M | 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 | 1.68M | case t_save: |
335 | | /* See the comment in isave.h regarding the following. */ |
336 | 1.68M | if (i_ctx_p->language_level <= 2) |
337 | 0 | continue; |
338 | 1.68M | 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 | 1.68M | if (ptr == 0) |
344 | 0 | return_error(gs_error_invalidrestore); |
345 | 1.68M | if (ptr == asave) |
346 | 292k | continue; |
347 | 1.38M | break; |
348 | 11.1M | default: |
349 | 11.1M | continue; |
350 | 16.8M | } |
351 | 4.69M | if (alloc_is_since_save(ptr, asave)) |
352 | 27 | return_error(gs_error_invalidrestore); |
353 | 4.69M | } |
354 | 610k | } while (ref_stack_enum_next(&rsenum)); |
355 | 609k | return 0; /* OK */ |
356 | 609k | } |
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 | 341k | { |
371 | 341k | ref_stack_enum_t rsenum; |
372 | | |
373 | 341k | ref_stack_enum_begin(&rsenum, pstack); |
374 | 341k | do { |
375 | 341k | ref *stkp = rsenum.ptr; |
376 | 341k | uint size = rsenum.size; |
377 | | |
378 | 10.1M | for (; size; stkp++, size--) { |
379 | 9.85M | r_clear_attrs(stkp, l_new); /* always do it, no harm */ |
380 | 9.85M | if (is_estack) { |
381 | 7.46M | ref ofile; |
382 | | |
383 | 7.46M | ref_assign(&ofile, stkp); |
384 | 7.46M | 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 | 264k | case t_file: |
396 | 264k | if (alloc_is_since_save(stkp->value.pfile, |
397 | 264k | asave) |
398 | 264k | ) { |
399 | 0 | make_invalid_file(i_ctx_p, stkp); |
400 | 0 | break; |
401 | 0 | } |
402 | 264k | continue; |
403 | 7.20M | default: |
404 | 7.20M | continue; |
405 | 7.46M | } |
406 | 0 | r_copy_attrs(stkp, a_all | a_executable, |
407 | 0 | &ofile); |
408 | 0 | } |
409 | 9.85M | } |
410 | 341k | } while (ref_stack_enum_next(&rsenum)); |
411 | 341k | } |
412 | | |
413 | | /* - vmstatus <save_level> <vm_used> <vm_maximum> */ |
414 | | static int |
415 | | zvmstatus(i_ctx_t *i_ctx_p) |
416 | 2.14M | { |
417 | 2.14M | os_ptr op = osp; |
418 | 2.14M | gs_memory_status_t mstat, dstat; |
419 | | |
420 | 2.14M | gs_memory_status(imemory, &mstat); |
421 | 2.14M | if (imemory == imemory_global) { |
422 | 1.23M | gs_memory_status_t sstat; |
423 | | |
424 | 1.23M | gs_memory_status(imemory_system, &sstat); |
425 | 1.23M | mstat.allocated += sstat.allocated; |
426 | 1.23M | mstat.used += sstat.used; |
427 | 1.23M | } |
428 | 2.14M | gs_memory_status(imemory->non_gc_memory, &dstat); |
429 | 2.14M | push(3); |
430 | 2.14M | make_int(op - 2, imemory_save_level(iimemory_local)); |
431 | 2.14M | make_int(op - 1, mstat.used); |
432 | 2.14M | make_int(op, mstat.allocated + dstat.allocated - dstat.used); |
433 | 2.14M | return 0; |
434 | 2.14M | } |
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 | | }; |