Coverage Report

Created: 2026-04-29 07:01

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