Coverage Report

Created: 2026-02-09 06:05

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
11.7k
{
143
11.7k
  if (input.find(';') == std::string::npos) {
144
7.19k
    return input;
145
7.19k
  }
146
4.58k
  std::string result;
147
4.58k
  result.reserve(input.size());
148
149
4.58k
  char const* c = input.c_str();
150
4.58k
  char const* last = c;
151
4.58k
  bool skipSemiColons = true;
152
1.78M
  for (; *c; ++c) {
153
1.78M
    if (*c == ';') {
154
78.1k
      if (skipSemiColons) {
155
10.4k
        result.append(last, c - last);
156
10.4k
        last = c + 1;
157
10.4k
      }
158
78.1k
      skipSemiColons = true;
159
1.70M
    } else {
160
1.70M
      skipSemiColons = false;
161
1.70M
    }
162
1.78M
  }
163
4.58k
  result.append(last);
164
165
4.58k
  if (!result.empty() && *(result.end() - 1) == ';') {
166
881
    result.resize(result.size() - 1);
167
881
  }
168
169
4.58k
  return result;
170
11.7k
}
171
172
static std::string extractAllGeneratorExpressions(
173
  cm::string_view input,
174
  std::map<std::string, std::vector<std::string>>* collected)
175
4.71k
{
176
4.71k
  std::string result;
177
4.71k
  std::string::size_type pos = 0;
178
4.71k
  std::string::size_type lastPos = pos;
179
4.71k
  std::stack<char const*> starts; // indices of "$<"
180
4.71k
  std::stack<char const*> colons; // indices of ":"
181
63.9k
  while ((pos = input.find("$<", lastPos)) != std::string::npos) {
182
59.2k
    result += input.substr(lastPos, pos - lastPos);
183
59.2k
    starts.push(input.data() + pos);
184
59.2k
    pos += 2;
185
59.2k
    char const* c = input.data() + pos;
186
59.2k
    char const* const cStart = c;
187
2.12M
    for (; *c; ++c) {
188
2.07M
      if (cmGeneratorExpression::StartsWithGeneratorExpression(c)) {
189
784k
        starts.push(c);
190
784k
        ++c;
191
784k
        continue;
192
784k
      }
193
1.28M
      if (c[0] == ':') {
194
314k
        if (colons.size() < starts.size()) {
195
257k
          colons.push(c);
196
257k
        }
197
974k
      } else if (c[0] == '>') {
198
164k
        if (!colons.empty() && !starts.empty() &&
199
159k
            starts.top() < colons.top()) {
200
118k
          if (collected) {
201
59.2k
            (*collected)[std::string(starts.top() + 2, colons.top())]
202
59.2k
              .push_back(std::string(colons.top() + 1, c));
203
59.2k
          }
204
118k
          colons.pop();
205
118k
        }
206
164k
        if (!starts.empty()) {
207
164k
          starts.pop();
208
164k
        }
209
164k
        if (starts.empty()) {
210
9.79k
          break;
211
9.79k
        }
212
164k
      }
213
1.28M
    }
214
59.2k
    std::string::size_type const traversed = (c - cStart) + 1;
215
59.2k
    if (!*c) {
216
49.4k
      result += cmStrCat("$<", input.substr(pos, traversed));
217
49.4k
    }
218
59.2k
    pos += traversed;
219
59.2k
    lastPos = pos;
220
59.2k
  }
221
4.71k
  if (starts.empty()) {
222
2.13k
    result += input.substr(lastPos);
223
2.13k
  }
224
4.71k
  return cmGeneratorExpression::StripEmptyListElements(result);
225
4.71k
}
226
227
static std::string stripAllGeneratorExpressions(cm::string_view input)
228
2.35k
{
229
2.35k
  return extractAllGeneratorExpressions(input, nullptr);
230
2.35k
}
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.71k
{
253
4.71k
  std::string result;
254
255
4.71k
  int nestingLevel = 0;
256
4.71k
  std::string::size_type pos = 0;
257
4.71k
  std::string::size_type lastPos = pos;
258
17.4k
  while (true) {
259
17.4k
    std::string::size_type bPos = input.find("$<BUILD_INTERFACE:", lastPos);
260
17.4k
    std::string::size_type iPos = input.find("$<INSTALL_INTERFACE:", lastPos);
261
17.4k
    std::string::size_type lPos =
262
17.4k
      input.find("$<BUILD_LOCAL_INTERFACE:", lastPos);
263
264
17.4k
    pos = std::min({ bPos, iPos, lPos });
265
17.4k
    if (pos == std::string::npos) {
266
4.71k
      break;
267
4.71k
    }
268
269
12.7k
    result += input.substr(lastPos, pos - lastPos);
270
12.7k
    enum class FoundGenex
271
12.7k
    {
272
12.7k
      BuildInterface,
273
12.7k
      InstallInterface,
274
12.7k
      BuildLocalInterface,
275
12.7k
    } foundGenex = FoundGenex::BuildInterface;
276
12.7k
    if (pos == bPos) {
277
6.28k
      foundGenex = FoundGenex::BuildInterface;
278
6.28k
      pos += cmStrLen("$<BUILD_INTERFACE:");
279
6.48k
    } else if (pos == iPos) {
280
5.08k
      foundGenex = FoundGenex::InstallInterface;
281
5.08k
      pos += cmStrLen("$<INSTALL_INTERFACE:");
282
5.08k
    } else if (pos == lPos) {
283
1.39k
      foundGenex = FoundGenex::BuildLocalInterface;
284
1.39k
      pos += cmStrLen("$<BUILD_LOCAL_INTERFACE:");
285
1.39k
    } else {
286
0
      assert(false && "Invalid position found");
287
0
    }
288
12.7k
    nestingLevel = 1;
289
12.7k
    char const* c = input.data() + pos;
290
12.7k
    char const* const cStart = c;
291
420k
    for (; *c; ++c) {
292
415k
      if (cmGeneratorExpression::StartsWithGeneratorExpression(c)) {
293
124k
        ++nestingLevel;
294
124k
        ++c;
295
124k
        continue;
296
124k
      }
297
290k
      if (c[0] == '>') {
298
35.6k
        --nestingLevel;
299
35.6k
        if (nestingLevel != 0) {
300
27.6k
          continue;
301
27.6k
        }
302
7.95k
        if (context == cmGeneratorExpression::BuildInterface &&
303
3.97k
            foundGenex == FoundGenex::BuildInterface) {
304
2.31k
          result += input.substr(pos, c - cStart);
305
5.64k
        } else if (context == cmGeneratorExpression::InstallInterface &&
306
3.97k
                   foundGenex == FoundGenex::InstallInterface) {
307
1.46k
          std::string const content =
308
1.46k
            static_cast<std::string>(input.substr(pos, c - cStart));
309
1.46k
          if (!importPrefix.empty()) {
310
0
            prefixItems(content, result, importPrefix);
311
1.46k
          } else {
312
1.46k
            result += content;
313
1.46k
          }
314
1.46k
        }
315
7.95k
        break;
316
35.6k
      }
317
290k
    }
318
12.7k
    std::string::size_type const traversed = (c - cStart) + 1;
319
12.7k
    if (!*c) {
320
4.80k
      auto remaining = input.substr(pos, traversed);
321
4.80k
      switch (foundGenex) {
322
1.66k
        case FoundGenex::BuildInterface:
323
1.66k
          result = cmStrCat(result, "$<BUILD_INTERFACE:", remaining);
324
1.66k
          break;
325
2.15k
        case FoundGenex::InstallInterface:
326
2.15k
          result = cmStrCat(result, "$<INSTALL_INTERFACE:", remaining);
327
2.15k
          break;
328
992
        case FoundGenex::BuildLocalInterface:
329
992
          result = cmStrCat(result, "$<BUILD_LOCAL_INTERFACE:", remaining);
330
992
          break;
331
4.80k
      }
332
4.80k
    }
333
12.7k
    pos += traversed;
334
12.7k
    lastPos = pos;
335
12.7k
  }
336
4.71k
  if (nestingLevel == 0) {
337
4.05k
    result += input.substr(lastPos);
338
4.05k
  }
339
340
4.71k
  return cmGeneratorExpression::StripEmptyListElements(result);
341
4.71k
}
342
343
void cmGeneratorExpression::Split(std::string const& input,
344
                                  std::vector<std::string>& output)
