Coverage Report

Created: 2026-09-14 06:43

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/CMake/Source/cmConditionEvaluator.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 "cmConditionEvaluator.h"
4
5
#include <array>
6
#include <cstdio>
7
#include <cstdlib>
8
#include <functional>
9
#include <iterator>
10
#include <list>
11
#include <sstream>
12
#include <utility>
13
14
#include <cm/optional>
15
#include <cm/string_view>
16
#include <cmext/algorithm>
17
18
#include "cmsys/RegularExpression.hxx"
19
20
#include "cmCMakePath.h"
21
#include "cmDiagnostics.h"
22
#include "cmExpandedCommandArgument.h"
23
#include "cmList.h"
24
#include "cmMakefile.h"
25
#include "cmMessageType.h"
26
#include "cmState.h"
27
#include "cmStringAlgorithms.h"
28
#include "cmSystemTools.h"
29
#include "cmValue.h"
30
31
namespace {
32
auto const keyAND = "AND"_s;
33
auto const keyCOMMAND = "COMMAND"_s;
34
auto const keyDEFINED = "DEFINED"_s;
35
auto const keyDIAGNOSTIC = "DIAGNOSTIC"_s;
36
auto const keyEQUAL = "EQUAL"_s;
37
auto const keyEXISTS = "EXISTS"_s;
38
auto const keyIS_READABLE = "IS_READABLE"_s;
39
auto const keyIS_WRITABLE = "IS_WRITABLE"_s;
40
auto const keyIS_EXECUTABLE = "IS_EXECUTABLE"_s;
41
auto const keyGREATER = "GREATER"_s;
42
auto const keyGREATER_EQUAL = "GREATER_EQUAL"_s;
43
auto const keyIN_LIST = "IN_LIST"_s;
44
auto const keyIS_ABSOLUTE = "IS_ABSOLUTE"_s;
45
auto const keyIS_DIRECTORY = "IS_DIRECTORY"_s;
46
auto const keyIS_NEWER_THAN = "IS_NEWER_THAN"_s;
47
auto const keyIS_SYMLINK = "IS_SYMLINK"_s;
48
auto const keyLESS = "LESS"_s;
49
auto const keyLESS_EQUAL = "LESS_EQUAL"_s;
50
auto const keyMATCHES = "MATCHES"_s;
51
auto const keyNOT = "NOT"_s;
52
auto const keyOR = "OR"_s;
53
auto const keyParenL = "("_s;
54
auto const keyParenR = ")"_s;
55
auto const keyPOLICY = "POLICY"_s;
56
auto const keySTREQUAL = "STREQUAL"_s;
57
auto const keySTRGREATER = "STRGREATER"_s;
58
auto const keySTRGREATER_EQUAL = "STRGREATER_EQUAL"_s;
59
auto const keySTRLESS = "STRLESS"_s;
60
auto const keySTRLESS_EQUAL = "STRLESS_EQUAL"_s;
61
auto const keyTARGET = "TARGET"_s;
62
auto const keyTEST = "TEST"_s;
63
auto const keyVERSION_EQUAL = "VERSION_EQUAL"_s;
64
auto const keyVERSION_GREATER = "VERSION_GREATER"_s;
65
auto const keyVERSION_GREATER_EQUAL = "VERSION_GREATER_EQUAL"_s;
66
auto const keyVERSION_LESS = "VERSION_LESS"_s;
67
auto const keyVERSION_LESS_EQUAL = "VERSION_LESS_EQUAL"_s;
68
auto const keyPATH_EQUAL = "PATH_EQUAL"_s;
69
auto const keyPATH_IS_PREFIX = "PATH_IS_PREFIX"_s;
70
71
cmSystemTools::CompareOp const MATCH2CMPOP[5] = {
72
  cmSystemTools::OP_LESS, cmSystemTools::OP_LESS_EQUAL,
73
  cmSystemTools::OP_GREATER, cmSystemTools::OP_GREATER_EQUAL,
74
  cmSystemTools::OP_EQUAL
75
};
76
77
// Run-Time to Compile-Time template selector
78
template <template <typename> class Comp, template <typename> class... Ops>
79
struct cmRt2CtSelector
80
{
81
  template <typename T>
82
  static bool eval(int r, T lhs, T rhs)
83
0
  {
84
0
    switch (r) {
85
0
      case 0:
86
0
        return false;
87
0
      case 1:
88
0
        return Comp<T>()(lhs, rhs);
89
0
      default:
90
0
        return cmRt2CtSelector<Ops...>::eval(r - 1, lhs, rhs);
91
0
    }
92
0
  }
Unexecuted instantiation: cmConditionEvaluator.cxx:bool (anonymous namespace)::cmRt2CtSelector<std::__1::less, std::__1::less_equal, std::__1::greater, std::__1::greater_equal, std::__1::equal_to>::eval<double>(int, double, double)
Unexecuted instantiation: cmConditionEvaluator.cxx:bool (anonymous namespace)::cmRt2CtSelector<std::__1::less_equal, std::__1::greater, std::__1::greater_equal, std::__1::equal_to>::eval<double>(int, double, double)
Unexecuted instantiation: cmConditionEvaluator.cxx:bool (anonymous namespace)::cmRt2CtSelector<std::__1::greater, std::__1::greater_equal, std::__1::equal_to>::eval<double>(int, double, double)
Unexecuted instantiation: cmConditionEvaluator.cxx:bool (anonymous namespace)::cmRt2CtSelector<std::__1::greater_equal, std::__1::equal_to>::eval<double>(int, double, double)
Unexecuted instantiation: cmConditionEvaluator.cxx:bool (anonymous namespace)::cmRt2CtSelector<std::__1::less, std::__1::less_equal, std::__1::greater, std::__1::greater_equal, std::__1::equal_to>::eval<int>(int, int, int)
Unexecuted instantiation: cmConditionEvaluator.cxx:bool (anonymous namespace)::cmRt2CtSelector<std::__1::less_equal, std::__1::greater, std::__1::greater_equal, std::__1::equal_to>::eval<int>(int, int, int)
Unexecuted instantiation: cmConditionEvaluator.cxx:bool (anonymous namespace)::cmRt2CtSelector<std::__1::greater, std::__1::greater_equal, std::__1::equal_to>::eval<int>(int, int, int)
Unexecuted instantiation: cmConditionEvaluator.cxx:bool (anonymous namespace)::cmRt2CtSelector<std::__1::greater_equal, std::__1::equal_to>::eval<int>(int, int, int)
Unexecuted instantiation: cmConditionEvaluator.cxx:bool (anonymous namespace)::cmRt2CtSelector<std::__1::logical_and, std::__1::logical_or>::eval<bool>(int, bool, bool)
93
};
94
95
template <template <typename> class Comp>
96
struct cmRt2CtSelector<Comp>
97
{
98
  template <typename T>
99
  static bool eval(int r, T lhs, T rhs)
100
0
  {
101
0
    return r == 1 && Comp<T>()(lhs, rhs);
102
0
  }
Unexecuted instantiation: cmConditionEvaluator.cxx:bool (anonymous namespace)::cmRt2CtSelector<std::__1::equal_to>::eval<double>(int, double, double)
Unexecuted instantiation: cmConditionEvaluator.cxx:bool (anonymous namespace)::cmRt2CtSelector<std::__1::equal_to>::eval<int>(int, int, int)
Unexecuted instantiation: cmConditionEvaluator.cxx:bool (anonymous namespace)::cmRt2CtSelector<std::__1::logical_or>::eval<bool>(int, bool, bool)
103
};
104
105
std::string bool2string(bool const value)
106
0
{
107
0
  return std::string(static_cast<std::size_t>(1),
108
0
                     static_cast<char>('0' + static_cast<int>(value)));
109
0
}
110
111
bool looksLikeSpecialVariable(std::string const& var,
112
                              cm::static_string_view prefix,
113
                              std::size_t const varNameLen)
114
0
{
115
  // NOTE Expecting a variable name at least 1 char length:
116
  // <prefix> + `{` + <varname> + `}`
117
0
  return ((prefix.size() + 3) <= varNameLen) &&
118
0
    cmHasPrefix(var, cmStrCat(prefix, '{')) && var[varNameLen - 1] == '}';
119
0
}
120
} // anonymous namespace
121
122
#if defined(__SUNPRO_CC)
123
#  define CM_INHERIT_CTOR(Class, Base, Tpl)                                   \
124
    template <typename... Args>                                               \
