Coverage Report

Created: 2026-09-14 06:43

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/CMake/Source/cmGeneratorExpression.cxx
Line
Count
Source
1
/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
2
   file LICENSE.rst or https://cmake.org/licensing for details.  */
3
#include "cmGeneratorExpression.h"
4
5
#include <algorithm>
6
#include <cstddef>
7
#include <memory>
8
#include <stack>
9
#include <utility>
10
11
#include <cm/string_view>
12
13
#include "cmsys/RegularExpression.hxx"
14
15
#include "cmGenExContext.h"
16
#include "cmGenExEvaluation.h"
17
#include "cmGeneratorExpressionDAGChecker.h"
18
#include "cmGeneratorExpressionEvaluator.h"
19
#include "cmGeneratorExpressionLexer.h"
20
#include "cmGeneratorExpressionParser.h"
21
#include "cmGeneratorTarget.h"
22
#include "cmList.h"
23
#include "cmLocalGenerator.h"
24
#include "cmMakefile.h"
25
#include "cmMessageType.h"
26
#include "cmStringAlgorithms.h"
27
#include "cmSystemTools.h"
28
#include "cmUnreachable.h"
29
#include "cmake.h"
30
31
cmGeneratorExpression::cmGeneratorExpression(cmake& cmakeInstance,
32
                                             cmListFileBacktrace backtrace)
33
0
  : CMakeInstance(cmakeInstance)
34
0
  , Backtrace(std::move(backtrace))
35
0
{
36
0
}
37
38
0
cmCompiledGeneratorExpression::~cmCompiledGeneratorExpression() = default;
39
40
0
cmGeneratorExpression::~cmGeneratorExpression() = default;
41
42
std::unique_ptr<cmCompiledGeneratorExpression> cmGeneratorExpression::Parse(
43
  std::string input) const
44
0
{
45
0
  return std::unique_ptr<cmCompiledGeneratorExpression>(
46
0
    new cmCompiledGeneratorExpression(this->CMakeInstance, this->Backtrace,
47
0
                                      std::move(input)));
48
0
}
49
50
std::string cmGeneratorExpression::Evaluate(
51
  std::string input, cmLocalGenerator const* lg, std::string const& config,
52
  cmGeneratorTarget const* headTarget,
53
  cmGeneratorExpressionDAGChecker* dagChecker,
54
  cmGeneratorTarget const* currentTarget, std::string const& language)
55
0
{
56
0
  if (Find(input) != std::string::npos) {
57
0
#ifndef CMAKE_BOOTSTRAP
58
0
    auto profilingRAII = lg->GetCMakeInstance()->CreateProfilingEntry(
59
0
      "genex_compile_eval", input);
60
0
#endif
61
62
0
    cm::GenEx::Context context(lg, config, language);
63
0
    cmCompiledGeneratorExpression cge(*lg->GetCMakeInstance(),
64
0
                                      cmListFileBacktrace(), std::move(input));
65
0
    return cge.Evaluate(context, dagChecker, headTarget, currentTarget);
66
0
  }
67
0
  return input;
68
0
}
69
70
std::string const& cmCompiledGeneratorExpression::Evaluate(
71
  cmLocalGenerator const* lg, std::string const& config,
72
  cmGeneratorTarget const* headTarget) const
73
0
{
74
0
  cm::GenEx::Context context(lg, config);
75
0
  return this->Evaluate(context, nullptr, headTarget);
76
0
}
77
78
std::string const& cmCompiledGeneratorExpression::Evaluate(
79
  cm::GenEx::Context const& context,
80
  cmGeneratorExpressionDAGChecker* dagChecker,
81
  cmGeneratorTarget const* headTarget,
82
  cmGeneratorTarget const* currentTarget) const
