Coverage Report

Created: 2025-12-05 06:54

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