Coverage Report

Created: 2026-04-29 07:01

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/CMake/Source/cmIfCommand.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 "cmIfCommand.h"
4
5
#include <string>
6
#include <utility>
7
8
#include <cm/memory>
9
#include <cm/string_view>
10
#include <cmext/string_view>
11
12
#include "cmConditionEvaluator.h"
13
#include "cmDiagnostics.h"
14
#include "cmExecutionStatus.h"
15
#include "cmExpandedCommandArgument.h"
16
#include "cmFunctionBlocker.h"
17
#include "cmListFileCache.h"
18
#include "cmMakefile.h"
19
#include "cmMessageType.h"
20
#include "cmOutputConverter.h"
21
#include "cmStringAlgorithms.h"
22
#include "cmSystemTools.h"
23
#include "cmake.h"
24
25
static std::string cmIfCommandError(
26
  std::vector<cmExpandedCommandArgument> const& args)
27
0
{
28
0
  std::string err = "given arguments:\n ";
29
0
  for (cmExpandedCommandArgument const& i : args) {
30
0
    err += " ";
31
0
    err += cmOutputConverter::EscapeForCMake(i.GetValue());
32
0
  }
33
0
  err += "\n";
34
0
  return err;
35
0
}
36
37
class cmIfFunctionBlocker : public cmFunctionBlocker
38
{
39
public:
40
0
  cm::string_view StartCommandName() const override { return "if"_s; }
41
0
  cm::string_view EndCommandName() const override { return "endif"_s; }
42
43
  bool ArgumentsMatch(cmListFileFunction const& lff,
44
                      cmMakefile&) const override;
45
46
  bool Replay(std::vector<cmListFileFunction> functions,
47
              cmExecutionStatus& inStatus) override;
48
49
  std::vector<cmListFileArgument> Args;
50
  bool IsBlocking;
51
  bool HasRun = false;
52
  bool ElseSeen = false;
53
};
54
55
bool cmIfFunctionBlocker::ArgumentsMatch(cmListFileFunction const& lff,
56
                                         cmMakefile&) const
57
0
{
58
0
  return lff.Arguments().empty() || lff.Arguments() == this->Args;
59
0
}
60
61
bool cmIfFunctionBlocker::Replay(std::vector<cmListFileFunction> functions,
62
                                 cmExecutionStatus& inStatus)