125
    Class(Args&&... args)                                                     \
126
      : Base Tpl(std::forward<Args>(args)...)                                 \
127
    {                                                                         \
128
    }
129
#else
130
#  define CM_INHERIT_CTOR(Class, Base, Tpl) using Base Tpl ::Base
131
#endif
132
133
// BEGIN cmConditionEvaluator::cmArgumentList
134
class cmConditionEvaluator::cmArgumentList
135
  : public std::list<cmExpandedCommandArgument>
136
{
137
  using base_t = std::list<cmExpandedCommandArgument>;
138
139
public:
140
  CM_INHERIT_CTOR(cmArgumentList, list, <cmExpandedCommandArgument>);
141
142
  class CurrentAndNextIter
143
  {
144
    friend class cmConditionEvaluator::cmArgumentList;
145
146
  public:
147
    base_t::iterator current;
148
    base_t::iterator next;
149
150
    CurrentAndNextIter advance(base_t& args)
151
0
    {
152
0
      this->current = std::next(this->current);
153
0
      this->next =
154
0
        std::next(this->current,
155
0
                  static_cast<difference_type>(this->current != args.end()));
156
0
      return *this;
157
0
    }
158
159
  private:
160
    CurrentAndNextIter(base_t& args)
161
0
      : current(args.begin())
162
      , next(
163
0
          std::next(this->current,
164
0
                    static_cast<difference_type>(this->current != args.end())))
165
0
    {
166
0
    }
167
  };
168
169
  class CurrentAndTwoMoreIter
170
  {
171
    friend class cmConditionEvaluator::cmArgumentList;
172
173
  public:
174
    base_t::iterator current;
175
    base_t::iterator next;
176
    base_t::iterator nextnext;
177
178
    CurrentAndTwoMoreIter advance(base_t& args)
179
0
    {
180
0
      this->current = std::next(this->current);
181
0
      this->next =
182
0
        std::next(this->current,
183
0
                  static_cast<difference_type>(this->current != args.end()));
184
0
      this->nextnext = std::next(
185
0
        this->next, static_cast<difference_type>(this->next != args.end()));
186
0
      return *this;
187
0
    }
188
189
  private:
190
    CurrentAndTwoMoreIter(base_t& args)
191
0
      : current(args.begin())
192
      , next(
193
0
          std::next(this->current,
194
0
                    static_cast<difference_type>(this->current != args.end())))
195
0
      , nextnext(std::next(
196
0
          this->next, static_cast<difference_type>(this->next != args.end())))
197
0
    {
198
0
    }
199
  };
200
201
0
  CurrentAndNextIter make2ArgsIterator() { return *this; }
202
0
  CurrentAndTwoMoreIter make3ArgsIterator() { return *this; }
203
204
  template <typename Iter>
205
  void ReduceOneArg(bool const value, Iter args)
206
0
  {
207
0
    *args.current = cmExpandedCommandArgument(bool2string(value), true);
208
0
    this->erase(args.next);
209
0
  }
Unexecuted instantiation: void cmConditionEvaluator::cmArgumentList::ReduceOneArg<cmConditionEvaluator::cmArgumentList::CurrentAndNextIter>(bool, cmConditionEvaluator::cmArgumentList::CurrentAndNextIter)
Unexecuted instantiation: void cmConditionEvaluator::cmArgumentList::ReduceOneArg<cmConditionEvaluator::cmArgumentList::CurrentAndTwoMoreIter>(bool, cmConditionEvaluator::cmArgumentList::CurrentAndTwoMoreIter)
210
211
  void ReduceTwoArgs(bool const value, CurrentAndTwoMoreIter args)
212
0
  {
213
0
    *args.current = cmExpandedCommandArgument(bool2string(value), true);
214
0
    this->erase(args.nextnext);
215
0
    this->erase(args.next);
216
0
  }
217
};
218
219
// END cmConditionEvaluator::cmArgumentList
220
221
cmConditionEvaluator::cmConditionEvaluator(cmMakefile& makefile,
222
                                           cmListFileBacktrace bt)
