Coverage Report

Created: 2026-10-28 06:38

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