Coverage Report

Created: 2026-04-29 07:01

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