mirror of
https://github.com/Kitware/CMake.git
synced 2026-06-30 19:57:41 +00:00
Find places that are currently relying on diagnostic-specific message types to issue diagnostics and replace these with calls to the new diagnostic methods.
514 lines
16 KiB
C++
514 lines
16 KiB
C++
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
|
|
file LICENSE.rst or https://cmake.org/licensing for details. */
|
|
#define cmListFileCache_cxx
|
|
#include "cmListFileCache.h"
|
|
|
|
#include <memory>
|
|
#include <ostream>
|
|
#include <utility>
|
|
|
|
#ifdef _WIN32
|
|
# include <cmsys/Encoding.hxx>
|
|
#endif
|
|
|
|
#include <cm/string_view>
|
|
|
|
#include "cmDiagnostics.h"
|
|
#include "cmList.h"
|
|
#include "cmListFileLexer.h"
|
|
#include "cmMakefile.h"
|
|
#include "cmMessageType.h"
|
|
#include "cmStringAlgorithms.h"
|
|
#include "cmSystemTools.h"
|
|
#include "cmake.h"
|
|
|
|
namespace {
|
|
|
|
enum class NestingStateEnum
|
|
{
|
|
If,
|
|
Else,
|
|
While,
|
|
Foreach,
|
|
Function,
|
|
Macro,
|
|
Block
|
|
};
|
|
|
|
struct NestingState
|
|
{
|
|
NestingStateEnum State;
|
|
cmListFileContext Context;
|
|
};
|
|
|
|
bool TopIs(std::vector<NestingState>& stack, NestingStateEnum state)
|
|
{
|
|
return !stack.empty() && stack.back().State == state;
|
|
}
|
|
|
|
class cmListFileParser
|
|
{
|
|
public:
|
|
cmListFileParser(cmListFile* lf, cmListFileBacktrace lfbt,
|
|
cmMakefile const* mf, std::string const& filename);
|
|
cmListFileParser(cmListFileParser const&) = delete;
|
|
cmListFileParser& operator=(cmListFileParser const&) = delete;
|
|
|
|
bool ParseFile();
|
|
bool ParseString(cm::string_view str);
|
|
|
|
private:
|
|
bool Parse();
|
|
bool ParseFunction(cm::string_view name, long line);
|
|
bool AddArgument(cmListFileLexer_Token* token,
|
|
cmListFileArgument::Delimiter delim);
|
|
void IssueFileOpenError(std::string const& text) const;
|
|
void IssueError(std::string const& text) const;
|
|
|
|
cm::optional<cmListFileContext> CheckNesting() const;
|
|
|
|
enum
|
|
{
|
|
SeparationOkay,
|
|
SeparationWarning,
|
|
SeparationError
|
|
} Separation;
|
|
|
|
cmListFile* ListFile;
|
|
cmListFileBacktrace Backtrace;
|
|
cmMakefile const* Makefile;
|
|
std::string const& FileName;
|
|
std::unique_ptr<cmListFileLexer, void (*)(cmListFileLexer*)> Lexer;
|
|
std::string FunctionName;
|
|
long FunctionLine;
|
|
long FunctionLineEnd;
|
|
std::vector<cmListFileArgument> FunctionArguments;
|
|
};
|
|
|
|
cmListFileParser::cmListFileParser(cmListFile* lf, cmListFileBacktrace lfbt,
|
|
cmMakefile const* mf,
|
|
std::string const& filename)
|
|
: ListFile(lf)
|
|
, Backtrace(std::move(lfbt))
|
|
, Makefile(mf)
|
|
, FileName(filename)
|
|
, Lexer(cmListFileLexer_New(), cmListFileLexer_Delete)
|
|
{
|
|
}
|
|
|
|
void cmListFileParser::IssueFileOpenError(std::string const& text) const
|
|
{
|
|
if (this->Makefile) {
|
|
this->Makefile->GetCMakeInstance()->IssueMessage(MessageType::FATAL_ERROR,
|
|
text, this->Backtrace);
|
|
}
|
|
}
|
|
|
|
void cmListFileParser::IssueError(std::string const& text) const
|
|
{
|
|
if (this->Makefile) {
|
|
cmListFileContext lfc;
|
|
lfc.FilePath = this->FileName;
|
|
lfc.Line = cmListFileLexer_GetCurrentLine(this->Lexer.get());
|
|
cmListFileBacktrace lfbt = this->Backtrace;
|
|
lfbt = lfbt.Push(lfc);
|
|
this->Makefile->GetCMakeInstance()->IssueMessage(MessageType::FATAL_ERROR,
|
|
text, lfbt);
|
|
}
|
|
cmSystemTools::SetFatalErrorOccurred();
|
|
}
|
|
|
|
bool cmListFileParser::ParseFile()
|
|
{
|
|
std::string const* filename = &this->FileName;
|
|
|
|
#ifdef _WIN32
|
|
std::string expandedFileName = cmsys::Encoding::ToNarrow(
|
|
cmSystemTools::ConvertToWindowsExtendedPath(*filename));
|
|
filename = &expandedFileName;
|
|
#endif
|
|
|
|
// Open the file.
|
|
cmListFileLexer_BOM bom;
|
|
if (!cmListFileLexer_SetFileName(this->Lexer.get(), filename->c_str(),
|
|
&bom)) {
|
|
this->IssueFileOpenError("cmListFileCache: error can not open file.");
|
|
return false;
|
|
}
|
|
|
|
// Verify the Byte-Order-Mark, if any.
|
|
if (bom != cmListFileLexer_BOM_None && bom != cmListFileLexer_BOM_UTF8) {
|
|
cmListFileLexer_SetFileName(this->Lexer.get(), nullptr, nullptr);
|
|
this->IssueFileOpenError(
|
|
"File starts with a Byte-Order-Mark that is not UTF-8.");
|
|
return false;
|
|
}
|
|
|
|
return this->Parse();
|
|
}
|
|
|
|
bool cmListFileParser::ParseString(cm::string_view str)
|
|
{
|
|
if (!cmListFileLexer_SetString(this->Lexer.get(), str.data(),
|
|
str.length())) {
|
|
this->IssueFileOpenError("cmListFileCache: cannot allocate buffer.");
|
|
return false;
|
|
}
|
|
|
|
return this->Parse();
|
|
}
|
|
|
|
bool cmListFileParser::Parse()
|
|
{
|
|
// Use a simple recursive-descent parser to process the token
|
|
// stream.
|
|
bool haveNewline = true;
|
|
while (cmListFileLexer_Token* token =
|
|
cmListFileLexer_Scan(this->Lexer.get())) {
|
|
if (token->type == cmListFileLexer_Token_Space) {
|
|
} else if (token->type == cmListFileLexer_Token_Newline) {
|
|
haveNewline = true;
|
|
} else if (token->type == cmListFileLexer_Token_CommentBracket) {
|
|
haveNewline = false;
|
|
} else if (token->type == cmListFileLexer_Token_Identifier) {
|
|
if (haveNewline) {
|
|
haveNewline = false;
|
|
if (this->ParseFunction(cm::string_view(token->text, token->length),
|
|
token->line)) {
|
|
this->ListFile->Functions.emplace_back(
|
|
std::move(this->FunctionName), this->FunctionLine,
|
|
this->FunctionLineEnd, std::move(this->FunctionArguments));
|
|
} else {
|
|
return false;
|
|
}
|
|
} else {
|
|
auto error = cmStrCat(
|
|
"Parse error. Expected a newline, got ",
|
|
cmListFileLexer_GetTypeAsString(this->Lexer.get(), token->type),
|
|
" with text \"", cm::string_view(token->text, token->length), "\".");
|
|
this->IssueError(error);
|
|
return false;
|
|
}
|
|
} else {
|
|
auto error = cmStrCat(
|
|
"Parse error. Expected a command name, got ",
|
|
cmListFileLexer_GetTypeAsString(this->Lexer.get(), token->type),
|
|
" with text \"", cm::string_view(token->text, token->length), "\".");
|
|
this->IssueError(error);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Check if all functions are nested properly.
|
|
if (auto badNesting = this->CheckNesting()) {
|
|
if (this->Makefile) {
|
|
this->Makefile->GetCMakeInstance()->IssueMessage(
|
|
MessageType::FATAL_ERROR,
|
|
"Flow control statements are not properly nested.",
|
|
this->Backtrace.Push(*badNesting));
|
|
}
|
|
cmSystemTools::SetFatalErrorOccurred();
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool cmListFileParser::ParseFunction(cm::string_view name, long line)
|
|
{
|
|
// Ininitialize a new function call.
|
|
this->FunctionName.assign(name.data(), name.size());
|
|
this->FunctionLine = line;
|
|
|
|
// Command name has already been parsed. Read the left paren.
|
|
cmListFileLexer_Token* token;
|
|
while ((token = cmListFileLexer_Scan(this->Lexer.get())) &&
|
|
token->type == cmListFileLexer_Token_Space) {
|
|
}
|
|
if (!token) {
|
|
this->IssueError("Unexpected end of file.\n"
|
|
"Parse error. Function missing opening \"(\".");
|
|
return false;
|
|
}
|
|
if (token->type != cmListFileLexer_Token_ParenLeft) {
|
|
auto error = cmStrCat(
|
|
"Parse error. Expected \"(\", got ",
|
|
cmListFileLexer_GetTypeAsString(this->Lexer.get(), token->type),
|
|
" with text \"", cm::string_view(token->text, token->length), "\".");
|
|
this->IssueError(error);
|
|
return false;
|
|
}
|
|
|
|
// Arguments.
|
|
unsigned long parenDepth = 0;
|
|
this->Separation = SeparationOkay;
|
|
while ((token = cmListFileLexer_Scan(this->Lexer.get()))) {
|
|
if (token->type == cmListFileLexer_Token_Space ||
|
|
token->type == cmListFileLexer_Token_Newline) {
|
|
this->Separation = SeparationOkay;
|
|
continue;
|
|
}
|
|
if (token->type == cmListFileLexer_Token_ParenLeft) {
|
|
parenDepth++;
|
|
this->Separation = SeparationOkay;
|
|
if (!this->AddArgument(token, cmListFileArgument::Unquoted)) {
|
|
return false;
|
|
}
|
|
} else if (token->type == cmListFileLexer_Token_ParenRight) {
|
|
if (parenDepth == 0) {
|
|
this->FunctionLineEnd = token->line;
|
|
return true;
|
|
}
|
|
parenDepth--;
|
|
this->Separation = SeparationOkay;
|
|
if (!this->AddArgument(token, cmListFileArgument::Unquoted)) {
|
|
return false;
|
|
}
|
|
this->Separation = SeparationWarning;
|
|
} else if (token->type == cmListFileLexer_Token_Identifier ||
|
|
token->type == cmListFileLexer_Token_ArgumentUnquoted) {
|
|
if (!this->AddArgument(token, cmListFileArgument::Unquoted)) {
|
|
return false;
|
|
}
|
|
this->Separation = SeparationWarning;
|
|
} else if (token->type == cmListFileLexer_Token_ArgumentQuoted) {
|
|
if (!this->AddArgument(token, cmListFileArgument::Quoted)) {
|
|
return false;
|
|
}
|
|
this->Separation = SeparationWarning;
|
|
} else if (token->type == cmListFileLexer_Token_ArgumentBracket) {
|
|
if (!this->AddArgument(token, cmListFileArgument::Bracket)) {
|
|
return false;
|
|
}
|
|
this->Separation = SeparationError;
|
|
} else if (token->type == cmListFileLexer_Token_CommentBracket) {
|
|
this->Separation = SeparationError;
|
|
} else {
|
|
// Error.
|
|
auto error = cmStrCat(
|
|
"Parse error. Function missing ending \")\". "
|
|
"Instead found ",
|
|
cmListFileLexer_GetTypeAsString(this->Lexer.get(), token->type),
|
|
" with text \"", cm::string_view(token->text, token->length), "\".");
|
|
this->IssueError(error);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (this->Makefile) {
|
|
cmListFileContext lfc;
|
|
lfc.FilePath = this->FileName;
|
|
lfc.Line = line;
|
|
cmListFileBacktrace lfbt = this->Backtrace;
|
|
lfbt = lfbt.Push(lfc);
|
|
this->Makefile->GetCMakeInstance()->IssueMessage(
|
|
MessageType::FATAL_ERROR,
|
|
"Parse error. Function missing ending \")\". "
|
|
"End of file reached.",
|
|
lfbt);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool cmListFileParser::AddArgument(cmListFileLexer_Token* token,
|
|
cmListFileArgument::Delimiter delim)
|
|
{
|
|
this->FunctionArguments.emplace_back(
|
|
cm::string_view(token->text, token->length), delim, token->line);
|
|
if (this->Separation == SeparationOkay) {
|
|
return true;
|
|
}
|
|
bool isError = (this->Separation == SeparationError ||
|
|
delim == cmListFileArgument::Bracket);
|
|
cmListFileContext lfc;
|
|
lfc.FilePath = this->FileName;
|
|
lfc.Line = token->line;
|
|
cmListFileBacktrace lfbt = this->Backtrace;
|
|
lfbt = lfbt.Push(lfc);
|
|
auto msg =
|
|
cmStrCat("Syntax ", (isError ? "Error" : "Warning"),
|
|
" in cmake code at column ", token->column,
|
|
"\n"
|
|
"Argument not separated from preceding token by whitespace.");
|
|
if (isError) {
|
|
if (this->Makefile) {
|
|
this->Makefile->GetCMakeInstance()->IssueMessage(
|
|
MessageType::FATAL_ERROR, msg, lfbt);
|
|
}
|
|
return false;
|
|
}
|
|
if (this->Makefile) {
|
|
this->Makefile->IssueDiagnostic(cmDiagnostics::CMD_AUTHOR, msg, lfbt);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
cm::optional<cmListFileContext> cmListFileParser::CheckNesting() const
|
|
{
|
|
std::vector<NestingState> stack;
|
|
|
|
for (auto const& func : this->ListFile->Functions) {
|
|
auto const& name = func.LowerCaseName();
|
|
if (name == "if") {
|
|
stack.push_back({
|
|
NestingStateEnum::If,
|
|
cmListFileContext::FromListFileFunction(func, this->FileName),
|
|
});
|
|
} else if (name == "elseif") {
|
|
if (!TopIs(stack, NestingStateEnum::If)) {
|
|
return cmListFileContext::FromListFileFunction(func, this->FileName);
|
|
}
|
|
stack.back() = {
|
|
NestingStateEnum::If,
|
|
cmListFileContext::FromListFileFunction(func, this->FileName),
|
|
};
|
|
} else if (name == "else") {
|
|
if (!TopIs(stack, NestingStateEnum::If)) {
|
|
return cmListFileContext::FromListFileFunction(func, this->FileName);
|
|
}
|
|
stack.back() = {
|
|
NestingStateEnum::Else,
|
|
cmListFileContext::FromListFileFunction(func, this->FileName),
|
|
};
|
|
} else if (name == "endif") {
|
|
if (!TopIs(stack, NestingStateEnum::If) &&
|
|
!TopIs(stack, NestingStateEnum::Else)) {
|
|
return cmListFileContext::FromListFileFunction(func, this->FileName);
|
|
}
|
|
stack.pop_back();
|
|
} else if (name == "while") {
|
|
stack.push_back({
|
|
NestingStateEnum::While,
|
|
cmListFileContext::FromListFileFunction(func, this->FileName),
|
|
});
|
|
} else if (name == "endwhile") {
|
|
if (!TopIs(stack, NestingStateEnum::While)) {
|
|
return cmListFileContext::FromListFileFunction(func, this->FileName);
|
|
}
|
|
stack.pop_back();
|
|
} else if (name == "foreach") {
|
|
stack.push_back({
|
|
NestingStateEnum::Foreach,
|
|
cmListFileContext::FromListFileFunction(func, this->FileName),
|
|
});
|
|
} else if (name == "endforeach") {
|
|
if (!TopIs(stack, NestingStateEnum::Foreach)) {
|
|
return cmListFileContext::FromListFileFunction(func, this->FileName);
|
|
}
|
|
stack.pop_back();
|
|
} else if (name == "function") {
|
|
stack.push_back({
|
|
NestingStateEnum::Function,
|
|
cmListFileContext::FromListFileFunction(func, this->FileName),
|
|
});
|
|
} else if (name == "endfunction") {
|
|
if (!TopIs(stack, NestingStateEnum::Function)) {
|
|
return cmListFileContext::FromListFileFunction(func, this->FileName);
|
|
}
|
|
stack.pop_back();
|
|
} else if (name == "macro") {
|
|
stack.push_back({
|
|
NestingStateEnum::Macro,
|
|
cmListFileContext::FromListFileFunction(func, this->FileName),
|
|
});
|
|
} else if (name == "endmacro") {
|
|
if (!TopIs(stack, NestingStateEnum::Macro)) {
|
|
return cmListFileContext::FromListFileFunction(func, this->FileName);
|
|
}
|
|
stack.pop_back();
|
|
} else if (name == "block") {
|
|
stack.push_back({
|
|
NestingStateEnum::Block,
|
|
cmListFileContext::FromListFileFunction(func, this->FileName),
|
|
});
|
|
} else if (name == "endblock") {
|
|
if (!TopIs(stack, NestingStateEnum::Block)) {
|
|
return cmListFileContext::FromListFileFunction(func, this->FileName);
|
|
}
|
|
stack.pop_back();
|
|
}
|
|
}
|
|
|
|
if (!stack.empty()) {
|
|
return stack.back().Context;
|
|
}
|
|
|
|
return cm::nullopt;
|
|
}
|
|
|
|
} // anonymous namespace
|
|
|
|
bool cmListFile::ParseFile(std::string const& filename, cmMakefile const* mf,
|
|
cmListFileBacktrace const& lfbt)
|
|
{
|
|
if (!cmSystemTools::FileExists(filename) ||
|
|
cmSystemTools::FileIsDirectory(filename)) {
|
|
return false;
|
|
}
|
|
|
|
cmListFileParser parser(this, lfbt, mf, filename);
|
|
return parser.ParseFile();
|
|
}
|
|
|
|
bool cmListFile::ParseString(cm::string_view str,
|
|
std::string const& virtual_filename,
|
|
cmMakefile const* mf,
|
|
cmListFileBacktrace const& lfbt)
|
|
{
|
|
cmListFileParser parser(this, lfbt, mf, virtual_filename);
|
|
return parser.ParseString(str);
|
|
}
|
|
|
|
#include "cmConstStack.tcc"
|
|
template class cmConstStack<cmListFileContext, cmListFileBacktrace>;
|
|
|
|
std::ostream& operator<<(std::ostream& os, cmListFileContext const& lfc)
|
|
{
|
|
os << lfc.FilePath;
|
|
if (lfc.Line > 0) {
|
|
os << ':' << lfc.Line;
|
|
if (!lfc.Name.empty()) {
|
|
os << " (" << lfc.Name << ')';
|
|
}
|
|
} else if (lfc.Line == cmListFileContext::DeferPlaceholderLine) {
|
|
os << ":DEFERRED";
|
|
}
|
|
return os;
|
|
}
|
|
|
|
bool operator<(cmListFileContext const& lhs, cmListFileContext const& rhs)
|
|
{
|
|
if (lhs.Line != rhs.Line) {
|
|
return lhs.Line < rhs.Line;
|
|
}
|
|
return lhs.FilePath < rhs.FilePath;
|
|
}
|
|
|
|
bool operator==(cmListFileContext const& lhs, cmListFileContext const& rhs)
|
|
{
|
|
return lhs.Line == rhs.Line && lhs.FilePath == rhs.FilePath;
|
|
}
|
|
|
|
bool operator!=(cmListFileContext const& lhs, cmListFileContext const& rhs)
|
|
{
|
|
return !(lhs == rhs);
|
|
}
|
|
|
|
std::ostream& operator<<(std::ostream& os, BT<std::string> const& s)
|
|
{
|
|
return os << s.Value;
|
|
}
|
|
|
|
std::vector<BT<std::string>> cmExpandListWithBacktrace(
|
|
std::string const& list, cmListFileBacktrace const& bt,
|
|
cmList::EmptyElements emptyArgs)
|
|
{
|
|
std::vector<BT<std::string>> result;
|
|
cmList tmp{ list, emptyArgs };
|
|
result.reserve(tmp.size());
|
|
for (std::string& i : tmp) {
|
|
result.emplace_back(std::move(i), bt);
|
|
}
|
|
return result;
|
|
}
|