Coverage Report

Created: 2026-04-29 07:01

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 <cassert>
7
#include <cstddef>
8
#include <memory>
9
#include <stack>
10
#include <utility>
11
12
#include <cm/string_view>
13
14
#include "cmsys/RegularExpression.hxx"
15
16
#include "cmGenExContext.h"
17
#include "cmGenExEvaluation.h"
18
#include "cmGeneratorExpressionDAGChecker.h"
19
#include "cmGeneratorExpressionEvaluator.h"
20
#include "cmGeneratorExpressionLexer.h"
21
#include "cmGeneratorExpressionParser.h"
22
#include "cmGeneratorTarget.h"
23
#include "cmList.h"
24
#include "cmLocalGenerator.h"
25
#include "cmMakefile.h"
26
#include "cmMessageType.h"
27
#include "cmStringAlgorithms.h"
28
#include "cmSystemTools.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.6k
{
143
12.6k
  if (input.find(';') == std::string::npos) {
144
7.92k
    return input;
145
7.92k
  }
146
4.72k
  std::string result;
147
4.72k
  result.reserve(input.size());
148
149
4.72k
  char const* c = input.c_str();
150
4.72k
  char const* last = c;
151
4.72k
  bool skipSemiColons = true;
152
1.64M
  for (; *c; ++c) {
153
1.63M
    if (*c == ';') {
154
121k
      if (skipSemiColons) {
155
9.37k
        result.append(last, c - last);
156
9.37k
        last = c + 1;
157
9.37k
      }
158
121k
      skipSemiColons = true;
159
1.51M
    } else {
160
1.51M
      skipSemiColons = false;
161
1.51M
    }
162
1.63M
  }
163
4.72k
  result.append(last);
164
165
4.72k
  if (!result.empty() && *(result.end() - 1) == ';') {
166
885
    result.resize(result.size() - 1);
167
885
  }
168
169
4.72k
  return result;
170
12.6k
}
171
172
static std::string extractAllGeneratorExpressions(
173
  cm::string_view input,
174
  std::map<std::string, std::vector<std::string>>* collected)
