Coverage Report

Created: 2026-09-14 07:34

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/ghostpdl/psi/zfproc.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
/* Procedure-based filter stream support */
18
#include "memory_.h"
19
#include "stat_.h" /* get system header early to avoid name clash on Cygwin */
20
#include "ghost.h"
21
#include "oper.h"   /* for ifilter.h */
22
#include "estack.h"
23
#include "gsstruct.h"
24
#include "ialloc.h"
25
#include "istruct.h"    /* for RELOC_REF_VAR */
26
#include "stream.h"
27
#include "strimpl.h"
28
#include "ifilter.h"
29
#include "files.h"
30
#include "store.h"
31
32
#include "isave.h"
33
#include "isstate.h"
34
/* ---------------- Generic ---------------- */
35
36
/* GC procedures */
37
static
38
CLEAR_MARKS_PROC(sproc_clear_marks)
39
692k
{
40
692k
    stream_proc_state *const pptr = vptr;
41
42
692k
    r_clear_attrs(&pptr->proc, l_mark);
43
692k
    r_clear_attrs(&pptr->data, l_mark);
44
692k
}
45
static
46
65.5k
ENUM_PTRS_WITH(sproc_enum_ptrs, stream_proc_state *pptr) return 0;
47
21.8k
case 0:
48
21.8k
ENUM_RETURN_REF(&pptr->proc);
49
21.8k
case 1:
50
21.8k
ENUM_RETURN_REF(&pptr->data);
51
65.5k
ENUM_PTRS_END
52
21.8k
static RELOC_PTRS_WITH(sproc_reloc_ptrs, stream_proc_state *pptr);
53
21.8k
RELOC_REF_VAR(pptr->proc);
54
21.8k
r_clear_attrs(&pptr->proc, l_mark);
55
21.8k
RELOC_REF_VAR(pptr->data);
56
21.8k
r_clear_attrs(&pptr->data, l_mark);
57
21.8k
RELOC_PTRS_END
58
59
/* Structure type for procedure-based streams. */
60
private_st_stream_proc_state();
61
62
/* Allocate and open a procedure-based filter. */
63
/* The caller must have checked that *sop is a procedure. */
64
static int
65
s_proc_init(ref * sop, stream ** psstrm, uint mode,
66
            const stream_template * temp, const stream_procs * procs,
67
            gs_ref_memory_t *imem)
68
942k
{
69
942k
    gs_memory_t *const mem = (gs_memory_t *)imem;
70
942k
    stream *sstrm = file_alloc_stream(mem, "s_proc_init(stream)");
71
942k
    stream_proc_state *state = (stream_proc_state *)
72
942k
        s_alloc_state(mem, &st_sproc_state, "s_proc_init(state)");
73
74
942k
    if (sstrm == 0 || state == 0) {
75
0
        gs_free_object(mem, state, "s_proc_init(state)");
76
        /*gs_free_object(mem, sstrm, "s_proc_init(stream)"); *//* just leave it on the file list */
77
0
        return_error(gs_error_VMerror);
78
0
    }
79
942k
    s_std_init(sstrm, NULL, 0, procs, mode);
80
942k
    sstrm->procs.process = temp->process;
81
942k
    state->templat = temp;
82
942k
    state->memory = mem;
83
942k
    state->eof = 0;
84
942k
    state->proc = *sop;
85
942k
    make_empty_string(&state->data, a_all);
86
942k
    state->data_memory = NULL;
87
942k
    state->data_save_id = 0;
88
942k
    state->index = 0;
89
942k
    sstrm->state = (stream_state *) state;
90
942k
    *psstrm = sstrm;
91
942k
    return 0;
92
942k
}
93
94
/*
95
 * Note where the data string just stored in ss->data was allocated, and
96
 * which save was innermost at that time (if the space the string was created
97
 * in was local VM). The procedure may hand us a string allocated after an
98
 *  enclosing 'save' while the stream state itself predates that save;
99
 * if that save is restored, the string is freed but the stream survives,
100
 * so we must be able to detect this before using the string again.
101
 * The PLRM is explicit that PostScript program should not do this, and the
102
 * result is 'unpredictable'.
103
 */