345
2.35k
{
346
2.35k
  std::string::size_type pos = 0;
347
2.35k
  std::string::size_type lastPos = pos;
348
29.7k
  while ((pos = input.find("$<", lastPos)) != std::string::npos) {
349
27.3k
    std::string part = input.substr(lastPos, pos - lastPos);
350
27.3k
    std::string preGenex;
351
27.3k
    if (!part.empty()) {
352
18.1k
      std::string::size_type startPos = input.rfind(';', pos);
353
18.1k
      if (startPos == std::string::npos) {
354
3.79k
        preGenex = part;
355
3.79k
        part.clear();
356
14.3k
      } else if (startPos != pos - 1 && startPos >= lastPos) {
357
5.74k
        part = input.substr(lastPos, startPos - lastPos);
358
5.74k
        preGenex = input.substr(startPos + 1, pos - startPos - 1);
359
5.74k
      }
360
18.1k
      if (!part.empty()) {
361
12.7k
        cmExpandList(part, output);
362
12.7k
      }
363
18.1k
    }
364
27.3k
    pos += 2;
365
27.3k
    int nestingLevel = 1;
366
27.3k
    char const* c = input.c_str() + pos;
367
27.3k
    char const* const cStart = c;
368
872k
    for (; *c; ++c) {
369
848k
      if (cmGeneratorExpression::StartsWithGeneratorExpression(c)) {
370
363k
        ++nestingLevel;
371
363k
        ++c;
372
363k
        continue;
373
363k
      }
374
485k
      if (c[0] == '>') {
375
45.5k
        --nestingLevel;
376
45.5k
        if (nestingLevel == 0) {
377
3.46k
          break;
378
3.46k
        }
379
45.5k
      }
380
485k
    }
381
256k
    for (; *c; ++c) {
382
      // Capture the part after the genex and before the next ';'
383
231k
      if (c[0] == ';') {
384
2.06k
        --c;
385
2.06k
        break;
386
2.06k
      }
387
231k
    }
388
27.3k
    std::string::size_type const traversed = (c - cStart) + 1;
389
27.3k
    output.push_back(cmStrCat(preGenex, "$<", input.substr(pos, traversed)));
390
27.3k
    pos += traversed;
391
27.3k
    lastPos = pos;
392
27.3k
  }
393
2.35k
  if (lastPos < input.size()) {
394
751
    cmExpandList(input.substr(lastPos), output);
395
751
  }
396
2.35k
}
397
398
std::string cmGeneratorExpression::Preprocess(cm::string_view input,
399
                                              PreprocessContext context,
400
                                              cm::string_view importPrefix)