83
0
{
84
0
  cm::GenEx::Evaluation eval(context, this->Quiet, headTarget,
85
0
                             currentTarget ? currentTarget : headTarget,
86
0
                             this->EvaluateForBuildsystem, this->Backtrace);
87
88
0
  if (!this->NeedsEvaluation) {
89
0
    return this->Input;
90
0
  }
91
92
0
  this->Output.clear();
93
94
0
  for (auto const& it : this->Evaluators) {
95
0
    this->Output += it->Evaluate(&eval, dagChecker);
96
97
0
    this->SeenTargetProperties.insert(eval.SeenTargetProperties.cbegin(),
98
0
                                      eval.SeenTargetProperties.cend());
99
0
    if (eval.HadError) {
100
0
      this->Output.clear();
101
0
      break;
102
0
    }
103
0
  }
104
105
0
  this->MaxLanguageStandard = eval.MaxLanguageStandard;
106
107
0
  if (!eval.HadError) {
108
0
    this->HadContextSensitiveCondition = eval.HadContextSensitiveCondition;
109
0
    this->HadHeadSensitiveCondition = eval.HadHeadSensitiveCondition;
110
0
    this->HadLinkLanguageSensitiveCondition =
111
0
      eval.HadLinkLanguageSensitiveCondition;
112
0
    this->SourceSensitiveTargets = eval.SourceSensitiveTargets;
113
0
  }
114
115
0
  this->DependTargets = eval.DependTargets;
116
0
  this->AllTargetsSeen = eval.AllTargets;
117
0
  return this->Output;
118
0
}
119
120
cmCompiledGeneratorExpression::cmCompiledGeneratorExpression(
121
  cmake& cmakeInstance, cmListFileBacktrace backtrace, std::string input)
122
0
  : Backtrace(std::move(backtrace))
123
0
  , Input(std::move(input))
124
0
{
125
0
#ifndef CMAKE_BOOTSTRAP
126
0
  auto profilingRAII =
127
0
    cmakeInstance.CreateProfilingEntry("genex_compile", this->Input);
128
0
#endif
129
130
0
  cmGeneratorExpressionLexer l;
131
0
  std::vector<cmGeneratorExpressionToken> tokens = l.Tokenize(this->Input);
132
0
  this->NeedsEvaluation = l.GetSawGeneratorExpression();
133
134
0
  if (this->NeedsEvaluation) {
135
0
    cmGeneratorExpressionParser p(tokens);
136
0
    p.Parse(this->Evaluators);
137
0
  }
138
0
}
139
140
std::string cmGeneratorExpression::StripEmptyListElements(
141
  std::string const& input)
142
12.4k
{
143
12.4k
  if (input.find(';') == std::string::npos) {
144
7.82k
    return input;
145
7.82k
  }
146
4.60k
  std::string result;
147
4.60k
  result.reserve(input.size());
148
149
4.60k
  char const* c = input.c_str();
150
4.60k
  char const* last = c;
151
4.60k
  bool skipSemiColons = true;
152
1.80M
  for (; *c; ++c) {
153
1.80M
    if (*c == ';') {
154
106k
      if (skipSemiColons) {
155
9.39k
        result.append(last, c - last);
156
9.39k
        last = c + 1;
157
9.39k
      }
158
106k
      skipSemiColons = true;
159
1.69M
    } else {
160
1.69M
      skipSemiColons = false;
161
1.69M
    }
162
1.80M
  }
163
4.60k
  result.append(last);
164
165
4.60k
  if (!result.empty() && *(result.end() - 1) == ';') {
166
828
    result.resize(result.size() - 1);
167
828
  }
168
169
4.60k
  return result;
170
12.4k
}
171
172
static std::string extractAllGeneratorExpressions(
173
  cm::string_view input,
174
  std::map<std::string, std::vector<std::string>>* collected)
