Coverage Report

Created: 2026-06-16 06:38

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/qpdf/libqpdf/Pl_RunLength.cc
Line
Count
Source
1
#include <qpdf/Pl_RunLength.hh>
2
3
#include <qpdf/QTC.hh>
4
#include <qpdf/Util.hh>
5
#include <qpdf/global_private.hh>
6
7
using namespace qpdf;
8
9
namespace
10
{
11
    static unsigned long long const& memory_limit{global::Limits::run_length_max_memory()};
12
} // namespace
13
14
class Pl_RunLength::Members
15
{
16
  public:
17
    Members(action_e action) :
18
0
        action(action)
19
0
    {
20
0
    }
21
    Members(Members const&) = delete;
22
0
    ~Members() = default;
23
24
    action_e action;
25
    state_e state{st_top};
26
    unsigned char buf[128];
27
    unsigned int length{0};
28
    std::string out;
29
};
30
31
Pl_RunLength::Pl_RunLength(char const* identifier, Pipeline* next, action_e action) :
32
0
    Pipeline(identifier, next),
33
0
    m(std::make_unique<Members>(action))
34
0
{
35
0
    util::assertion(next, "Attempt to create Pl_RunLength with nullptr as next");
36
0
}
37
38
void
39
Pl_RunLength::setMemoryLimit(unsigned long long limit)
40
0
{
41
0
    global::Limits::run_length_max_memory(limit);
42
0
}
43
44
0
Pl_RunLength::~Pl_RunLength() = default;
45
46
void
47
Pl_RunLength::write(unsigned char const* data, size_t len)
48
0
{
49
0
    if (m->action == a_encode) {
50
0
        encode(data, len);
51
0
    } else {
52
0
        decode(data, len);
53
0
    }
54
0
}
55
56
void
57
Pl_RunLength::encode(unsigned char const* data, size_t len)
58
0
{
59
0
    for (size_t i = 0; i < len; ++i) {
60
0
        util::assertion(
61
0
            (m->state == st_top) == (m->length <= 1),
62
0
            "Pl_RunLength::encode: state/length inconsistency");
63
0
        unsigned char ch = data[i];
64
0
        if (m->length > 0 && (m->state == st_copying || m->length < 128) &&
65
0
            ch == m->buf[m->length - 1]) {
66
0
            QTC::TC("libtests", "Pl_RunLength: switch to run", (m->length == 128) ? 0 : 1);
67
0
            if (m->state == st_copying) {
68
0
                --m->length;
69
0
                flush_encode();
70
0
                m->buf[0] = ch;
71
0
                m->length = 1;
72
0
            }
73
0
            m->state = st_run;
74
0
            m->buf[m->length] = ch;
75
0
            ++m->length;
76
0
        } else {
77
0
            if (m->length == 128 || m->state == st_run) {
78
0
                flush_encode();
79
0
            } else if (m->length > 0) {
80
0
                m->state = st_copying;
81
0
            }
82
0
            m->buf[m->length] = ch;
83
0
            ++m->length;
84
0
        }
85
0
    }
86
0
}
87
88
void
89
Pl_RunLength::decode(unsigned char const* data, size_t len)
90
0
{
91
0
    util::no_ci_rt_error_if(
92
0
        memory_limit && (len + m->out.size()) > memory_limit, "Pl_RunLength memory limit exceeded");
93
0
    m->out.reserve(len);
94
0
    for (size_t i = 0; i < len; ++i) {
95
0
        unsigned char const& ch = data[i];
96
0
        switch (m->state) {
97
0
        case st_top:
98
0
            if (ch < 128) {
99
                // length represents remaining number of bytes to copy
100
0
                m->length = 1U + ch;
101
0
                m->state = st_copying;
102
0
            } else if (ch > 128) {
103
                // length represents number of copies of next byte
104
0
                m->length = 257U - ch;
105
0
                m->state = st_run;
106
0
            } else // ch == 128
107
0
            {
108
                // EOD; stay in this state
109
0
            }
110
0
            break;
111
112
0
        case st_copying:
113
0
            m->out.append(1, static_cast<char>(ch));
114
0
            if (--m->length == 0) {
115
0
                m->state = st_top;
116
0
            }
117
0
            break;
118
119
0
        case st_run:
120
0
            m->out.append(m->length, static_cast<char>(ch));
121
0
            m->state = st_top;
122
0
            break;
123
0
        }
124
0
    }
125
0
}
126
127
void
128
Pl_RunLength::flush_encode()
129
0
{
130
0
    if (m->length == 128) {
131
0
        QTC::TC(
132
0
            "libtests",
133
0
            "Pl_RunLength flush full buffer",
134
0
            (m->state == st_copying   ? 0
135
0
                 : m->state == st_run ? 1
136
0
                                      : -1));
137
0
    }
138
0
    if (m->length == 0) {
139
0
        QTC::TC("libtests", "Pl_RunLength flush empty buffer");
140
0
    }
141
0
    if (m->state == st_run) {
142
0
        util::assertion(
143
0
            !(m->length < 2 || m->length > 128),
144
0
            "Pl_RunLength: invalid length in flush_encode for run");
145
0
        auto ch = static_cast<unsigned char>(257 - m->length);
146
0
        next()->write(&ch, 1);
147
0
        next()->write(&m->buf[0], 1);
148
0
    } else if (m->length > 0) {
149
0
        auto ch = static_cast<unsigned char>(m->length - 1);
150
0
        next()->write(&ch, 1);
151
0
        next()->write(m->buf, m->length);
152
0
    }
153
0
    m->state = st_top;
154
0
    m->length = 0;
155
0
}
156
157
void
158
Pl_RunLength::finish()
159
0
{
160
    // When decoding, we might have read a length byte not followed by data, which means the stream
161
    // was terminated early, but we will just ignore this case since this is the only sensible thing
162
    // to do.
163
0
    if (m->action == a_encode) {
164
0
        flush_encode();
165
0
        unsigned char ch = 128;
166
0
        next()->write(&ch, 1);
167
0
    } else {
168
0
        if (memory_limit && (m->out.size()) > memory_limit) {
169
0
            throw std::runtime_error("Pl_RunLength memory limit exceeded");
170
0
        }
171
0
        next()->writeString(m->out);
172
0
    }
173
0
    next()->finish();
174
0
}