Coverage Report

Created: 2026-10-28 08:05

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
756k
{
40
756k
    stream_proc_state *const pptr = vptr;
41
42
756k
    r_clear_attrs(&pptr->proc, l_mark);
43
756k
    r_clear_attrs(&pptr->data, l_mark);
44
756k
}
45
static
46
68.3k
ENUM_PTRS_WITH(sproc_enum_ptrs, stream_proc_state *pptr) return 0;
47
22.7k
case 0:
48
22.7k
ENUM_RETURN_REF(&pptr->proc);
49
22.7k
case 1:
50
22.7k
ENUM_RETURN_REF(&pptr->data);
51
68.3k
ENUM_PTRS_END
52
22.7k
static RELOC_PTRS_WITH(sproc_reloc_ptrs, stream_proc_state *pptr);
53
22.7k
RELOC_REF_VAR(pptr->proc);
54
22.7k
r_clear_attrs(&pptr->proc, l_mark);
55
22.7k
RELOC_REF_VAR(pptr->data);
56
22.7k
r_clear_attrs(&pptr->data, l_mark);
57
22.7k
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
1.03M
{
69
1.03M
    gs_memory_t *const mem = (gs_memory_t *)imem;
70
1.03M
    stream *sstrm = file_alloc_stream(mem, "s_proc_init(stream)");
71
1.03M
    stream_proc_state *state = (stream_proc_state *)
72
1.03M
        s_alloc_state(mem, &st_sproc_state, "s_proc_init(state)");
73
74
1.03M
    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
1.03M
    s_std_init(sstrm, NULL, 0, procs, mode);
80
1.03M
    sstrm->procs.process = temp->process;
81
1.03M
    state->templat = temp;
82
1.03M
    state->memory = mem;
83
1.03M
    state->eof = 0;
84
1.03M
    state->proc = *sop;
85
1.03M
    make_empty_string(&state->data, a_all);
86
1.03M
    state->data_memory = NULL;
87
1.03M
    state->data_save_id = 0;
88
1.03M
    state->index = 0;
89
1.03M
    sstrm->state = (stream_state *) state;
90
1.03M
    *psstrm = sstrm;
91
1.03M
    return 0;
92
1.03M
}
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.95M
{
107
1.95M
    int space_index = r_space_index(pdata);
108
1.95M
    gs_ref_memory_t *mem;
109
110
1.95M
    ss->data_memory = NULL;
111
1.95M
    ss->data_save_id = 0;
112
1.95M
    if (space_index != i_vm_local)
113
1.95M
        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.84M
{
132
3.84M
    const alloc_save_t *save;
133
134
3.84M
    if (ss->data_memory == NULL)
135
3.84M
        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
960k
{
229
960k
    int code =
230
960k
        s_proc_init(sop, psstrm, s_mode_read, &s_proc_read_template,
231
960k
                    &s_proc_read_procs, imem);
232
233
960k
    if (code < 0)
234
0
        return code;
235
960k
    (*psstrm)->end_status = CALLC;
236
960k
    return code;
237
960k
}
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.36M
{
244
    /* Move data from the string returned by the procedure */
245
    /* into the stream buffer, or ask for a callback. */
246
3.36M
    stream_proc_state *const ss = (stream_proc_state *) st;
247
3.36M
    uint count;
248
249
3.36M
    if (!s_proc_data_valid(ss))
250
0
        return_error(gs_error_ioerror);
251
3.36M
    count = r_size(&ss->data) - ss->index;
252
253
3.36M
    if (count > 0) {
254
1.61M
        uint wcount = pw->limit - pw->ptr;
255
256
1.61M
        if (wcount < count)
257
649k
            count = wcount;
258
1.61M
        memcpy(pw->ptr + 1, ss->data.value.bytes + ss->index, count);
259
1.61M
        pw->ptr += count;
260
1.61M
        ss->index += count;
261
1.61M
        return 1;
262
1.61M
    }
263
1.75M
    return (ss->eof ? EOFC : CALLC);
264
3.36M
}
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.75M
{
272
1.75M
    int npush = nstate + 4;
273
1.75M
    stream *ps;
274
275
1.75M
    switch (status) {
276
0
        case INTC:
277
0
            return s_handle_intc(i_ctx_p, pstate, nstate, cont);
278
1.75M
        case CALLC:
279
1.75M
            break;
280
0
        default:
281
0
            return_error(gs_error_ioerror);
282
1.75M
    }
283
    /* Find the stream whose buffer needs refilling. */
284
3.51M
    for (ps = fptr(fop); ps->strm != 0;)
285
1.75M
        ps = ps->strm;
286
1.75M
    check_estack(npush);
287
1.75M
    if (nstate)
288
1.75M
        memcpy(esp + 2, pstate, nstate * sizeof(ref));
289
1.75M
    make_op_estack(esp + 1, cont);
290
1.75M
    esp += npush;
291
1.75M
    make_op_estack(esp - 2, s_proc_read_continue);
292
1.75M
    esp[-1] = *fop;
293
1.75M
    r_clear_attrs(esp - 1, a_executable);
294
1.75M
    *esp = ((stream_proc_state *) ps->state)->proc;
295
1.75M
    return o_push_estack;
296
1.75M
}
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.75M
{
304
1.75M
    os_ptr op = osp;
305
1.75M
    os_ptr opbuf = op - 1;
306
1.75M
    stream *ps;
307
1.75M
    stream_proc_state *ss;
308
309
1.75M
    check_file(ps, op);
310
1.75M
    check_read_type(*opbuf, t_string);
311
3.51M
    while ((ps->end_status = 0, ps->strm) != 0)
312
1.75M
        ps = ps->strm;
313
1.75M
    if (!r_is_local(op) && r_is_local(opbuf)) {
314
0
        ref copy;
315
0
        int code = 0;
316
317
0
        code = s_proc_copy_string(i_ctx_p, &copy, opbuf, r_space(op));
318
0
        if (code < 0)
319
0
            return code;
320
0
        *opbuf = copy;
321
0
    }
322
1.75M
    ss = (stream_proc_state *) ps->state;
323
1.75M
    ss->data = *opbuf;
324
1.75M
    s_proc_record_data(i_ctx_p, ss, opbuf);
325
1.75M
    ss->index = 0;
326
1.75M
    if (r_size(opbuf) == 0)
327
798k
        ss->eof = true;
328
1.75M
    pop(2);
329
1.75M
    return 0;
330
1.75M
}
331
332
/* ---------------- Write streams ---------------- */
333
334
/* Forward references */
335
static stream_proc_flush(s_proc_write_flush);
336
static stream_proc_process(s_proc_write_process);
337
static int s_proc_write_continue(i_ctx_t *);
338
339
/* Stream templates */
340
static const stream_template s_proc_write_template = {
341
    &st_sproc_state, NULL, s_proc_write_process, 1, 1,
342
    NULL, s_proc_set_defaults
343
};
344
static const stream_procs s_proc_write_procs = {
345
    s_std_noavailable, s_std_noseek, s_std_write_reset,
346
    s_proc_write_flush, s_std_null, NULL
347
};
348
349
/* Allocate and open a procedure-based write stream. */
350
/* The caller must have checked that *sop is a procedure. */
351
int
352
swrite_proc(ref * sop, stream ** psstrm, gs_ref_memory_t *imem)
353
74.5k
{
354
74.5k
    return s_proc_init(sop, psstrm, s_mode_write, &s_proc_write_template,
355
74.5k
                       &s_proc_write_procs, imem);
356
74.5k
}
357
358
/* Handle an output request. */
359
static int
360
s_proc_write_process(stream_state * st, stream_cursor_read * pr,
361
                     stream_cursor_write * ignore_pw, bool last)
362
272k
{
363
    /* Move data from the stream buffer to the string */
364
    /* returned by the procedure, or ask for a callback. */
365
272k
    stream_proc_state *const ss = (stream_proc_state *) st;
366
272k
    uint rcount = pr->limit - pr->ptr;
367
368
272k
    if (!s_proc_data_valid(ss))
369
0
        return_error(gs_error_ioerror);
370
    /* if 'last' return CALLC even when rcount == 0. ss->eof terminates */
371
272k
    if (rcount > 0 || (last && !ss->eof)) {
372
198k
        uint wcount = r_size(&ss->data) - ss->index;
373
198k
        uint count = min(rcount, wcount);
374
375
198k
        memcpy(ss->data.value.bytes + ss->index, pr->ptr + 1, count);
376
198k
        pr->ptr += count;
377
198k
        ss->index += count;
378
198k
        if (rcount > wcount)
379
123k
            return CALLC;
380
74.5k
        else if (last) {
381
74.5k
            ss->eof = true;
382
74.5k
            return CALLC;
383
74.5k
        } else
384
0
            return 0;
385
198k
    }
386
74.5k
    return ((ss->eof = last) ? EOFC : 0);
387
272k
}
388
389
/* Flush the output.  This is non-standard because it must call the */
390
/* procedure. */
391
static int
392
s_proc_write_flush(stream *s)
393
0
{
394
0
    int result = s_process_write_buf(s, false);
395
0
    stream_proc_state *const ss = (stream_proc_state *)s->state;
396
397
0
    return (result < 0 || ss->index == 0 ? result : CALLC);
398
0
}
399
400
/* Handle an exception (INTC or CALLC) from a write stream */
401
/* whose buffer is full. */
402
int
403
s_handle_write_exception(i_ctx_t *i_ctx_p, int status, const ref * fop,
404
                         const ref * pstate, int nstate, op_proc_t cont)
405
198k
{
406
198k
    stream *ps;
407
198k
    stream_proc_state *psst;
408
409
198k
    switch (status) {
410
0
        case INTC:
411
0
            return s_handle_intc(i_ctx_p, pstate, nstate, cont);
412
198k
        case CALLC:
413
198k
            break;
414
0
        default:
415
0
            return_error(gs_error_ioerror);
416
198k
    }
417
    /* Find the stream whose buffer needs emptying. */
418
396k
    for (ps = fptr(fop); ps->strm != 0;)
419
198k
        ps = ps->strm;
420
198k
    psst = (stream_proc_state *) ps->state;
421
198k
    if (!s_proc_data_valid(psst))
422
0
        return_error(gs_error_ioerror);
423
198k
    {
424
198k
        int npush = nstate + 6;
425
426
198k
        check_estack(npush);
427
198k
        if (nstate)
428
0
            memcpy(esp + 2, pstate, nstate * sizeof(ref));
429
198k
        make_op_estack(esp + 1, cont);
430
198k
        esp += npush;
431
198k
        make_op_estack(esp - 4, s_proc_write_continue);
432
198k
        esp[-3] = *fop;
433
198k
        r_clear_attrs(esp - 3, a_executable);
434
198k
        make_bool(esp - 1, !psst->eof);
435
198k
    }
436
198k
    esp[-2] = psst->proc;
437
198k
    *esp = psst->data;
438
198k
    r_set_size(esp, psst->index);
439
198k
    return o_push_estack;
440
198k
}
441
/* Continue a write operation after returning from a procedure callout. */
442
/* osp[0] contains the file (pushed on the e-stack by handle_write_status); */
443
/* osp[-1] contains the new buffer string (pushed by the procedure). */
444
/* The top of the e-stack contains the real continuation. */
445
static int
446
s_proc_write_continue(i_ctx_t *i_ctx_p)
447
198k
{
448
198k
    os_ptr op = osp;
449
198k
    os_ptr opbuf = op - 1;
450
198k
    stream *ps;
451
198k
    stream_proc_state *ss;
452
453
198k
    check_file(ps, op);
454
198k
    check_write_type(*opbuf, t_string);
455
396k
    while (ps->strm != 0) {
456
198k
        if (ps->end_status == CALLC)
457
0
            ps->end_status = 0;
458
198k
        ps = ps->strm;
459
198k
    }
460
198k
    ps->end_status = 0;
461
198k
    if (!r_is_local(op) && r_is_local(opbuf)) {
462
0
        ref copy;
463
0
        int code = 0;
464
465
0
        code = s_proc_copy_string(i_ctx_p, &copy, opbuf, r_space(op));
466
0
        if (code < 0)
467
0
            return code;
468
0
        *opbuf = copy;
469
0
    }
470
198k
    ss = (stream_proc_state *) ps->state;
471
198k
    ss->data = *opbuf;
472
198k
    s_proc_record_data(i_ctx_p, ss, opbuf);
473
198k
    ss->index = 0;
474
198k
    pop(2);
475
198k
    return 0;
476
198k
}
477
478
/* ------ More generic ------ */
479
480
/* Test whether a stream is procedure-based. */
481
bool
482
s_is_proc(const stream *s)
483
0
{
484
0
    return (s->procs.process == s_proc_read_process ||
485
0
            s->procs.process == s_proc_write_process);
486
0
}
487
488
/* ------ Initialization procedure ------ */
489
490
const op_def zfproc_op_defs[] =
491
{
492
                /* Internal operators */
493
    {"2%s_proc_read_continue", s_proc_read_continue},
494
    {"2%s_proc_write_continue", s_proc_write_continue},
495
    op_def_end(0)
496
};