You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
511 lines
15 KiB
511 lines
15 KiB
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
|
|
file Copyright.txt 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 "cmList.h"
|
|
#include "cmListFileLexer.h"
|
|
#include "cmMessageType.h"
|
|
#include "cmMessenger.h"
|
|
#include "cmStringAlgorithms.h"
|
|
#include "cmSystemTools.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,
|
|
cmMessenger* messenger);
|
|
cmListFileParser(const cmListFileParser&) = delete;
|
|
cmListFileParser& operator=(const cmListFileParser&) = delete;
|
|
|
|
bool ParseFile(const char* filename);
|
|
bool ParseString(const char* str, const char* virtual_filename);
|
|
|
|
private:
|
|
bool Parse();
|
|
bool ParseFunction(const char* 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;
|
|
cmMessenger* Messenger;
|
|
const char* FileName = nullptr;
|
|
std::unique_ptr<cmListFileLexer, void (*)(cmListFileLexer*)> Lexer;
|
|
std::string FunctionName;
|
|
long FunctionLine;
|
|
long FunctionLineEnd;
|
|
std::vector<cmListFileArgument> FunctionArguments;
|
|
};
|
|
|
|
cmListFileParser::cmListFileParser(cmListFile* lf, cmListFileBacktrace lfbt,
|
|
cmMessenger* messenger)
|
|
: ListFile(lf)
|
|
, Backtrace(std::move(lfbt))
|
|
, Messenger(messenger)
|
|
, Lexer(cmListFileLexer_New(), cmListFileLexer_Delete)
|
|
{
|
|
}
|
|
|
|
void cmListFileParser::IssueFileOpenError(const std::string& text) const
|
|
{
|
|
this->Messenger->IssueMessage(MessageType::FATAL_ERROR, text,
|
|
this->Backtrace);
|
|
}
|
|
|
|
void cmListFileParser::IssueError(const std::string& text) const
|
|
{
|
|
cmListFileContext lfc;
|
|
lfc.FilePath = this->FileName;
|
|
lfc.Line = cmListFileLexer_GetCurrentLine(this->Lexer.get());
|
|
cmListFileBacktrace lfbt = this->Backtrace;
|
|
lfbt = lfbt.Push(lfc);
|
|
this->Messenger->IssueMessage(MessageType::FATAL_ERROR, text, lfbt);
|
|
cmSystemTools::SetFatalErrorOccurred();
|
|
}
|
|
|
|
bool cmListFileParser::ParseFile(const char* filename)
|
|
{
|
|
this->FileName = filename;
|
|
|
|
#ifdef _WIN32
|
|
std::string expandedFileName = cmsys::Encoding::ToNarrow(
|
|
cmSystemTools::ConvertToWindowsExtendedPath(filename));
|
|
filename = expandedFileName.c_str();
|
|
#endif
|
|
|
|
// Open the file.
|
|
cmListFileLexer_BOM bom;
|
|
if (!cmListFileLexer_SetFileName(this->Lexer.get(), filename, &bom)) {
|
|
this->IssueFileOpenError("cmListFileCache: error can not open file.");
|
|
return false;
|
|
}
|
|
|
|
if (bom == cmListFileLexer_BOM_Broken) {
|
|
cmListFileLexer_SetFileName(this->Lexer.get(), nullptr, nullptr);
|
|
this->IssueFileOpenError("Error while reading Byte-Order-Mark. "
|
|
"File not seekable?");
|
|
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(const char* str,
|
|
const char* virtual_filename)
|
|
{
|
|
this->FileName = virtual_filename;
|
|
|
|
if (!cmListFileLexer_SetString(this->Lexer.get(), str)) {
|
|
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(token->text, 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 \"", token->text, "\".");
|
|
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 \"", token->text, "\".");
|
|
this->IssueError(error);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Check if all functions are nested properly.
|
|
if (auto badNesting = this->CheckNesting()) {
|
|
this->Messenger->IssueMessage(
|
|
MessageType::FATAL_ERROR,
|
|
"Flow control statements are not properly nested.",
|
|
this->Backtrace.Push(*badNesting));
|
|
cmSystemTools::SetFatalErrorOccurred();
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool cmListFileParser::ParseFunction(const char* name, long line)
|
|
{
|
|
// Ininitialize a new function call.
|
|
this->FunctionName = name;
|
|
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 \"", token->text, "\".");
|
|
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 \"", token->text, "\".");
|
|
this->IssueError(error);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
cmListFileContext lfc;
|
|
lfc.FilePath = this->FileName;
|
|
lfc.Line = line;
|
|
cmListFileBacktrace lfbt = this->Backtrace;
|
|
lfbt = lfbt.Push(lfc);
|
|
this->Messenger->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(token->text, 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) {
|
|
this->Messenger->IssueMessage(MessageType::FATAL_ERROR, msg, lfbt);
|
|
return false;
|
|
}
|
|
this->Messenger->IssueMessage(MessageType::AUTHOR_WARNING, 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(const char* filename, cmMessenger* messenger,
|
|
cmListFileBacktrace const& lfbt)
|
|
{
|
|
if (!cmSystemTools::FileExists(filename) ||
|
|
cmSystemTools::FileIsDirectory(filename)) {
|
|
return false;
|
|
}
|
|
|
|
bool parseError = false;
|
|
|
|
{
|
|
cmListFileParser parser(this, lfbt, messenger);
|
|
parseError = !parser.ParseFile(filename);
|
|
}
|
|
|
|
return !parseError;
|
|
}
|
|
|
|
bool cmListFile::ParseString(const char* str, const char* virtual_filename,
|
|
cmMessenger* messenger,
|
|
const cmListFileBacktrace& lfbt)
|
|
{
|
|
bool parseError = false;
|
|
|
|
{
|
|
cmListFileParser parser(this, lfbt, messenger);
|
|
parseError = !parser.ParseString(str, virtual_filename);
|
|
}
|
|
|
|
return !parseError;
|
|
}
|
|
|
|
#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<(const cmListFileContext& lhs, const cmListFileContext& rhs)
|
|
{
|
|
if (lhs.Line != rhs.Line) {
|
|
return lhs.Line < rhs.Line;
|
|
}
|
|
return lhs.FilePath < rhs.FilePath;
|
|
}
|
|
|
|
bool operator==(const cmListFileContext& lhs, const cmListFileContext& rhs)
|
|
{
|
|
return lhs.Line == rhs.Line && lhs.FilePath == rhs.FilePath;
|
|
}
|
|
|
|
bool operator!=(const cmListFileContext& lhs, const cmListFileContext& 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;
|
|
}
|