Coverage Report

Created: 2026-03-12 06:35

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