223
0
  : Makefile(makefile)
224
0
  , Backtrace(std::move(bt))
225
0
  , Policy139Status(makefile.GetPolicyStatus(cmPolicies::CMP0139))
226
0
  , Policy222Status(makefile.GetPolicyStatus(cmPolicies::CMP0222))
227
0
{
228
0
}
229
230
//=========================================================================
231
// order of operations,
232
// 1.   ( )   -- parenthetical groups
233
// 2.  IS_DIRECTORY EXISTS COMMAND DEFINED etc predicates
234
// 3. MATCHES LESS GREATER EQUAL STRLESS STRGREATER STREQUAL etc binary ops
235
// 4. NOT
236
// 5. AND OR
237
//
238
// There is an issue on whether the arguments should be values of references,
239
// for example IF (FOO AND BAR) should that compare the strings FOO and BAR
240
// or should it really do IF (${FOO} AND ${BAR}) Currently IS_DIRECTORY
241
// EXISTS COMMAND and DEFINED all take values. EQUAL, LESS and GREATER can
242
// take numeric values or variable names. STRLESS and STRGREATER take
243
// variable names but if the variable name is not found it will use the name
244
// directly. AND OR take variables or the values 0 or 1.
245
246
bool cmConditionEvaluator::IsTrue(
247
  std::vector<cmExpandedCommandArgument> const& args, std::string& errorString,
248
  MessageType& status)