401
7.06k
{
402
7.06k
  if (context == StripAllGeneratorExpressions) {
403
2.35k
    return stripAllGeneratorExpressions(input);
404
2.35k
  }
405
4.71k
  if (context == BuildInterface || context == InstallInterface) {
406
4.71k
    return stripExportInterface(input, context, importPrefix);
407
4.71k
  }
408
409
4.71k
  assert(false &&
410
0
         "cmGeneratorExpression::Preprocess called with invalid args");
411
0
  return std::string();
412
4.71k
}
413
414
std::string cmGeneratorExpression::Collect(
415
  std::string const& input,
416
  std::map<std::string, std::vector<std::string>>& collected)
417
2.35k
{
418
2.35k
  return extractAllGeneratorExpressions(input, &collected);
419
2.35k
}
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.35k
{
467
2.35k
  cm::string_view::size_type const openpos = input.find("$<");
468
2.35k
  if (openpos != cm::string_view::npos &&
469
1.87k
      input.find('>', openpos) != cm::string_view::npos) {
470
1.07k
    return openpos;
471
1.07k
  }
472
1.28k
  return cm::string_view::npos;
473
2.35k
}
474
475
bool cmGeneratorExpression::IsValidTargetName(std::string const& input)
476
2.35k
{
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.35k
  static cmsys::RegularExpression targetNameValidator("^[A-Za-z0-9_.:+-]+$");
480
481
2.35k
  return targetNameValidator.find(input);
482
2.35k
}
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
}