/src/ghostpdl/psi/zvmem.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* Copyright (C) 2001-2021 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., 1305 Grant Avenue - Suite 200, Novato, |
13 | | CA 94945, U.S.A., +1(415)492-9861, 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 | 516k | { |
48 | 516k | 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 | 516k | } |
55 | | |
56 | | /* - save <save> */ |
57 | | int |
58 | | zsave(i_ctx_t *i_ctx_p) |
59 | 199k | { |
60 | 199k | os_ptr op = osp; |
61 | 199k | uint space = icurrent_space; |
62 | 199k | vm_save_t *vmsave; |
63 | 199k | ulong sid; |
64 | 199k | int code; |
65 | 199k | gs_gstate *prev; |
66 | | |
67 | 199k | if (I_VALIDATE_BEFORE_SAVE) |
68 | 199k | ivalidate_clean_spaces(i_ctx_p); |
69 | 199k | ialloc_set_space(idmemory, avm_local); |
70 | 199k | vmsave = ialloc_struct(vm_save_t, &st_vm_save, "zsave"); |
71 | 199k | ialloc_set_space(idmemory, space); |
72 | 199k | if (vmsave == 0) |
73 | 0 | return_error(gs_error_VMerror); |
74 | 199k | vmsave->gsave = NULL; /* Ensure constructed enough to destroy safely */ |
75 | 199k | code = alloc_save_state(idmemory, vmsave, &sid); |
76 | | |
77 | 199k | 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 | 199k | if_debug2m('u', imemory, "[u]vmsave "PRI_INTPTR", id = %lu\n", |
85 | 199k | (intptr_t) vmsave, (ulong) sid); |
86 | 199k | code = gs_gsave_for_save(igs, &prev); |
87 | 199k | 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 | 199k | vmsave->gsave = prev; |
107 | 199k | push(1); |
108 | 199k | make_tav(op, t_save, 0, saveid, sid); |
109 | 199k | if (I_VALIDATE_AFTER_SAVE) |
110 | 199k | ivalidate_clean_spaces(i_ctx_p); |
111 | 199k | return 0; |
112 | 199k | } |
113 | | |
114 | | /* <save> restore - */ |
115 | | static int restore_check_operand(os_ptr, 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 | 72.1k | { |
123 | 72.1k | os_ptr op = osp; |
124 | 72.1k | int code = restore_check_operand(op, asave, idmemory); |
125 | | |
126 | 72.1k | if (code < 0) |
127 | 78 | return code; |
128 | 72.1k | if_debug2m('u', imemory, "[u]vmrestore "PRI_INTPTR", id = %lu\n", |
129 | 72.0k | (intptr_t) alloc_save_client_data(*asave), |
130 | 72.0k | (ulong) op->value.saveid); |
131 | 72.0k | if (I_VALIDATE_BEFORE_RESTORE) |
132 | 72.0k | ivalidate_clean_spaces(i_ctx_p); |
133 | | /* Check the contents of the stacks. */ |
134 | 72.0k | osp--; |
135 | 72.0k | { |
136 | 72.0k | int code; |
137 | | |
138 | 72.0k | if ((code = restore_check_stack(i_ctx_p, &o_stack, *asave, false)) < 0 || |
139 | 72.0k | (code = restore_check_stack(i_ctx_p, &e_stack, *asave, true)) < 0 || |
140 | 72.0k | (code = restore_check_stack(i_ctx_p, &d_stack, *asave, false)) < 0 |
141 | 72.0k | ) { |
142 | 5 | osp++; |
143 | 5 | return code; |
144 | 5 | } |
145 | 72.0k | } |
146 | 72.0k | osp++; |
147 | 72.0k | return 0; |
148 | 72.0k | } |
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 | 44.8k | { |
162 | 44.8k | bool last; |
163 | 44.8k | vm_save_t *vmsave; |
164 | 44.8k | int code; |
165 | | |
166 | 44.8k | osp--; |
167 | | |
168 | | /* Reset l_new in all stack entries if the new save level is zero. */ |
169 | | /* Also do some special fixing on the e-stack. */ |
170 | 44.8k | restore_fix_stack(i_ctx_p, &o_stack, asave, false); |
171 | 44.8k | restore_fix_stack(i_ctx_p, &e_stack, asave, true); |
172 | 44.8k | restore_fix_stack(i_ctx_p, &d_stack, asave, false); |
173 | | /* Iteratively restore the state of memory, */ |
174 | | /* also doing a grestoreall at each step. */ |
175 | 46.0k | do { |
176 | 46.0k | vmsave = alloc_save_client_data(alloc_save_current(idmemory)); |
177 | | /* Restore the graphics state. */ |
178 | | /* The only time vmsave->gsave should be NULL is if we are |
179 | | cleaning up after a VMerror during a save operation. |
180 | | */ |
181 | 46.0k | if (vmsave->gsave != NULL) |
182 | 46.0k | gs_grestoreall_for_restore(igs, vmsave->gsave); |
183 | | /* |
184 | | * If alloc_save_space decided to do a second save, the vmsave |
185 | | * object was allocated one save level less deep than the |
186 | | * current level, so ifree_object won't actually free it; |
187 | | * however, it points to a gsave object that definitely |
188 | | * *has* been freed. In order not to trip up the garbage |
189 | | * collector, we clear the gsave pointer now. |
190 | | */ |
191 | 46.0k | vmsave->gsave = 0; |
192 | | /* Now it's safe to restore the state of memory. */ |
193 | 46.0k | code = alloc_restore_step_in(idmemory, asave); |
194 | 46.0k | if (code < 0) |
195 | 0 | return code; |
196 | 46.0k | last = code; |
197 | 46.0k | } |
198 | 46.0k | while (!last); |
199 | 44.8k | { |
200 | 44.8k | uint space = icurrent_space; |
201 | | |
202 | 44.8k | ialloc_set_space(idmemory, avm_local); |
203 | 44.8k | ifree_object(vmsave, "zrestore"); |
204 | 44.8k | ialloc_set_space(idmemory, space); |
205 | 44.8k | } |
206 | 44.8k | dict_set_top(); /* reload dict stack cache */ |
207 | 44.8k | if (I_VALIDATE_AFTER_RESTORE) |
208 | 44.8k | ivalidate_clean_spaces(i_ctx_p); |
209 | | /* If the i_ctx_p LockFilePermissions is true, but the userparams */ |
210 | | /* we just restored is false, we need to make sure that we do not */ |
211 | | /* cause an 'invalidaccess' in setuserparams. Temporarily set */ |
212 | | /* LockFilePermissions false until the gs_lev2.ps can do a */ |
213 | | /* setuserparams from the restored userparam dictionary. */ |
214 | | /* NOTE: This is safe to do here, since the restore has */ |
215 | | /* successfully completed - this should never come before any */ |
216 | | /* operation that can trigger an error */ |
217 | 44.8k | i_ctx_p->LockFilePermissions = false; |
218 | 44.8k | return 0; |
219 | 44.8k | } |
220 | | |
221 | | int |
222 | | zrestore(i_ctx_t *i_ctx_p) |
223 | 0 | { |
224 | 0 | alloc_save_t *asave; |
225 | 0 | int code = restore_check_save(i_ctx_p, &asave); |
226 | 0 | if (code < 0) |
227 | 0 | return code; |
228 | | |
229 | 0 | return dorestore(i_ctx_p, asave); |
230 | 0 | } |
231 | | |
232 | | /* Check the operand of a restore. */ |
233 | | static int |
234 | | restore_check_operand(os_ptr op, alloc_save_t ** pasave, |
235 | | gs_dual_memory_t *idmem) |
236 | 72.1k | { |
237 | 72.1k | vm_save_t *vmsave; |
238 | 72.1k | ulong sid; |
239 | 72.1k | alloc_save_t *asave; |
240 | | |
241 | 72.1k | check_type(*op, t_save); |
242 | 72.0k | vmsave = r_ptr(op, vm_save_t); |
243 | 72.0k | if (vmsave == 0) /* invalidated save */ |
244 | 0 | return_error(gs_error_invalidrestore); |
245 | 72.0k | sid = op->value.saveid; |
246 | 72.0k | asave = alloc_find_save(idmem, sid); |
247 | 72.0k | if (asave == 0) |
248 | 0 | return_error(gs_error_invalidrestore); |
249 | 72.0k | *pasave = asave; |
250 | 72.0k | return 0; |
251 | 72.0k | } |
252 | | |
253 | | /* Check a stack to make sure all its elements are older than a save. */ |
254 | | static int |
255 | | restore_check_stack(const i_ctx_t *i_ctx_p, const ref_stack_t * pstack, |
256 | | const alloc_save_t * asave, bool is_estack) |
257 | 216k | { |
258 | 216k | ref_stack_enum_t rsenum; |
259 | | |
260 | 216k | ref_stack_enum_begin(&rsenum, pstack); |
261 | 216k | do { |
262 | 216k | const ref *stkp = rsenum.ptr; |
263 | 216k | uint size = rsenum.size; |
264 | | |
265 | 5.58M | for (; size; stkp++, size--) { |
266 | 5.36M | const void *ptr; |
267 | | |
268 | 5.36M | switch (r_type(stkp)) { |
269 | 1.23k | case t_array: |
270 | | /* |
271 | | * Zero-length arrays are a special case: see the |
272 | | * t_*array case (label rr:) in igc.c:gc_trace. |
273 | | */ |
274 | 1.23k | if (r_size(stkp) == 0) { |
275 | | /*stkp->value.refs = (void *)0;*/ |
276 | 417 | continue; |
277 | 417 | } |
278 | 817 | ptr = stkp->value.refs; |
279 | 817 | break; |
280 | 282k | case t_dictionary: |
281 | 282k | ptr = stkp->value.pdict; |
282 | 282k | break; |
283 | 287k | case t_file: |
284 | | /* Don't check executable or closed literal */ |
285 | | /* files on the e-stack. */ |
286 | 287k | { |
287 | 287k | stream *s; |
288 | | |
289 | 287k | if (is_estack && |
290 | 287k | (r_has_attr(stkp, a_executable) || |
291 | 156k | file_is_invalid(s, stkp)) |
292 | 287k | ) |
293 | 150k | continue; |
294 | 287k | } |
295 | 136k | ptr = stkp->value.pfile; |
296 | 136k | break; |
297 | 83.7k | case t_name: |
298 | | /* Names are special because of how they are allocated. */ |
299 | 83.7k | if (alloc_name_is_since_save((const gs_memory_t *)pstack->memory, |
300 | 83.7k | stkp, asave)) |
301 | 0 | return_error(gs_error_invalidrestore); |
302 | 83.7k | continue; |
303 | 83.7k | case t_string: |
304 | | /* Don't check empty executable strings */ |
305 | | /* on the e-stack. */ |
306 | 65.3k | if (r_size(stkp) == 0 && |
307 | 65.3k | r_has_attr(stkp, a_executable) && is_estack |
308 | 65.3k | ) |
309 | 0 | continue; |
310 | 65.3k | ptr = stkp->value.bytes; |
311 | 65.3k | break; |
312 | 610k | case t_mixedarray: |
313 | 709k | case t_shortarray: |
314 | | /* See the t_array case above. */ |
315 | 709k | if (r_size(stkp) == 0) { |
316 | | /*stkp->value.packed = (void *)0;*/ |
317 | 0 | continue; |
318 | 0 | } |
319 | 709k | ptr = stkp->value.packed; |
320 | 709k | break; |
321 | 0 | case t_device: |
322 | 0 | ptr = stkp->value.pdevice; |
323 | 0 | break; |
324 | 0 | case t_fontID: |
325 | 0 | case t_struct: |
326 | 0 | case t_astruct: |
327 | 0 | case t_pdfctx: |
328 | 0 | ptr = stkp->value.pstruct; |
329 | 0 | break; |
330 | 222k | case t_save: |
331 | | /* See the comment in isave.h regarding the following. */ |
332 | 222k | if (i_ctx_p->language_level <= 2) |
333 | 0 | continue; |
334 | 222k | ptr = alloc_find_save(&gs_imemory, stkp->value.saveid); |
335 | | /* |
336 | | * Invalid save objects aren't supposed to be possible |
337 | | * in LL3, but just in case.... |
338 | | */ |
339 | 222k | if (ptr == 0) |
340 | 0 | return_error(gs_error_invalidrestore); |
341 | 222k | if (ptr == asave) |
342 | 99.3k | continue; |
343 | 122k | break; |
344 | 3.71M | default: |
345 | 3.71M | continue; |
346 | 5.36M | } |
347 | 1.31M | if (alloc_is_since_save(ptr, asave)) |
348 | 5 | return_error(gs_error_invalidrestore); |
349 | 1.31M | } |
350 | 216k | } while (ref_stack_enum_next(&rsenum)); |
351 | 216k | return 0; /* OK */ |
352 | 216k | } |
353 | | /* |
354 | | * If the new save level is zero, fix up the contents of a stack |
355 | | * by clearing the l_new bit in all the entries (since we can't tolerate |
356 | | * values with l_new set if the save level is zero). |
357 | | * Also, in any case, fix up the e-stack by replacing empty executable |
358 | | * strings and closed executable files that are newer than the save |
359 | | * with canonical ones that aren't. |
360 | | * |
361 | | * Note that this procedure is only called if restore_check_stack succeeded. |
362 | | */ |
363 | | static void |
364 | | restore_fix_stack(i_ctx_t *i_ctx_p, ref_stack_t * pstack, |
365 | | const alloc_save_t * asave, bool is_estack) |
366 | 134k | { |
367 | 134k | ref_stack_enum_t rsenum; |
368 | | |
369 | 134k | ref_stack_enum_begin(&rsenum, pstack); |
370 | 134k | do { |
371 | 134k | ref *stkp = rsenum.ptr; |
372 | 134k | uint size = rsenum.size; |
373 | | |
374 | 3.57M | for (; size; stkp++, size--) { |
375 | 3.44M | r_clear_attrs(stkp, l_new); /* always do it, no harm */ |
376 | 3.44M | if (is_estack) { |
377 | 2.82M | ref ofile; |
378 | | |
379 | 2.82M | ref_assign(&ofile, stkp); |
380 | 2.82M | switch (r_type(stkp)) { |
381 | 0 | case t_string: |
382 | 0 | if (r_size(stkp) == 0 && |
383 | 0 | alloc_is_since_save(stkp->value.bytes, |
384 | 0 | asave) |
385 | 0 | ) { |
386 | 0 | make_empty_const_string(stkp, |
387 | 0 | avm_foreign); |
388 | 0 | break; |
389 | 0 | } |
390 | 0 | continue; |
391 | 96.9k | case t_file: |
392 | 96.9k | if (alloc_is_since_save(stkp->value.pfile, |
393 | 96.9k | asave) |
394 | 96.9k | ) { |
395 | 0 | make_invalid_file(i_ctx_p, stkp); |
396 | 0 | break; |
397 | 0 | } |
398 | 96.9k | continue; |
399 | 2.73M | default: |
400 | 2.73M | continue; |
401 | 2.82M | } |
402 | 0 | r_copy_attrs(stkp, a_all | a_executable, |
403 | 0 | &ofile); |
404 | 0 | } |
405 | 3.44M | } |
406 | 134k | } while (ref_stack_enum_next(&rsenum)); |
407 | 134k | } |
408 | | |
409 | | /* - vmstatus <save_level> <vm_used> <vm_maximum> */ |
410 | | static int |
411 | | zvmstatus(i_ctx_t *i_ctx_p) |
412 | 1.11M | { |
413 | 1.11M | os_ptr op = osp; |
414 | 1.11M | gs_memory_status_t mstat, dstat; |
415 | | |
416 | 1.11M | gs_memory_status(imemory, &mstat); |
417 | 1.11M | if (imemory == imemory_global) { |
418 | 644k | gs_memory_status_t sstat; |
419 | | |
420 | 644k | gs_memory_status(imemory_system, &sstat); |
421 | 644k | mstat.allocated += sstat.allocated; |
422 | 644k | mstat.used += sstat.used; |
423 | 644k | } |
424 | 1.11M | gs_memory_status(imemory->non_gc_memory, &dstat); |
425 | 1.11M | push(3); |
426 | 1.11M | make_int(op - 2, imemory_save_level(iimemory_local)); |
427 | 1.11M | make_int(op - 1, mstat.used); |
428 | 1.11M | make_int(op, mstat.allocated + dstat.allocated - dstat.used); |
429 | 1.11M | return 0; |
430 | 1.11M | } |
431 | | |
432 | | /* ------ Non-standard extensions ------ */ |
433 | | |
434 | | /* <save> .forgetsave - */ |
435 | | static int |
436 | | zforgetsave(i_ctx_t *i_ctx_p) |
437 | 0 | { |
438 | 0 | os_ptr op = osp; |
439 | 0 | alloc_save_t *asave; |
440 | 0 | vm_save_t *vmsave; |
441 | 0 | int code = restore_check_operand(op, &asave, idmemory); |
442 | |
|
443 | 0 | if (code < 0) |
444 | 0 | return 0; |
445 | 0 | vmsave = alloc_save_client_data(asave); |
446 | | /* Reset l_new in all stack entries if the new save level is zero. */ |
447 | 0 | restore_fix_stack(i_ctx_p, &o_stack, asave, false); |
448 | 0 | restore_fix_stack(i_ctx_p, &e_stack, asave, false); |
449 | 0 | restore_fix_stack(i_ctx_p, &d_stack, asave, false); |
450 | | /* |
451 | | * Forget the gsaves, by deleting the bottom gstate on |
452 | | * the current stack and the top one on the saved stack and then |
453 | | * concatenating the stacks together. |
454 | | */ |
455 | 0 | { |
456 | 0 | gs_gstate *pgs = igs; |
457 | 0 | gs_gstate *last; |
458 | |
|
459 | 0 | while (gs_gstate_saved(last = gs_gstate_saved(pgs)) != 0) |
460 | 0 | pgs = last; |
461 | 0 | gs_gstate_swap_saved(last, vmsave->gsave); |
462 | 0 | gs_grestore(last); |
463 | 0 | gs_grestore(last); |
464 | 0 | } |
465 | | /* Forget the save in the memory manager. */ |
466 | 0 | code = alloc_forget_save_in(idmemory, asave); |
467 | 0 | if (code < 0) |
468 | 0 | return code; |
469 | 0 | { |
470 | 0 | uint space = icurrent_space; |
471 | |
|
472 | 0 | ialloc_set_space(idmemory, avm_local); |
473 | | /* See above for why we clear the gsave pointer here. */ |
474 | 0 | vmsave->gsave = 0; |
475 | 0 | ifree_object(vmsave, "zrestore"); |
476 | 0 | ialloc_set_space(idmemory, space); |
477 | 0 | } |
478 | 0 | pop(1); |
479 | 0 | return 0; |
480 | 0 | } |
481 | | |
482 | | /* ------ Initialization procedure ------ */ |
483 | | |
484 | | const op_def zvmem_op_defs[] = |
485 | | { |
486 | | {"1.forgetsave", zforgetsave}, |
487 | | {"1restore", zrestore}, |
488 | | {"0save", zsave}, |
489 | | {"0vmstatus", zvmstatus}, |
490 | | op_def_end(0) |
491 | | }; |