175
5.05k
{
176
5.05k
  std::string result;
177
5.05k
  std::string::size_type pos = 0;
178
5.05k
  std::string::size_type lastPos = pos;
179
5.05k
  std::stack<char const*> starts; // indices of "$<"
180
5.05k
  std::stack<char const*> colons; // indices of ":"
181
60.9k
  while ((pos = input.find("$<", lastPos)) != std::string::npos) {
182
55.9k
    result += input.substr(lastPos, pos - lastPos);
183
55.9k
    starts.push(input.data() + pos);
184
55.9k
    pos += 2;
185
55.9k
    char const* c = input.data() + pos;
186
55.9k
    char const* const cStart = c;
187
2.17M
    for (; *c; ++c) {
188
2.13M
      if (cmGeneratorExpression::StartsWithGeneratorExpression(c)) {
189
815k
        starts.push(c);
190
815k
        ++c;
191
815k
        continue;
192
815k
      }
193
1.31M
      if (c[0] == ':') {
194
350k
        if (colons.size() < starts.size()) {
195
295k
          colons.push(c);
196
295k
        }
197
969k
      } else if (c[0] == '>') {
198
150k
        if (!colons.empty() && !starts.empty() &&
199
145k
            starts.top() < colons.top()) {
200
128k
          if (collected) {
201
64.4k
            (*collected)[std::string(starts.top() + 2, colons.top())]
202
64.4k
              .push_back(std::string(colons.top() + 1, c));
203
64.4k
          }
204
128k
          colons.pop();
205
128k
        }
206
150k
        if (!starts.empty()) {
207
150k
          starts.pop();
208
150k
        }
209
150k
        if (starts.empty()) {
210
11.0k
          break;
211
11.0k
        }
212
150k
      }
213
1.31M
    }
214
55.9k
    std::string::size_type const traversed = (c - cStart) + 1;
215
55.9k
    if (!*c) {
216
44.8k
      result += cmStrCat("$<", input.substr(pos, traversed));
217
44.8k
    }
218
55.9k
    pos += traversed;
219
55.9k
    lastPos = pos;
220
55.9k
  }
221
5.05k
  if (starts.empty()) {
222
2.40k
    result += input.substr(lastPos);
223
2.40k
  }
224
5.05k
  return cmGeneratorExpression::StripEmptyListElements(result);
225
5.05k
}
226
227
static std::string stripAllGeneratorExpressions(cm::string_view input)
228
2.52k
{
229
2.52k
  return extractAllGeneratorExpressions(input, nullptr);
230
2.52k
}
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
5.05k
{
253
5.05k
  std::string result;
254
255
5.05k
  int nestingLevel = 0;
256
5.05k
  std::string::size_type pos = 0;
257
5.05k
  std::string::size_type lastPos = pos;
258
21.0k
  while (true) {
259
21.0k
    std::string::size_type bPos = input.find("$<BUILD_INTERFACE:", lastPos);
260
21.0k
    std::string::size_type iPos = input.find("$<INSTALL_INTERFACE:", lastPos);
261
21.0k
    std::string::size_type lPos =
262
21.0k
      input.find("$<BUILD_LOCAL_INTERFACE:", lastPos);
263
264
21.0k
    pos = std::min({ bPos, iPos, lPos });
265
21.0k
    if (pos == std::string::npos) {
266
5.05k
      break;
267
5.05k
    }
268
269
16.0k
    result += input.substr(lastPos, pos - lastPos);
270
16.0k
    enum class FoundGenex
271
16.0k
    {
272
16.0k
      BuildInterface,
273
16.0k
      InstallInterface,
274
16.0k
      BuildLocalInterface,
275
16.0k
    } foundGenex = FoundGenex::BuildInterface;
276
16.0k
    if (pos == bPos) {
277
10.2k
      foundGenex = FoundGenex::BuildInterface;
278
10.2k
      pos += cmStrLen("$<BUILD_INTERFACE:");
279
10.2k
    } else if (pos == iPos) {
280
4.61k
      foundGenex = FoundGenex::InstallInterface;
281
4.61k
      pos += cmStrLen("$<INSTALL_INTERFACE:");
282
4.61k
    } else if (pos == lPos) {
283
1.14k
      foundGenex = FoundGenex::BuildLocalInterface;
284
1.14k
      pos += cmStrLen("$<BUILD_LOCAL_INTERFACE:");
285
1.14k
    } else {
286
0
      assert(false && "Invalid position found");
287
0
    }
288
16.0k
    nestingLevel = 1;
289
16.0k
    char const* c = input.data() + pos;
290
16.0k
    char const* const cStart = c;
291
399k
    for (; *c; ++c) {
292
395k
      if (cmGeneratorExpression::StartsWithGeneratorExpression(c)) {
293
105k
        ++nestingLevel;
294
105k
        ++c;
295
105k
        continue;
296
105k
      }
297
289k
      if (c[0] == '>') {
298
22.0k
        --nestingLevel;
299
22.0k
        if (nestingLevel != 0) {
300
10.2k
          continue;
301
10.2k
        }
302
11.7k
        if (context == cmGeneratorExpression::BuildInterface &&
303
5.88k
            foundGenex == FoundGenex::BuildInterface) {
304
3.90k
          result += input.substr(pos, c - cStart);
305
7.86k
        } else if (context == cmGeneratorExpression::InstallInterface &&
306
5.88k
                   foundGenex == FoundGenex::InstallInterface) {
307
1.92k
          std::string const content =
308
1.92k
            static_cast<std::string>(input.substr(pos, c - cStart));
309
1.92k
          if (!importPrefix.empty()) {
310
0
            prefixItems(content, result, importPrefix);
311
1.92k
          } else {
312
1.92k
            result += content;
313
1.92k
          }
314
1.92k
        }
315
11.7k
        break;
316
22.0k
      }
317
289k
    }
318
16.0k
    std::string::size_type const traversed = (c - cStart) + 1;
319
16.0k
    if (!*c) {
320
4.23k
      auto remaining = input.substr(pos, traversed);
321
4.23k
      switch (foundGenex) {
322
2.44k
        case FoundGenex::BuildInterface:
323
2.44k
          result = cmStrCat(result, "$<BUILD_INTERFACE:", remaining);
324
2.44k
          break;
325
758
        case FoundGenex::InstallInterface:
326
758
          result = cmStrCat(result, "$<INSTALL_INTERFACE:", remaining);
327
758
          break;
328
1.03k
        case FoundGenex::BuildLocalInterface:
329
1.03k
          result = cmStrCat(result, "$<BUILD_LOCAL_INTERFACE:", remaining);
330
1.03k
          break;
331
4.23k
      }
332
4.23k
    }
333
16.0k
    pos += traversed;
334
16.0k
    lastPos = pos;
335
16.0k
  }
336
5.05k
  if (nestingLevel == 0) {
337
4.36k
    result += input.substr(lastPos);
338
4.36k
  }
339
340
5.05k
  return cmGeneratorExpression::StripEmptyListElements(result);
341
5.05k
}
342
343
void cmGeneratorExpression::Split(std::string const& input,
344
                                  std::vector<std::string>& output)