104
static void
105
s_proc_record_data(i_ctx_t *i_ctx_p, stream_proc_state *ss, const ref *pdata)
106
1.78M
{
107
1.78M
    int space_index = r_space_index(pdata);
108
1.78M
    gs_ref_memory_t *mem;
109
110
1.78M
    ss->data_memory = NULL;
111
1.78M
    ss->data_save_id = 0;
112
1.78M
    if (space_index != i_vm_local)
113
1.78M
        return;      /* global, foreign or system VM: not restorable */
114
0
    mem = idmemory->spaces_indexed[space_index];
115
0
    if (mem == NULL || mem->saved == NULL)
116
0
        return;     /* no save active: string cannot be restored away */
117
0
    ss->data_memory = mem;
118
    /* Save ids are unique and never reused; 0 means 'invisible' save. */
119
0
    ss->data_save_id = (mem->saved->id != 0 ? mem->saved->id : (ulong)-1);
120
0
}
121
122
/*
123
 * Check that the string in ss->data has not been freed by a restore,
124
 * i.e. that the save that was innermost when it was stored is still
125
 * active.  If it is not, the string may no longer exist: replace the
126
 * dangling reference with an empty string so it cannot be used.
127
 * Returns true if ss->data is safe to use.
128
 */
129
static bool
130
s_proc_data_valid(stream_proc_state *ss)
131
3.49M
{
132
3.49M
    const alloc_save_t *save;
133
134
3.49M
    if (ss->data_memory == NULL)
135
3.49M
        return true;
136
0
    for (save = ss->data_memory->saved; save != NULL;
137
0
        save = save->state.saved)
138
0
        if (save->id == ss->data_save_id)
139
0
            return true;
140
0
    make_empty_string(&ss->data, a_all);
141
0
    ss->index = 0;
142
0
    ss->data_memory = NULL;
143
0
    ss->data_save_id = 0;
144
0
    return false;
145
0
}
146
147
/* Handle an interrupt during a stream operation. */
148
/* This is logically unrelated to procedure streams, */
149
/* but it is also associated with the interpreter stream machinery. */
150
static int
151
s_handle_intc(i_ctx_t *i_ctx_p, const ref *pstate, int nstate,
152
              op_proc_t cont)
153
0
{
154
0
    int npush = nstate + 2;
155
156
0
    check_estack(npush);
157
0
    if (nstate)
158
0
        memcpy(esp + 2, pstate, nstate * sizeof(ref));
159
#if 0       /* **************** */
160
    {
161
        int code = gs_interpret_error(gs_error_interrupt, (ref *) (esp + npush));
162
163
        if (code < 0)
164
            return code;
165
    }
166
#else /* **************** */
167
0
    npush--;
168
0
#endif /* **************** */
169
0
    make_op_estack(esp + 1, cont);
170
0
    esp += npush;
171
0
    return o_push_estack;
172
0
}
173
174
/* Set default parameter values (actually, just clear pointers). */
175
static void
176
s_proc_set_defaults(stream_state * st)
177
0
{
178
0
    stream_proc_state *const ss = (stream_proc_state *) st;
179
180
0
    make_null(&ss->proc);
181
0
    make_null(&ss->data);
182
0
}
183
184
static int s_proc_copy_string(i_ctx_t * i_ctx_p, ref *dest, ref *src, uint mem)
185
0
{
186
0
    int code = 0;
187
0
    uint saved_space = avm_local;
188
189
0
    saved_space = imemory_space(iimemory);
190
191
0
    if (imemory_space(iimemory) != mem)
192
0
        ialloc_set_space(idmemory, mem);
193
194
0
    code = gs_alloc_string_ref(iimemory, dest, 0, r_size(src), "copy_cspace_string");
195
196
0
    if (imemory_space(iimemory) != saved_space)
197
0
        ialloc_set_space(idmemory, saved_space);
198
199
0
    if (code < 0)
200
0
        return code;
201
202
0
    r_copy_attrs(dest, a_all, src);
203
204
0
    memcpy(dest->value.bytes, src->value.bytes, r_size(src));
205
0
    return 0;
206
0
}
207
208
/* ---------------- Read streams ---------------- */
209
210
/* Forward references */
211
static stream_proc_process(s_proc_read_process);
212
static int s_proc_read_continue(i_ctx_t *);
213
214
/* Stream templates */
215
static const stream_template s_proc_read_template = {
216
    &st_sproc_state, NULL, s_proc_read_process, 1, 1,
217
    NULL, s_proc_set_defaults
218
};
219
static const stream_procs s_proc_read_procs = {
220
    s_std_noavailable, s_std_noseek, s_std_read_reset,
221
    s_std_read_flush, s_std_null, NULL
222
};
223
224
/* Allocate and open a procedure-based read stream. */
225
/* The caller must have checked that *sop is a procedure. */
226
int
227
sread_proc(ref * sop, stream ** psstrm, gs_ref_memory_t *imem)
228
874k
{
229
874k
    int code =
230
874k
        s_proc_init(sop, psstrm, s_mode_read, &s_proc_read_template,
231
874k
                    &s_proc_read_procs, imem);
232
233
874k
    if (code < 0)
234
0
        return code;
235
874k
    (*psstrm)->end_status = CALLC;
236
874k
    return code;
237
874k
}
238
239
/* Handle an input request. */
240
static int
241
s_proc_read_process(stream_state * st, stream_cursor_read * ignore_pr,
242
                    stream_cursor_write * pw, bool last)