63
0
{
64
0
  cmMakefile& mf = inStatus.GetMakefile();
65
  // execute the functions for the true parts of the if statement
66
0
  int scopeDepth = 0;
67
0
  for (cmListFileFunction const& func : functions) {
68
    // keep track of scope depth
69
0
    if (func.LowerCaseName() == "if") {
70
0
      scopeDepth++;
71
0
    }
72
0
    if (func.LowerCaseName() == "endif") {
73
0
      scopeDepth--;
74
0
    }
75
    // watch for our state change
76
0
    if (scopeDepth == 0 && func.LowerCaseName() == "else") {
77
0
      cmListFileBacktrace elseBT = mf.GetBacktrace().Push(
78
0
        cmListFileContext{ func.OriginalName(),
79
0
                           this->GetStartingContext().FilePath, func.Line() });
80
81
0
      if (this->ElseSeen) {
82
0
        mf.IssueMessage(
83
0
          MessageType::FATAL_ERROR,
84
0
          "A duplicate ELSE command was found inside an IF block.", elseBT);
85
0
        cmSystemTools::SetFatalErrorOccurred();
86
0
        return true;
87
0
      }
88
89
0
      this->IsBlocking = this->HasRun;
90
0
      this->HasRun = true;
91
0
      this->ElseSeen = true;
92
93
      // if trace is enabled, print a (trivially) evaluated "else"
94
      // statement
95
0
      if (!this->IsBlocking && mf.GetCMakeInstance()->GetTrace()) {
96
0
        mf.PrintCommandTrace(func, elseBT,
97
0
                             cmMakefile::CommandMissingFromStack::Yes);
98
0
      }
99
0
    } else if (scopeDepth == 0 && func.LowerCaseName() == "elseif") {
100
0
      cmListFileBacktrace elseifBT = mf.GetBacktrace().Push(
101
0
        cmListFileContext{ func.OriginalName(),
102
0
                           this->GetStartingContext().FilePath, func.Line() });
103
0
      if (this->ElseSeen) {
104
0
        mf.IssueMessage(MessageType::FATAL_ERROR,
105
0
                        "An ELSEIF command was found after an ELSE command.",
106
0
                        elseifBT);
107
0
        cmSystemTools::SetFatalErrorOccurred();
108
0
        return true;
109
0
      }
110
111
0
      if (func.Arguments().empty()) {
112
0
        mf.IssueDiagnostic(
113
0
          cmDiagnostics::CMD_AUTHOR,
114
0
          "ELSEIF called with no arguments, it will be skipped.", elseifBT);
115
0
      }
116
117
0
      if (this->HasRun) {
118
0
        this->IsBlocking = true;
119
0
      } else {
120
        // if trace is enabled, print the evaluated "elseif" statement
121
0
        if (mf.GetCMakeInstance()->GetTrace()) {
122
0
          mf.PrintCommandTrace(func, elseifBT,
123
0
                               cmMakefile::CommandMissingFromStack::Yes);
124
0
        }
125
126
0
        std::string errorString;
127
128
0
        std::vector<cmExpandedCommandArgument> expandedArguments;
129
0
        mf.ExpandArguments(func.Arguments(), expandedArguments);
130
131
0
        MessageType messType;
132
133
0
        cmConditionEvaluator conditionEvaluator(mf, elseifBT);
134
135
0
        bool isTrue =
136
0
          conditionEvaluator.IsTrue(expandedArguments, errorString, messType);
137
138
0
        if (!errorString.empty()) {
139
0
          std::string err =
140
0
            cmStrCat(cmIfCommandError(expandedArguments), errorString);
141
0
          mf.IssueMessage(messType, err, elseifBT);
142
0
          if (messType == MessageType::FATAL_ERROR) {
143
0
            cmSystemTools::SetFatalErrorOccurred();
144
0
            return true;
145
0
          }
146
0
        }
147
148
0
        if (isTrue) {
149
0
          this->IsBlocking = false;
150
0
          this->HasRun = true;
151
0
        }
152
0
      }
153
0
    }
154
155
    // should we execute?
156
0
    else if (!this->IsBlocking) {
157
0
      cmExecutionStatus status(mf);
158
0
      mf.ExecuteCommand(func, status);
159
0
      if (status.GetReturnInvoked()) {
160
0
        inStatus.SetReturnInvoked(status.GetReturnVariables());
161
0
        return true;
162
0
      }
163
0
      if (status.GetBreakInvoked()) {
164
0
        inStatus.SetBreakInvoked();
165
0
        return true;
166
0
      }
167
0
      if (status.GetContinueInvoked()) {
168
0
        inStatus.SetContinueInvoked();
169
0
        return true;
170
0
      }
171
0
      if (status.HasExitCode()) {
172
0
        inStatus.SetExitCode(status.GetExitCode());
173
0
        return true;
174
0
      }
175
0
    }
176
0
  }
177
0
  return true;
178
0
}
179
180
//=========================================================================
181
bool cmIfCommand(std::vector<cmListFileArgument> const& args,
182
                 cmExecutionStatus& inStatus)
183
0
{
184
0
  cmMakefile& makefile = inStatus.GetMakefile();
185
0
  std::string errorString;
186
187
0
  std::vector<cmExpandedCommandArgument> expandedArguments;
188
0
  makefile.ExpandArguments(args, expandedArguments);
189
190
0
  MessageType status;
191
192
0
  cmConditionEvaluator conditionEvaluator(makefile, makefile.GetBacktrace());
193
194
0
  bool isTrue =
195
0
    conditionEvaluator.IsTrue(expandedArguments, errorString, status);
196
197
0
  if (!errorString.empty()) {
198
0
    std::string err =
199
0
      cmStrCat("if ", cmIfCommandError(expandedArguments), errorString);
200
0
    if (status == MessageType::FATAL_ERROR) {
201
0
      makefile.IssueMessage(MessageType::FATAL_ERROR, err);
202
0
      cmSystemTools::SetFatalErrorOccurred();
203
0
      return true;
204
0
    }
205
0
    makefile.IssueMessage(status, err);
206
0
  }
207
208
0
  {
209
0
    auto fb = cm::make_unique<cmIfFunctionBlocker>();
210
    // if is isn't true block the commands
211
0
    fb->IsBlocking = !isTrue;
212
0
    if (isTrue) {
213
0
      fb->HasRun = true;
214
0
    }
215
0
    fb->Args = args;
216
0
    makefile.AddFunctionBlocker(std::move(fb));
217
0
  }
218
219
0
  return true;
220
0
}