Coverage Report

Created: 2026-06-15 07:03

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