Coverage Report

Created: 2026-02-09 06:05

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/CMake/Source/cmFunctionCommand.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 "cmFunctionCommand.h"
4
5
#include <functional>
6
#include <utility>
7
8
#include <cm/memory>
9
#include <cm/string_view>
10
#include <cmext/algorithm>
11
#include <cmext/string_view>
12
13
#include "cmExecutionStatus.h"
14
#include "cmFunctionBlocker.h"
15
#include "cmList.h"
16
#include "cmListFileCache.h"
17
#include "cmMakefile.h"
18
#include "cmPolicies.h"
19
#include "cmRange.h"
20
#include "cmState.h"
21
#include "cmStringAlgorithms.h"
22
#include "cmSystemTools.h"
23
24
namespace {
25
std::string const ARGC = "ARGC";
26
std::string const ARGN = "ARGN";
27
std::string const ARGV = "ARGV";
28
std::string const CMAKE_CURRENT_FUNCTION = "CMAKE_CURRENT_FUNCTION";
29
std::string const CMAKE_CURRENT_FUNCTION_LIST_FILE =
30
  "CMAKE_CURRENT_FUNCTION_LIST_FILE";
31
std::string const CMAKE_CURRENT_FUNCTION_LIST_DIR =
32
  "CMAKE_CURRENT_FUNCTION_LIST_DIR";
33
std::string const CMAKE_CURRENT_FUNCTION_LIST_LINE =
34
  "CMAKE_CURRENT_FUNCTION_LIST_LINE";
35
36
// define the class for function commands
37
class cmFunctionHelperCommand
38
{
39
public:
40
  /**
41
   * This is called when the command is first encountered in
42
   * the CMakeLists.txt file.
43
   */
44
  bool operator()(std::vector<cmListFileArgument> const& args,
45
                  cmExecutionStatus& inStatus) const;
46
47
  std::vector<std::string> Args;
48
  std::vector<cmListFileFunction> Functions;
49
  cmPolicies::PolicyMap Policies;
50
  std::string FilePath;
51
  long Line;
52
};
53
54
bool cmFunctionHelperCommand::operator()(
55
  std::vector<cmListFileArgument> const& args,
56
  cmExecutionStatus& inStatus) const
57
0
{
58
0
  cmMakefile& makefile = inStatus.GetMakefile();
59
60
  // Expand the argument list to the function.
61
0
  std::vector<std::string> expandedArgs;
62
0
  makefile.ExpandArguments(args, expandedArgs);
63
64
  // make sure the number of arguments passed is at least the number
65
  // required by the signature
66
0
  if (expandedArgs.size() < this->Args.size() - 1) {
67
0
    auto const errorMsg = cmStrCat(
68
0
      "Function invoked with incorrect arguments for function named: ",
69
0
      this->Args.front());
70
0
    inStatus.SetError(errorMsg);
71
0
    return false;
72
0
  }
73
74
0
  cmMakefile::FunctionPushPop functionScope(&makefile, this->FilePath,
75
0
                                            this->Policies);
76
77
  // set the value of argc
78
0
  makefile.AddDefinition(ARGC, std::to_string(expandedArgs.size()));
79
0
  makefile.MarkVariableAsUsed(ARGC);
80
81
  // set the values for ARGV0 ARGV1 ...
82
0
  for (auto t = 0u; t < expandedArgs.size(); ++t) {
83
0
    auto const value = cmStrCat(ARGV, t);
84
0
    makefile.AddDefinition(value, expandedArgs[t]);
85
0
    makefile.MarkVariableAsUsed(value);
86
0
  }
87
88
  // define the formal arguments
89
0
  for (auto j = 1u; j < this->Args.size(); ++j) {
90
0
    makefile.AddDefinition(this->Args[j], expandedArgs[j - 1]);
91
0
  }
92
93
  // define ARGV and ARGN
94
0
  auto const argvDef = cmList::to_string(expandedArgs);
95
0
  auto const expIt = expandedArgs.begin() + (this->Args.size() - 1);
96
0
  auto const argnDef =
97
0
    cmList::to_string(cmMakeRange(expIt, expandedArgs.end()));
98
0
  makefile.AddDefinition(ARGV, argvDef);
99
0
  makefile.MarkVariableAsUsed(ARGV);
100
0
  makefile.AddDefinition(ARGN, argnDef);
101
0
  makefile.MarkVariableAsUsed(ARGN);
102
103
0
  makefile.AddDefinition(CMAKE_CURRENT_FUNCTION, this->Args.front());
104
0
  makefile.MarkVariableAsUsed(CMAKE_CURRENT_FUNCTION);
105
0
  makefile.AddDefinition(CMAKE_CURRENT_FUNCTION_LIST_FILE, this->FilePath);
106
0
  makefile.MarkVariableAsUsed(CMAKE_CURRENT_FUNCTION_LIST_FILE);
107
0
  makefile.AddDefinition(CMAKE_CURRENT_FUNCTION_LIST_DIR,
108
0
                         cmSystemTools::GetFilenamePath(this->FilePath));
109
0
  makefile.MarkVariableAsUsed(CMAKE_CURRENT_FUNCTION_LIST_DIR);
110
0
  makefile.AddDefinition(CMAKE_CURRENT_FUNCTION_LIST_LINE,
111
0
                         std::to_string(this->Line));
112
0
  makefile.MarkVariableAsUsed(CMAKE_CURRENT_FUNCTION_LIST_LINE);
113
114
  // Invoke all the functions that were collected in the block.
115
  // for each function
116
0
  for (cmListFileFunction const& func : this->Functions) {
117
0
    cmExecutionStatus status(makefile);
118
0
    if (!makefile.ExecuteCommand(func, status) || status.GetNestedError()) {
119
      // The error message should have already included the call stack
120
      // so we do not need to report an error here.
121
0
      functionScope.Quiet();
122
0
      inStatus.SetNestedError();
123
0
      return false;
124
0
    }
125
0
    if (status.GetReturnInvoked()) {
126
0
      makefile.RaiseScope(status.GetReturnVariables());
127
0
      break;
128
0
    }
129
0
    if (status.HasExitCode()) {
130
0
      inStatus.SetExitCode(status.GetExitCode());
131
0
      break;
132
0
    }
133
0
  }
134
135
  // pop scope on the makefile
136
0
  return true;
137
0
}
138
139
class cmFunctionFunctionBlocker : public cmFunctionBlocker
140
{
141
public:
142
0
  cm::string_view StartCommandName() const override { return "function"_s; }
143
0
  cm::string_view EndCommandName() const override { return "endfunction"_s; }
144
145
  bool ArgumentsMatch(cmListFileFunction const&,
146
                      cmMakefile& mf) const override;
147
148
  bool Replay(std::vector<cmListFileFunction> functions,
149
              cmExecutionStatus& status) override;
150
151
  std::vector<std::string> Args;
152
};
153
154
bool cmFunctionFunctionBlocker::ArgumentsMatch(cmListFileFunction const& lff,
155
                                               cmMakefile& mf) const
156
0
{
157
0
  std::vector<std::string> expandedArguments;
158
0
  mf.ExpandArguments(lff.Arguments(), expandedArguments);
159
0
  return expandedArguments.empty() ||
160
0
    expandedArguments.front() == this->Args.front();
161
0
}
162
163
bool cmFunctionFunctionBlocker::Replay(
164
  std::vector<cmListFileFunction> functions, cmExecutionStatus& status)
165
0
{
166
0
  cmMakefile& mf = status.GetMakefile();
167
  // create a new command and add it to cmake
168
0
  cmFunctionHelperCommand f;
169
0
  f.Args = this->Args;
170
0
  f.Functions = std::move(functions);
171
0
  f.FilePath = this->GetStartingContext().FilePath;
172
0
  f.Line = this->GetStartingContext().Line;
173
0
  mf.RecordPolicies(f.Policies);
174
0
  return mf.GetState()->AddScriptedCommand(
175
0
    this->Args.front(),
176
0
    BT<cmState::Command>(std::move(f),
177
0
                         mf.GetBacktrace().Push(this->GetStartingContext())),
178
0
    mf);
179
0
}
180
181
} // anonymous namespace
182
183
bool cmFunctionCommand(std::vector<std::string> const& args,
184
                       cmExecutionStatus& status)
185
0
{
186
0
  if (args.empty()) {
187
0
    status.SetError("called with incorrect number of arguments");
188
0
    return false;
189
0
  }
190
191
  // create a function blocker
192
0
  auto fb = cm::make_unique<cmFunctionFunctionBlocker>();
193
0
  cm::append(fb->Args, args);
194
0
  status.GetMakefile().AddFunctionBlocker(std::move(fb));
195
196
0
  return true;
197
0
}