resolve cyclic dependency with zstd
[platform/upstream/cmake.git] / Source / cmListFileCache.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 #define cmListFileCache_cxx
4 #include "cmListFileCache.h"
5
6 #include <memory>
7 #include <sstream>
8 #include <utility>
9
10 #ifdef _WIN32
11 #  include <cmsys/Encoding.hxx>
12 #endif
13
14 #include "cmListFileLexer.h"
15 #include "cmMessageType.h"
16 #include "cmMessenger.h"
17 #include "cmStringAlgorithms.h"
18 #include "cmSystemTools.h"
19
20 struct cmListFileParser
21 {
22   cmListFileParser(cmListFile* lf, cmListFileBacktrace lfbt,
23                    cmMessenger* messenger);
24   ~cmListFileParser();
25   cmListFileParser(const cmListFileParser&) = delete;
26   cmListFileParser& operator=(const cmListFileParser&) = delete;
27   void IssueFileOpenError(std::string const& text) const;
28   void IssueError(std::string const& text) const;
29   bool ParseFile(const char* filename);
30   bool ParseString(const char* str, const char* virtual_filename);
31   bool Parse();
32   bool ParseFunction(const char* name, long line);
33   bool AddArgument(cmListFileLexer_Token* token,
34                    cmListFileArgument::Delimiter delim);
35   cm::optional<cmListFileContext> CheckNesting() const;
36   cmListFile* ListFile;
37   cmListFileBacktrace Backtrace;
38   cmMessenger* Messenger;
39   const char* FileName = nullptr;
40   cmListFileLexer* Lexer;
41   std::string FunctionName;
42   long FunctionLine;
43   long FunctionLineEnd;
44   std::vector<cmListFileArgument> FunctionArguments;
45   enum
46   {
47     SeparationOkay,
48     SeparationWarning,
49     SeparationError
50   } Separation;
51 };
52
53 cmListFileParser::cmListFileParser(cmListFile* lf, cmListFileBacktrace lfbt,
54                                    cmMessenger* messenger)
55   : ListFile(lf)
56   , Backtrace(std::move(lfbt))
57   , Messenger(messenger)
58   , Lexer(cmListFileLexer_New())
59 {
60 }
61
62 cmListFileParser::~cmListFileParser()
63 {
64   cmListFileLexer_Delete(this->Lexer);
65 }
66
67 void cmListFileParser::IssueFileOpenError(const std::string& text) const
68 {
69   this->Messenger->IssueMessage(MessageType::FATAL_ERROR, text,
70                                 this->Backtrace);
71 }
72
73 void cmListFileParser::IssueError(const std::string& text) const
74 {
75   cmListFileContext lfc;
76   lfc.FilePath = this->FileName;
77   lfc.Line = cmListFileLexer_GetCurrentLine(this->Lexer);
78   cmListFileBacktrace lfbt = this->Backtrace;
79   lfbt = lfbt.Push(lfc);
80   this->Messenger->IssueMessage(MessageType::FATAL_ERROR, text, lfbt);
81   cmSystemTools::SetFatalErrorOccurred();
82 }
83
84 bool cmListFileParser::ParseFile(const char* filename)
85 {
86   this->FileName = filename;
87
88 #ifdef _WIN32
89   std::string expandedFileName = cmsys::Encoding::ToNarrow(
90     cmSystemTools::ConvertToWindowsExtendedPath(filename));
91   filename = expandedFileName.c_str();
92 #endif
93
94   // Open the file.
95   cmListFileLexer_BOM bom;
96   if (!cmListFileLexer_SetFileName(this->Lexer, filename, &bom)) {
97     this->IssueFileOpenError("cmListFileCache: error can not open file.");
98     return false;
99   }
100
101   if (bom == cmListFileLexer_BOM_Broken) {
102     cmListFileLexer_SetFileName(this->Lexer, nullptr, nullptr);
103     this->IssueFileOpenError("Error while reading Byte-Order-Mark. "
104                              "File not seekable?");
105     return false;
106   }
107
108   // Verify the Byte-Order-Mark, if any.
109   if (bom != cmListFileLexer_BOM_None && bom != cmListFileLexer_BOM_UTF8) {
110     cmListFileLexer_SetFileName(this->Lexer, nullptr, nullptr);
111     this->IssueFileOpenError(
112       "File starts with a Byte-Order-Mark that is not UTF-8.");
113     return false;
114   }
115
116   return this->Parse();
117 }
118
119 bool cmListFileParser::ParseString(const char* str,
120                                    const char* virtual_filename)
121 {
122   this->FileName = virtual_filename;
123
124   if (!cmListFileLexer_SetString(this->Lexer, str)) {
125     this->IssueFileOpenError("cmListFileCache: cannot allocate buffer.");
126     return false;
127   }
128
129   return this->Parse();
130 }
131
132 bool cmListFileParser::Parse()
133 {
134   // Use a simple recursive-descent parser to process the token
135   // stream.
136   bool haveNewline = true;
137   while (cmListFileLexer_Token* token = cmListFileLexer_Scan(this->Lexer)) {
138     if (token->type == cmListFileLexer_Token_Space) {
139     } else if (token->type == cmListFileLexer_Token_Newline) {
140       haveNewline = true;
141     } else if (token->type == cmListFileLexer_Token_CommentBracket) {
142       haveNewline = false;
143     } else if (token->type == cmListFileLexer_Token_Identifier) {
144       if (haveNewline) {
145         haveNewline = false;
146         if (this->ParseFunction(token->text, token->line)) {
147           this->ListFile->Functions.emplace_back(
148             std::move(this->FunctionName), this->FunctionLine,
149             this->FunctionLineEnd, std::move(this->FunctionArguments));
150         } else {
151           return false;
152         }
153       } else {
154         std::ostringstream error;
155         error << "Parse error.  Expected a newline, got "
156               << cmListFileLexer_GetTypeAsString(this->Lexer, token->type)
157               << " with text \"" << token->text << "\".";
158         this->IssueError(error.str());
159         return false;
160       }
161     } else {
162       std::ostringstream error;
163       error << "Parse error.  Expected a command name, got "
164             << cmListFileLexer_GetTypeAsString(this->Lexer, token->type)
165             << " with text \"" << token->text << "\".";
166       this->IssueError(error.str());
167       return false;
168     }
169   }
170
171   // Check if all functions are nested properly.
172   if (auto badNesting = this->CheckNesting()) {
173     this->Messenger->IssueMessage(
174       MessageType::FATAL_ERROR,
175       "Flow control statements are not properly nested.",
176       this->Backtrace.Push(*badNesting));
177     cmSystemTools::SetFatalErrorOccurred();
178     return false;
179   }
180
181   return true;
182 }
183
184 bool cmListFile::ParseFile(const char* filename, cmMessenger* messenger,
185                            cmListFileBacktrace const& lfbt)
186 {
187   if (!cmSystemTools::FileExists(filename) ||
188       cmSystemTools::FileIsDirectory(filename)) {
189     return false;
190   }
191
192   bool parseError = false;
193
194   {
195     cmListFileParser parser(this, lfbt, messenger);
196     parseError = !parser.ParseFile(filename);
197   }
198
199   return !parseError;
200 }
201
202 bool cmListFile::ParseString(const char* str, const char* virtual_filename,
203                              cmMessenger* messenger,
204                              const cmListFileBacktrace& lfbt)
205 {
206   bool parseError = false;
207
208   {
209     cmListFileParser parser(this, lfbt, messenger);
210     parseError = !parser.ParseString(str, virtual_filename);
211   }
212
213   return !parseError;
214 }
215
216 bool cmListFileParser::ParseFunction(const char* name, long line)
217 {
218   // Ininitialize a new function call.
219   this->FunctionName = name;
220   this->FunctionLine = line;
221
222   // Command name has already been parsed.  Read the left paren.
223   cmListFileLexer_Token* token;
224   while ((token = cmListFileLexer_Scan(this->Lexer)) &&
225          token->type == cmListFileLexer_Token_Space) {
226   }
227   if (!token) {
228     std::ostringstream error;
229     /* clang-format off */
230     error << "Unexpected end of file.\n"
231           << "Parse error.  Function missing opening \"(\".";
232     /* clang-format on */
233     this->IssueError(error.str());
234     return false;
235   }
236   if (token->type != cmListFileLexer_Token_ParenLeft) {
237     std::ostringstream error;
238     error << "Parse error.  Expected \"(\", got "
239           << cmListFileLexer_GetTypeAsString(this->Lexer, token->type)
240           << " with text \"" << token->text << "\".";
241     this->IssueError(error.str());
242     return false;
243   }
244
245   // Arguments.
246   unsigned long parenDepth = 0;
247   this->Separation = SeparationOkay;
248   while ((token = cmListFileLexer_Scan(this->Lexer))) {
249     if (token->type == cmListFileLexer_Token_Space ||
250         token->type == cmListFileLexer_Token_Newline) {
251       this->Separation = SeparationOkay;
252       continue;
253     }
254     if (token->type == cmListFileLexer_Token_ParenLeft) {
255       parenDepth++;
256       this->Separation = SeparationOkay;
257       if (!this->AddArgument(token, cmListFileArgument::Unquoted)) {
258         return false;
259       }
260     } else if (token->type == cmListFileLexer_Token_ParenRight) {
261       if (parenDepth == 0) {
262         this->FunctionLineEnd = token->line;
263         return true;
264       }
265       parenDepth--;
266       this->Separation = SeparationOkay;
267       if (!this->AddArgument(token, cmListFileArgument::Unquoted)) {
268         return false;
269       }
270       this->Separation = SeparationWarning;
271     } else if (token->type == cmListFileLexer_Token_Identifier ||
272                token->type == cmListFileLexer_Token_ArgumentUnquoted) {
273       if (!this->AddArgument(token, cmListFileArgument::Unquoted)) {
274         return false;
275       }
276       this->Separation = SeparationWarning;
277     } else if (token->type == cmListFileLexer_Token_ArgumentQuoted) {
278       if (!this->AddArgument(token, cmListFileArgument::Quoted)) {
279         return false;
280       }
281       this->Separation = SeparationWarning;
282     } else if (token->type == cmListFileLexer_Token_ArgumentBracket) {
283       if (!this->AddArgument(token, cmListFileArgument::Bracket)) {
284         return false;
285       }
286       this->Separation = SeparationError;
287     } else if (token->type == cmListFileLexer_Token_CommentBracket) {
288       this->Separation = SeparationError;
289     } else {
290       // Error.
291       std::ostringstream error;
292       error << "Parse error.  Function missing ending \")\".  "
293             << "Instead found "
294             << cmListFileLexer_GetTypeAsString(this->Lexer, token->type)
295             << " with text \"" << token->text << "\".";
296       this->IssueError(error.str());
297       return false;
298     }
299   }
300
301   std::ostringstream error;
302   cmListFileContext lfc;
303   lfc.FilePath = this->FileName;
304   lfc.Line = line;
305   cmListFileBacktrace lfbt = this->Backtrace;
306   lfbt = lfbt.Push(lfc);
307   error << "Parse error.  Function missing ending \")\".  "
308         << "End of file reached.";
309   this->Messenger->IssueMessage(MessageType::FATAL_ERROR, error.str(), lfbt);
310   return false;
311 }
312
313 bool cmListFileParser::AddArgument(cmListFileLexer_Token* token,
314                                    cmListFileArgument::Delimiter delim)
315 {
316   this->FunctionArguments.emplace_back(token->text, delim, token->line);
317   if (this->Separation == SeparationOkay) {
318     return true;
319   }
320   bool isError = (this->Separation == SeparationError ||
321                   delim == cmListFileArgument::Bracket);
322   std::ostringstream m;
323   cmListFileContext lfc;
324   lfc.FilePath = this->FileName;
325   lfc.Line = token->line;
326   cmListFileBacktrace lfbt = this->Backtrace;
327   lfbt = lfbt.Push(lfc);
328
329   m << "Syntax " << (isError ? "Error" : "Warning") << " in cmake code at "
330     << "column " << token->column << "\n"
331     << "Argument not separated from preceding token by whitespace.";
332   /* clang-format on */
333   if (isError) {
334     this->Messenger->IssueMessage(MessageType::FATAL_ERROR, m.str(), lfbt);
335     return false;
336   }
337   this->Messenger->IssueMessage(MessageType::AUTHOR_WARNING, m.str(), lfbt);
338   return true;
339 }
340
341 namespace {
342 enum class NestingStateEnum
343 {
344   If,
345   Else,
346   While,
347   Foreach,
348   Function,
349   Macro,
350   Block
351 };
352
353 struct NestingState
354 {
355   NestingStateEnum State;
356   cmListFileContext Context;
357 };
358
359 bool TopIs(std::vector<NestingState>& stack, NestingStateEnum state)
360 {
361   return !stack.empty() && stack.back().State == state;
362 }
363 }
364
365 cm::optional<cmListFileContext> cmListFileParser::CheckNesting() const
366 {
367   std::vector<NestingState> stack;
368
369   for (auto const& func : this->ListFile->Functions) {
370     auto const& name = func.LowerCaseName();
371     if (name == "if") {
372       stack.push_back({
373         NestingStateEnum::If,
374         cmListFileContext::FromListFileFunction(func, this->FileName),
375       });
376     } else if (name == "elseif") {
377       if (!TopIs(stack, NestingStateEnum::If)) {
378         return cmListFileContext::FromListFileFunction(func, this->FileName);
379       }
380       stack.back() = {
381         NestingStateEnum::If,
382         cmListFileContext::FromListFileFunction(func, this->FileName),
383       };
384     } else if (name == "else") {
385       if (!TopIs(stack, NestingStateEnum::If)) {
386         return cmListFileContext::FromListFileFunction(func, this->FileName);
387       }
388       stack.back() = {
389         NestingStateEnum::Else,
390         cmListFileContext::FromListFileFunction(func, this->FileName),
391       };
392     } else if (name == "endif") {
393       if (!TopIs(stack, NestingStateEnum::If) &&
394           !TopIs(stack, NestingStateEnum::Else)) {
395         return cmListFileContext::FromListFileFunction(func, this->FileName);
396       }
397       stack.pop_back();
398     } else if (name == "while") {
399       stack.push_back({
400         NestingStateEnum::While,
401         cmListFileContext::FromListFileFunction(func, this->FileName),
402       });
403     } else if (name == "endwhile") {
404       if (!TopIs(stack, NestingStateEnum::While)) {
405         return cmListFileContext::FromListFileFunction(func, this->FileName);
406       }
407       stack.pop_back();
408     } else if (name == "foreach") {
409       stack.push_back({
410         NestingStateEnum::Foreach,
411         cmListFileContext::FromListFileFunction(func, this->FileName),
412       });
413     } else if (name == "endforeach") {
414       if (!TopIs(stack, NestingStateEnum::Foreach)) {
415         return cmListFileContext::FromListFileFunction(func, this->FileName);
416       }
417       stack.pop_back();
418     } else if (name == "function") {
419       stack.push_back({
420         NestingStateEnum::Function,
421         cmListFileContext::FromListFileFunction(func, this->FileName),
422       });
423     } else if (name == "endfunction") {
424       if (!TopIs(stack, NestingStateEnum::Function)) {
425         return cmListFileContext::FromListFileFunction(func, this->FileName);
426       }
427       stack.pop_back();
428     } else if (name == "macro") {
429       stack.push_back({
430         NestingStateEnum::Macro,
431         cmListFileContext::FromListFileFunction(func, this->FileName),
432       });
433     } else if (name == "endmacro") {
434       if (!TopIs(stack, NestingStateEnum::Macro)) {
435         return cmListFileContext::FromListFileFunction(func, this->FileName);
436       }
437       stack.pop_back();
438     } else if (name == "block") {
439       stack.push_back({
440         NestingStateEnum::Block,
441         cmListFileContext::FromListFileFunction(func, this->FileName),
442       });
443     } else if (name == "endblock") {
444       if (!TopIs(stack, NestingStateEnum::Block)) {
445         return cmListFileContext::FromListFileFunction(func, this->FileName);
446       }
447       stack.pop_back();
448     }
449   }
450
451   if (!stack.empty()) {
452     return stack.back().Context;
453   }
454
455   return cm::nullopt;
456 }
457
458 #include "cmConstStack.tcc"
459 template class cmConstStack<cmListFileContext, cmListFileBacktrace>;
460
461 std::ostream& operator<<(std::ostream& os, cmListFileContext const& lfc)
462 {
463   os << lfc.FilePath;
464   if (lfc.Line > 0) {
465     os << ":" << lfc.Line;
466     if (!lfc.Name.empty()) {
467       os << " (" << lfc.Name << ")";
468     }
469   } else if (lfc.Line == cmListFileContext::DeferPlaceholderLine) {
470     os << ":DEFERRED";
471   }
472   return os;
473 }
474
475 bool operator<(const cmListFileContext& lhs, const cmListFileContext& rhs)
476 {
477   if (lhs.Line != rhs.Line) {
478     return lhs.Line < rhs.Line;
479   }
480   return lhs.FilePath < rhs.FilePath;
481 }
482
483 bool operator==(const cmListFileContext& lhs, const cmListFileContext& rhs)
484 {
485   return lhs.Line == rhs.Line && lhs.FilePath == rhs.FilePath;
486 }
487
488 bool operator!=(const cmListFileContext& lhs, const cmListFileContext& rhs)
489 {
490   return !(lhs == rhs);
491 }
492
493 std::ostream& operator<<(std::ostream& os, BT<std::string> const& s)
494 {
495   return os << s.Value;
496 }
497
498 std::vector<BT<std::string>> cmExpandListWithBacktrace(
499   std::string const& list, cmListFileBacktrace const& bt, bool emptyArgs)
500 {
501   std::vector<BT<std::string>> result;
502   std::vector<std::string> tmp = cmExpandedList(list, emptyArgs);
503   result.reserve(tmp.size());
504   for (std::string& i : tmp) {
505     result.emplace_back(std::move(i), bt);
506   }
507   return result;
508 }