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: 93.9% 215 / 0 / 229
Functions: 100.0% 11 / 0 / 11
Branches: 82.5% 260 / 0 / 315

presentation/RuleMatcher.cpp
Line Branch Exec Source
1 #include "RuleMatcher.h"
2
3 #include <QRegularExpression>
4
5 using Sieve::Condition;
6
7 namespace {
8
9 // i;ascii-casemap folds only A–Z (RFC 4790); i;unicode-casemap folds fully.
10 10906 QString fold(const QString &text, const QString &comparator) {
11
2/2
✓ Branch 4 → 5 taken 24 times.
✓ Branch 4 → 6 taken 10882 times.
10906 if (comparator == QLatin1String("i;octet"))
12 24 return text;
13
2/2
✓ Branch 8 → 9 taken 2 times.
✓ Branch 8 → 11 taken 10880 times.
10882 if (comparator == QLatin1String("i;unicode-casemap"))
14
1/1
✓ Branch 9 → 10 taken 2 times.
2 return text.toCaseFolded();
15 10880 QString out = text;
16
4/4
✓ Branch 12 → 13 taken 10880 times.
✓ Branch 13 → 14 taken 10880 times.
✓ Branch 32 → 15 taken 211727 times.
✓ Branch 32 → 33 taken 10880 times.
222607 for (QChar &c : out)
17
6/6
✓ Branch 19 → 20 taken 175037 times.
✓ Branch 19 → 26 taken 36690 times.
✓ Branch 24 → 25 taken 1694 times.
✓ Branch 24 → 26 taken 173343 times.
✓ Branch 27 → 28 taken 1694 times.
✓ Branch 27 → 31 taken 210033 times.
211727 if (c >= QLatin1Char('A') && c <= QLatin1Char('Z'))
18 1694 c = QChar(c.unicode() + 32);
19 10880 return out;
20 10880 }
21
22 // Sieve :matches — '*' any run, '?' one character, '\\' escapes the next
23 // one. S106-323: iterative, backtracking only to the last '*' — O(value ×
24 // pattern); the recursive form was exponential on "*a*a*a*…b" patterns.
25 13 bool globMatch(const QString &value, const QString &pattern) {
26 13 qsizetype vi = 0, pi = 0, starPi = -1, starVi = 0;
27
2/2
✓ Branch 37 → 3 taken 311 times.
✓ Branch 37 → 38 taken 12 times.
323 while (vi < value.size()) {
28
2/2
✓ Branch 4 → 5 taken 295 times.
✓ Branch 4 → 33 taken 16 times.
311 if (pi < pattern.size()) {
29 295 const QChar p = pattern.at(pi);
30
2/2
✓ Branch 9 → 10 taken 23 times.
✓ Branch 9 → 11 taken 272 times.
295 if (p == QLatin1Char('*')) {
31 23 starPi = pi++;
32 23 starVi = vi;
33 82 continue;
34 }
35
2/2
✓ Branch 14 → 15 taken 3 times.
✓ Branch 14 → 16 taken 269 times.
272 if (p == QLatin1Char('?')) {
36 3 ++vi;
37 3 ++pi;
38 3 continue;
39 }
40 269 QChar literal = p;
41 269 qsizetype step = 1;
42
5/6
✓ Branch 19 → 20 taken 6 times.
✓ Branch 19 → 23 taken 263 times.
✓ Branch 21 → 22 taken 6 times.
✗ Branch 21 → 23 not taken.
✓ Branch 24 → 25 taken 6 times.
✓ Branch 24 → 27 taken 263 times.
269 if (p == QLatin1Char('\\') && pi + 1 < pattern.size()) {
43 6 literal = pattern.at(pi + 1);
44 6 step = 2;
45 }
46
2/2
✓ Branch 29 → 30 taken 56 times.
✓ Branch 29 → 31 taken 213 times.
269 if (value.at(vi) == literal) {
47 56 ++vi;
48 56 pi += step;
49 56 continue;
50 }
51 }
52
2/2
✓ Branch 33 → 34 taken 1 time.
✓ Branch 33 → 35 taken 228 times.
229 if (starPi < 0)
53 1 return false;
54 228 pi = starPi + 1;
55 228 vi = ++starVi;
56 }
57
6/6
✓ Branch 41 → 42 taken 5 times.
✓ Branch 41 → 48 taken 8 times.
✓ Branch 46 → 47 taken 1 time.
✓ Branch 46 → 48 taken 4 times.
✓ Branch 49 → 39 taken 1 time.
✓ Branch 49 → 50 taken 12 times.
13 while (pi < pattern.size() && pattern.at(pi) == QLatin1Char('*'))
58 1 ++pi;
59 12 return pi == pattern.size();
60 }
61
62 3494 QStringList addressParts(const QStringList &addresses, const QString &part) {
63 3494 QStringList result;
64
2/2
✓ Branch 29 → 4 taken 3462 times.
✓ Branch 29 → 30 taken 3494 times.
6956 for (const QString &address : addresses) {
65 3462 const int at = address.lastIndexOf(QLatin1Char('@'));
66
2/2
✓ Branch 10 → 11 taken 1 time.
✓ Branch 10 → 17 taken 3461 times.
3462 if (part == QLatin1String("localpart"))
67
3/4
✗ Branch 11 → 12 not taken.
✓ Branch 11 → 13 taken 1 time.
✓ Branch 13 → 14 taken 1 time.
✓ Branch 14 → 15 taken 1 time.
1 result.append(at < 0 ? address : address.left(at));
68
2/2
✓ Branch 19 → 20 taken 385 times.
✓ Branch 19 → 26 taken 3076 times.
3461 else if (part == QLatin1String("domain"))
69
3/4
✗ Branch 20 → 21 not taken.
✓ Branch 20 → 22 taken 385 times.
✓ Branch 22 → 23 taken 385 times.
✓ Branch 23 → 24 taken 385 times.
385 result.append(at < 0 ? QString() : address.mid(at + 1));
70 else
71
1/1
✓ Branch 26 → 27 taken 3076 times.
3076 result.append(address);
72 }
73 3494 return result;
74 ✗ }
75
76 } // namespace
77
78 3601 QStringList RuleMatcher::addresses(const QString &headerValue) {
79 // S106-324: a quoted local part ("foo bar"@example.com, RFC 5322) is an
80 // address too; the server's address test sees it.
81
4/7
✓ Branch 2 → 3 taken 7 times.
✓ Branch 2 → 15 taken 3594 times.
✓ Branch 4 → 5 taken 7 times.
✗ Branch 4 → 15 not taken.
✓ Branch 9 → 10 taken 7 times.
✗ Branch 91 → 92 not taken.
✗ Branch 91 → 93 not taken.
3608 static const QRegularExpression angle(QStringLiteral("<((?:\"[^\"\\r\\n]*\"|[^<>\\s\"]+)@[^<>\\s]+)>"));
82
4/7
✓ Branch 15 → 16 taken 7 times.
✓ Branch 15 → 28 taken 3594 times.
✓ Branch 17 → 18 taken 7 times.
✗ Branch 17 → 28 not taken.
✓ Branch 22 → 23 taken 7 times.
✗ Branch 101 → 102 not taken.
✗ Branch 101 → 103 not taken.
3608 static const QRegularExpression bare(QStringLiteral("((?:\"[^\"\\r\\n]*\"|[^\\s<>,;\"']+)@[^\\s<>,;\"']+)"));
83 3601 QStringList result;
84 // Split on commas outside quotes; then take <addr> or a bare address.
85 3601 QStringList entries;
86 3601 QString current;
87 3601 bool quoted = false;
88
2/2
✓ Branch 48 → 30 taken 104479 times.
✓ Branch 48 → 49 taken 3601 times.
108080 for (const QChar c : headerValue) {
89
2/2
✓ Branch 33 → 34 taken 14 times.
✓ Branch 33 → 35 taken 104465 times.
104479 if (c == QLatin1Char('"'))
90 14 quoted = !quoted;
91
6/6
✓ Branch 38 → 39 taken 8 times.
✓ Branch 38 → 41 taken 104471 times.
✓ Branch 39 → 40 taken 4 times.
✓ Branch 39 → 41 taken 4 times.
✓ Branch 42 → 43 taken 4 times.
✓ Branch 42 → 46 taken 104475 times.
104479 if (c == QLatin1Char(',') && !quoted) {
92
1/1
✓ Branch 43 → 44 taken 4 times.
4 entries.append(current);
93 4 current.clear();
94 4 continue;
95 }
96
1/1
✓ Branch 46 → 47 taken 104475 times.
104475 current.append(c);
97 }
98
1/1
✓ Branch 49 → 50 taken 3601 times.
3601 entries.append(current);
99
4/4
✓ Branch 50 → 51 taken 3601 times.
✓ Branch 51 → 52 taken 3601 times.
✓ Branch 79 → 53 taken 3605 times.
✓ Branch 79 → 80 taken 3601 times.
7206 for (const QString &entry : entries) {
100
1/1
✓ Branch 55 → 56 taken 3605 times.
3605 const QRegularExpressionMatch inAngle = angle.match(entry);
101
3/3
✓ Branch 56 → 57 taken 3605 times.
✓ Branch 57 → 58 taken 1970 times.
✓ Branch 57 → 62 taken 1635 times.
3605 if (inAngle.hasMatch()) {
102
2/2
✓ Branch 58 → 59 taken 1970 times.
✓ Branch 59 → 60 taken 1970 times.
1970 result.append(inAngle.captured(1));
103 1970 continue;
104 }
105
1/1
✓ Branch 63 → 64 taken 1635 times.
1635 const QRegularExpressionMatch plain = bare.match(entry);
106
3/3
✓ Branch 64 → 65 taken 1635 times.
✓ Branch 65 → 66 taken 1552 times.
✓ Branch 65 → 70 taken 83 times.
1635 if (plain.hasMatch())
107
2/2
✓ Branch 66 → 67 taken 1552 times.
✓ Branch 67 → 68 taken 1552 times.
1552 result.append(plain.captured(1));
108 3605 }
109 3601 return result;
110 3601 }
111
112 5086 bool RuleMatcher::matchValue(const QString &value, const QString &key, const QString &matchType,
113 const QString &comparator) {
114
2/2
✓ Branch 4 → 5 taken 1 time.
✓ Branch 4 → 23 taken 5085 times.
5086 if (matchType == QLatin1String("regex")) {
115 1 QRegularExpression::PatternOptions options = QRegularExpression::NoPatternOption;
116
1/2
✓ Branch 7 → 8 taken 1 time.
✗ Branch 7 → 9 not taken.
1 if (comparator != QLatin1String("i;octet"))
117 1 options |= QRegularExpression::CaseInsensitiveOption;
118
1/1
✓ Branch 9 → 10 taken 1 time.
1 const QRegularExpression expression(key, options);
119
6/11
✓ Branch 10 → 11 taken 1 time.
✓ Branch 11 → 12 taken 1 time.
✗ Branch 11 → 17 not taken.
✓ Branch 13 → 14 taken 1 time.
✓ Branch 14 → 15 taken 1 time.
✓ Branch 15 → 16 taken 1 time.
✗ Branch 15 → 17 not taken.
✓ Branch 19 → 20 taken 1 time.
✗ Branch 19 → 21 not taken.
✗ Branch 39 → 40 not taken.
✗ Branch 39 → 41 not taken.
2 return expression.isValid() && expression.match(value).hasMatch();
120 1 }
121
1/1
✓ Branch 23 → 24 taken 5085 times.
5085 const QString folded = fold(value, comparator);
122
1/1
✓ Branch 24 → 25 taken 5085 times.
5085 const QString foldedKey = fold(key, comparator);
123
2/2
✓ Branch 27 → 28 taken 2342 times.
✓ Branch 27 → 30 taken 2743 times.
5085 if (matchType == QLatin1String("contains"))
124
1/1
✓ Branch 28 → 29 taken 2342 times.
2342 return folded.contains(foldedKey);
125
2/2
✓ Branch 32 → 33 taken 13 times.
✓ Branch 32 → 34 taken 2730 times.
2743 if (matchType == QLatin1String("matches"))
126 13 return globMatch(folded, foldedKey);
127 2730 return folded == foldedKey;
128 5085 }
129
130 5592 RuleMatcher::Result RuleMatcher::evaluate(const Condition &condition, const Message &message) {
131 5592 bool matched = false;
132
2/2
✓ Branch 2 → 3 taken 4 times.
✓ Branch 2 → 9 taken 5588 times.
5592 if (condition.kind == Condition::Kind::Size) {
133
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 4 times.
4 if (message.size < 0)
134 ✗ return Result::Unknown;
135
2/2
✓ Branch 5 → 6 taken 3 times.
✓ Branch 5 → 7 taken 1 time.
5 matched = condition.sizeOver ? quint64(message.size) > condition.sizeLimit
136 1 : quint64(message.size) < condition.sizeLimit;
137
2/2
✓ Branch 9 → 10 taken 3 times.
✓ Branch 9 → 24 taken 5585 times.
5588 } else if (condition.kind == Condition::Kind::Body) {
138
1/2
✓ Branch 10 → 11 taken 3 times.
✗ Branch 10 → 12 not taken.
3 if (!message.bodyKnown)
139 3 return Result::Unknown;
140 ✗ for (const QString &key : condition.values)
141 ✗ matched = matched || matchValue(message.body, key, condition.matchType, condition.comparator);
142 } else {
143
2/2
✓ Branch 24 → 25 taken 2173 times.
✓ Branch 24 → 37 taken 3412 times.
5585 if (!message.allHeaders)
144
2/2
✓ Branch 35 → 27 taken 2173 times.
✓ Branch 35 → 36 taken 1833 times.
4006 for (const QString &header : condition.headers)
145
3/3
✓ Branch 28 → 29 taken 2173 times.
✓ Branch 31 → 32 taken 340 times.
✓ Branch 31 → 33 taken 1833 times.
2173 if (!message.headers.contains(header.toLower()))
146 340 return Result::Unknown;
147 5245 QStringList values;
148
2/2
✓ Branch 60 → 39 taken 5902 times.
✓ Branch 60 → 61 taken 5245 times.
11147 for (const QString &header : condition.headers) {
149
1/1
✓ Branch 40 → 41 taken 5902 times.
5902 const QStringList raw = message.headers.value(header.toLower());
150
2/2
✓ Branch 43 → 44 taken 4078 times.
✓ Branch 43 → 56 taken 1824 times.
5902 if (condition.kind == Condition::Kind::Address) {
151
2/2
✓ Branch 54 → 46 taken 3494 times.
✓ Branch 54 → 55 taken 4078 times.
7572 for (const QString &entry : raw)
152
3/3
✓ Branch 47 → 48 taken 3494 times.
✓ Branch 48 → 49 taken 3494 times.
✓ Branch 49 → 50 taken 3494 times.
3494 values += addressParts(addresses(entry), condition.addressPart);
153 } else {
154
1/1
✓ Branch 56 → 57 taken 1824 times.
1824 values += raw;
155 }
156 5902 }
157
4/4
✓ Branch 61 → 62 taken 5245 times.
✓ Branch 62 → 63 taken 5245 times.
✓ Branch 78 → 64 taken 4949 times.
✓ Branch 78 → 79 taken 4804 times.
9753 for (const QString &value : values) {
158
2/2
✓ Branch 73 → 67 taken 5075 times.
✓ Branch 73 → 74 taken 4508 times.
9583 for (const QString &key : condition.values)
159
3/3
✓ Branch 68 → 69 taken 5075 times.
✓ Branch 69 → 70 taken 441 times.
✓ Branch 69 → 71 taken 4634 times.
5075 if (matchValue(value, key, condition.matchType, condition.comparator)) {
160 441 matched = true;
161 441 break;
162 }
163
2/2
✓ Branch 74 → 75 taken 441 times.
✓ Branch 74 → 76 taken 4508 times.
4949 if (matched)
164 441 break;
165 }
166 5245 }
167
2/2
✓ Branch 81 → 82 taken 443 times.
✓ Branch 81 → 83 taken 4806 times.
5249 return matched != condition.negated ? Result::Match : Result::NoMatch;
168 }
169
170 5493 RuleMatcher::Result RuleMatcher::evaluate(const Sieve::Rule &rule, const Message &message) {
171
2/2
✓ Branch 3 → 4 taken 1 time.
✓ Branch 3 → 5 taken 5492 times.
5493 if (rule.conditions.isEmpty())
172 1 return Result::Match;
173 5492 bool unknown = false;
174
2/2
✓ Branch 19 → 7 taken 5592 times.
✓ Branch 19 → 20 taken 685 times.
6277 for (const Condition &condition : rule.conditions) {
175
1/1
✓ Branch 8 → 9 taken 5592 times.
5592 const Result result = evaluate(condition, message);
176
2/2
✓ Branch 9 → 10 taken 343 times.
✓ Branch 9 → 11 taken 5249 times.
5592 if (result == Result::Unknown) {
177 343 unknown = true;
178 343 continue;
179 }
180
4/4
✓ Branch 11 → 12 taken 5248 times.
✓ Branch 11 → 14 taken 1 time.
✓ Branch 12 → 13 taken 4806 times.
✓ Branch 12 → 14 taken 442 times.
5249 if (rule.matchAll && result == Result::NoMatch)
181 4807 return Result::NoMatch;
182
3/4
✓ Branch 14 → 15 taken 1 time.
✓ Branch 14 → 17 taken 442 times.
✓ Branch 15 → 16 taken 1 time.
✗ Branch 15 → 17 not taken.
443 if (!rule.matchAll && result == Result::Match)
183 1 return Result::Match;
184 }
185
2/2
✓ Branch 20 → 22 taken 342 times.
✓ Branch 20 → 23 taken 343 times.
685 if (unknown)
186 342 return Result::Unknown;
187
1/2
✓ Branch 23 → 24 taken 343 times.
✗ Branch 23 → 25 not taken.
343 return rule.matchAll ? Result::Match : Result::NoMatch;
188 }
189
190 668 QList<int> RuleMatcher::matchingBlocks(const Sieve::Document &document, const Message &message,
191 int *skippedAdvanced) {
192 668 QList<int> result;
193 668 int skipped = 0;
194
2/2
✓ Branch 17 → 3 taken 6874 times.
✓ Branch 17 → 18 taken 385 times.
7259 for (int i = 0; i < document.blocks.size(); ++i) {
195 6874 const Sieve::Block &block = document.blocks.at(i);
196
2/2
✓ Branch 4 → 5 taken 275 times.
✓ Branch 4 → 6 taken 6599 times.
6874 if (block.kind == Sieve::BlockKind::Advanced) {
197 275 ++skipped;
198 275 continue;
199 }
200
4/4
✓ Branch 6 → 7 taken 5476 times.
✓ Branch 6 → 8 taken 1123 times.
✓ Branch 7 → 8 taken 344 times.
✓ Branch 7 → 9 taken 5132 times.
6599 if (block.kind != Sieve::BlockKind::Rule || !block.rule.enabled)
201 1467 continue;
202
3/3
✓ Branch 9 → 10 taken 5132 times.
✓ Branch 10 → 11 taken 4847 times.
✓ Branch 10 → 12 taken 285 times.
5132 if (evaluate(block.rule, message) != Result::Match)
203 4847 continue;
204
1/1
✓ Branch 12 → 13 taken 285 times.
285 result.append(i);
205
2/2
✓ Branch 13 → 14 taken 283 times.
✓ Branch 13 → 15 taken 2 times.
285 if (block.rule.stop)
206 283 break;
207 }
208
2/2
✓ Branch 18 → 19 taken 236 times.
✓ Branch 18 → 20 taken 432 times.
668 if (skippedAdvanced)
209 236 *skippedAdvanced = skipped;
210 668 return result;
211 ✗ }
212
213 namespace {
214
215 // `broad` matches every value `narrow` matches (same field and comparator).
216 992 bool conditionCovers(const Condition &broad, const Condition &narrow) {
217
4/6
✓ Branch 2 → 3 taken 550 times.
✓ Branch 2 → 5 taken 442 times.
✓ Branch 3 → 4 taken 550 times.
✗ Branch 3 → 5 not taken.
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 7 taken 550 times.
992 if (broad.kind != narrow.kind || broad.negated || narrow.negated)
218
1/1
✓ Branch 5 → 6 taken 442 times.
442 return broad == narrow;
219
1/2
✗ Branch 7 → 8 not taken.
✓ Branch 7 → 15 taken 550 times.
550 if (broad.kind == Condition::Kind::Size)
220 ✗ return broad.sizeOver == narrow.sizeOver &&
221 ✗ (broad.sizeOver ? broad.sizeLimit <= narrow.sizeLimit : broad.sizeLimit >= narrow.sizeLimit);
222 550 QStringList broadHeaders, narrowHeaders;
223
2/2
✓ Branch 23 → 17 taken 554 times.
✓ Branch 23 → 24 taken 550 times.
1104 for (const QString &header : broad.headers)
224
2/2
✓ Branch 18 → 19 taken 554 times.
✓ Branch 19 → 20 taken 554 times.
554 broadHeaders.append(header.toLower());
225
2/2
✓ Branch 32 → 26 taken 550 times.
✓ Branch 32 → 33 taken 550 times.
1100 for (const QString &header : narrow.headers)
226
2/2
✓ Branch 27 → 28 taken 550 times.
✓ Branch 28 → 29 taken 550 times.
550 narrowHeaders.append(header.toLower());
227
4/4
✓ Branch 33 → 34 taken 550 times.
✓ Branch 34 → 35 taken 550 times.
✓ Branch 42 → 36 taken 550 times.
✓ Branch 42 → 43 taken 439 times.
989 for (const QString &header : narrowHeaders)
228
2/2
✓ Branch 38 → 39 taken 111 times.
✓ Branch 38 → 40 taken 439 times.
550 if (!broadHeaders.contains(header))
229 111 return false;
230
5/6
✓ Branch 44 → 45 taken 361 times.
✓ Branch 44 → 47 taken 78 times.
✗ Branch 46 → 47 not taken.
✓ Branch 46 → 48 taken 361 times.
✓ Branch 49 → 50 taken 78 times.
✓ Branch 49 → 51 taken 361 times.
439 if (broad.addressPart != narrow.addressPart || broad.comparator != narrow.comparator)
231 78 return false;
232 361 const QString &bm = broad.matchType;
233 361 const QString &nm = narrow.matchType;
234
2/2
✓ Branch 100 → 53 taken 361 times.
✓ Branch 100 → 101 taken 94 times.
455 for (const QString &key : narrow.values) {
235 361 bool covered = false;
236
2/2
✓ Branch 95 → 56 taken 368 times.
✓ Branch 95 → 96 taken 267 times.
635 for (const QString &broadKey : broad.values) {
237
1/1
✓ Branch 57 → 58 taken 368 times.
368 const QString a = fold(broadKey, broad.comparator);
238
1/1
✓ Branch 58 → 59 taken 368 times.
368 const QString b = fold(key, narrow.comparator);
239
6/6
✓ Branch 60 → 61 taken 263 times.
✓ Branch 60 → 64 taken 105 times.
✓ Branch 62 → 63 taken 17 times.
✓ Branch 62 → 64 taken 246 times.
✓ Branch 65 → 66 taken 17 times.
✓ Branch 65 → 67 taken 351 times.
368 if (bm == nm && a == b)
240 17 covered = true;
241
7/8
✓ Branch 69 → 70 taken 267 times.
✓ Branch 69 → 79 taken 84 times.
✓ Branch 72 → 73 taken 63 times.
✓ Branch 72 → 76 taken 204 times.
✓ Branch 75 → 76 taken 63 times.
✗ Branch 75 → 79 not taken.
✓ Branch 80 → 81 taken 77 times.
✓ Branch 80 → 82 taken 274 times.
618 else if (bm == QLatin1String("contains") && (nm == QLatin1String("contains") || nm == QLatin1String("is")) &&
242
3/3
✓ Branch 76 → 77 taken 267 times.
✓ Branch 77 → 78 taken 77 times.
✓ Branch 77 → 79 taken 190 times.
267 b.contains(a))
243 77 covered = true;
244
2/2
✓ Branch 82 → 83 taken 94 times.
✓ Branch 82 → 84 taken 274 times.
368 if (covered)
245 94 break;
246 462 }
247
2/2
✓ Branch 96 → 97 taken 267 times.
✓ Branch 96 → 98 taken 94 times.
361 if (!covered)
248 267 return false;
249 }
250 94 return true;
251 550 }
252
253 } // namespace
254
255 910 bool RuleMatcher::shadows(const Sieve::Rule &earlier, const Sieve::Rule &later) {
256
3/4
✓ Branch 2 → 3 taken 910 times.
✗ Branch 2 → 4 not taken.
✓ Branch 3 → 4 taken 12 times.
✓ Branch 3 → 5 taken 898 times.
910 if (!earlier.enabled || !earlier.stop)
257 12 return false;
258
2/2
✓ Branch 6 → 7 taken 1 time.
✓ Branch 6 → 8 taken 897 times.
898 if (earlier.conditions.isEmpty())
259 1 return true;
260
1/2
✗ Branch 9 → 10 not taken.
✓ Branch 9 → 11 taken 897 times.
897 if (later.conditions.isEmpty())
261 ✗ return false;
262
2/2
✓ Branch 11 → 12 taken 896 times.
✓ Branch 11 → 39 taken 1 time.
897 if (earlier.matchAll) {
263 // Every condition of `earlier` must be implied by some condition `later`
264 // requires (later must be allof, or have a single condition).
265
5/6
✓ Branch 12 → 13 taken 1 time.
✓ Branch 12 → 16 taken 895 times.
✓ Branch 14 → 15 taken 1 time.
✗ Branch 14 → 16 not taken.
✓ Branch 17 → 18 taken 1 time.
✓ Branch 17 → 19 taken 895 times.
896 if (!later.matchAll && later.conditions.size() > 1)
266 1 return false;
267
2/2
✓ Branch 37 → 21 taken 902 times.
✓ Branch 37 → 38 taken 86 times.
988 for (const Condition &broad : earlier.conditions) {
268 902 bool implied = false;
269
2/2
✓ Branch 32 → 24 taken 999 times.
✓ Branch 32 → 33 taken 902 times.
1901 for (const Condition &narrow : later.conditions)
270
5/5
✓ Branch 25 → 26 taken 991 times.
✓ Branch 25 → 28 taken 8 times.
✓ Branch 26 → 27 taken 991 times.
✓ Branch 27 → 28 taken 93 times.
✓ Branch 27 → 29 taken 898 times.
999 implied = implied || conditionCovers(broad, narrow);
271
2/2
✓ Branch 33 → 34 taken 809 times.
✓ Branch 33 → 35 taken 93 times.
902 if (!implied)
272 809 return false;
273 }
274 86 return true;
275 }
276 // earlier is anyof: one covering condition suffices for an allof (or
277 // single-condition) rule; an anyof rule needs every alternative covered.
278 1 const auto coveredByEarlier = [&earlier](const Condition &narrow) {
279
1/2
✓ Branch 10 → 4 taken 1 time.
✗ Branch 10 → 11 not taken.
1 for (const Condition &broad : earlier.conditions)
280
2/3
✓ Branch 5 → 6 taken 1 time.
✓ Branch 6 → 7 taken 1 time.
✗ Branch 6 → 8 not taken.
1 if (conditionCovers(broad, narrow))
281 1 return true;
282 ✗ return false;
283 1 };
284
2/6
✗ Branch 39 → 40 not taken.
✓ Branch 39 → 42 taken 1 time.
✗ Branch 41 → 42 not taken.
✗ Branch 41 → 43 not taken.
✓ Branch 44 → 45 taken 1 time.
✗ Branch 44 → 55 not taken.
1 if (later.matchAll || later.conditions.size() == 1) {
285
1/2
✓ Branch 53 → 47 taken 1 time.
✗ Branch 53 → 54 not taken.
1 for (const Condition &narrow : later.conditions)
286
2/3
✓ Branch 48 → 49 taken 1 time.
✓ Branch 49 → 50 taken 1 time.
✗ Branch 49 → 51 not taken.
1 if (coveredByEarlier(narrow))
287 1 return true;
288 ✗ return false;
289 }
290 ✗ for (const Condition &narrow : later.conditions)
291 ✗ if (!coveredByEarlier(narrow))
292 ✗ return false;
293 ✗ return true;
294 }
295