175
4.97k
{
176
4.97k
  std::string result;
177
4.97k
  std::string::size_type pos = 0;
178
4.97k
  std::string::size_type lastPos = pos;
179
4.97k
  std::stack<char const*> starts; // indices of "$<"
180
4.97k
  std::stack<char const*> colons; // indices of ":"
181
68.4k
  while ((pos = input.find("$<", lastPos)) != std::string::npos) {
182
63.5k
    result += input.substr(lastPos, pos - lastPos);
183
63.5k
    starts.push(input.data() + pos);
184
63.5k
    pos += 2;
185
63.5k
    char const* c = input.data() + pos;
186
63.5k
    char const* const cStart = c;
187
2.32M
    for (; *c; ++c) {
188
2.27M
      if (cmGeneratorExpression::StartsWithGeneratorExpression(c)) {
189
855k
        starts.push(c);
190
855k
        ++c;
191
855k
        continue;
192
855k
      }
193
1.42M
      if (c[0] == ':') {
194
376k
        if (colons.size() < starts.size()) {
195
303k
          colons.push(c);
196
303k
        }
197
1.04M
      } else if (c[0] == '>') {
198
198k
        if (!colons.empty() && !starts.empty() &&
199
185k
            starts.top() < colons.top()) {
200
172k
          if (collected) {
201
86.0k
            (*collected)[std::string(starts.top() + 2, colons.top())]
202
86.0k
              .push_back(std::string(colons.top() + 1, c));
203
86.0k
          }
204
172k
          colons.pop();
205
172k
        }
206
198k
        if (!starts.empty()) {
207
198k
          starts.pop();
208
198k
        }
209
198k
        if (starts.empty()) {
210
12.4k
          break;
211
12.4k
        }
212
198k
      }
213
1.42M
    }
214
63.5k
    std::string::size_type const traversed = (c - cStart) + 1;
215
63.5k
    if (!*c) {
216
51.0k
      result += cmStrCat("$<", input.substr(pos, traversed));
217
51.0k
    }
218
63.5k
    pos += traversed;
219
63.5k
    lastPos = pos;
220
63.5k
  }
221
4.97k
  if (starts.empty()) {
222
2.36k
    result += input.substr(lastPos);
223
2.36k
  }
224
4.97k
  return cmGeneratorExpression::StripEmptyListElements(result);
225
4.97k
}
226
227
static std::string stripAllGeneratorExpressions(cm::string_view input)
228
2.48k
{
229
2.48k
  return extractAllGeneratorExpressions(input, nullptr);
230
2.48k
}
231
232
static void prefixItems(std::string const& content, std::string& result,
233
                        cm::string_view prefix)
234
0
{
235
0
  std::vector<std::string> entries;
236
0
  cmGeneratorExpression::Split(content, entries);
237
0
  char const* sep = "";
238
0
  for (std::string const& e : entries) {
239
0
    result += sep;
240
0
    sep = ";";
241
0
    if (!cmSystemTools::FileIsFullPath(e) &&
242
0
        cmGeneratorExpression::Find(e) != 0) {
243
0
      result += prefix;
244
0
    }
245
0
    result += e;
246
0
  }
247
0
}
248
249
static std::string stripExportInterface(
250
  cm::string_view input, cmGeneratorExpression::PreprocessContext context,
251
  cm::string_view importPrefix)
