MailJDTourDownloadDocsQualityTestsCoverage

Which source lines and branches the tests execute. Lines 91.5 %, branches 75.0 % (minimum 85 % and 72 %). main @ 399ffe3d · 21 October 2026 · pipeline 7238.

GCC Code Coverage Report


Directory: src/
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 87.4% 97 / 0 / 111
Functions: 100.0% 14 / 0 / 14
Branches: 64.5% 98 / 0 / 152

data/ComposeImageLocation.cpp
Line Branch Exec Source
1 #include "ComposeImageLocation.h"
2
3 namespace ComposeImageLocation {
4
5 namespace {
6
7 class Tiff {
8 public:
9 16 Tiff(QByteArray &data, qsizetype start, qsizetype size) : m_data(data), m_start(start), m_size(size) {}
10 16 bool readHeader() {
11
1/2
✗ Branch 2 → 3 not taken.
✓ Branch 2 → 4 taken 16 times.
16 if (m_size < 8) return false;
12 16 const char a = at(0), b = at(1);
13
2/4
✓ Branch 6 → 7 taken 16 times.
✗ Branch 6 → 9 not taken.
✓ Branch 7 → 8 taken 16 times.
✗ Branch 7 → 9 not taken.
16 if (a == 'I' && b == 'I') m_little = true;
14 ✗ else if (a == 'M' && b == 'M') m_little = false;
15 ✗ else return false;
16 16 return u16(2) == 42;
17 }
18 60 bool inside(qint64 offset, qint64 length) const {
19
5/8
✓ Branch 2 → 3 taken 60 times.
✗ Branch 2 → 7 not taken.
✓ Branch 3 → 4 taken 60 times.
✗ Branch 3 → 7 not taken.
✓ Branch 4 → 5 taken 58 times.
✓ Branch 4 → 7 taken 2 times.
✓ Branch 5 → 6 taken 58 times.
✗ Branch 5 → 7 not taken.
60 return offset >= 0 && length >= 0 && offset <= m_size && length <= m_size - offset;
20 }
21 90 quint16 u16(qint64 offset) const {
22 90 const quint8 x = quint8(at(offset)), y = quint8(at(offset + 1));
23
1/2
✓ Branch 4 → 5 taken 90 times.
✗ Branch 4 → 6 not taken.
90 return m_little ? quint16(x | (y << 8)) : quint16((x << 8) | y);
24 }
25 48 quint32 u32(qint64 offset) const {
26 48 const quint32 x = quint8(at(offset)), y = quint8(at(offset + 1)), z = quint8(at(offset + 2)),
27 48 w = quint8(at(offset + 3));
28
1/2
✓ Branch 6 → 7 taken 48 times.
✗ Branch 6 → 8 not taken.
48 return m_little ? (x | (y << 8) | (z << 16) | (w << 24)) : ((x << 24) | (y << 16) | (z << 8) | w);
29 }
30 4 void setU16(qint64 offset, quint16 value) {
31
1/2
✓ Branch 2 → 3 taken 4 times.
✗ Branch 2 → 5 not taken.
4 if (m_little) {
32 4 put(offset, char(value & 0xff));
33 4 put(offset + 1, char(value >> 8));
34 } else {
35 ✗ put(offset, char(value >> 8));
36 ✗ put(offset + 1, char(value & 0xff));
37 }
38 4 }
39 8 void zero(qint64 offset, qint64 length) {
40
2/2
✓ Branch 5 → 3 taken 192 times.
✓ Branch 5 → 6 taken 8 times.
200 for (qint64 i = 0; i < length; ++i) put(offset + i, 0);
41 8 }
42
43 private:
44 QByteArray &m_data;
45 qsizetype m_start;
46 qsizetype m_size;
47 bool m_little = true;
48 404 char at(qint64 offset) const { return m_data.at(m_start + offset); }
49 200 void put(qint64 offset, char value) { m_data[m_start + offset] = value; }
50 };
51
52 8 int typeSize(quint16 type) {
53
2/5
✓ Branch 2 → 3 taken 4 times.
✗ Branch 2 → 4 not taken.
✗ Branch 2 → 5 not taken.
✓ Branch 2 → 6 taken 4 times.
✗ Branch 2 → 7 not taken.
8 switch (type) {
54 4 case 1: case 2: case 6: case 7: return 1;
55 ✗ case 3: case 8: return 2;
56 ✗ case 4: case 9: case 11: return 4;
57 4 case 5: case 10: case 12: return 8;
58 ✗ default: return 0;
59 }
60 }
61
62 // Empties every GPS IFD that IFD0 points to (a hostile file can name more
63 // than one) of one EXIF payload in place; true when one held data.
64 16 bool clearGps(QByteArray &data, qsizetype tiffStart, qsizetype tiffSize) {
65 16 Tiff tiff(data, tiffStart, tiffSize);
66
2/3
✓ Branch 3 → 4 taken 16 times.
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 6 taken 16 times.
16 if (!tiff.readHeader()) return false;
67
1/1
✓ Branch 6 → 7 taken 16 times.
16 const quint32 ifd0 = tiff.u32(4);
68
1/2
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 10 taken 16 times.
16 if (!tiff.inside(ifd0, 2)) return false;
69
1/1
✓ Branch 10 → 11 taken 16 times.
16 const quint16 count = tiff.u16(ifd0);
70
3/6
✓ Branch 11 → 12 taken 16 times.
✗ Branch 11 → 14 not taken.
✗ Branch 13 → 14 not taken.
✓ Branch 13 → 15 taken 16 times.
✗ Branch 16 → 17 not taken.
✓ Branch 16 → 18 taken 16 times.
16 if (count > 512 || !tiff.inside(ifd0 + 2, qint64(count) * 12)) return false;
71 16 bool cleared = false;
72
2/2
✓ Branch 50 → 19 taken 32 times.
✓ Branch 50 → 51 taken 16 times.
48 for (quint16 i = 0; i < count; ++i) {
73 32 const qint64 entry = ifd0 + 2 + qint64(i) * 12;
74
3/3
✓ Branch 19 → 20 taken 32 times.
✓ Branch 20 → 21 taken 12 times.
✓ Branch 20 → 22 taken 20 times.
32 if (tiff.u16(entry) != 0x8825) continue;
75
1/1
✓ Branch 22 → 23 taken 20 times.
20 const quint32 gps = tiff.u32(entry + 8);
76
2/2
✓ Branch 24 → 25 taken 2 times.
✓ Branch 24 → 26 taken 18 times.
20 if (!tiff.inside(gps, 2)) continue;
77
1/1
✓ Branch 26 → 27 taken 18 times.
18 const quint16 gpsCount = tiff.u16(gps);
78
6/8
✓ Branch 27 → 28 taken 4 times.
✓ Branch 27 → 31 taken 14 times.
✓ Branch 28 → 29 taken 4 times.
✗ Branch 28 → 31 not taken.
✗ Branch 30 → 31 not taken.
✓ Branch 30 → 32 taken 4 times.
✓ Branch 33 → 34 taken 14 times.
✓ Branch 33 → 35 taken 4 times.
18 if (gpsCount == 0 || gpsCount > 256 || !tiff.inside(gps + 2, qint64(gpsCount) * 12)) continue;
79
2/2
✓ Branch 45 → 36 taken 8 times.
✓ Branch 45 → 46 taken 4 times.
12 for (quint16 j = 0; j < gpsCount; ++j) {
80 8 const qint64 gpsEntry = gps + 2 + qint64(j) * 12;
81
2/2
✓ Branch 36 → 37 taken 8 times.
✓ Branch 38 → 39 taken 8 times.
8 const qint64 bytes = qint64(typeSize(tiff.u16(gpsEntry + 2))) * tiff.u32(gpsEntry + 4);
82
2/2
✓ Branch 39 → 40 taken 4 times.
✓ Branch 39 → 44 taken 4 times.
8 if (bytes > 4) {
83
1/1
✓ Branch 40 → 41 taken 4 times.
4 const quint32 valueOffset = tiff.u32(gpsEntry + 8);
84
2/3
✓ Branch 42 → 43 taken 4 times.
✗ Branch 42 → 44 not taken.
✓ Branch 43 → 44 taken 4 times.
4 if (tiff.inside(valueOffset, bytes)) tiff.zero(valueOffset, bytes);
85 }
86 }
87
1/1
✓ Branch 46 → 47 taken 4 times.
4 tiff.zero(gps + 2, qint64(gpsCount) * 12);
88
1/1
✓ Branch 47 → 48 taken 4 times.
4 tiff.setU16(gps, 0);
89 4 cleared = true;
90 }
91 16 return cleared;
92 }
93
94 5 bool xmpWithLocation(const QByteArray &payload) {
95
1/6
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 11 taken 5 times.
✗ Branch 7 → 8 not taken.
✗ Branch 7 → 11 not taken.
✗ Branch 10 → 11 not taken.
✗ Branch 10 → 12 not taken.
5 return payload.contains("GPSLatitude") || payload.contains("GPSLongitude") || payload.contains("exif:GPS");
96 }
97
98 } // namespace
99
100 namespace {
101 30 Result stripOnce(const QByteArray &input) {
102 30 Result result;
103 30 result.data = input;
104
7/10
✓ Branch 4 → 5 taken 30 times.
✗ Branch 4 → 11 not taken.
✓ Branch 6 → 7 taken 30 times.
✗ Branch 6 → 11 not taken.
✓ Branch 8 → 9 taken 20 times.
✓ Branch 8 → 11 taken 10 times.
✗ Branch 10 → 11 not taken.
✓ Branch 10 → 12 taken 20 times.
✓ Branch 13 → 14 taken 10 times.
✓ Branch 13 → 15 taken 20 times.
30 if (input.size() < 4 || input.size() > MaxBytes || quint8(input.at(0)) != 0xFF || quint8(input.at(1)) != 0xD8)
105 10 return result;
106 20 result.jpeg = true;
107 20 QByteArray &data = result.data;
108 20 qsizetype position = 2;
109 // Where the current run of fill bytes began: after a segment is removed
110 // the fill bytes before it meet the bytes after it, so parsing resumes
111 // there — the next pass would otherwise read them differently.
112 20 qsizetype runStart = position;
113
1/2
✓ Branch 82 → 16 taken 93 times.
✗ Branch 82 → 83 not taken.
93 while (position + 4 <= data.size()) {
114
1/2
✗ Branch 17 → 18 not taken.
✓ Branch 17 → 19 taken 93 times.
113 if (quint8(data.at(position)) != 0xFF) break; // not a marker: stop, leave the rest
115 93 quint8 marker = quint8(data.at(position + 1));
116
2/2
✓ Branch 20 → 21 taken 36 times.
✓ Branch 20 → 22 taken 57 times.
93 if (marker == 0xFF) { // fill byte
117 36 ++position;
118 41 continue;
119 }
120
5/8
✓ Branch 22 → 23 taken 57 times.
✗ Branch 22 → 26 not taken.
✓ Branch 23 → 24 taken 57 times.
✗ Branch 23 → 26 not taken.
✓ Branch 24 → 25 taken 47 times.
✓ Branch 24 → 27 taken 10 times.
✗ Branch 25 → 26 not taken.
✓ Branch 25 → 27 taken 47 times.
57 if (marker == 0xD8 || marker == 0x01 || (marker >= 0xD0 && marker <= 0xD7)) {
121 ✗ position += 2;
122 ✗ runStart = position;
123 ✗ continue;
124 }
125
3/4
✓ Branch 27 → 28 taken 57 times.
✗ Branch 27 → 79 not taken.
✓ Branch 28 → 29 taken 39 times.
✓ Branch 28 → 79 taken 18 times.
57 if (marker == 0xD9 || marker == 0xDA) break; // end of image / start of scan data
126 39 const qsizetype length = (quint8(data.at(position + 2)) << 8) | quint8(data.at(position + 3));
127
5/6
✓ Branch 31 → 32 taken 39 times.
✗ Branch 31 → 34 not taken.
✓ Branch 33 → 34 taken 2 times.
✓ Branch 33 → 35 taken 37 times.
✓ Branch 36 → 37 taken 2 times.
✓ Branch 36 → 38 taken 37 times.
39 if (length < 2 || position + 2 + length > data.size()) break;
128 37 const qsizetype payload = position + 4, payloadSize = length - 2;
129
2/2
✓ Branch 38 → 39 taken 21 times.
✓ Branch 38 → 78 taken 16 times.
37 if (marker == 0xE1) {
130
1/1
✓ Branch 40 → 41 taken 21 times.
21 const QByteArray head = data.mid(payload, qMin<qsizetype>(payloadSize, 40));
131
7/10
✓ Branch 45 → 46 taken 16 times.
✓ Branch 45 → 48 taken 5 times.
✓ Branch 46 → 47 taken 16 times.
✗ Branch 46 → 48 not taken.
✓ Branch 49 → 50 taken 21 times.
✗ Branch 49 → 51 not taken.
✓ Branch 51 → 52 taken 21 times.
✗ Branch 51 → 53 not taken.
✓ Branch 53 → 54 taken 16 times.
✓ Branch 53 → 56 taken 5 times.
21 if (head.startsWith(QByteArrayLiteral("Exif\0\0")) && payloadSize > 6) {
132
1/1
✓ Branch 54 → 55 taken 16 times.
16 result.removed |= clearGps(data, payload + 6, payloadSize - 6);
133
2/6
✗ Branch 58 → 59 not taken.
✓ Branch 58 → 62 taken 5 times.
✗ Branch 61 → 62 not taken.
✗ Branch 61 → 66 not taken.
✓ Branch 64 → 65 taken 5 times.
✗ Branch 64 → 66 not taken.
10 } else if ((head.startsWith("http://ns.adobe.com/xap/1.0/") || head.startsWith("http://ns.adobe.com/xmp/extension/")) &&
134
3/7
✓ Branch 62 → 63 taken 5 times.
✓ Branch 67 → 68 taken 5 times.
✗ Branch 67 → 69 not taken.
✓ Branch 69 → 70 taken 5 times.
✗ Branch 69 → 72 not taken.
✗ Branch 86 → 87 not taken.
✗ Branch 86 → 88 not taken.
10 xmpWithLocation(data.mid(payload, payloadSize))) {
135
1/1
✓ Branch 70 → 71 taken 5 times.
5 data.remove(position, 2 + length);
136 5 result.removed = true;
137 5 position = runStart;
138 5 continue;
139 }
140 21 }
141 32 position += 2 + length;
142 32 runStart = position;
143 }
144 20 return result;
145 ✗ }
146 } // namespace
147
148 // Zeroing values can change what a hostile file's overlapping structures
149 // say, and a removed segment can bring the next one into reach, so the
150 // stripper runs until nothing changes: the result
151 // strips to itself (fuzz_exif_location).
152 20 Result stripJpeg(const QByteArray &input) {
153 20 Result result = stripOnce(input);
154 // Every pass only zeroes bytes or drops a segment, so it converges; the
155 // bound keeps a hostile file's cost linear in practice.
156
3/4
✓ Branch 19 → 20 taken 20 times.
✗ Branch 19 → 21 not taken.
✓ Branch 20 → 4 taken 10 times.
✓ Branch 20 → 21 taken 10 times.
20 for (int pass = 0; pass < 64 && result.jpeg; ++pass) {
157
1/1
✓ Branch 4 → 5 taken 10 times.
10 const Result again = stripOnce(result.data);
158
3/6
✓ Branch 5 → 6 taken 10 times.
✗ Branch 5 → 9 not taken.
✓ Branch 7 → 8 taken 10 times.
✗ Branch 7 → 9 not taken.
✓ Branch 10 → 11 taken 10 times.
✗ Branch 10 → 12 not taken.
10 if (!again.removed && again.data == result.data) break;
159 ✗ result.data = again.data;
160 ✗ result.removed = true;
161 10 }
162 20 return result;
163 ✗ }
164
165 } // namespace ComposeImageLocation
166