Coverage Report

Created: 2026-04-12 07:00

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
842
        action(action)
19
842
    {
20
842
    }
21
    Members(Members const&) = delete;
22
842
    ~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
842
    Pipeline(identifier, next),
33
842
    m(std::make_unique<Members>(action))
34
842
{
35
842
    util::assertion(next, "Attempt to create Pl_RunLength with nullptr as next");
36
842
}
37
38
void
39
Pl_RunLength::setMemoryLimit(unsigned long long limit)
40
19.5k
{
41
19.5k
    global::Limits::run_length_max_memory(limit);
42
19.5k
}
43
44
842
Pl_RunLength::~Pl_RunLength() = default;
45
46
void
47
Pl_RunLength::write(unsigned char const* data, size_t len)
48
61.2k
{
49
61.2k
    if (m->action == a_encode) {
50
0
        encode(data, len);
51
61.2k
    } else {
52
61.2k
        decode(data, len);
53
61.2k
    }
54
61.2k
}
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
61.2k
{
91
61.2k
    util::no_ci_rt_error_if(
92
61.2k
        memory_limit && (len + m->out.size()) > memory_limit, "Pl_RunLength memory limit exceeded");
93
61.2k
    m->out.reserve(len);
94
3.63M
    for (size_t i = 0; i < len; ++i) {
95
3.56M
        unsigned char const& ch = data[i];
96
3.56M
        switch (m->state) {
97
1.71M
        case st_top:
98
1.71M
            if (ch < 128) {
99
                // length represents remaining number of bytes to copy
100
7.24k
                m->length = 1U + ch;
101
7.24k
                m->state = st_copying;
102
1.70M
            } else if (ch > 128) {
103
                // length represents number of copies of next byte
104
1.70M
                m->length = 257U - ch;
105
1.70M
                m->state = st_run;
106
1.70M
            } else // ch == 128
107
374
            {
108
                // EOD; stay in this state
109
374
            }
110
1.71M
            break;
111
112
154k
        case st_copying:
113
154k
            m->out.append(1, static_cast<char>(ch));
114
154k
            if (--m->length == 0) {
115
6.84k
                m->state = st_top;
116
6.84k
            }
117
154k
            break;
118
119
1.70M
        case st_run:
120
1.70M
            m->out.append(m->length, static_cast<char>(ch));
121
1.70M
            m->state = st_top;
122
1.70M
            break;
123
3.56M
        }
124
3.56M
    }
125
61.2k
}
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
731
{
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
731
    if (m->action == a_encode) {
164
0
        flush_encode();
165
0
        unsigned char ch = 128;
166
0
        next()->write(&ch, 1);
167
731
    } else {
168
731
        if (memory_limit && (m->out.size()) > memory_limit) {
169
26
            throw std::runtime_error("Pl_RunLength memory limit exceeded");
170
26
        }
171
705
        next()->writeString(m->out);
172
705
    }
173
705
    next()->finish();
174
705
}