252
4.97k
{
253
4.97k
  std::string result;
254
255
4.97k
  int nestingLevel = 0;
256
4.97k
  std::string::size_type pos = 0;
257
4.97k
  std::string::size_type lastPos = pos;
258
19.2k
  while (true) {
259
19.2k
    std::string::size_type bPos = input.find("$<BUILD_INTERFACE:", lastPos);
260
19.2k
    std::string::size_type iPos = input.find("$<INSTALL_INTERFACE:", lastPos);
261
19.2k
    std::string::size_type lPos =
262
19.2k
      input.find("$<BUILD_LOCAL_INTERFACE:", lastPos);
263
264
19.2k
    pos = std::min({ bPos, iPos, lPos });
265
19.2k
    if (pos == std::string::npos) {
266
4.97k
      break;
267
4.97k
    }
268
269
14.2k
    result += input.substr(lastPos, pos - lastPos);
270
14.2k
    enum class FoundGenex
271
14.2k
    {
272
14.2k
      BuildInterface,
273
14.2k
      InstallInterface,
274
14.2k
      BuildLocalInterface,
275
14.2k
    } foundGenex = FoundGenex::BuildInterface;
276
14.2k
    if (pos == bPos) {
277
7.07k
      foundGenex = FoundGenex::BuildInterface;
278
7.07k
      pos += cmStrLen("$<BUILD_INTERFACE:");
279
7.15k
    } else if (pos == iPos) {
280
5.52k
      foundGenex = FoundGenex::InstallInterface;
281
5.52k
      pos += cmStrLen("$<INSTALL_INTERFACE:");
282
5.52k
    } else if (pos == lPos) {
283
1.63k
      foundGenex = FoundGenex::BuildLocalInterface;
284
1.63k
      pos += cmStrLen("$<BUILD_LOCAL_INTERFACE:");
285
1.63k
    } else {
286
0
      CM_UNREACHABLE;
287
0
    }
288
14.2k
    nestingLevel = 1;
289
14.2k
    char const* c = input.data() + pos;
290
14.2k
    char const* const cStart = c;
291
503k
    for (; *c; ++c) {
292
497k
      if (cmGeneratorExpression::StartsWithGeneratorExpression(c)) {
293
146k
        ++nestingLevel;
294
146k
        ++c;
295
146k
        continue;
296
146k
      }
297
351k
      if (c[0] == '>') {
298
29.2k
        --nestingLevel;
299
29.2k
        if (nestingLevel != 0) {
300
20.4k
          continue;
301
20.4k
        }
302
8.80k
        if (context == cmGeneratorExpression::BuildInterface &&
303
4.40k
            foundGenex == FoundGenex::BuildInterface) {
304
2.05k
          result += input.substr(pos, c - cStart);
305
6.74k
        } else if (context == cmGeneratorExpression::InstallInterface &&
306
4.40k
                   foundGenex == FoundGenex::InstallInterface) {
307
2.30k
          std::string const content =
308
2.30k
            static_cast<std::string>(input.substr(pos, c - cStart));
309
2.30k
          if (!importPrefix.empty()) {
310
0
            prefixItems(content, result, importPrefix);
311
2.30k
          } else {
312
2.30k
            result += content;
313
2.30k
          }
314
2.30k
        }
315
8.80k
        break;
316
29.2k
      }
317
351k
    }
318
14.2k
    std::string::size_type const traversed = (c - cStart) + 1;
319
14.2k
    if (!*c) {
320
5.43k
      auto remaining = input.substr(pos, traversed);
321
5.43k
      switch (foundGenex) {
322
2.97k
        case FoundGenex::BuildInterface:
323
2.97k
          result = cmStrCat(result, "$<BUILD_INTERFACE:", remaining);
324
2.97k
          break;
325
916
        case FoundGenex::InstallInterface:
326
916
          result = cmStrCat(result, "$<INSTALL_INTERFACE:", remaining);
327
916
          break;
328
1.54k
        case FoundGenex::BuildLocalInterface:
329
1.54k
          result = cmStrCat(result, "$<BUILD_LOCAL_INTERFACE:", remaining);
330
1.54k
          break;
331
5.43k
      }
332
5.43k
    }
333
14.2k
    pos += traversed;
334
14.2k
    lastPos = pos;
335
14.2k
  }
336
4.97k
  if (nestingLevel == 0) {
337
4.25k
    result += input.substr(lastPos);
338
4.25k
  }
339
340
4.97k
  return cmGeneratorExpression::StripEmptyListElements(result);
341
4.97k
}
342
343
void cmGeneratorExpression::Split(std::string const& input,
344
                                  std::vector<std::string>& output)
