Coverage Report

Created: 2026-06-15 07:03

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/CMake/Source/cmWhileCommand.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 "cmWhileCommand.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 "cmExecutionStatus.h"
14
#include "cmExpandedCommandArgument.h"
15
#include "cmFunctionBlocker.h"
16
#include "cmListFileCache.h"
17
#include "cmMakefile.h"
18
#include "cmMessageType.h"
19
#include "cmOutputConverter.h"
20
#include "cmPolicies.h"
21
#include "cmSystemTools.h"
22
23
class cmWhileFunctionBlocker : public cmFunctionBlocker
24
{
25
public:
26
  cmWhileFunctionBlocker(cmMakefile* mf, std::vector<cmListFileArgument> args);
27
  ~cmWhileFunctionBlocker() override;
28
29
0
  cm::string_view StartCommandName() const override { return "while"_s; }
30
0
  cm::string_view EndCommandName() const override { return "endwhile"_s; }
31
32
  bool ArgumentsMatch(cmListFileFunction const& lff,
33
                      cmMakefile& mf) const override;
34
35
  bool Replay(std::vector<cmListFileFunction> functions,
36
              cmExecutionStatus& inStatus) override;
37
38
private:
39
  cmMakefile* Makefile;
40
  std::vector<cmListFileArgument> Args;
41
};
42
43
cmWhileFunctionBlocker::cmWhileFunctionBlocker(
44
  cmMakefile* const mf, std::vector<cmListFileArgument> args)
45
0
  : Makefile{ mf }
46
0
  , Args{ std::move(args) }
47
0
{
48
0
  this->Makefile->PushLoopBlock();
49
0
}
50
51
cmWhileFunctionBlocker::~cmWhileFunctionBlocker()
52
0
{
53
0
  this->Makefile->PopLoopBlock();
54
0
}
55
56
bool cmWhileFunctionBlocker::ArgumentsMatch(cmListFileFunction const& lff,
57
                                            cmMakefile&) const
58
0
{
59
0
  return lff.Arguments().empty() || lff.Arguments() == this->Args;
60
0
}
61
62
bool cmWhileFunctionBlocker::Replay(std::vector<cmListFileFunction> functions,
63
                                    cmExecutionStatus& inStatus)
64
0
{
65
0
  auto& mf = inStatus.GetMakefile();
66
67
0
  cmListFileBacktrace whileBT =
68
0
    mf.GetBacktrace().Push(this->GetStartingContext());
69
70
0
  std::vector<cmExpandedCommandArgument> expandedArguments;
71
  // At least same size expected for `expandedArguments` as `Args`
72
0
  expandedArguments.reserve(this->Args.size());
73
74
0
  auto expandArgs = [&mf](std::vector<cmListFileArgument> const& args,
75
0
                          std::vector<cmExpandedCommandArgument>& out)
76
0
    -> std::vector<cmExpandedCommandArgument>& {
77
0
    out.clear();
78
0
    mf.ExpandArguments(args, out);
79
0
    return out;
80
0
  };
81
82
  // For compatibility with projects that do not set CMP0130 to NEW,
83
  // we tolerate condition errors that evaluate to false.
84
0
  bool enforceError = true;
85
0
  std::string errorString;
86
0
  MessageType messageType;
87
88
0
  for (cmConditionEvaluator conditionEvaluator(mf, whileBT);
89
0
       (enforceError = /* enforce condition errors that evaluate to true */
90
0
        conditionEvaluator.IsTrue(expandArgs(this->Args, expandedArguments),
91
0
                                  errorString, messageType));) {
92
    // Invoke all the functions that were collected in the block.
93
0
    for (cmListFileFunction const& fn : functions) {
94
0
      cmExecutionStatus status(mf);
95
0
      mf.ExecuteCommand(fn, status);
96
0
      if (status.GetReturnInvoked()) {
97
0
        inStatus.SetReturnInvoked(status.GetReturnVariables());
98
0
        return true;
99
0
      }
100
0
      if (status.GetBreakInvoked()) {
101
0
        return true;
102
0
      }
103
0
      if (status.GetContinueInvoked()) {
104
0
        break;
105
0
      }
106
0
      if (status.HasExitCode()) {
107
0
        inStatus.SetExitCode(status.GetExitCode());
108
0
        return true;
109
0
      }
110
0
      if (cmSystemTools::GetFatalErrorOccurred()) {
111
0
        return true;
112
0
      }
113
0
    }
114
0
  }
115
116
0
  if (!errorString.empty() && !enforceError) {
117
    // This error should only be enforced if CMP0130 is NEW.
118
0
    if (mf.GetPolicyStatus(cmPolicies::CMP0130) != cmPolicies::OLD) {
119
0
      enforceError = true;
120
0
    }
121
0
  }
122
123
0
  if (!errorString.empty() && enforceError) {
124
0
    std::string err = "while() given incorrect arguments:\n ";
125
0
    for (auto const& i : expandedArguments) {
126
0
      err += " ";
127
0
      err += cmOutputConverter::EscapeForCMake(i.GetValue());
128
0
    }
129
0
    err += "\n";
130
0
    err += errorString;
131
0
    if (mf.GetPolicyStatus(cmPolicies::CMP0130) == cmPolicies::WARN) {
132
0
      mf.IssuePolicyWarning(cmPolicies::CMP0130, {}, err, whileBT);
133
0
    } else {
134
0
      mf.IssueMessage(messageType, err, whileBT);
135
0
      if (messageType == MessageType::FATAL_ERROR) {
136
0
        cmSystemTools::SetFatalErrorOccurred();
137
0
      }
138
0
    }
139
0
  }
140
141
0
  return true;
142
0
}
143
144
bool cmWhileCommand(std::vector<cmListFileArgument> const& args,
145
                    cmExecutionStatus& status)
146
0
{
147
0
  if (args.empty()) {
148
0
    status.SetError("called with incorrect number of arguments");
149
0
    return false;
150
0
  }
151
152
  // create a function blocker
153
0
  auto& makefile = status.GetMakefile();
154
0
  makefile.AddFunctionBlocker(
155
0
    cm::make_unique<cmWhileFunctionBlocker>(&makefile, args));
156
157
0
  return true;
158
0
}