249
0
{
250
0
  errorString.clear();
251
252
  // handle empty invocation
253
0
  if (args.empty()) {
254
0
    return false;
255
0
  }
256
257
  // store the reduced args in this vector
258
0
  cmArgumentList newArgs(args.begin(), args.end());
259
260
  // now loop through the arguments and see if we can reduce any of them
261
  // we do this multiple times. Once for each level of precedence
262
  // parens
263
0
  using handlerFn_t = bool (cmConditionEvaluator::*)(
264
0
    cmArgumentList&, std::string&, MessageType&);
265
0
  std::array<handlerFn_t, 5> const handlers = { {
266
0
    &cmConditionEvaluator::HandleLevel0, // parenthesis
267
0
    &cmConditionEvaluator::HandleLevel1, // predicates
268
0
    &cmConditionEvaluator::HandleLevel2, // binary ops
269
0
    &cmConditionEvaluator::HandleLevel3, // NOT
270
0
    &cmConditionEvaluator::HandleLevel4  // AND OR
271
0
  } };
272
0
  for (auto fn : handlers) {
273
    // Call the reducer 'till there is anything to reduce...
274
    // (i.e., if after an iteration the size becomes smaller)
275
0
    auto levelResult = true;
276
0
    for (auto beginSize = newArgs.size();
277
0
         (levelResult = (this->*fn)(newArgs, errorString, status)) &&
278
0
         newArgs.size() < beginSize;
279
0
         beginSize = newArgs.size()) {
280
0
    }
281
282
0
    if (!levelResult) {
283
      // NOTE `errorString` supposed to be set already
284
0
      return false;
285
0
    }
286
0
  }
287
288
  // now at the end there should only be one argument left
289
0
  if (newArgs.size() != 1) {
290
0
    errorString = "Unknown arguments specified";
291
0
    status = MessageType::FATAL_ERROR;
292
0
    return false;
293
0
  }
294
295
0
  return this->GetBooleanValue(newArgs.front());
296
0
}
297
298
//=========================================================================
299
cmValue cmConditionEvaluator::GetDefinitionIfUnquoted(
300
  cmExpandedCommandArgument const& argument) const
301
0
{
302
0
  if (argument.WasQuoted()) {
303
0
    return nullptr;
304
0
  }
305
306
0
  return this->Makefile.GetDefinition(argument.GetValue());
307
0
}
308
309
//=========================================================================
310
cmValue cmConditionEvaluator::GetVariableOrString(
311
  cmExpandedCommandArgument const& argument) const
312
0
{
313
0
  cmValue def = this->GetDefinitionIfUnquoted(argument);
314
315
0
  if (!def) {
316
0
    def = cmValue(argument.GetValue());
317
0
  }
318
319
0
  return def;
320
0
}
321
322
//=========================================================================
323
bool cmConditionEvaluator::IsKeyword(
324
  cm::static_string_view keyword,
325
  cmExpandedCommandArgument const& argument) const
326
0
{
327
0
  if (argument.WasQuoted()) {
328
0
    return false;
329
0
  }
330
331
0
  return argument.GetValue() == keyword;
332
0
}
333
334
//=========================================================================
335
bool cmConditionEvaluator::GetBooleanValue(
336
  cmExpandedCommandArgument& arg) const
337
0
{
338
  // Check basic and named constants.
339
0
  if (cmIsOn(arg.GetValue())) {
340
0
    return true;
341
0
  }
342
0
  if (cmIsOff(arg.GetValue())) {
343
0
    return false;
344
0
  }
345
346
  // Check for numbers.
347
0
  if (!arg.empty()) {
348
0
    char* end;
349
0
    double const d = std::strtod(arg.GetValue().c_str(), &end);
350
0
    if (*end == '\0') {
351
      // The whole string is a number.  Use C conversion to bool.
352
0
      return static_cast<bool>(d);
353
0
    }
354
0
  }
355
356
  // Check definition.
357
0
  cmValue def = this->GetDefinitionIfUnquoted(arg);
358
0
  return !def.IsOff();
359
0
}
360
361
template <int N>
362
inline int cmConditionEvaluator::matchKeysImpl(
363
  cmExpandedCommandArgument const&)
364
0
{
365
  // Zero means "not found"
366
0
  return 0;
367
0
}
Unexecuted instantiation: int cmConditionEvaluator::matchKeysImpl<6>(cmExpandedCommandArgument const&)
Unexecuted instantiation: int cmConditionEvaluator::matchKeysImpl<3>(cmExpandedCommandArgument const&)
368
369
template <int N, typename T, typename... Keys>
370
inline int cmConditionEvaluator::matchKeysImpl(
371
  cmExpandedCommandArgument const& arg, T current, Keys... key)