345
2.48k
{
346
2.48k
  std::string::size_type pos = 0;
347
2.48k
  std::string::size_type lastPos = pos;
348
31.0k
  while ((pos = input.find("$<", lastPos)) != std::string::npos) {
349
28.6k
    std::string part = input.substr(lastPos, pos - lastPos);
350
28.6k
    std::string preGenex;
351
28.6k
    if (!part.empty()) {
352
21.8k
      std::string::size_type startPos = input.rfind(';', pos);
353
21.8k
      if (startPos == std::string::npos) {
354
1.76k
        preGenex = part;
355
1.76k
        part.clear();
356
20.0k
      } else if (startPos != pos - 1 && startPos >= lastPos) {
357
5.02k
        part = input.substr(lastPos, startPos - lastPos);
358
5.02k
        preGenex = input.substr(startPos + 1, pos - startPos - 1);
359
5.02k
      }
360
21.8k
      if (!part.empty()) {
361
18.2k
        cmExpandList(part, output);
362
18.2k
      }
363
21.8k
    }
364
28.6k
    pos += 2;
365
28.6k
    int nestingLevel = 1;
366
28.6k
    char const* c = input.c_str() + pos;
367
28.6k
    char const* const cStart = c;
368
912k
    for (; *c; ++c) {
369
887k
      if (cmGeneratorExpression::StartsWithGeneratorExpression(c)) {
370
398k
        ++nestingLevel;
371
398k
        ++c;
372
398k
        continue;
373
398k
      }
374
489k
      if (c[0] == '>') {
375
57.4k
        --nestingLevel;
376
57.4k
        if (nestingLevel == 0) {
377
3.88k
          break;
378
3.88k
        }
379
57.4k
      }
380
489k
    }
381
306k
    for (; *c; ++c) {
382
      // Capture the part after the genex and before the next ';'
383
279k
      if (c[0] == ';') {
384
2.41k
        --c;
385
2.41k
        break;
386
2.41k
      }
387
279k
    }
388
28.6k
    std::string::size_type const traversed = (c - cStart) + 1;
389
28.6k
    output.push_back(cmStrCat(preGenex, "$<", input.substr(pos, traversed)));
390
28.6k
    pos += traversed;
391
28.6k
    lastPos = pos;
392
28.6k
  }
393
2.48k
  if (lastPos < input.size()) {
394
705
    cmExpandList(input.substr(lastPos), output);
395
705
  }
396
2.48k
}
397
398
std::string cmGeneratorExpression::Preprocess(cm::string_view input,
399
                                              PreprocessContext context,
400
                                              cm::string_view importPrefix)
401
7.45k
{
402
7.45k
  if (context == StripAllGeneratorExpressions) {
403
2.48k
    return stripAllGeneratorExpressions(input);
404
2.48k
  }
405
4.97k
  if (context == BuildInterface || context == InstallInterface) {
406
4.97k
    return stripExportInterface(input, context, importPrefix);
407
4.97k
  }
408
409
0
  CM_UNREACHABLE;
410
0
  return std::string();
411
4.97k
}
412
413
std::string cmGeneratorExpression::Collect(
414
  std::string const& input,
415
  std::map<std::string, std::vector<std::string>>& collected)
416
2.48k
{
417
2.48k
  return extractAllGeneratorExpressions(input, &collected);
418
2.48k
}
419
420
bool cmGeneratorExpression::ForbidGeneratorExpressions(
421
  cmGeneratorTarget const* target, std::string const& propertyName,
422
  std::string const& propertyValue)
423
0
{
424
0
  std::map<std::string, std::vector<std::string>> allowList;
425
0
  std::string evaluatedValue;
426
0
  return ForbidGeneratorExpressions(target, propertyName, propertyValue,
427
0
                                    evaluatedValue, allowList);
428
0
}
429
430
bool cmGeneratorExpression::ForbidGeneratorExpressions(
431
  cmGeneratorTarget const* target, std::string const& propertyName,
432
  std::string const& propertyValue, std::string& evaluatedValue,
433
  std::map<std::string, std::vector<std::string>>& allowList)