345
2.52k
{
346
2.52k
  std::string::size_type pos = 0;
347
2.52k
  std::string::size_type lastPos = pos;
348
27.0k
  while ((pos = input.find("$<", lastPos)) != std::string::npos) {
349
24.5k
    std::string part = input.substr(lastPos, pos - lastPos);
350
24.5k
    std::string preGenex;
351
24.5k
    if (!part.empty()) {
352
17.4k
      std::string::size_type startPos = input.rfind(';', pos);
353
17.4k
      if (startPos == std::string::npos) {
354
2.05k
        preGenex = part;
355
2.05k
        part.clear();
356
15.3k
      } else if (startPos != pos - 1 && startPos >= lastPos) {
357
5.75k
        part = input.substr(lastPos, startPos - lastPos);
358
5.75k
        preGenex = input.substr(startPos + 1, pos - startPos - 1);
359
5.75k
      }
360
17.4k
      if (!part.empty()) {
361
13.5k
        cmExpandList(part, output);
362
13.5k
      }
363
17.4k
    }
364
24.5k
    pos += 2;
365
24.5k
    int nestingLevel = 1;
366
24.5k
    char const* c = input.c_str() + pos;
367
24.5k
    char const* const cStart = c;
368
822k
    for (; *c; ++c) {
369
801k
      if (cmGeneratorExpression::StartsWithGeneratorExpression(c)) {
370
349k
        ++nestingLevel;
371
349k
        ++c;
372
349k
        continue;
373
349k
      }
374
452k
      if (c[0] == '>') {
375
48.5k
        --nestingLevel;
376
48.5k
        if (nestingLevel == 0) {
377
3.18k
          break;
378
3.18k
        }
379
48.5k
      }
380
452k
    }
381
356k
    for (; *c; ++c) {
382
      // Capture the part after the genex and before the next ';'
383
333k
      if (c[0] == ';') {
384
1.72k
        --c;
385
1.72k
        break;
386
1.72k
      }
387
333k
    }
388
24.5k
    std::string::size_type const traversed = (c - cStart) + 1;
389
24.5k
    output.push_back(cmStrCat(preGenex, "$<", input.substr(pos, traversed)));
390
24.5k
    pos += traversed;
391
24.5k
    lastPos = pos;
392
24.5k
  }
393
2.52k
  if (lastPos < input.size()) {
394
722
    cmExpandList(input.substr(lastPos), output);
395
722
  }
396
2.52k
}
397
398
std::string cmGeneratorExpression::Preprocess(cm::string_view input,
399
                                              PreprocessContext context,
400
                                              cm::string_view importPrefix)
401
7.58k
{
402
7.58k
  if (context == StripAllGeneratorExpressions) {
403
2.52k
    return stripAllGeneratorExpressions(input);
404
2.52k
  }
405
5.05k
  if (context == BuildInterface || context == InstallInterface) {
406
5.05k
    return stripExportInterface(input, context, importPrefix);
407
5.05k
  }
408
409
5.05k
  assert(false &&
410
0
         "cmGeneratorExpression::Preprocess called with invalid args");
411
0
  return std::string();
412
5.05k
}
413
414
std::string cmGeneratorExpression::Collect(
415
  std::string const& input,
416
  std::map<std::string, std::vector<std::string>>& collected)
417
2.52k
{
418
2.52k
  return extractAllGeneratorExpressions(input, &collected);
419
2.52k
}
420
421
bool cmGeneratorExpression::ForbidGeneratorExpressions(
422
  cmGeneratorTarget const* target, std::string const& propertyName,
423
  std::string const& propertyValue)
424
0
{
425
0
  std::map<std::string, std::vector<std::string>> allowList;
426
0
  std::string evaluatedValue;
427
0
  return ForbidGeneratorExpressions(target, propertyName, propertyValue,
428
0
                                    evaluatedValue, allowList);
429
0
}
430
431
bool cmGeneratorExpression::ForbidGeneratorExpressions(
432
  cmGeneratorTarget const* target, std::string const& propertyName,
433
  std::string const& propertyValue, std::string& evaluatedValue,
434
  std::map<std::string, std::vector<std::string>>& allowList)