372
0
{
373
0
  if (this->IsKeyword(current, arg)) {
374
    // Stop searching as soon as smth has found
375
0
    return N;
376
0
  }
377
0
  return matchKeysImpl<N + 1>(arg, key...);
378
0
}
Unexecuted instantiation: int cmConditionEvaluator::matchKeysImpl<1, cm::static_string_view, cm::static_string_view, cm::static_string_view, cm::static_string_view, cm::static_string_view>(cmExpandedCommandArgument const&, cm::static_string_view, cm::static_string_view, cm::static_string_view, cm::static_string_view, cm::static_string_view)
Unexecuted instantiation: int cmConditionEvaluator::matchKeysImpl<2, cm::static_string_view, cm::static_string_view, cm::static_string_view, cm::static_string_view>(cmExpandedCommandArgument const&, cm::static_string_view, cm::static_string_view, cm::static_string_view, cm::static_string_view)
Unexecuted instantiation: int cmConditionEvaluator::matchKeysImpl<3, cm::static_string_view, cm::static_string_view, cm::static_string_view>(cmExpandedCommandArgument const&, cm::static_string_view, cm::static_string_view, cm::static_string_view)
Unexecuted instantiation: int cmConditionEvaluator::matchKeysImpl<4, cm::static_string_view, cm::static_string_view>(cmExpandedCommandArgument const&, cm::static_string_view, cm::static_string_view)
Unexecuted instantiation: int cmConditionEvaluator::matchKeysImpl<5, cm::static_string_view>(cmExpandedCommandArgument const&, cm::static_string_view)
Unexecuted instantiation: int cmConditionEvaluator::matchKeysImpl<1, cm::static_string_view, cm::static_string_view>(cmExpandedCommandArgument const&, cm::static_string_view, cm::static_string_view)
Unexecuted instantiation: int cmConditionEvaluator::matchKeysImpl<2, cm::static_string_view>(cmExpandedCommandArgument const&, cm::static_string_view)
379
380
template <typename... Keys>
381
inline int cmConditionEvaluator::matchKeys(
382
  cmExpandedCommandArgument const& arg, Keys... key)
383
0
{
384
  // Get index of the matched key (1-based)
385
0
  return matchKeysImpl<1>(arg, key...);
386
0
}
Unexecuted instantiation: int cmConditionEvaluator::matchKeys<cm::static_string_view, cm::static_string_view, cm::static_string_view, cm::static_string_view, cm::static_string_view>(cmExpandedCommandArgument const&, cm::static_string_view, cm::static_string_view, cm::static_string_view, cm::static_string_view, cm::static_string_view)
Unexecuted instantiation: int cmConditionEvaluator::matchKeys<cm::static_string_view, cm::static_string_view>(cmExpandedCommandArgument const&, cm::static_string_view, cm::static_string_view)
387
388
//=========================================================================
389
// level 0 processes parenthetical expressions
390
bool cmConditionEvaluator::HandleLevel0(cmArgumentList& newArgs,
391
                                        std::string& errorString,
392
                                        MessageType& status)
393
0
{
394
0
  for (auto arg = newArgs.begin(); arg != newArgs.end(); ++arg) {
395
0
    if (this->IsKeyword(keyParenL, *arg)) {
396
      // search for the closing paren for this opening one
397
0
      auto depth = 1;
398
0
      auto argClose = std::next(arg);
399
0
      for (; argClose != newArgs.end() && depth; ++argClose) {
400
0
        depth += int(this->IsKeyword(keyParenL, *argClose)) -
401
0
          int(this->IsKeyword(keyParenR, *argClose));
402
0
      }
403
0
      if (depth) {
404
0
        errorString = "mismatched parenthesis in condition";
405
0
        status = MessageType::FATAL_ERROR;
406
0
        return false;
407
0
      }
408
409
      // store the reduced args in this vector
410
0
      auto argOpen = std::next(arg);
411
0
      std::vector<cmExpandedCommandArgument> const subExpr(
412
0
        argOpen, std::prev(argClose));
413
414
      // now recursively invoke IsTrue to handle the values inside the
415
      // parenthetical expression
416
0
      auto const value = this->IsTrue(subExpr, errorString, status);
417
0
      if (!errorString.empty()) {
418
0
        return false;
419
0
      }
420
0
      *arg = cmExpandedCommandArgument(bool2string(value), true);
421
0
      argOpen = std::next(arg);
422
      // remove the now evaluated parenthetical expression
423
0
      newArgs.erase(argOpen, argClose);
424
0
    }
425
0
  }
426
0
  return true;
427
0
}
428
429
//=========================================================================
430
// level one handles most predicates except for NOT
431
bool cmConditionEvaluator::HandleLevel1(cmArgumentList& newArgs, std::string&,
432
                                        MessageType&)
