CMake/Source/cmListFileCache.h
Matthew Woehlke dcaa52c6c3 PrintCallStack: "Fix" entry suppression
Backtraces may contain non-specific entries (n.b. FromListFilePath in
cmListFileContext). Our prior expectation is that these will ALWAYS
replaced by more specific entries. However, with the previous change to
allow users to create arbitrary stack entries, this is no longer the
case. Modify PrintCallStack to only skip printing these non-specific
entries if they were in fact preceded by a more specific entry.

This, and the functionality of the preceding commit, is intended to be
used for CPS dependency resolution. Because said resolution may fail at
a non-trivial depth, it is important to be able to provide the user with
the path of packages that led to the failure. However, because this
happens through parsing JSON, there is no listfile function to associate
with the stack entries, nor do we attempt to keep track of line numbers
from the JSON. As a result, these entries will never be more specific
than the name of the CPS file whose dependencies we are trying to
locate.
2024-12-07 09:12:54 -05:00

248 lines
7.2 KiB
C++

/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
file Copyright.txt or https://cmake.org/licensing for details. */
#pragma once
#include "cmConfigure.h" // IWYU pragma: keep
#include <iosfwd>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include <cm/optional>
#include "cmConstStack.h"
#include "cmList.h"
#include "cmSystemTools.h"
/** \class cmListFileCache
* \brief A class to cache list file contents.
*
* cmListFileCache is a class used to cache the contents of parsed
* cmake list files.
*/
class cmMessenger;
struct cmListFileArgument
{
enum Delimiter
{
Unquoted,
Quoted,
Bracket
};
cmListFileArgument() = default;
cmListFileArgument(std::string v, Delimiter d, long line)
: Value(std::move(v))
, Delim(d)
, Line(line)
{
}
bool operator==(const cmListFileArgument& r) const
{
return (this->Value == r.Value) && (this->Delim == r.Delim);
}
bool operator!=(const cmListFileArgument& r) const { return !(*this == r); }
std::string Value;
Delimiter Delim = Unquoted;
long Line = 0;
};
class cmListFileFunction
{
public:
cmListFileFunction(std::string name, long line, long lineEnd,
std::vector<cmListFileArgument> args)
: Impl{ std::make_shared<Implementation>(std::move(name), line, lineEnd,
std::move(args)) }
{
}
std::string const& OriginalName() const noexcept
{
return this->Impl->OriginalName;
}
std::string const& LowerCaseName() const noexcept
{
return this->Impl->LowerCaseName;
}
long Line() const noexcept { return this->Impl->Line; }
long LineEnd() const noexcept { return this->Impl->LineEnd; }
std::vector<cmListFileArgument> const& Arguments() const noexcept
{
return this->Impl->Arguments;
}
private:
struct Implementation
{
Implementation(std::string name, long line, long lineEnd,
std::vector<cmListFileArgument> args)
: OriginalName{ std::move(name) }
, LowerCaseName{ cmSystemTools::LowerCase(this->OriginalName) }
, Line{ line }
, LineEnd{ lineEnd }
, Arguments{ std::move(args) }
{
}
std::string OriginalName;
std::string LowerCaseName;
long Line = 0;
long LineEnd = 0;
std::vector<cmListFileArgument> Arguments;
};
std::shared_ptr<Implementation const> Impl;
};
class cmListFileContext
{
public:
std::string Name;
std::string FilePath;
long Line = 0;
static long const DeferPlaceholderLine = -1;
cm::optional<std::string> DeferId;
cmListFileContext() = default;
// This move constructor is marked `noexcept` yet `clang-tidy` 14 reports it
// as being able to throw an exception. Suppress the warning as there doesn't
// seem to be any way for this to happen given the member types.
// NOLINTNEXTLINE(bugprone-exception-escape)
cmListFileContext(cmListFileContext&& /*other*/) noexcept = default;
cmListFileContext(const cmListFileContext& /*other*/) = default;
cmListFileContext& operator=(const cmListFileContext& /*other*/) = default;
#if __cplusplus >= 201703L || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L)
cmListFileContext& operator=(cmListFileContext&& /*other*/) noexcept =
default;
#else
// The move assignment operators for several STL classes did not become
// noexcept until C++17, which causes some tools to warn about this move
// assignment operator throwing an exception when it shouldn't.
cmListFileContext& operator=(cmListFileContext&& /*other*/) noexcept =
delete;
#endif
cmListFileContext(std::string name, std::string filePath, long line)
: Name(std::move(name))
, FilePath(std::move(filePath))
, Line(line)
{
}
static cmListFileContext FromListFilePath(std::string const& filePath)
{
// 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 if preceded by a more specific entry.
cmListFileContext lfc;
lfc.FilePath = filePath;
return lfc;
}
static cmListFileContext FromListFileFunction(
cmListFileFunction const& lff, std::string const& fileName,
cm::optional<std::string> deferId = {})
{
cmListFileContext lfc;
lfc.FilePath = fileName;
lfc.Line = lff.Line();
lfc.Name = lff.OriginalName();
lfc.DeferId = std::move(deferId);
return lfc;
}
};
std::ostream& operator<<(std::ostream&, cmListFileContext const&);
bool operator<(const cmListFileContext& lhs, const cmListFileContext& rhs);
bool operator==(cmListFileContext const& lhs, cmListFileContext const& rhs);
bool operator!=(cmListFileContext const& lhs, cmListFileContext const& rhs);
// Represent a backtrace (call stack) with efficient value semantics.
class cmListFileBacktrace
: public cmConstStack<cmListFileContext, cmListFileBacktrace>
{
using cmConstStack::cmConstStack;
friend class cmConstStack<cmListFileContext, cmListFileBacktrace>;
};
#ifndef cmListFileCache_cxx
extern template class cmConstStack<cmListFileContext, cmListFileBacktrace>;
#endif
// Wrap type T as a value with a backtrace. For purposes of
// ordering and equality comparison, only the original value is
// used. The backtrace is considered incidental.
template <typename T>
class BT
{
public:
BT(T v = T(), cmListFileBacktrace bt = cmListFileBacktrace())
: Value(std::move(v))
, Backtrace(std::move(bt))
{
}
T Value;
cmListFileBacktrace Backtrace;
friend bool operator==(BT<T> const& l, BT<T> const& r)
{
return l.Value == r.Value;
}
friend bool operator<(BT<T> const& l, BT<T> const& r)
{
return l.Value < r.Value;
}
friend bool operator==(BT<T> const& l, T const& r) { return l.Value == r; }
friend bool operator==(T const& l, BT<T> const& r) { return l == r.Value; }
};
std::ostream& operator<<(std::ostream& os, BT<std::string> const& s);
// Wrap type T as a value with potentially multiple backtraces. For purposes
// of ordering and equality comparison, only the original value is used. The
// backtrace is considered incidental.
template <typename T>
class BTs
{
public:
BTs(T v = T(), cmListFileBacktrace bt = cmListFileBacktrace())
: Value(std::move(v))
{
this->Backtraces.emplace_back(std::move(bt));
}
T Value;
std::vector<cmListFileBacktrace> Backtraces;
friend bool operator==(BTs<T> const& l, BTs<T> const& r)
{
return l.Value == r.Value;
}
friend bool operator<(BTs<T> const& l, BTs<T> const& r)
{
return l.Value < r.Value;
}
friend bool operator==(BTs<T> const& l, T const& r) { return l.Value == r; }
friend bool operator==(T const& l, BTs<T> const& r) { return l == r.Value; }
};
std::vector<BT<std::string>> cmExpandListWithBacktrace(
std::string const& list,
cmListFileBacktrace const& bt = cmListFileBacktrace(),
cmList::EmptyElements emptyArgs = cmList::EmptyElements::No);
struct cmListFile
{
bool ParseFile(const char* path, cmMessenger* messenger,
cmListFileBacktrace const& lfbt);
bool ParseString(const char* str, const char* virtual_filename,
cmMessenger* messenger, cmListFileBacktrace const& lfbt);
std::vector<cmListFileFunction> Functions;
};