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