/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 | | }; |