5a7b30f1931f4f1ab26aef058a704f7ecb1e7b55
[platform/upstream/cmake.git] / Source / cmLoadCommandCommand.cxx
1 /* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
2    file Copyright.txt or https://cmake.org/licensing for details.  */
3
4 #if !defined(_WIN32) && !defined(__sun)
5 // POSIX APIs are needed
6 // NOLINTNEXTLINE(bugprone-reserved-identifier)
7 #  define _POSIX_C_SOURCE 200809L
8 #endif
9 #if defined(__OpenBSD__) || defined(__FreeBSD__) || defined(__NetBSD__)
10 // For isascii
11 // NOLINTNEXTLINE(bugprone-reserved-identifier)
12 #  define _XOPEN_SOURCE 700
13 #endif
14
15 #include "cmLoadCommandCommand.h"
16
17 #include <csignal>
18 #include <cstdio>
19 #include <cstdlib>
20 #include <cstring>
21 #include <utility>
22
23 #include <cm/memory>
24
25 #include "cmCPluginAPI.h"
26 #include "cmCommand.h"
27 #include "cmDynamicLoader.h"
28 #include "cmExecutionStatus.h"
29 #include "cmListFileCache.h"
30 #include "cmLocalGenerator.h"
31 #include "cmMakefile.h"
32 #include "cmState.h"
33 #include "cmStringAlgorithms.h"
34 #include "cmSystemTools.h"
35
36 // NOLINTNEXTLINE(bugprone-suspicious-include)
37 #include "cmCPluginAPI.cxx"
38
39 #ifdef __QNX__
40 #  include <malloc.h> /* for malloc/free on QNX */
41 #endif
42
43 namespace {
44
45 const char* LastName = nullptr;
46
47 extern "C" void TrapsForSignals(int sig)
48 {
49   fprintf(stderr, "CMake loaded command %s crashed with signal: %d.\n",
50           LastName, sig);
51 }
52
53 struct SignalHandlerGuard
54 {
55   explicit SignalHandlerGuard(const char* name)
56   {
57     LastName = name != nullptr ? name : "????";
58
59     signal(SIGSEGV, TrapsForSignals);
60 #ifdef SIGBUS
61     signal(SIGBUS, TrapsForSignals);
62 #endif
63     signal(SIGILL, TrapsForSignals);
64   }
65
66   ~SignalHandlerGuard()
67   {
68     signal(SIGSEGV, nullptr);
69 #ifdef SIGBUS
70     signal(SIGBUS, nullptr);
71 #endif
72     signal(SIGILL, nullptr);
73   }
74
75   SignalHandlerGuard(SignalHandlerGuard const&) = delete;
76   SignalHandlerGuard& operator=(SignalHandlerGuard const&) = delete;
77 };
78
79 struct LoadedCommandImpl : cmLoadedCommandInfo
80 {
81   explicit LoadedCommandImpl(CM_INIT_FUNCTION init)
82     : cmLoadedCommandInfo{ 0,       0,       &cmStaticCAPI, 0,
83                            nullptr, nullptr, nullptr,       nullptr,
84                            nullptr, nullptr, nullptr,       nullptr }
85   {
86     init(this);
87   }
88
89   ~LoadedCommandImpl()
90   {
91     if (this->Destructor) {
92       SignalHandlerGuard guard(this->Name);
93       this->Destructor(this);
94     }
95     if (this->Error != nullptr) {
96       free(this->Error);
97     }
98   }
99
100   LoadedCommandImpl(LoadedCommandImpl const&) = delete;
101   LoadedCommandImpl& operator=(LoadedCommandImpl const&) = delete;
102
103   int DoInitialPass(cmMakefile* mf, int argc, char* argv[])
104   {
105     SignalHandlerGuard guard(this->Name);
106     return this->InitialPass(this, mf, argc, argv);
107   }
108
109   void DoFinalPass(cmMakefile* mf)
110   {
111     SignalHandlerGuard guard(this->Name);
112     this->FinalPass(this, mf);
113   }
114 };
115
116 // a class for loadabple commands
117 class cmLoadedCommand : public cmCommand
118 {
119 public:
120   cmLoadedCommand() = default;
121   explicit cmLoadedCommand(CM_INIT_FUNCTION init)
122     : Impl(std::make_shared<LoadedCommandImpl>(init))
123   {
124   }
125
126   /**
127    * This is a virtual constructor for the command.
128    */
129   std::unique_ptr<cmCommand> Clone() override
130   {
131     auto newC = cm::make_unique<cmLoadedCommand>();
132     // we must copy when we clone
133     newC->Impl = this->Impl;
134     return std::unique_ptr<cmCommand>(std::move(newC));
135   }
136
137   /**
138    * This is called when the command is first encountered in
139    * the CMakeLists.txt file.
140    */
141   bool InitialPass(std::vector<std::string> const& args,
142                    cmExecutionStatus&) override;
143
144 private:
145   std::shared_ptr<LoadedCommandImpl> Impl;
146 };
147
148 bool cmLoadedCommand::InitialPass(std::vector<std::string> const& args,
149                                   cmExecutionStatus&)
150 {
151   if (!this->Impl->InitialPass) {
152     return true;
153   }
154
155   // clear the error string
156   if (this->Impl->Error) {
157     free(this->Impl->Error);
158   }
159
160   // create argc and argv and then invoke the command
161   int argc = static_cast<int>(args.size());
162   char** argv = nullptr;
163   if (argc) {
164     argv = static_cast<char**>(malloc(argc * sizeof(char*)));
165   }
166   int i;
167   for (i = 0; i < argc; ++i) {
168     argv[i] = strdup(args[i].c_str());
169   }
170   int result = this->Impl->DoInitialPass(this->Makefile, argc, argv);
171   cmFreeArguments(argc, argv);
172
173   if (result) {
174     if (this->Impl->FinalPass) {
175       auto impl = this->Impl;
176       this->Makefile->AddGeneratorAction(
177         [impl](cmLocalGenerator& lg, const cmListFileBacktrace&) {
178           impl->DoFinalPass(lg.GetMakefile());
179         });
180     }
181     return true;
182   }
183
184   /* Initial Pass must have failed so set the error string */
185   if (this->Impl->Error) {
186     this->SetError(this->Impl->Error);
187   }
188   return false;
189 }
190
191 } // namespace
192
193 // cmLoadCommandCommand
194 bool cmLoadCommandCommand(std::vector<std::string> const& args,
195                           cmExecutionStatus& status)
196 {
197   if (args.empty()) {
198     return true;
199   }
200
201   // Construct a variable to report what file was loaded, if any.
202   // Start by removing the definition in case of failure.
203   std::string reportVar = cmStrCat("CMAKE_LOADED_COMMAND_", args[0]);
204   status.GetMakefile().RemoveDefinition(reportVar);
205
206   // the file must exist
207   std::string moduleName = cmStrCat(
208     status.GetMakefile().GetRequiredDefinition("CMAKE_SHARED_MODULE_PREFIX"),
209     "cm", args[0],
210     status.GetMakefile().GetRequiredDefinition("CMAKE_SHARED_MODULE_SUFFIX"));
211
212   // search for the file
213   std::vector<std::string> path;
214   for (unsigned int j = 1; j < args.size(); j++) {
215     // expand variables
216     std::string exp = args[j];
217     cmSystemTools::ExpandRegistryValues(exp);
218
219     // Glob the entry in case of wildcards.
220     cmSystemTools::GlobDirs(exp, path);
221   }
222
223   // Try to find the program.
224   std::string fullPath = cmSystemTools::FindFile(moduleName, path);
225   if (fullPath.empty()) {
226     status.SetError(cmStrCat("Attempt to load command failed from file \"",
227                              moduleName, "\""));
228     return false;
229   }
230
231   // try loading the shared library / dll
232   cmsys::DynamicLoader::LibraryHandle lib =
233     cmDynamicLoader::OpenLibrary(fullPath.c_str());
234   if (!lib) {
235     std::string err =
236       cmStrCat("Attempt to load the library ", fullPath, " failed.");
237     const char* error = cmsys::DynamicLoader::LastError();
238     if (error) {
239       err += " Additional error info is:\n";
240       err += error;
241     }
242     status.SetError(err);
243     return false;
244   }
245
246   // Report what file was loaded for this command.
247   status.GetMakefile().AddDefinition(reportVar, fullPath);
248
249   // find the init function
250   std::string initFuncName = args[0] + "Init";
251   CM_INIT_FUNCTION initFunction = reinterpret_cast<CM_INIT_FUNCTION>(
252     cmsys::DynamicLoader::GetSymbolAddress(lib, initFuncName));
253   if (!initFunction) {
254     initFuncName = cmStrCat('_', args[0], "Init");
255     initFunction = reinterpret_cast<CM_INIT_FUNCTION>(
256       cmsys::DynamicLoader::GetSymbolAddress(lib, initFuncName));
257   }
258   // if the symbol is found call it to set the name on the
259   // function blocker
260   if (initFunction) {
261     return status.GetMakefile().GetState()->AddScriptedCommand(
262       args[0],
263       BT<cmState::Command>(
264         cmLegacyCommandWrapper(cm::make_unique<cmLoadedCommand>(initFunction)),
265         status.GetMakefile().GetBacktrace()),
266       status.GetMakefile());
267   }
268   status.SetError("Attempt to load command failed. "
269                   "No init function found.");
270   return false;
271 }