433
0
{
434
0
  for (auto args = newArgs.make2ArgsIterator(); args.current != newArgs.end();
435
0
       args.advance(newArgs)) {
436
    // NOTE Fail fast: All the predicates below require the next arg to be
437
    // valid
438
0
    if (args.next == newArgs.end()) {
439
0
      continue;
440
0
    }
441
442
    // does a file exist
443
0
    if (this->IsKeyword(keyEXISTS, *args.current)) {
444
0
      newArgs.ReduceOneArg(cmSystemTools::FileExists(args.next->GetValue()),
445
0
                           args);
446
0
    }
447
    // check if a file is readable
448
0
    else if (this->IsKeyword(keyIS_READABLE, *args.current)) {
449
0
      newArgs.ReduceOneArg(cmSystemTools::TestFileAccess(
450
0
                             args.next->GetValue(), cmsys::TEST_FILE_READ),
451
0
                           args);
452
0
    }
453
    // check if a file is writable
454
0
    else if (this->IsKeyword(keyIS_WRITABLE, *args.current)) {
455
0
      newArgs.ReduceOneArg(cmSystemTools::TestFileAccess(
456
0
                             args.next->GetValue(), cmsys::TEST_FILE_WRITE),
457
0
                           args);
458
0
    }
459
    // check if a file is executable
460
0
    else if (this->IsKeyword(keyIS_EXECUTABLE, *args.current)) {
461
0
      newArgs.ReduceOneArg(cmSystemTools::TestFileAccess(
462
0
                             args.next->GetValue(), cmsys::TEST_FILE_EXECUTE),
463
0
                           args);
464
0
    }
465
    // does a directory with this name exist
466
0
    else if (this->IsKeyword(keyIS_DIRECTORY, *args.current)) {
467
0
      newArgs.ReduceOneArg(
468
0
        cmSystemTools::FileIsDirectory(args.next->GetValue()), args);
469
0
    }
470
    // does a symlink with this name exist
471
0
    else if (this->IsKeyword(keyIS_SYMLINK, *args.current)) {
472
0
      newArgs.ReduceOneArg(cmSystemTools::FileIsSymlink(args.next->GetValue()),
473
0
                           args);
474
0
    }
475
    // is the given path an absolute path ?
476
0
    else if (this->IsKeyword(keyIS_ABSOLUTE, *args.current)) {
477
0
      newArgs.ReduceOneArg(
478
0
        cmSystemTools::FileIsFullPath(args.next->GetValue()), args);
479
0
    }
480
    // does a command exist
481
0
    else if (this->IsKeyword(keyCOMMAND, *args.current)) {
482
0
      newArgs.ReduceOneArg(
483
0
        static_cast<bool>(
484
0
          this->Makefile.GetState()->GetCommand(args.next->GetValue())),
485
0
        args);
486
0
    }
487
    // does a diagnostic exist
488
0
    else if (this->IsKeyword(keyDIAGNOSTIC, *args.current)) {
489
0
      newArgs.ReduceOneArg(
490
0
        cmDiagnostics::GetDiagnosticCategory(args.next->GetValue())
491
0
          .has_value(),
492
0
        args);
493
0
    }
494
    // does a policy exist
495
0
    else if (this->IsKeyword(keyPOLICY, *args.current)) {
496
0
      cmPolicies::PolicyID pid;
497
0
      newArgs.ReduceOneArg(
498
0
        cmPolicies::GetPolicyID(args.next->GetValue().c_str(), pid), args);
499
0
    }
500
    // does a target exist
501
0
    else if (this->IsKeyword(keyTARGET, *args.current)) {
502
0
      newArgs.ReduceOneArg(static_cast<bool>(this->Makefile.FindTargetToUse(
503
0
                             args.next->GetValue())),
504
0
                           args);
505
0
    }
506
    // is a variable defined
507
0
    else if (this->IsKeyword(keyDEFINED, *args.current)) {
508
0
      auto const& var = args.next->GetValue();
509
0
      auto const varNameLen = var.size();
510
511
0
      auto result = false;
512
0
      if (looksLikeSpecialVariable(var, "ENV"_s, varNameLen)) {
513
0
        auto const env = args.next->GetValue().substr(4, varNameLen - 5);
514
0
        result = cmSystemTools::HasEnv(env);
515
0
      }
516
517
0
      else if (looksLikeSpecialVariable(var, "CACHE"_s, varNameLen)) {
518
0
        auto const cache = args.next->GetValue().substr(6, varNameLen - 7);
519
0
        result = static_cast<bool>(
520
0
          this->Makefile.GetState()->GetCacheEntryValue(cache));
521
0
      }
522
523
0
      else {
524
0
        result = this->Makefile.IsDefinitionSet(args.next->GetValue());
525
0
      }
526
0
      newArgs.ReduceOneArg(result, args);
527
0
    }
528
    // does a test exist
529
0
    else if (this->IsKeyword(keyTEST, *args.current)) {
530
0
      newArgs.ReduceOneArg(
531
0
        static_cast<bool>(this->Makefile.GetTest(args.next->GetValue())),
532
0
        args);
533
0
    }
534
0
  }
535
0
  return true;
536
0
}
537
538
//=========================================================================
539
// level two handles most binary operations except for AND  OR
540
bool cmConditionEvaluator::HandleLevel2(cmArgumentList& newArgs,
541
                                        std::string& errorString,
542
                                        MessageType& status)
