diff --git a/lldb/include/lldb/Utility/SourceLocationSpec.h b/lldb/include/lldb/Utility/SourceLocationSpec.h new file mode 100644 --- /dev/null +++ b/lldb/include/lldb/Utility/SourceLocationSpec.h @@ -0,0 +1,203 @@ +//===-- SourceLocationSpec.h ------------------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLDB_UTILITY_SOURCELOCATIONSPEC_H +#define LLDB_UTILITY_SOURCELOCATIONSPEC_H + +#include "lldb/Utility/FileSpec.h" +#include "lldb/lldb-defines.h" + +#include "llvm/ADT/Optional.h" + +#include +#include +#include + +namespace lldb_private { + +/// \class SourceLocationSpec SourceLocationSpec.h +/// "lldb/Utility/SourceLocationSpec.h" A source location utility class. +/// +/// A source location specifier class that holds a FileSpec object with line and +/// column information. The column line is optional. +/// It also holds search flags that can be fetched by resolvers to look inlined +/// declarations and/or exact matches. +class SourceLocationSpec { +private: + /// Constructor. + /// + /// Takes a \a file_spec with a \a line number and a \a column number. If + /// \a column is null or not provided, it is set to llvm::None. + /// + /// \param[in] file_spec + /// The full or partial path to a file. + /// + /// \param[in] line + /// The line number in the source file. + /// + /// \param[in] column + /// The column number in the line of the source file. + /// + /// \param[in] check_inlines + /// Whether to look for a match in inlined declaration. + /// + /// \param[in] exact_match + /// Whether to look for an exact match. + /// + explicit SourceLocationSpec(FileSpec file_spec, uint32_t line, + llvm::Optional column, + bool check_inlines, bool exact_match); + + SourceLocationSpec() = delete; + +public: + static llvm::Expected + Create(FileSpec file_spec, uint32_t line, + llvm::Optional column = llvm::None, + bool check_inlines = false, bool exact_match = false); + + static llvm::Expected + Create(FileSpec file_spec, uint32_t line, + uint16_t column = LLDB_INVALID_COLUMN_NUMBER, + bool check_inlines = false, bool exact_match = false); + + /// Equal to operator + /// + /// Tests if this object is equal to \a rhs. + /// + /// \param[in] rhs + /// A const SourceLocationSpec object reference to compare this object + /// to. + /// + /// \return + /// \b true if this object is equal to \a rhs, \b false + /// otherwise. + bool operator==(const SourceLocationSpec &rhs) const; + + /// Not equal to operator + /// + /// Tests if this object is not equal to \a rhs. + /// + /// \param[in] rhs + /// A const SourceLocationSpec object reference to compare this object + /// to. + /// + /// \return + /// \b true if this object is equal to \a rhs, \b false + /// otherwise. + bool operator!=(const SourceLocationSpec &rhs) const; + + /// Less than to operator + /// + /// Tests if this object is less than \a rhs. + /// + /// \param[in] rhs + /// A const SourceLocationSpec object reference to compare this object + /// to. + /// + /// \return + /// \b true if this object is less than \a rhs, \b false + /// otherwise. + bool operator<(const SourceLocationSpec &rhs) const; + + /// Convert to boolean operator. + /// + /// This allows code to check a SourceLocationSpec object to see if it + /// contains anything valid using code such as: + /// + /// \code + /// SourceLocationSpec location_spec(...); + /// if (location_spec) + /// { ... + /// \endcode + /// + /// \return + /// A pointer to this object if both the file_spec and the line are valid, + /// nullptr otherwise. + explicit operator bool() const; + + /// Logical NOT operator. + /// + /// This allows code to check a SourceLocationSpec object to see if it is + /// invalid using code such as: + /// + /// \code + /// SourceLocationSpec location_spec(...); + /// if (!location_spec) + /// { ... + /// \endcode + /// + /// \return + /// Returns \b true if the object has an invalid file_spec or line number, + /// \b false otherwise. + bool operator!() const; + + /// Compare two SourceLocationSpec objects. + /// + /// If \a full is true, then the file_spec, the line and column must match. + /// If \a full is false, then only the file_spec and line number for \a lhs + /// and \a rhs are compared. This allows a SourceLocationSpec object that have + /// no column information to match a SourceLocationSpec objects that have + /// column information with matching file_spec and line component. + /// + /// \param[in] lhs + /// A const reference to the Left Hand Side object to compare. + /// + /// \param[in] rhs + /// A const reference to the Right Hand Side object to compare. + /// + /// \param[in] full + /// If true, then the file_spec, the line and column must match for a + /// compare to return zero (equal to). If false, then only the file_spec + /// and line number for \a lhs and \a rhs are compared, else a full + /// comparison is done. + /// + /// \return -1 if \a lhs is less than \a rhs, 0 if \a lhs is equal to \a rhs, + /// 1 if \a lhs is greater than \a rhs + static int Compare(const SourceLocationSpec &lhs, + const SourceLocationSpec &rhs, bool full); + + static bool Equal(const SourceLocationSpec &lhs, + const SourceLocationSpec &rhs, bool full); + + /// Dump this object to a Stream. + /// + /// Dump the object to the supplied stream \a s, starting with the file name, + /// then the line number and if available the column number. + /// + /// \param[in] s + /// The stream to which to dump the object description. + void Dump(llvm::raw_ostream &s) const; + + const char *GetCString() const; + + FileSpec GetFileSpec() const { return m_file_spec; } + + uint32_t GetLine() const { return m_line; } + + bool HasColumn() const { return m_column.hasValue(); } + + llvm::Optional GetColumn() const { return m_column; } + + bool GetCheckInlines() const { return m_check_inlines; } + + bool GetExactMatch() const { return m_exact_match; } + +protected: + FileSpec m_file_spec; + uint32_t m_line; + llvm::Optional m_column; + bool m_check_inlines; + bool m_exact_match; +}; + +/// Dump a SourceLocationSpec object to a stream +Stream &operator<<(Stream &s, const SourceLocationSpec &loc); +} // namespace lldb_private + +#endif // LLDB_UTILITY_SOURCELOCATIONSPEC_H diff --git a/lldb/source/Utility/CMakeLists.txt b/lldb/source/Utility/CMakeLists.txt --- a/lldb/source/Utility/CMakeLists.txt +++ b/lldb/source/Utility/CMakeLists.txt @@ -54,6 +54,7 @@ Scalar.cpp SelectHelper.cpp State.cpp + SourceLocationSpec.cpp Status.cpp Stream.cpp StreamCallback.cpp diff --git a/lldb/source/Utility/SourceLocationSpec.cpp b/lldb/source/Utility/SourceLocationSpec.cpp new file mode 100644 --- /dev/null +++ b/lldb/source/Utility/SourceLocationSpec.cpp @@ -0,0 +1,112 @@ +//===-- SourceLocationSpec.cpp --------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "lldb/Utility/SourceLocationSpec.h" +#include "lldb/Utility/LLDBAssert.h" +#include "lldb/Utility/Stream.h" + +#include "llvm/Support/Errc.h" + +using namespace lldb; +using namespace lldb_private; + +llvm::Expected +SourceLocationSpec::Create(FileSpec file_spec, uint32_t line, + llvm::Optional column, bool check_inlines, + bool exact_match) { + if (!file_spec) + return llvm::createStringError( + llvm::errc::invalid_argument, + "SourceLocationSpec requires a valid FileSpec."); + + if (!line || line == LLDB_INVALID_LINE_NUMBER) + return llvm::createStringError( + llvm::errc::invalid_argument, + "SourceLocationSpec requires a valid line number."); + + return SourceLocationSpec(file_spec, line, column, check_inlines, + exact_match); +} + +llvm::Expected +SourceLocationSpec::Create(FileSpec file_spec, uint32_t line, uint16_t column, + bool check_inlines, bool exact_match) { + llvm::Optional optional_column = llvm::None; + if (column != LLDB_INVALID_COLUMN_NUMBER) + optional_column = column; + + return Create(file_spec, line, optional_column, check_inlines, exact_match); +} + +SourceLocationSpec::SourceLocationSpec(FileSpec file_spec, uint32_t line, + llvm::Optional column, + bool check_inlines, bool exact_match) + : m_file_spec(file_spec), m_line(line), m_column(column), + m_check_inlines(check_inlines), m_exact_match(exact_match) {} + +bool SourceLocationSpec::operator==(const SourceLocationSpec &rhs) const { + return m_file_spec == rhs.GetFileSpec() && m_line == rhs.GetLine() && + m_column == rhs.GetColumn() && + m_check_inlines == rhs.GetCheckInlines() && + m_exact_match == rhs.GetExactMatch(); +} + +bool SourceLocationSpec::operator!=(const SourceLocationSpec &rhs) const { + return !(*this == rhs); +} + +bool SourceLocationSpec::operator<(const SourceLocationSpec &rhs) const { + return SourceLocationSpec::Compare(*this, rhs, true) < 0; +} + +Stream &lldb_private::operator<<(Stream &s, const SourceLocationSpec &loc) { + loc.Dump(s.AsRawOstream()); + return s; +} + +int SourceLocationSpec::Compare(const SourceLocationSpec &a, + const SourceLocationSpec &b, bool full) { + + if (int result = FileSpec::Compare(a.GetFileSpec(), b.GetFileSpec(), full)) + return result; + + // If both file_spec matches, compare line numbers. + if (a.GetLine() != b.GetLine()) { + return (a.GetLine() < b.GetLine()) ? -1 : 1; + } + + // If both file_spec and line number match, and \a full comparaison is + // enabled, compare column number. + if (full && a.GetColumn() != b.GetColumn()) + return (a.GetColumn() < b.GetColumn()) ? -1 : 1; + + return 0; +} + +bool SourceLocationSpec::Equal(const SourceLocationSpec &lhs, + const SourceLocationSpec &rhs, bool full) { + return full ? lhs == rhs + : (lhs.GetFileSpec() == rhs.GetFileSpec() && + lhs.GetLine() == rhs.GetLine()); +} + +void SourceLocationSpec::Dump(llvm::raw_ostream &s) const { + GetFileSpec().Dump(s); + s << ':' << GetLine(); + + if (HasColumn()) + s << ':' << GetColumn(); +} + +const char *SourceLocationSpec::GetCString() const { + std::string data; + llvm::raw_string_ostream ss(data); + Dump(ss); + + return ConstString{ss.str()}.AsCString(); +} diff --git a/lldb/unittests/Utility/CMakeLists.txt b/lldb/unittests/Utility/CMakeLists.txt --- a/lldb/unittests/Utility/CMakeLists.txt +++ b/lldb/unittests/Utility/CMakeLists.txt @@ -25,6 +25,7 @@ ReproducerTest.cpp ScalarTest.cpp SharedClusterTest.cpp + SourceLocationSpecTest.cpp StateTest.cpp StatusTest.cpp StreamTeeTest.cpp @@ -38,9 +39,9 @@ TildeExpressionResolverTest.cpp TimeoutTest.cpp TimerTest.cpp + UUIDTest.cpp UriParserTest.cpp UserIDResolverTest.cpp - UUIDTest.cpp VASprintfTest.cpp VMRangeTest.cpp XcodeSDKTest.cpp diff --git a/lldb/unittests/Utility/SourceLocationSpecTest.cpp b/lldb/unittests/Utility/SourceLocationSpecTest.cpp new file mode 100644 --- /dev/null +++ b/lldb/unittests/Utility/SourceLocationSpecTest.cpp @@ -0,0 +1,215 @@ +//===-- SourceLocationSpecTest.cpp ----------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "gtest/gtest.h" + +#include "lldb/Utility/LLDBAssert.h" +#include "lldb/Utility/SourceLocationSpec.h" + +#include "llvm/Testing/Support/Error.h" + +using namespace lldb_private; + +TEST(SourceLocationSpecTest, CreateSourceLocationSpec) { + FileSpec fs("/foo/bar", FileSpec::Style::posix); + const uint32_t line = 19; + const uint32_t column = 4; + const bool check_inlines = false; + const bool exact_match = true; + + // Invalid FileSpec + ASSERT_THAT_EXPECTED( + SourceLocationSpec::Create(FileSpec(), line, check_inlines, exact_match), + llvm::Failed()); + // Null Line + ASSERT_THAT_EXPECTED( + SourceLocationSpec::Create(fs, 0, check_inlines, exact_match), + llvm::Failed()); + // Invalid Line + ASSERT_THAT_EXPECTED(SourceLocationSpec::Create(fs, LLDB_INVALID_LINE_NUMBER, + check_inlines, exact_match), + llvm::Failed()); + // Invalid FileSpec & Line + ASSERT_THAT_EXPECTED( + SourceLocationSpec::Create(FileSpec(), 0, check_inlines, exact_match), + llvm::Failed()); + + // Valid FileSpec & Line + ASSERT_THAT_EXPECTED( + SourceLocationSpec::Create(fs, line, check_inlines, exact_match), + llvm::Succeeded()); + // Valid FileSpec, Line & Column + ASSERT_THAT_EXPECTED( + SourceLocationSpec::Create(fs, line, column, check_inlines, exact_match), + llvm::Succeeded()); +} + +TEST(SourceLocationSpecTest, FileLineColumnComponents) { + FileSpec fs("/foo/bar", FileSpec::Style::posix); + const uint32_t line = 19; + const uint16_t column = 4; + + auto location_spec = SourceLocationSpec::Create( + fs, line, LLDB_INVALID_COLUMN_NUMBER, false, true); + ASSERT_THAT_EXPECTED(location_spec, llvm::Succeeded()); + SourceLocationSpec without_column = *location_spec; + EXPECT_EQ(fs, without_column.GetFileSpec()); + EXPECT_EQ(line, without_column.GetLine()); + EXPECT_FALSE(without_column.HasColumn()); + EXPECT_EQ(llvm::None, without_column.GetColumn()); + EXPECT_FALSE(without_column.GetCheckInlines()); + EXPECT_TRUE(without_column.GetExactMatch()); + EXPECT_STREQ("/foo/bar:19", without_column.GetCString()); + + location_spec = SourceLocationSpec::Create(fs, line, column, true, false); + ASSERT_THAT_EXPECTED(location_spec, llvm::Succeeded()); + SourceLocationSpec with_column = *location_spec; + EXPECT_TRUE(with_column.HasColumn()); + EXPECT_EQ(column, with_column.GetColumn()); + EXPECT_TRUE(with_column.GetCheckInlines()); + EXPECT_FALSE(with_column.GetExactMatch()); + EXPECT_STREQ("/foo/bar:19:4", with_column.GetCString()); +} + +TEST(SourceLocationSpecTest, Equal) { + auto Create = + [](bool check_inlines, bool exact_match, FileSpec fs, uint32_t line, + uint16_t column = LLDB_INVALID_COLUMN_NUMBER) -> SourceLocationSpec { + auto location = SourceLocationSpec::Create(fs, line, column, check_inlines, + exact_match); + lldbassert(location && "Invalid SourceLocationSpec."); + return *location; + }; + + auto Equal = [](SourceLocationSpec lhs, SourceLocationSpec rhs, bool full) { + return SourceLocationSpec::Equal(lhs, rhs, full); + }; + + const FileSpec fs("/foo/bar", FileSpec::Style::posix); + const FileSpec other_fs("/foo/baz", FileSpec::Style::posix); + + // mutating FileSpec + const Inlined, ExactMatch, Line + EXPECT_TRUE( + Equal(Create(false, false, fs, 4), Create(false, false, fs, 4), true)); + EXPECT_TRUE( + Equal(Create(true, true, fs, 4), Create(true, true, fs, 4), false)); + EXPECT_FALSE(Equal(Create(false, false, fs, 4), + Create(false, false, other_fs, 4), true)); + EXPECT_FALSE( + Equal(Create(true, true, fs, 4), Create(true, true, other_fs, 4), false)); + + // mutating FileSpec + const Inlined, ExactMatch, Line, Column + EXPECT_TRUE(Equal(Create(false, false, fs, 4, 19), + Create(false, false, fs, 4, 19), true)); + EXPECT_TRUE(Equal(Create(true, true, fs, 4, 19), + Create(true, true, fs, 4, 19), false)); + EXPECT_FALSE(Equal(Create(false, false, fs, 4, 19), + Create(false, false, other_fs, 4, 19), true)); + EXPECT_FALSE(Equal(Create(true, true, fs, 4, 19), + Create(true, true, other_fs, 4, 19), false)); + + // Asymetric match + EXPECT_FALSE( + Equal(Create(true, true, fs, 4), Create(true, true, fs, 4, 19), true)); + EXPECT_TRUE(Equal(Create(false, false, fs, 4), + Create(false, false, fs, 4, 19), false)); + + // mutating Inlined, ExactMatch + EXPECT_FALSE( + Equal(Create(true, false, fs, 4), Create(false, true, fs, 4), true)); + EXPECT_TRUE( + Equal(Create(false, true, fs, 4), Create(true, false, fs, 4), false)); + + // mutating Column + EXPECT_FALSE(Equal(Create(true, true, fs, 4, 96), + Create(true, true, fs, 4, 19), true)); + EXPECT_TRUE(Equal(Create(false, false, fs, 4, 96), + Create(false, false, fs, 4, 19), false)); +} + +TEST(SourceLocationSpecTest, Compare) { + auto Create = + [](bool check_inlines, bool exact_match, FileSpec fs, uint32_t line, + uint16_t column = LLDB_INVALID_COLUMN_NUMBER) -> SourceLocationSpec { + auto location = SourceLocationSpec::Create(fs, line, column, check_inlines, + exact_match); + lldbassert(location && "Invalid SourceLocationSpec."); + return *location; + }; + + auto Cmp = [](SourceLocationSpec a, SourceLocationSpec b, bool full) { + return SourceLocationSpec::Compare(a, b, full); + }; + + FileSpec fs("/foo/bar", FileSpec::Style::posix); + FileSpec other_fs("/foo/baz", FileSpec::Style::posix); + + // Asymetric comparaison + EXPECT_EQ( + -1, Cmp(Create(true, true, fs, 4), Create(true, true, fs, 4, 19), true)); + EXPECT_EQ(0, Cmp(Create(false, false, fs, 4), Create(false, false, fs, 4, 19), + false)); + EXPECT_EQ( + 1, Cmp(Create(true, true, fs, 4, 19), Create(true, true, fs, 4), true)); + + // mutating FS, const Line + EXPECT_EQ(-1, Cmp(Create(false, false, fs, 4), + Create(false, false, other_fs, 4), true)); + EXPECT_EQ(-1, Cmp(Create(true, true, fs, 4), Create(true, true, other_fs, 4), + false)); + EXPECT_EQ(1, Cmp(Create(false, true, other_fs, 4), Create(false, true, fs, 4), + true)); + EXPECT_EQ(1, Cmp(Create(true, false, other_fs, 4), Create(true, false, fs, 4), + false)); + + // const FS, mutating Line + EXPECT_EQ( + -1, Cmp(Create(false, false, fs, 1), Create(false, false, fs, 4), true)); + EXPECT_EQ(-1, + Cmp(Create(true, true, fs, 1), Create(true, true, fs, 4), false)); + EXPECT_EQ(0, + Cmp(Create(false, true, fs, 4), Create(false, true, fs, 4), true)); + EXPECT_EQ(0, + Cmp(Create(true, false, fs, 4), Create(true, false, fs, 4), false)); + EXPECT_EQ( + 1, Cmp(Create(false, false, fs, 4), Create(false, false, fs, 1), true)); + EXPECT_EQ(1, + Cmp(Create(true, true, fs, 4), Create(true, true, fs, 1), false)); + + // const FS, mutating Line, const Column + EXPECT_EQ(-1, Cmp(Create(false, true, fs, 1), Create(false, true, fs, 4, 19), + true)); + EXPECT_EQ( + -1, Cmp(Create(true, true, fs, 1), Create(true, true, fs, 4, 19), false)); + EXPECT_EQ( + 1, Cmp(Create(true, false, fs, 4, 19), Create(true, false, fs, 1), true)); + EXPECT_EQ(1, Cmp(Create(true, false, fs, 4, 19), Create(true, false, fs, 1), + false)); + + // mutating FS, const Line, const Column + EXPECT_EQ(-1, Cmp(Create(false, false, fs, 4, 19), + Create(false, false, other_fs, 4, 19), true)); + EXPECT_EQ(-1, Cmp(Create(true, true, fs, 4, 19), + Create(true, true, other_fs, 4, 19), false)); + EXPECT_EQ(0, Cmp(Create(false, false, fs, 4, 19), + Create(false, false, fs, 4, 19), true)); + EXPECT_EQ(0, Cmp(Create(true, true, fs, 4, 19), Create(true, true, fs, 4, 19), + false)); + EXPECT_EQ(1, Cmp(Create(false, true, other_fs, 4, 19), + Create(false, true, fs, 4, 19), true)); + EXPECT_EQ(1, Cmp(Create(true, false, other_fs, 4, 19), + Create(true, false, fs, 4, 19), false)); + + // const FS, const Line, mutating Column + EXPECT_EQ(-1, Cmp(Create(false, false, fs, 4, 19), + Create(false, false, fs, 4, 96), true)); + EXPECT_EQ(0, Cmp(Create(true, true, fs, 4, 96), Create(true, true, fs, 4, 19), + false)); + EXPECT_EQ(1, Cmp(Create(false, true, fs, 4, 96), + Create(false, true, fs, 4, 19), true)); +}