summaryrefslogtreecommitdiffstats
path: root/Source/cmListFileCache.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'Source/cmListFileCache.cxx')
-rw-r--r--Source/cmListFileCache.cxx493
1 files changed, 493 insertions, 0 deletions
diff --git a/Source/cmListFileCache.cxx b/Source/cmListFileCache.cxx
new file mode 100644
index 0000000..47679c9
--- /dev/null
+++ b/Source/cmListFileCache.cxx
@@ -0,0 +1,493 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#include "cmListFileCache.h"
+
+#include <cassert>
+#include <memory>
+#include <sstream>
+#include <utility>
+
+#include "cmListFileLexer.h"
+#include "cmMessageType.h"
+#include "cmMessenger.h"
+#include "cmState.h"
+#include "cmStateDirectory.h"
+#include "cmStringAlgorithms.h"
+#include "cmSystemTools.h"
+
+cmCommandContext::cmCommandName& cmCommandContext::cmCommandName::operator=(
+ std::string const& name)
+{
+ this->Original = name;
+ this->Lower = cmSystemTools::LowerCase(name);
+ return *this;
+}
+
+struct cmListFileParser
+{
+ cmListFileParser(cmListFile* lf, cmListFileBacktrace lfbt,
+ cmMessenger* messenger, const char* filename);
+ ~cmListFileParser();
+ cmListFileParser(const cmListFileParser&) = delete;
+ cmListFileParser& operator=(const cmListFileParser&) = delete;
+ void IssueFileOpenError(std::string const& text) const;
+ void IssueError(std::string const& text) const;
+ bool ParseFile();
+ bool ParseFunction(const char* name, long line);
+ bool AddArgument(cmListFileLexer_Token* token,
+ cmListFileArgument::Delimiter delim);
+ cmListFile* ListFile;
+ cmListFileBacktrace Backtrace;
+ cmMessenger* Messenger;
+ const char* FileName;
+ cmListFileLexer* Lexer;
+ cmListFileFunction Function;
+ enum
+ {
+ SeparationOkay,
+ SeparationWarning,
+ SeparationError
+ } Separation;
+};
+
+cmListFileParser::cmListFileParser(cmListFile* lf, cmListFileBacktrace lfbt,
+ cmMessenger* messenger,
+ const char* filename)
+ : ListFile(lf)
+ , Backtrace(std::move(lfbt))
+ , Messenger(messenger)
+ , FileName(filename)
+ , Lexer(cmListFileLexer_New())
+{
+}
+
+cmListFileParser::~cmListFileParser()
+{
+ cmListFileLexer_Delete(this->Lexer);
+}
+
+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);
+ cmListFileBacktrace lfbt = this->Backtrace;
+ lfbt = lfbt.Push(lfc);
+ this->Messenger->IssueMessage(MessageType::FATAL_ERROR, text, lfbt);
+ cmSystemTools::SetFatalErrorOccured();
+}
+
+bool cmListFileParser::ParseFile()
+{
+ // Open the file.
+ cmListFileLexer_BOM bom;
+ if (!cmListFileLexer_SetFileName(this->Lexer, this->FileName, &bom)) {
+ this->IssueFileOpenError("cmListFileCache: error can not open file.");
+ return false;
+ }
+
+ if (bom == cmListFileLexer_BOM_Broken) {
+ cmListFileLexer_SetFileName(this->Lexer, 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, nullptr, nullptr);
+ this->IssueFileOpenError(
+ "File starts with a Byte-Order-Mark that is not UTF-8.");
+ return false;
+ }
+
+ // Use a simple recursive-descent parser to process the token
+ // stream.
+ bool haveNewline = true;
+ while (cmListFileLexer_Token* token = cmListFileLexer_Scan(this->Lexer)) {
+ 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.push_back(this->Function);
+ } else {
+ return false;
+ }
+ } else {
+ std::ostringstream error;
+ error << "Parse error. Expected a newline, got "
+ << cmListFileLexer_GetTypeAsString(this->Lexer, token->type)
+ << " with text \"" << token->text << "\".";
+ this->IssueError(error.str());
+ return false;
+ }
+ } else {
+ std::ostringstream error;
+ error << "Parse error. Expected a command name, got "
+ << cmListFileLexer_GetTypeAsString(this->Lexer, token->type)
+ << " with text \"" << token->text << "\".";
+ this->IssueError(error.str());
+ return false;
+ }
+ }
+ return true;
+}
+
+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, filename);
+ parseError = !parser.ParseFile();
+ }
+
+ return !parseError;
+}
+
+bool cmListFileParser::ParseFunction(const char* name, long line)
+{
+ // Ininitialize a new function call.
+ this->Function = cmListFileFunction();
+ this->Function.Name = name;
+ this->Function.Line = line;
+
+ // Command name has already been parsed. Read the left paren.
+ cmListFileLexer_Token* token;
+ while ((token = cmListFileLexer_Scan(this->Lexer)) &&
+ token->type == cmListFileLexer_Token_Space) {
+ }
+ if (!token) {
+ std::ostringstream error;
+ /* clang-format off */
+ error << "Unexpected end of file.\n"
+ << "Parse error. Function missing opening \"(\".";
+ /* clang-format on */
+ this->IssueError(error.str());
+ return false;
+ }
+ if (token->type != cmListFileLexer_Token_ParenLeft) {
+ std::ostringstream error;
+ error << "Parse error. Expected \"(\", got "
+ << cmListFileLexer_GetTypeAsString(this->Lexer, token->type)
+ << " with text \"" << token->text << "\".";
+ this->IssueError(error.str());
+ return false;
+ }
+
+ // Arguments.
+ unsigned long parenDepth = 0;
+ this->Separation = SeparationOkay;
+ while ((token = cmListFileLexer_Scan(this->Lexer))) {
+ 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) {
+ 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.
+ std::ostringstream error;
+ error << "Parse error. Function missing ending \")\". "
+ << "Instead found "
+ << cmListFileLexer_GetTypeAsString(this->Lexer, token->type)
+ << " with text \"" << token->text << "\".";
+ this->IssueError(error.str());
+ return false;
+ }
+ }
+
+ std::ostringstream error;
+ cmListFileContext lfc;
+ lfc.FilePath = this->FileName;
+ lfc.Line = line;
+ cmListFileBacktrace lfbt = this->Backtrace;
+ lfbt = lfbt.Push(lfc);
+ error << "Parse error. Function missing ending \")\". "
+ << "End of file reached.";
+ this->Messenger->IssueMessage(MessageType::FATAL_ERROR, error.str(), lfbt);
+ return false;
+}
+
+bool cmListFileParser::AddArgument(cmListFileLexer_Token* token,
+ cmListFileArgument::Delimiter delim)
+{
+ this->Function.Arguments.emplace_back(token->text, delim, token->line);
+ if (this->Separation == SeparationOkay) {
+ return true;
+ }
+ bool isError = (this->Separation == SeparationError ||
+ delim == cmListFileArgument::Bracket);
+ std::ostringstream m;
+ cmListFileContext lfc;
+ lfc.FilePath = this->FileName;
+ lfc.Line = token->line;
+ cmListFileBacktrace lfbt = this->Backtrace;
+ lfbt = lfbt.Push(lfc);
+
+ m << "Syntax " << (isError ? "Error" : "Warning") << " in cmake code at "
+ << "column " << token->column << "\n"
+ << "Argument not separated from preceding token by whitespace.";
+ /* clang-format on */
+ if (isError) {
+ this->Messenger->IssueMessage(MessageType::FATAL_ERROR, m.str(), lfbt);
+ return false;
+ }
+ this->Messenger->IssueMessage(MessageType::AUTHOR_WARNING, m.str(), lfbt);
+ return true;
+}
+
+// We hold either the bottom scope of a directory or a call/file context.
+// Discriminate these cases via the parent pointer.
+struct cmListFileBacktrace::Entry
+{
+ Entry(cmStateSnapshot bottom)
+ : Bottom(bottom)
+ {
+ }
+
+ Entry(std::shared_ptr<Entry const> parent, cmListFileContext lfc)
+ : Context(std::move(lfc))
+ , Parent(std::move(parent))
+ {
+ }
+
+ ~Entry()
+ {
+ if (this->Parent) {
+ this->Context.~cmListFileContext();
+ } else {
+ this->Bottom.~cmStateSnapshot();
+ }
+ }
+
+ bool IsBottom() const { return !this->Parent; }
+
+ union
+ {
+ cmStateSnapshot Bottom;
+ cmListFileContext Context;
+ };
+ std::shared_ptr<Entry const> Parent;
+};
+
+cmListFileBacktrace::cmListFileBacktrace(cmStateSnapshot const& snapshot)
+ : TopEntry(std::make_shared<Entry const>(snapshot.GetCallStackBottom()))
+{
+}
+
+/* NOLINTNEXTLINE(performance-unnecessary-value-param) */
+cmListFileBacktrace::cmListFileBacktrace(std::shared_ptr<Entry const> parent,
+ cmListFileContext const& lfc)
+ : TopEntry(std::make_shared<Entry const>(std::move(parent), lfc))
+{
+}
+
+cmListFileBacktrace::cmListFileBacktrace(std::shared_ptr<Entry const> top)
+ : TopEntry(std::move(top))
+{
+}
+
+cmStateSnapshot cmListFileBacktrace::GetBottom() const
+{
+ cmStateSnapshot bottom;
+ if (Entry const* cur = this->TopEntry.get()) {
+ while (Entry const* parent = cur->Parent.get()) {
+ cur = parent;
+ }
+ bottom = cur->Bottom;
+ }
+ return bottom;
+}
+
+cmListFileBacktrace cmListFileBacktrace::Push(std::string const& file) const
+{
+ // We are entering a file-level scope but have not yet reached
+ // any specific line or command invocation within it. This context
+ // is useful to print when it is at the top but otherwise can be
+ // skipped during call stack printing.
+ cmListFileContext lfc;
+ lfc.FilePath = file;
+ return this->Push(lfc);
+}
+
+cmListFileBacktrace cmListFileBacktrace::Push(
+ cmListFileContext const& lfc) const
+{
+ assert(this->TopEntry);
+ assert(!this->TopEntry->IsBottom() || this->TopEntry->Bottom.IsValid());
+ return cmListFileBacktrace(this->TopEntry, lfc);
+}
+
+cmListFileBacktrace cmListFileBacktrace::Pop() const
+{
+ assert(this->TopEntry);
+ assert(!this->TopEntry->IsBottom());
+ return cmListFileBacktrace(this->TopEntry->Parent);
+}
+
+cmListFileContext const& cmListFileBacktrace::Top() const
+{
+ assert(this->TopEntry);
+ assert(!this->TopEntry->IsBottom());
+ return this->TopEntry->Context;
+}
+
+void cmListFileBacktrace::PrintTitle(std::ostream& out) const
+{
+ // The title exists only if we have a call on top of the bottom.
+ if (!this->TopEntry || this->TopEntry->IsBottom()) {
+ return;
+ }
+ cmListFileContext lfc = this->TopEntry->Context;
+ cmStateSnapshot bottom = this->GetBottom();
+ if (!bottom.GetState()->GetIsInTryCompile()) {
+ lfc.FilePath = bottom.GetDirectory().ConvertToRelPathIfNotContained(
+ bottom.GetState()->GetSourceDirectory(), lfc.FilePath);
+ }
+ out << (lfc.Line ? " at " : " in ") << lfc;
+}
+
+void cmListFileBacktrace::PrintCallStack(std::ostream& out) const
+{
+ // The call stack exists only if we have at least two calls on top
+ // of the bottom.
+ if (!this->TopEntry || this->TopEntry->IsBottom() ||
+ this->TopEntry->Parent->IsBottom()) {
+ return;
+ }
+
+ bool first = true;
+ cmStateSnapshot bottom = this->GetBottom();
+ for (Entry const* cur = this->TopEntry->Parent.get(); !cur->IsBottom();
+ cur = cur->Parent.get()) {
+ if (cur->Context.Name.empty()) {
+ // Skip this whole-file scope. When we get here we already will
+ // have printed a more-specific context within the file.
+ continue;
+ }
+ if (first) {
+ first = false;
+ out << "Call Stack (most recent call first):\n";
+ }
+ cmListFileContext lfc = cur->Context;
+ if (!bottom.GetState()->GetIsInTryCompile()) {
+ lfc.FilePath = bottom.GetDirectory().ConvertToRelPathIfNotContained(
+ bottom.GetState()->GetSourceDirectory(), lfc.FilePath);
+ }
+ out << " " << lfc << "\n";
+ }
+}
+
+size_t cmListFileBacktrace::Depth() const
+{
+ size_t depth = 0;
+ if (Entry const* cur = this->TopEntry.get()) {
+ for (; !cur->IsBottom(); cur = cur->Parent.get()) {
+ ++depth;
+ }
+ }
+ return depth;
+}
+
+bool cmListFileBacktrace::Empty() const
+{
+ return !this->TopEntry || this->TopEntry->IsBottom();
+}
+
+std::ostream& operator<<(std::ostream& os, cmListFileContext const& lfc)
+{
+ os << lfc.FilePath;
+ if (lfc.Line) {
+ os << ":" << lfc.Line;
+ if (!lfc.Name.empty()) {
+ os << " (" << lfc.Name << ")";
+ }
+ }
+ 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>> ExpandListWithBacktrace(
+ std::string const& list, cmListFileBacktrace const& bt)
+{
+ std::vector<BT<std::string>> result;
+ std::vector<std::string> tmp = cmExpandedList(list);
+ result.reserve(tmp.size());
+ for (std::string& i : tmp) {
+ result.emplace_back(std::move(i), bt);
+ }
+ return result;
+}