435
0
{
436
0
  size_t const initialAllowedGenExps = allowList.size();
437
0
  evaluatedValue = Collect(propertyValue, allowList);
438
0
  if (evaluatedValue != propertyValue &&
439
0
      allowList.size() > initialAllowedGenExps) {
440
0
    target->Makefile->IssueMessage(
441
0
      MessageType::FATAL_ERROR,
442
0
      cmStrCat("Property \"", propertyName, "\" of target \"",
443
0
               target->GetName(),
444
0
               "\" contains a generator expression. This is not allowed."));
445
0
    return false;
446
0
  }
447
448
  // Check for nested generator expressions (e.g., $<LINK_ONLY:$<...>>).
449
0
  for (auto const& genexp : allowList) {
450
0
    for (auto const& value : genexp.second) {
451
0
      if (value.find("$<") != std::string::npos) {
452
0
        target->Makefile->IssueMessage(
453
0
          MessageType::FATAL_ERROR,
454
0
          cmStrCat("$<", genexp.first, ":...> expression in \"", propertyName,
455
0
                   "\" of target \"", target->GetName(),
456
0
                   "\" contains a generator expression. This is not "
457
0
                   "allowed."));
458
0
        return false;
459
0
      }
460
0
    }
461
0
  }
462
0
  return true;
463
0
}
464
465
cm::string_view::size_type cmGeneratorExpression::Find(cm::string_view input)
466
2.52k
{
467
2.52k
  cm::string_view::size_type const openpos = input.find("$<");
468
2.52k
  if (openpos != cm::string_view::npos &&
469
2.01k
      input.find('>', openpos) != cm::string_view::npos) {
470
1.14k
    return openpos;
471
1.14k
  }
472
1.38k
  return cm::string_view::npos;
473
2.52k
}
474
475
bool cmGeneratorExpression::IsValidTargetName(std::string const& input)
476
2.52k
{
477
  // The ':' is supported to allow use with IMPORTED targets. At least
478
  // Qt 4 and 5 IMPORTED targets use ':' as the namespace delimiter.
479
2.52k
  static cmsys::RegularExpression targetNameValidator("^[A-Za-z0-9_.:+-]+$");
480
481
2.52k
  return targetNameValidator.find(input);
482
2.52k
}
483
484
void cmGeneratorExpression::ReplaceInstallPrefix(
485
  std::string& input, std::string const& replacement)
486
0
{
487
0
  std::string::size_type pos = 0;
488
0
  std::string::size_type lastPos = pos;
489
490
0
  while ((pos = input.find("$<INSTALL_PREFIX>", lastPos)) !=
491
0
         std::string::npos) {
492
0
    std::string::size_type endPos = pos + cmStrLen("$<INSTALL_PREFIX>");
493
0
    input.replace(pos, endPos - pos, replacement);
494
0
    lastPos = endPos;
495
0
  }
496
0
}
497
498
void cmCompiledGeneratorExpression::GetMaxLanguageStandard(
499
  cmGeneratorTarget const* tgt, std::map<std::string, std::string>& mapping)
500
0
{
501
0
  auto it = this->MaxLanguageStandard.find(tgt);
502
0
  if (it != this->MaxLanguageStandard.end()) {
503
0
    mapping = it->second;
504
0
  }
505
0
}
506
507
std::string const& cmGeneratorExpressionInterpreter::Evaluate(
508
  std::string expression, std::string const& property)
509
0
{
510
0
  this->CompiledGeneratorExpression =
511
0
    this->GeneratorExpression.Parse(std::move(expression));
512
513
0
  cm::GenEx::Context context(this->LocalGenerator, this->Config,
514
0
                             this->Language);
515
516
  // Specify COMPILE_OPTIONS to DAGchecker, same semantic as COMPILE_FLAGS
517
0
  cmGeneratorExpressionDAGChecker dagChecker{
518
0
    this->HeadTarget,
519
0
    property == "COMPILE_FLAGS" ? "COMPILE_OPTIONS" : property,
520
0
    nullptr,
521
0
    nullptr,
522
0
    context,
523
0
  };
524
525
0
  return this->CompiledGeneratorExpression->Evaluate(context, &dagChecker,
526
0
                                                     this->HeadTarget);
527
0
}