543
0
{
544
0
  for (auto args = newArgs.make3ArgsIterator(); args.current != newArgs.end();
545
0
       args.advance(newArgs)) {
546
547
0
    int matchNo;
548
549
    // NOTE Handle special case `if(... BLAH_BLAH MATCHES)`
550
    // (i.e., w/o regex to match which is possibly result of
551
    // variable expansion to an empty string)
552
0
    if (args.next != newArgs.end() &&
553
0
        this->IsKeyword(keyMATCHES, *args.current)) {
554
0
      newArgs.ReduceOneArg(false, args);
555
0
    }
556
557
    // NOTE Fail fast: All the binary ops below require 2 arguments.
558
0
    else if (args.next == newArgs.end() || args.nextnext == newArgs.end()) {
559
0
      continue;
560
0
    }
561
562
0
    else if (this->IsKeyword(keyMATCHES, *args.next)) {
563
0
      cmValue def = this->GetDefinitionIfUnquoted(*args.current);
564
565
0
      std::string def_buf;
566
0
      if (!def) {
567
0
        def = cmValue(args.current->GetValue());
568
0
      } else if (cmHasLiteralPrefix(args.current->GetValue(),
569
0
                                    "CMAKE_MATCH_")) {
570
        // The string to match is owned by our match result variables.
571
        // Move it to our own buffer before clearing them.
572
0
        def_buf = *def;
573
0
        def = cmValue(def_buf);
574
0
      }
575
576
0
      this->Makefile.ClearMatches();
577
578
0
      auto const& rex = args.nextnext->GetValue();
579
0
      cmsys::RegularExpression regEntry;
580
0
      if (!regEntry.compile(rex)) {
581
0
        std::ostringstream error;
582
0
        error << "Regular expression \"" << rex << "\" cannot compile";
583
0
        errorString = error.str();
584
0
        status = MessageType::FATAL_ERROR;
585
0
        return false;
586
0
      }
587
588
0
      auto const match = regEntry.find(*def);
589
0
      if (match) {
590
0
        this->Makefile.StoreMatches(regEntry);
591
0
      }
592
0
      newArgs.ReduceTwoArgs(match, args);
593
0
    }
594
595
0
    else if ((matchNo =
596
0
                this->matchKeys(*args.next, keyLESS, keyLESS_EQUAL, keyGREATER,
597
0
                                keyGREATER_EQUAL, keyEQUAL))) {
598
599
0
      cmValue ldef = this->GetVariableOrString(*args.current);
600
0
      cmValue rdef = this->GetVariableOrString(*args.nextnext);
601
602
0
      double lhs;
603
0
      double rhs;
604
0
      auto parseDoubles = [&]() {
605
0
        return std::sscanf(ldef->c_str(), "%lg", &lhs) == 1 &&
606
0
          std::sscanf(rdef->c_str(), "%lg", &rhs) == 1;
607
0
      };
608
      // clang-format off
609
0
      const auto result = parseDoubles() &&
610
0
        cmRt2CtSelector<
611
0
            std::less, std::less_equal,
612
0
            std::greater, std::greater_equal,
613
0
            std::equal_to
614
0
          >::eval(matchNo, lhs, rhs);
615
      // clang-format on
616
0
      newArgs.ReduceTwoArgs(result, args);
617
0
    }
618
619
0
    else if ((matchNo = this->matchKeys(*args.next, keySTRLESS,
620
0
                                        keySTRLESS_EQUAL, keySTRGREATER,
621
0
                                        keySTRGREATER_EQUAL, keySTREQUAL))) {
622
623
0
      cmValue const lhs = this->GetVariableOrString(*args.current);
624
0
      cmValue const rhs = this->GetVariableOrString(*args.nextnext);
625
0
      auto const val = (*lhs).compare(*rhs);
626
      // clang-format off
627
0
      const auto result = cmRt2CtSelector<
628
0
            std::less, std::less_equal,
629
0
            std::greater, std::greater_equal,
630
0
            std::equal_to
631
0
          >::eval(matchNo, val, 0);
632
      // clang-format on
633
0
      newArgs.ReduceTwoArgs(result, args);
634
0
    }
635
636
0
    else if ((matchNo =
637
0
                this->matchKeys(*args.next, keyVERSION_LESS,
638
0
                                keyVERSION_LESS_EQUAL, keyVERSION_GREATER,
639
0
                                keyVERSION_GREATER_EQUAL, keyVERSION_EQUAL))) {
640
0
      auto const op = MATCH2CMPOP[matchNo - 1];
641
0
      cmValue const lhs = this->GetVariableOrString(*args.current);
642
0
      cmValue const rhs = this->GetVariableOrString(*args.nextnext);
643
0
      auto const result = cmSystemTools::VersionCompare(op, lhs, rhs);
644
0
      newArgs.ReduceTwoArgs(result, args);
645
0
    }
646
647
    // is file A newer than file B
648
0
    else if (this->IsKeyword(keyIS_NEWER_THAN, *args.next)) {
649
0
      auto fileIsNewer = 0;
650
0
      cmsys::Status ftcStatus = cmSystemTools::FileTimeCompare(
651
0
        args.current->GetValue(), args.nextnext->GetValue(), &fileIsNewer);
652
0
      newArgs.ReduceTwoArgs(
653
0
        (!ftcStatus || fileIsNewer == 1 || fileIsNewer == 0), args);
654
0
    }
655
656
0
    else if (this->IsKeyword(keyIN_LIST, *args.next)) {
657
0
      cmValue lhs = this->GetVariableOrString(*args.current);
658
0
      cmValue rhs = this->Makefile.GetDefinition(args.nextnext->GetValue());
659
660
0
      newArgs.ReduceTwoArgs(
661
0
        rhs && cm::contains(cmList{ *rhs, cmList::EmptyElements::Yes }, *lhs),
662
0
        args);
663
0
    }
664
665
0
    else if (this->IsKeyword(keyPATH_EQUAL, *args.next)) {
666
667
0
      if (this->Policy139Status != cmPolicies::OLD &&
668
0
          this->Policy139Status != cmPolicies::WARN) {
669
670
0
        cmValue lhs = this->GetVariableOrString(*args.current);
671
0
        cmValue rhs = this->GetVariableOrString(*args.nextnext);
672
0
        auto const result = cmCMakePath{ *lhs } == cmCMakePath{ *rhs };
673
0
        newArgs.ReduceTwoArgs(result, args);
674
0
      }
675
676
0
      else if (this->Policy139Status == cmPolicies::WARN) {
677
0
        this->Makefile.IssuePolicyWarning(
678
0
          cmPolicies::CMP0139, {},
679
0
          "PATH_EQUAL will be interpreted as an operator "
680
0
          "when the policy is set to NEW.  "
681
0
          "Since the policy is not set the OLD behavior will be used."_s);
682
0
      }
683
0
    }
684
685
0
    else if (this->IsKeyword(keyPATH_IS_PREFIX, *args.next)) {
686
687
0
      if (this->Policy222Status != cmPolicies::OLD &&
688
0
          this->Policy222Status != cmPolicies::WARN) {
689
690
0
        cmValue lhs = this->GetVariableOrString(*args.current);
691
0
        cmValue rhs = this->GetVariableOrString(*args.nextnext);
692
0
        auto const result = cmCMakePath{ *lhs }.IsPrefix(cmCMakePath{ *rhs });
693
0
        newArgs.ReduceTwoArgs(result, args);
694
0
      }
695
696
0
      else if (this->Policy222Status == cmPolicies::WARN) {
697
0
        this->Makefile.IssuePolicyWarning(
698
0
          cmPolicies::CMP0222, {},
699
0
          "PATH_IS_PREFIX will be interpreted as an operator "
700
0
          "when the policy is set to NEW.  "
701
0
          "Since the policy is not set the OLD behavior will be used."_s);
702
0
      }
703
0
    }
704
0
  }
705
0
  return true;
706
0
}
707
708
//=========================================================================
709
// level 3 handles NOT
710
bool cmConditionEvaluator::HandleLevel3(cmArgumentList& newArgs, std::string&,
711
                                        MessageType&)
