Coverage Report

Created: 2026-08-14 06:11

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/icu/icu4c/source/i18n/messageformat2_checker.cpp
Line
Count
Source
1
// © 2024 and later: Unicode, Inc. and others.
2
// License & terms of use: http://www.unicode.org/copyright.html
3
4
#include "unicode/utypes.h"
5
6
#if !UCONFIG_NO_NORMALIZATION
7
8
#if !UCONFIG_NO_FORMATTING
9
10
#if !UCONFIG_NO_MF2
11
12
#include "unicode/messageformat2.h"
13
#include "messageformat2_allocation.h"
14
#include "messageformat2_checker.h"
15
#include "messageformat2_evaluation.h"
16
#include "messageformat2_function_registry_internal.h"
17
#include "messageformat2_macros.h"
18
#include "uvector.h" // U_ASSERT
19
20
U_NAMESPACE_BEGIN
21
22
namespace message2 {
23
24
/*
25
Checks data model errors
26
(see https://github.com/unicode-org/message-format-wg/blob/main/spec/formatting.md#error-handling )
27
28
The following are checked here:
29
Variant Key Mismatch
30
Duplicate Variant
31
Missing Fallback Variant (called NonexhaustivePattern here)
32
Missing Selector Annotation
33
Duplicate Declaration
34
  - Most duplicate declaration errors are checked by the parser,
35
    but the checker checks for declarations of input variables
36
    that were previously implicitly declared
37
(Duplicate option names and duplicate declarations are checked by the parser)
38
*/
39
40
// Type environments
41
// -----------------
42
43
1.92k
TypeEnvironment::TypeEnvironment(UErrorCode& status) {
44
1.92k
    CHECK_ERROR(status);
45
46
1.92k
    UVector* temp;
47
1.92k
    temp = createStringVectorNoAdopt(status);
48
1.92k
    CHECK_ERROR(status);
49
1.92k
    annotated.adoptInstead(temp);
50
1.92k
    temp = createStringVectorNoAdopt(status);
51
1.92k
    CHECK_ERROR(status);
52
1.92k
    unannotated.adoptInstead(temp);
53
1.92k
    temp = createStringVectorNoAdopt(status);
54
1.92k
    CHECK_ERROR(status);
55
1.92k
    freeVars.adoptInstead(temp);
56
1.92k
}
57
58
8.75k
 static bool has(const UVector& v, const VariableName& var) {
59
8.75k
     return v.contains(const_cast<void*>(static_cast<const void*>(&var)));
60
8.75k
 }
61
62
// Returns true if `var` was either previously used (implicit declaration),
63
// or is in scope by an explicit declaration
64
0
bool TypeEnvironment::known(const VariableName& var) const {
65
0
    return has(*annotated, var) || has(*unannotated, var) || has(*freeVars, var);
66
0
}
67
68
2.91k
TypeEnvironment::Type TypeEnvironment::get(const VariableName& var) const {
69
2.91k
    U_ASSERT(annotated.isValid());
70
2.91k
    if (has(*annotated, var)) {
71
0
        return Annotated;
72
0
    }
73
2.91k
    U_ASSERT(unannotated.isValid());
74
2.91k
    if (has(*unannotated, var)) {
75
0
        return Unannotated;
76
0
    }
77
2.91k
    U_ASSERT(freeVars.isValid());
78
2.91k
    if (has(*freeVars, var)) {
79
0
        return FreeVariable;
80
0
    }
81
    // This case is a "free variable without an implicit declaration",
82
    // i.e. one used only in a selector expression and not in a declaration RHS
83
2.91k
    return Unannotated;
84
2.91k
}
85
86
0
void TypeEnvironment::extend(const VariableName& var, TypeEnvironment::Type t, UErrorCode& status) {
87
0
    if (t == Unannotated) {
88
0
        U_ASSERT(unannotated.isValid());
89
        // See comment below
90
0
        unannotated->addElement(const_cast<void*>(static_cast<const void*>(&var)), status);
91
0
        return;
92
0
    }
93
94
0
    if (t == FreeVariable) {
95
0
        U_ASSERT(freeVars.isValid());
96
        // See comment below
97
0
        freeVars->addElement(const_cast<void*>(static_cast<const void*>(&var)), status);
98
0
        return;
99
0
    }
100
101
0
    U_ASSERT(annotated.isValid());
102
    // This is safe because elements of `annotated` are never written
103
    // and the lifetime of `var` is guaranteed to include the lifetime of
104
    // `annotated`
105
0
    annotated->addElement(const_cast<void*>(static_cast<const void*>(&var)), status);
106
0
}
107
108
1.92k
TypeEnvironment::~TypeEnvironment() {}
109
110
// ---------------------
111
112
42.2k
Key Checker::normalizeNFC(const Key& k) const {
113
42.2k
    if (k.isWildcard()) {
114
1.05k
        return k;
115
1.05k
    }
116
41.1k
    return Key(Literal(k.asLiteral().isQuoted(),
117
41.1k
                       StandardFunctions::normalizeNFC(k.asLiteral().unquoted())));
118
42.2k
}
119
120
7.34k
static bool areDefaultKeys(const Key* keys, int32_t len) {
121
7.34k
    U_ASSERT(len > 0);
122
7.51k
    for (int32_t i = 0; i < len; i++) {
123
7.47k
        if (!keys[i].isWildcard()) {
124
7.30k
            return false;
125
7.30k
        }
126
7.47k
    }
127
41
    return true;
128
7.34k
}
129
130
0
void Checker::addFreeVars(TypeEnvironment& t, const Operand& rand, UErrorCode& status) {
131
0
    CHECK_ERROR(status);
132
133
0
    if (rand.isVariable()) {
134
0
        const VariableName& v = rand.asVariable();
135
0
        if (!t.known(v)) {
136
0
            t.extend(v, TypeEnvironment::Type::FreeVariable, status);
137
0
        }
138
0
    }
139
0
}
140
141
0
void Checker::addFreeVars(TypeEnvironment& t, const OptionMap& opts, UErrorCode& status) {
142
0
    for (int32_t i = 0; i < opts.size(); i++) {
143
0
        const Option& o = opts.getOption(i, status);
144
0
        CHECK_ERROR(status);
145
0
        addFreeVars(t, o.getValue(), status);
146
0
    }
147
0
}
148
149
0
void Checker::addFreeVars(TypeEnvironment& t, const Operator& rator, UErrorCode& status) {
150
0
    CHECK_ERROR(status);
151
152
0
    addFreeVars(t, rator.getOptionsInternal(), status);
153
0
}
154
155
0
void Checker::addFreeVars(TypeEnvironment& t, const Expression& rhs, UErrorCode& status) {
156
0
    CHECK_ERROR(status);
157
158
0
    if (rhs.isFunctionCall()) {
159
0
        const Operator* rator = rhs.getOperator(status);
160
0
        U_ASSERT(U_SUCCESS(status));
161
0
        addFreeVars(t, *rator, status);
162
0
    }
163
0
    addFreeVars(t, rhs.getOperand(), status);
164
0
}
165
166
1.44k
void Checker::checkVariants(UErrorCode& status) {
167
1.44k
    CHECK_ERROR(status);
168
169
1.44k
    U_ASSERT(!dataModel.hasPattern());
170
171
    // Check that each variant has a key list with size
172
    // equal to the number of selectors
173
1.44k
    const Variant* variants = dataModel.getVariantsInternal();
174
175
    // Check that one variant includes only wildcards
176
1.44k
    bool defaultExists = false;
177
1.44k
    bool duplicatesExist = false;
178
179
8.78k
    for (int32_t i = 0; i < dataModel.numVariants(); i++) {
180
7.38k
        const SelectorKeys& k = variants[i].getKeys();
181
7.38k
        const Key* keys = k.getKeysInternal();
182
7.38k
        int32_t len = k.len;
183
7.38k
        if (len != dataModel.numSelectors()) {
184
            // Variant key mismatch
185
42
            errors.addError(StaticErrorType::VariantKeyMismatchError, status);
186
42
            return;
187
42
        }
188
7.34k
        defaultExists |= areDefaultKeys(keys, len);
189
190
        // Check if this variant's keys are duplicated by any other variant's keys
191
7.34k
        if (!duplicatesExist) {
192
            // This check takes quadratic time, but it can be optimized if checking
193
            // this property turns out to be a bottleneck.
194
26.7k
            for (int32_t j = 0; j < i; j++) {
195
20.5k
                const SelectorKeys& k1 = variants[j].getKeys();
196
20.5k
                const Key* keys1 = k1.getKeysInternal();
197
20.5k
                bool allEqual = true;
198
                // This variant was already checked,
199
                // so we know keys1.len == len
200
21.2k
                for (int32_t kk = 0; kk < len; kk++) {
201
21.1k
                    if (!(normalizeNFC(keys[kk]) == normalizeNFC(keys1[kk]))) {
202
20.4k
                        allEqual = false;
203
20.4k
                        break;
204
20.4k
                    }
205
21.1k
                }
206
20.5k
                if (allEqual) {
207
138
                    duplicatesExist = true;
208
138
                }
209
20.5k
            }
210
6.10k
        }
211
7.34k
    }
212
213
1.39k
    if (duplicatesExist) {
214
138
        errors.addError(StaticErrorType::DuplicateVariant, status);
215
138
    }
216
1.39k
    if (!defaultExists) {
217
1.38k
        errors.addError(StaticErrorType::NonexhaustivePattern, status);
218
1.38k
    }
219
1.39k
}
220
221
void Checker::requireAnnotated(const TypeEnvironment& t,
222
                               const VariableName& selectorVar,
223
2.91k
                               UErrorCode& status) {
224
2.91k
    CHECK_ERROR(status);
225
226
2.91k
    if (t.get(selectorVar) == TypeEnvironment::Type::Annotated) {
227
0
        return; // No error
228
0
    }
229
    // If this code is reached, an error was detected
230
2.91k
    errors.addError(StaticErrorType::MissingSelectorAnnotation, status);
231
2.91k
}
232
233
1.44k
void Checker::checkSelectors(const TypeEnvironment& t, UErrorCode& status) {
234
1.44k
    U_ASSERT(!dataModel.hasPattern());
235
236
    // Check each selector; if it's not annotated, emit a
237
    // "missing selector annotation" error
238
1.44k
    const VariableName* selectors = dataModel.getSelectorsInternal();
239
4.35k
    for (int32_t i = 0; i < dataModel.numSelectors(); i++) {
240
2.91k
        requireAnnotated(t, selectors[i], status);
241
2.91k
    }
242
1.44k
}
243
244
0
TypeEnvironment::Type typeOf(TypeEnvironment& t, const Expression& expr) {
245
0
    if (expr.isFunctionCall()) {
246
0
        return TypeEnvironment::Type::Annotated;
247
0
    }
248
0
    const Operand& rand = expr.getOperand();
249
0
    U_ASSERT(!rand.isNull());
250
0
    if (rand.isLiteral()) {
251
0
        return TypeEnvironment::Type::Unannotated;
252
0
    }
253
0
    U_ASSERT(rand.isVariable());
254
0
    return t.get(rand.asVariable());
255
0
}
256
257
1.92k
void Checker::checkDeclarations(TypeEnvironment& t, UErrorCode& status) {
258
1.92k
    CHECK_ERROR(status);
259
260
    // For each declaration, extend the type environment with its type
261
    // Only a very simple type system is necessary: variables
262
    // have the type "annotated", "unannotated", or "free".
263
    // For "missing selector annotation" checking, free variables
264
    // (message arguments) are treated as unannotated.
265
    // Free variables are also used for checking duplicate declarations.
266
1.92k
    const Binding* env = dataModel.getLocalVariablesInternal();
267
1.92k
    for (int32_t i = 0; i < dataModel.bindingsLen; i++) {
268
0
        const Binding& b = env[i];
269
0
        const VariableName& lhs = b.getVariable();
270
0
        const Expression& rhs = b.getValue();
271
272
        // First, add free variables from the RHS of b
273
        // This must be done first so we can catch:
274
        // .local $foo = {$foo}
275
        // (where the RHS is the first use of $foo)
276
0
        if (b.isLocal()) {
277
0
            addFreeVars(t, rhs, status);
278
279
            // Next, check if the LHS equals any free variables
280
            // whose implicit declarations are in scope
281
0
            if (t.known(lhs) && t.get(lhs) == TypeEnvironment::Type::FreeVariable) {
282
0
                errors.addError(StaticErrorType::DuplicateDeclarationError, status);
283
0
            }
284
0
        } else {
285
            // Input declaration; if b has no annotation, there's nothing to check
286
0
            if (!b.isLocal() && b.hasAnnotation()) {
287
0
                const OptionMap& opts = b.getOptionsInternal();
288
                // For .input declarations, we just need to add any variables
289
                // referenced in the options
290
0
                addFreeVars(t, opts, status);
291
0
             }
292
            // Next, check if the LHS equals any free variables
293
            // whose implicit declarations are in scope
294
0
            if (t.known(lhs) && t.get(lhs) == TypeEnvironment::Type::FreeVariable) {
295
0
                errors.addError(StaticErrorType::DuplicateDeclarationError, status);
296
0
            }
297
0
        }
298
        // Next, extend the type environment with a binding from lhs to its type
299
0
        t.extend(lhs, typeOf(t, rhs), status);
300
0
    }
301
1.92k
}
302
303
1.92k
void Checker::check(UErrorCode& status) {
304
1.92k
    CHECK_ERROR(status);
305
306
1.92k
    TypeEnvironment typeEnv(status);
307
1.92k
    checkDeclarations(typeEnv, status);
308
    // Pattern message
309
1.92k
    if (dataModel.hasPattern()) {
310
483
        return;
311
1.44k
    } else {
312
      // Selectors message
313
1.44k
      checkSelectors(typeEnv, status);
314
1.44k
      checkVariants(status);
315
1.44k
    }
316
1.92k
}
317
318
} // namespace message2
319
U_NAMESPACE_END
320
321
#endif /* #if !UCONFIG_NO_MF2 */
322
323
#endif /* #if !UCONFIG_NO_FORMATTING */
324
325
#endif /* #if !UCONFIG_NO_NORMALIZATION */