434
0
{
435
0
  size_t const initialAllowedGenExps = allowList.size();
436
0
  evaluatedValue = Collect(propertyValue, allowList);
437
0
  if (evaluatedValue != propertyValue &&
438
0
      allowList.size() > initialAllowedGenExps) {
439
0
    target->Makefile->IssueMessage(
440
0
      MessageType::FATAL_ERROR,
441
0
      cmStrCat("Property \"", propertyName, "\" of target \"",
442
0
               target->GetName(),
443
0
               "\" contains a generator expression. This is not allowed."));
444
0
    return false;
445
0
  }
446
447
  // Check for nested generator expressions (e.g., $<LINK_ONLY:$<...>>).
448
0
  for (auto const& genexp : allowList) {
449
0
    for (auto const& value : genexp.second) {
450
0
      if (value.find("$<") != std::string::npos) {
451
0
        target->Makefile->IssueMessage(
452
0
          MessageType::FATAL_ERROR,
453
0
          cmStrCat("$<", genexp.first, ":...> expression in \"", propertyName,
454
0
                   "\" of target \"", target->GetName(),
455
0
                   "\" contains a generator expression. This is not "
456
0
                   "allowed."));
457
0
        return false;
458
0
      }
459
0
    }
460
0
  }
461
0
  return true;
462
0
}
463
464
cm::string_view::size_type cmGeneratorExpression::Find(cm::string_view input)
465
2.48k
{
466
2.48k
  cm::string_view::size_type const openpos = input.find("$<");
467
2.48k
  if (openpos != cm::string_view::npos &&
468
1.99k
      input.find('>', openpos) != cm::string_view::npos) {
469
1.12k
    return openpos;
470
1.12k
  }
471
1.35k
  return cm::string_view::npos;
472
2.48k
}
473
474
bool cmGeneratorExpression::IsValidTargetName(std::string const& input)
475
2.48k
{
476
  // The ':' is supported to allow use with IMPORTED targets. At least
477
  // Qt 4 and 5 IMPORTED targets use ':' as the namespace delimiter.
478
2.48k
  static cmsys::RegularExpression targetNameValidator("^[A-Za-z0-9_.:+-]+$");
479
480
2.48k
  return targetNameValidator.find(input);
481
2.48k
}
482
483
void cmGeneratorExpression::ReplaceInstallPrefix(
484
  std::string& input, std::string const& replacement)
485
0
{
486
0
  std::string::size_type pos = 0;
487
0
  std::string::size_type lastPos = pos;
488
489
0
  while ((pos = input.find("$<INSTALL_PREFIX>", lastPos)) !=
490
0
         std::string::npos) {
491
0
    std::string::size_type endPos = pos + cmStrLen("$<INSTALL_PREFIX>");
492
0
    input.replace(pos, endPos - pos, replacement);
493
0
    lastPos = endPos;
494
0
  }
495
0
}
496
497
void cmCompiledGeneratorExpression::GetMaxLanguageStandard(
498
  cmGeneratorTarget const* tgt, std::map<std::string, std::string>& mapping)
499
0
{
500
0
  auto it = this->MaxLanguageStandard.find(tgt);
501
0
  if (it != this->MaxLanguageStandard.end()) {
502
0
    mapping = it->second;
503
0
  }
504
0
}
505
506
std::string const& cmGeneratorExpressionInterpreter::Evaluate(
507
  std::string expression, std::string const& property)
508
0
{
509
0
  this->CompiledGeneratorExpression =
510
0
    this->GeneratorExpression.Parse(std::move(expression));
511
512
0
  cm::GenEx::Context context(this->LocalGenerator, this->Config,
513
0
                             this->Language);
514
515
  // Specify COMPILE_OPTIONS to DAGchecker, same semantic as COMPILE_FLAGS
516
0
  cmGeneratorExpressionDAGChecker dagChecker{
517
0
    this->HeadTarget,
518
0
    property == "COMPILE_FLAGS" ? "COMPILE_OPTIONS" : property,
519
0
    nullptr,
520
0
    nullptr,
521
0
    context,
522
0
  };
523
0
  return this->CompiledGeneratorExpression->Evaluate(context, &dagChecker,
524
0
                                                     this->HeadTarget);
525
0
}