243
3.06M
{
244
    /* Move data from the string returned by the procedure */
245
    /* into the stream buffer, or ask for a callback. */
246
3.06M
    stream_proc_state *const ss = (stream_proc_state *) st;
247
3.06M
    uint count;
248
249
3.06M
    if (!s_proc_data_valid(ss))
250
0
        return_error(gs_error_ioerror);
251
3.06M
    count = r_size(&ss->data) - ss->index;
252
253
3.06M
    if (count > 0) {
254
1.46M
        uint wcount = pw->limit - pw->ptr;
255
256
1.46M
        if (wcount < count)
257
588k
            count = wcount;
258
1.46M
        memcpy(pw->ptr + 1, ss->data.value.bytes + ss->index, count);
259
1.46M
        pw->ptr += count;
260
1.46M
        ss->index += count;
261
1.46M
        return 1;
262
1.46M
    }
263
1.60M
    return (ss->eof ? EOFC : CALLC);
264
3.06M
}
265
266
/* Handle an exception (INTC or CALLC) from a read stream */
267
/* whose buffer is empty. */
268
int
269
s_handle_read_exception(i_ctx_t *i_ctx_p, int status, const ref * fop,
270
                        const ref * pstate, int nstate, op_proc_t cont)
271
1.60M
{
272
1.60M
    int npush = nstate + 4;
273
1.60M
    stream *ps;
274
275
1.60M
    switch (status) {
276
0
        case INTC:
277
0
            return s_handle_intc(i_ctx_p, pstate, nstate, cont);
278
1.60M
        case CALLC:
279
1.60M
            break;
280
0
        default:
281
0
            return_error(gs_error_ioerror);
282
1.60M
    }
283
    /* Find the stream whose buffer needs refilling. */
284
3.20M
    for (ps = fptr(fop); ps->strm != 0;)
285
1.60M
        ps = ps->strm;
286
1.60M
    check_estack(npush);
287
1.60M
    if (nstate)
288
1.60M
        memcpy(esp + 2, pstate, nstate * sizeof(ref));
289
1.60M
    make_op_estack(esp + 1, cont);
290
1.60M
    esp += npush;
291
1.60M
    make_op_estack(esp - 2, s_proc_read_continue);
292
1.60M
    esp[-1] = *fop;
293
1.60M
    r_clear_attrs(esp - 1, a_executable);
294
1.60M
    *esp = ((stream_proc_state *) ps->state)->proc;
295
1.60M
    return o_push_estack;
296
1.60M
}
297
/* Continue a read operation after returning from a procedure callout. */
298
/* osp[0] contains the file (pushed on the e-stack by handle_read_status); */
299
/* osp[-1] contains the new data string (pushed by the procedure). */
300
/* The top of the e-stack contains the real continuation. */
301
static int
302
s_proc_read_continue(i_ctx_t *i_ctx_p)
303
1.60M
{
304
1.60M
    os_ptr op = osp;
305
1.60M
    os_ptr opbuf = op - 1;
306
1.60M
    stream *ps;
307
1.60M
    stream_proc_state *ss;
308
1.60M
    uint s1, s2;
309
310
1.60M
    check_file(ps, op);
311
1.60M
    check_read_type(*opbuf, t_string);
312
3.20M
    while ((ps->end_status = 0, ps->strm) != 0)
313
1.60M
        ps = ps->strm;
314
1.60M
    s1 = r_space(op);
315
1.60M
    s2 = r_space(opbuf);
316
1.60M
    if (!r_is_local(op) && r_is_local(opbuf)) {
317
0
        ref copy;
318
0
        int code = 0;
319
320
0
        code = s_proc_copy_string(i_ctx_p, &copy, opbuf, r_space(op));
321
0
        if (code < 0)
322
0
            return code;
323
0
        *opbuf = copy;
324
0
    }
325
1.60M
    ss = (stream_proc_state *) ps->state;
326
1.60M
    ss->data = *opbuf;
327
1.60M
    s_proc_record_data(i_ctx_p, ss, opbuf);
328
1.60M
    ss->index = 0;
329
1.60M
    if (r_size(opbuf) == 0)
330
727k
        ss->eof = true;
331
1.60M
    pop(2);
332
1.60M
    return 0;
333
1.60M
}
334
335
/* ---------------- Write streams ---------------- */
336
337
/* Forward references */
338
static stream_proc_flush(s_proc_write_flush);
339
static stream_proc_process(s_proc_write_process);
340
static int s_proc_write_continue(i_ctx_t *);
341
342
/* Stream templates */
343
static const stream_template s_proc_write_template = {
344
    &st_sproc_state, NULL, s_proc_write_process, 1, 1,
345
    NULL, s_proc_set_defaults
346
};
347
static const stream_procs s_proc_write_procs = {
348
    s_std_noavailable, s_std_noseek, s_std_write_reset,
349
    s_proc_write_flush, s_std_null, NULL
350
};
351
352
/* Allocate and open a procedure-based write stream. */
353
/* The caller must have checked that *sop is a procedure. */
354
int
355
swrite_proc(ref * sop, stream ** psstrm, gs_ref_memory_t *imem)
356
67.5k
{
357
67.5k
    return s_proc_init(sop, psstrm, s_mode_write, &s_proc_write_template,
358
67.5k
                       &s_proc_write_procs, imem);
359
67.5k
}
360
361
/* Handle an output request. */
362
static int
363
s_proc_write_process(stream_state * st, stream_cursor_read * pr,
364
                     stream_cursor_write * ignore_pw, bool last)
