Coverage Report

Created: 2026-05-30 06:15

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