712
0
{
713
0
  for (auto args = newArgs.make2ArgsIterator(); args.next != newArgs.end();
714
0
       args.advance(newArgs)) {
715
0
    if (this->IsKeyword(keyNOT, *args.current)) {
716
0
      auto const rhs = this->GetBooleanValue(*args.next);
717
0
      newArgs.ReduceOneArg(!rhs, args);
718
0
    }
719
0
  }
720
0
  return true;
721
0
}
722
723
//=========================================================================
724
// level 4 handles AND OR
725
bool cmConditionEvaluator::HandleLevel4(cmArgumentList& newArgs, std::string&,
726
                                        MessageType&)
727
0
{
728
0
  for (auto args = newArgs.make3ArgsIterator(); args.nextnext != newArgs.end();
729
0
       args.advance(newArgs)) {
730
731
0
    int matchNo;
732
733
0
    if ((matchNo = this->matchKeys(*args.next, keyAND, keyOR))) {
734
0
      auto const lhs = this->GetBooleanValue(*args.current);
735
0
      auto const rhs = this->GetBooleanValue(*args.nextnext);
736
      // clang-format off
737
0
      const auto result =
738
0
        cmRt2CtSelector<
739
0
            std::logical_and, std::logical_or
740
0
          >::eval(matchNo, lhs, rhs);
741
      // clang-format on
742
0
      newArgs.ReduceTwoArgs(result, args);
743
0
    }
744
0
  }
745
0
  return true;
746
0
}