365
250k
{
366
    /* Move data from the stream buffer to the string */
367
    /* returned by the procedure, or ask for a callback. */
368
250k
    stream_proc_state *const ss = (stream_proc_state *) st;
369
250k
    uint rcount = pr->limit - pr->ptr;
370
371
250k
    if (!s_proc_data_valid(ss))
372
0
        return_error(gs_error_ioerror);
373
    /* if 'last' return CALLC even when rcount == 0. ss->eof terminates */
374
250k
    if (rcount > 0 || (last && !ss->eof)) {
375
183k
        uint wcount = r_size(&ss->data) - ss->index;
376
183k
        uint count = min(rcount, wcount);
377
378
183k
        memcpy(ss->data.value.bytes + ss->index, pr->ptr + 1, count);
379
183k
        pr->ptr += count;
380
183k
        ss->index += count;
381
183k
        if (rcount > wcount)
382
115k
            return CALLC;
383
67.5k
        else if (last) {
384
67.5k
            ss->eof = true;
385
67.5k
            return CALLC;
386
67.5k
        } else
387
0
            return 0;
388
183k
    }
389
67.5k
    return ((ss->eof = last) ? EOFC : 0);
390
250k
}
391
392
/* Flush the output.  This is non-standard because it must call the */
393
/* procedure. */
394
static int
395
s_proc_write_flush(stream *s)
396
0
{
397
0
    int result = s_process_write_buf(s, false);
398
0
    stream_proc_state *const ss = (stream_proc_state *)s->state;
399
400
0
    return (result < 0 || ss->index == 0 ? result : CALLC);
401
0
}
402
403
/* Handle an exception (INTC or CALLC) from a write stream */
404
/* whose buffer is full. */
405
int
406
s_handle_write_exception(i_ctx_t *i_ctx_p, int status, const ref * fop,
407
                         const ref * pstate, int nstate, op_proc_t cont)
