| /* 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; | |
| } |