408
183k
{
409
183k
    stream *ps;
410
183k
    stream_proc_state *psst;
411
412
183k
    switch (status) {
413
0
        case INTC:
414
0
            return s_handle_intc(i_ctx_p, pstate, nstate, cont);
415
183k
        case CALLC:
416
183k
            break;
417
0
        default:
418
0
            return_error(gs_error_ioerror);
419
183k
    }
420
    /* Find the stream whose buffer needs emptying. */
421
366k
    for (ps = fptr(fop); ps->strm != 0;)
422
183k
        ps = ps->strm;
423
183k
    psst = (stream_proc_state *) ps->state;
424
183k
    if (!s_proc_data_valid(psst))
425
0
        return_error(gs_error_ioerror);
426
183k
    {
427
183k
        int npush = nstate + 6;
428
429
183k
        check_estack(npush);
430
183k
        if (nstate)
431
0
            memcpy(esp + 2, pstate, nstate * sizeof(ref));
432
183k
        make_op_estack(esp + 1, cont);
433
183k
        esp += npush;
434
183k
        make_op_estack(esp - 4, s_proc_write_continue);
435
183k
        esp[-3] = *fop;
436
183k
        r_clear_attrs(esp - 3, a_executable);
437
183k
        make_bool(esp - 1, !psst->eof);
438
183k
    }
439
183k
    esp[-2] = psst->proc;
440
183k
    *esp = psst->data;
441
183k
    r_set_size(esp, psst->index);
442
183k
    return o_push_estack;
443
183k
}
444
/* Continue a write operation after returning from a procedure callout. */
445
/* osp[0] contains the file (pushed on the e-stack by handle_write_status); */
446
/* osp[-1] contains the new buffer string (pushed by the procedure). */
447
/* The top of the e-stack contains the real continuation. */
448
static int
449
s_proc_write_continue(i_ctx_t *i_ctx_p)
450
183k
{
451
183k
    os_ptr op = osp;
452
183k
    os_ptr opbuf = op - 1;
453
183k
    stream *ps;
454
183k
    stream_proc_state *ss;
455
456
183k
    check_file(ps, op);
457
183k
    check_write_type(*opbuf, t_string);
458
366k
    while (ps->strm != 0) {
459
183k
        if (ps->end_status == CALLC)
460
0
            ps->end_status = 0;
461
183k
        ps = ps->strm;
462
183k
    }
463
183k
    ps->end_status = 0;
464
183k
    if (!r_is_local(op) && r_is_local(opbuf)) {
465
0
        ref copy;
466
0
        int code = 0;
467
468
0
        code = s_proc_copy_string(i_ctx_p, &copy, opbuf, r_space(op));
469
0
        if (code < 0)
470
0
            return code;
471
0
        *opbuf = copy;
472
0
    }
473
183k
    ss = (stream_proc_state *) ps->state;
474
183k
    ss->data = *opbuf;
475
183k
    s_proc_record_data(i_ctx_p, ss, opbuf);
476
183k
    ss->index = 0;
477
183k
    pop(2);
478
183k
    return 0;
479
183k
}
480
481
/* ------ More generic ------ */
482
483
/* Test whether a stream is procedure-based. */
484
bool
485
s_is_proc(const stream *s)
486
0
{
487
0
    return (s->procs.process == s_proc_read_process ||
488
0
            s->procs.process == s_proc_write_process);
489
0
}
490
491
/* ------ Initialization procedure ------ */
492
493
const op_def zfproc_op_defs[] =
494
{
495
                /* Internal operators */
496
    {"2%s_proc_read_continue", s_proc_read_continue},
497
    {"2%s_proc_write_continue", s_proc_write_continue},
498
    op_def_end(0)
499
};