diff options
Diffstat (limited to 'lldb/unittests')
41 files changed, 3580 insertions, 613 deletions
diff --git a/lldb/unittests/Core/CMakeLists.txt b/lldb/unittests/Core/CMakeLists.txt index 6e609a6..d69432d 100644 --- a/lldb/unittests/Core/CMakeLists.txt +++ b/lldb/unittests/Core/CMakeLists.txt @@ -7,7 +7,9 @@ add_lldb_unittest(LLDBCoreTests DumpRegisterInfoTest.cpp FormatEntityTest.cpp MangledTest.cpp + ModuleListTest.cpp ModuleSpecTest.cpp + ModuleTest.cpp PluginManagerTest.cpp ProgressReportTest.cpp RichManglingContextTest.cpp diff --git a/lldb/unittests/Core/MangledTest.cpp b/lldb/unittests/Core/MangledTest.cpp index cbc0c5d..706e678 100644 --- a/lldb/unittests/Core/MangledTest.cpp +++ b/lldb/unittests/Core/MangledTest.cpp @@ -636,6 +636,16 @@ DemanglingPartsTestCase g_demangling_parts_test_cases[] = { /*.basename=*/"operator()", /*.scope=*/"dyld4::Loader::runInitializersBottomUpPlusUpwardLinks(dyld4::RuntimeState&) const::$_0::", /*.qualifiers=*/" const", + }, + {"_Z4funcILN3foo4EnumE1EEvv", + { + /*.BasenameRange=*/{5, 9}, /*.TemplateArgumentsRange=*/{9, 23}, /*.ScopeRange=*/{5, 5}, + /*.ArgumentsRange=*/{23, 25}, /*.QualifiersRange=*/{25, 25}, /*.NameQualifiersRange=*/{0, 0}, + /*.PrefixRange=*/{0, 0}, /*.SuffixRange=*/{0, 0} + }, + /*.basename=*/"func", + /*.scope=*/"", + /*.qualifiers=*/"", } // clang-format on }; diff --git a/lldb/unittests/Core/ModuleListTest.cpp b/lldb/unittests/Core/ModuleListTest.cpp new file mode 100644 index 0000000..3c70b0a --- /dev/null +++ b/lldb/unittests/Core/ModuleListTest.cpp @@ -0,0 +1,178 @@ +//===-- ModuleListTest.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/Core/ModuleList.h" +#include "TestingSupport/SubsystemRAII.h" +#include "TestingSupport/TestUtilities.h" +#include "lldb/Core/Module.h" +#include "lldb/Core/ModuleSpec.h" +#include "lldb/Host/FileSystem.h" +#include "lldb/Utility/ArchSpec.h" +#include "lldb/Utility/UUID.h" + +#include "Plugins/ObjectFile/ELF/ObjectFileELF.h" + +#include "gtest/gtest.h" + +using namespace lldb; +using namespace lldb_private; + +// Test that when we already have a module in the shared_module_list with a +// specific UUID, the next call to GetSharedModule with a module_spec with the +// same UUID should return the existing module instead of creating a new one. +TEST(ModuleListTest, GetSharedModuleReusesExistingModuleWithSameUUID) { + SubsystemRAII<FileSystem, ObjectFileELF> subsystems; + + auto ExpectedFile = TestFile::fromYaml(R"( +--- !ELF +FileHeader: + Class: ELFCLASS64 + Data: ELFDATA2LSB + Type: ET_DYN + Machine: EM_X86_64 +Sections: + - Name: .text + Type: SHT_PROGBITS + Flags: [ SHF_ALLOC, SHF_EXECINSTR ] + AddressAlign: 0x0000000000000010 +... +)"); + ASSERT_THAT_EXPECTED(ExpectedFile, llvm::Succeeded()); + + // First, let's verify that calling GetSharedModule twice with the same + // module_spec returns the same module pointer + + ModuleSP first_module; + bool first_did_create = false; + Status error_first = + ModuleList::GetSharedModule(ExpectedFile->moduleSpec(), first_module, + nullptr, &first_did_create, false); + + // Second call with the same spec + ModuleSP second_module; + bool second_did_create = false; + Status error_second = + ModuleList::GetSharedModule(ExpectedFile->moduleSpec(), second_module, + nullptr, &second_did_create, false); + + if (error_first.Success() && error_second.Success()) { + // If both succeeded, verify they're the same module + EXPECT_EQ(first_module.get(), second_module.get()) + << "GetSharedModule should return the same module for the same spec"; + EXPECT_TRUE(first_did_create) << "First call should create the module"; + EXPECT_FALSE(second_did_create) + << "Second call should reuse the existing module"; + } +} + +// Test that UUID-based lookup finds existing modules +TEST(ModuleListTest, FindSharedModuleByUUID) { + SubsystemRAII<FileSystem, ObjectFileELF> subsystems; + + auto ExpectedFile = TestFile::fromYaml(R"( +--- !ELF +FileHeader: + Class: ELFCLASS64 + Data: ELFDATA2LSB + Type: ET_DYN + Machine: EM_X86_64 +Sections: + - Name: .text + Type: SHT_PROGBITS + Flags: [ SHF_ALLOC, SHF_EXECINSTR ] + AddressAlign: 0x0000000000000010 +... +)"); + ASSERT_THAT_EXPECTED(ExpectedFile, llvm::Succeeded()); + + // Create and add a module to the shared module list using the moduleSpec() + ModuleSP created_module; + bool did_create = false; + Status error = ModuleList::GetSharedModule( + ExpectedFile->moduleSpec(), created_module, nullptr, &did_create, false); + + if (error.Success() && created_module) { + // Get the UUID of the created module + UUID module_uuid = created_module->GetUUID(); + + if (module_uuid.IsValid()) { + // Now try to find the module by UUID + ModuleSP found_module = ModuleList::FindSharedModule(module_uuid); + + ASSERT_NE(found_module.get(), nullptr) + << "FindSharedModule should find the module by UUID"; + EXPECT_EQ(found_module.get(), created_module.get()) + << "FindSharedModule should return the same module instance"; + EXPECT_EQ(found_module->GetUUID(), module_uuid) + << "Found module should have the same UUID"; + } + } +} + +// Test that GetSharedModule with UUID finds existing module even with different +// path +TEST(ModuleListTest, GetSharedModuleByUUIDIgnoresPath) { + SubsystemRAII<FileSystem, ObjectFileELF> subsystems; + + auto ExpectedFile = TestFile::fromYaml(R"( +--- !ELF +FileHeader: + Class: ELFCLASS64 + Data: ELFDATA2LSB + Type: ET_DYN + Machine: EM_X86_64 +Sections: + - Name: .text + Type: SHT_PROGBITS + Flags: [ SHF_ALLOC, SHF_EXECINSTR ] + AddressAlign: 0x0000000000000010 +... +)"); + ASSERT_THAT_EXPECTED(ExpectedFile, llvm::Succeeded()); + + // Create and add a module to the shared module list + ModuleSP first_module; + bool first_did_create = false; + Status first_error = + ModuleList::GetSharedModule(ExpectedFile->moduleSpec(), first_module, + nullptr, &first_did_create, false); + + if (first_error.Success() && first_module) { + UUID module_uuid = first_module->GetUUID(); + + if (module_uuid.IsValid()) { + // Now try to get a module with the same UUID but different path + ModuleSpec second_spec; + second_spec.GetFileSpec() = FileSpec("/different/path/to/module.so"); + second_spec.GetArchitecture() = ArchSpec("x86_64-pc-linux"); + second_spec.GetUUID() = module_uuid; + + ModuleSP second_module; + bool second_did_create = false; + Status second_error = ModuleList::GetSharedModule( + second_spec, second_module, nullptr, &second_did_create, false); + + if (second_error.Success() && second_module) { + // If we got a module back, check if it's the same one + bool is_same_module = (second_module.get() == first_module.get()); + + // Document the behavior: ideally UUID should take precedence + // and return the existing module + EXPECT_TRUE(is_same_module) + << "GetSharedModule with matching UUID should return existing " + "module, " + << "even with different path (per PR #160199)"; + + if (is_same_module) { + EXPECT_FALSE(second_did_create) + << "Should not create a new module when UUID matches"; + } + } + } + } +} diff --git a/lldb/unittests/Core/ModuleTest.cpp b/lldb/unittests/Core/ModuleTest.cpp new file mode 100644 index 0000000..011554d --- /dev/null +++ b/lldb/unittests/Core/ModuleTest.cpp @@ -0,0 +1,125 @@ +//===----------------------------------------------------------------------===// +// +// 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/Core/Module.h" +#include "Plugins/Language/CPlusPlus/CPlusPlusLanguage.h" +#include "Plugins/ObjectFile/ELF/ObjectFileELF.h" +#include "Plugins/SymbolFile/Symtab/SymbolFileSymtab.h" +#include "TestingSupport/SubsystemRAII.h" +#include "TestingSupport/TestUtilities.h" +#include "lldb/Core/PluginManager.h" +#include "lldb/Host/FileSystem.h" +#include "lldb/Host/HostInfo.h" +#include "lldb/Target/Language.h" +#include "gtest/gtest.h" +#include <optional> + +using namespace lldb; +using namespace lldb_private; + +// Test that Module::FindFunctions correctly finds C++ mangled symbols +// even when multiple language plugins are registered. +TEST(ModuleTest, FindFunctionsCppMangledName) { + // Create a mock language. The point of this language is to return something + // in GetFunctionNameInfo that would interfere with the C++ language plugin, + // were they sharing the same LookupInfo. + class MockLanguageWithBogusLookupInfo : public Language { + public: + MockLanguageWithBogusLookupInfo() = default; + ~MockLanguageWithBogusLookupInfo() override = default; + + lldb::LanguageType GetLanguageType() const override { + // The language here doesn't really matter, it just has to be something + // that is not C/C++/ObjC. + return lldb::eLanguageTypeSwift; + } + + llvm::StringRef GetPluginName() override { return "mock-bogus-language"; } + + bool IsSourceFile(llvm::StringRef file_path) const override { + return file_path.ends_with(".swift"); + } + + std::pair<lldb::FunctionNameType, std::optional<ConstString>> + GetFunctionNameInfo(ConstString name) const override { + // Say that every function is a selector. + return {lldb::eFunctionNameTypeSelector, ConstString("BOGUS_BASENAME")}; + } + + static void Initialize() { + PluginManager::RegisterPlugin(GetPluginNameStatic(), "Mock Language", + CreateInstance); + } + + static void Terminate() { PluginManager::UnregisterPlugin(CreateInstance); } + + static lldb_private::Language *CreateInstance(lldb::LanguageType language) { + if (language == lldb::eLanguageTypeSwift) + return new MockLanguageWithBogusLookupInfo(); + return nullptr; + } + + static llvm::StringRef GetPluginNameStatic() { + return "mock-bogus-language"; + } + }; + SubsystemRAII<FileSystem, HostInfo, ObjectFileELF, SymbolFileSymtab, + CPlusPlusLanguage, MockLanguageWithBogusLookupInfo> + subsystems; + + // Create a simple ELF module with std::vector::size() as the only symbol. + auto ExpectedFile = TestFile::fromYaml(R"( +--- !ELF +FileHeader: + Class: ELFCLASS64 + Data: ELFDATA2LSB + Type: ET_DYN + Machine: EM_X86_64 +Sections: + - Name: .text + Type: SHT_PROGBITS + Flags: [ SHF_ALLOC, SHF_EXECINSTR ] + Address: 0x1000 + AddressAlign: 0x10 + Size: 0x100 +Symbols: + - Name: _ZNSt6vectorIiE4sizeEv + Type: STT_FUNC + Section: .text + Value: 0x1030 + Size: 0x20 +... +)"); + ASSERT_THAT_EXPECTED(ExpectedFile, llvm::Succeeded()); + + auto module_sp = std::make_shared<Module>(ExpectedFile->moduleSpec()); + + // Verify both C++ and our mock language are registered. + Language *cpp_lang = Language::FindPlugin(lldb::eLanguageTypeC_plus_plus); + Language *mock_lang = Language::FindPlugin(lldb::eLanguageTypeSwift); + ASSERT_NE(cpp_lang, nullptr) << "C++ language plugin should be registered"; + ASSERT_NE(mock_lang, nullptr) + << "Mock Swift language plugin should be registered"; + + ModuleFunctionSearchOptions function_options; + function_options.include_symbols = true; + + ConstString symbol_name("_ZNSt6vectorIiE4sizeEv"); + SymbolContextList results; + module_sp->FindFunctions(symbol_name, CompilerDeclContext(), + eFunctionNameTypeAuto, function_options, results); + + // Assert that we found one symbol. + ASSERT_EQ(results.GetSize(), 1u); + + auto result = results[0]; + auto name = result.GetFunctionName(); + // Assert that the symbol we found is what we expected. + ASSERT_EQ(name, "std::vector<int>::size()"); + ASSERT_EQ(result.GetLanguage(), eLanguageTypeC_plus_plus); +} diff --git a/lldb/unittests/DAP/CMakeLists.txt b/lldb/unittests/DAP/CMakeLists.txt index a08414c..9fef37e 100644 --- a/lldb/unittests/DAP/CMakeLists.txt +++ b/lldb/unittests/DAP/CMakeLists.txt @@ -1,5 +1,8 @@ add_lldb_unittest(DAPTests + ClientLauncherTest.cpp DAPErrorTest.cpp + DAPLogTest.cpp + DAPSessionManagerTest.cpp DAPTest.cpp DAPTypesTest.cpp FifoFilesTest.cpp @@ -7,6 +10,7 @@ add_lldb_unittest(DAPTests Handler/ContinueTest.cpp JSONUtilsTest.cpp LLDBUtilsTest.cpp + ProtocolRequestsTest.cpp ProtocolTypesTest.cpp ProtocolUtilsTest.cpp TestBase.cpp diff --git a/lldb/unittests/DAP/ClientLauncherTest.cpp b/lldb/unittests/DAP/ClientLauncherTest.cpp new file mode 100644 index 0000000..dbaf9ee --- /dev/null +++ b/lldb/unittests/DAP/ClientLauncherTest.cpp @@ -0,0 +1,71 @@ +//===----------------------------------------------------------------------===// +// +// 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 "ClientLauncher.h" +#include "llvm/ADT/StringRef.h" +#include "gtest/gtest.h" +#include <optional> + +using namespace lldb_dap; +using namespace llvm; + +TEST(ClientLauncherTest, GetClientFromVSCode) { + std::optional<ClientLauncher::Client> result = + ClientLauncher::GetClientFrom("vscode"); + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(ClientLauncher::VSCode, result.value()); +} + +TEST(ClientLauncherTest, GetClientFromVSCodeUpperCase) { + std::optional<ClientLauncher::Client> result = + ClientLauncher::GetClientFrom("VSCODE"); + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(ClientLauncher::VSCode, result.value()); +} + +TEST(ClientLauncherTest, GetClientFromVSCodeMixedCase) { + std::optional<ClientLauncher::Client> result = + ClientLauncher::GetClientFrom("VSCode"); + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(ClientLauncher::VSCode, result.value()); +} + +TEST(ClientLauncherTest, GetClientFromInvalidString) { + std::optional<ClientLauncher::Client> result = + ClientLauncher::GetClientFrom("invalid"); + EXPECT_FALSE(result.has_value()); +} + +TEST(ClientLauncherTest, GetClientFromEmptyString) { + std::optional<ClientLauncher::Client> result = + ClientLauncher::GetClientFrom(""); + EXPECT_FALSE(result.has_value()); +} + +TEST(ClientLauncherTest, URLEncode) { + EXPECT_EQ("", VSCodeLauncher::URLEncode("")); + EXPECT_EQ( + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~", + VSCodeLauncher::URLEncode("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRST" + "UVWXYZ0123456789-_.~")); + EXPECT_EQ("hello%20world", VSCodeLauncher::URLEncode("hello world")); + EXPECT_EQ("hello%21%40%23%24", VSCodeLauncher::URLEncode("hello!@#$")); + EXPECT_EQ("%2Fpath%2Fto%2Ffile", VSCodeLauncher::URLEncode("/path/to/file")); + EXPECT_EQ("key%3Dvalue%26key2%3Dvalue2", + VSCodeLauncher::URLEncode("key=value&key2=value2")); + EXPECT_EQ("100%25complete", VSCodeLauncher::URLEncode("100%complete")); + EXPECT_EQ("file_name%20with%20spaces%20%26%20special%21.txt", + VSCodeLauncher::URLEncode("file_name with spaces & special!.txt")); + EXPECT_EQ("%00%01%02", + VSCodeLauncher::URLEncode(llvm::StringRef("\x00\x01\x02", 3))); + EXPECT_EQ("test-file_name.txt~", + VSCodeLauncher::URLEncode("test-file_name.txt~")); + + // UTF-8 encoded characters should be percent-encoded byte by byte. + EXPECT_EQ("%C3%A9", VSCodeLauncher::URLEncode("é")); +} diff --git a/lldb/unittests/DAP/DAPLogTest.cpp b/lldb/unittests/DAP/DAPLogTest.cpp new file mode 100644 index 0000000..2756e77 --- /dev/null +++ b/lldb/unittests/DAP/DAPLogTest.cpp @@ -0,0 +1,53 @@ +//===----------------------------------------------------------------------===// +// +// 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 "DAPLog.h" +#include "llvm/Support/raw_ostream.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +using namespace lldb_dap; +using namespace llvm; +using namespace testing; + +static llvm::StringRef last_line(llvm::StringRef str) { + size_t index = str.find_last_of('\n', str.size() - 1); + if (index == llvm::StringRef::npos) + return str; + return str.substr(index + 1); +} + +#define TIMESTAMP_PATTERN "\\[[0-9]{2}:[0-9]{2}:[0-9]{2}\\.[0-9]{3}\\] " + +TEST(DAPLog, Emit) { + Log::Mutex mux; + std::string outs; + raw_string_ostream os(outs); + Log log(os, mux); + Log inner_log = log.WithPrefix("my_prefix:"); + + log.Emit("Hi"); + EXPECT_THAT(last_line(outs), MatchesRegex(TIMESTAMP_PATTERN "Hi\n")); + + inner_log.Emit("foobar"); + EXPECT_THAT(last_line(outs), + MatchesRegex(TIMESTAMP_PATTERN "my_prefix: foobar\n")); + + log.Emit("Hello from a file/line.", "file.cpp", 42); + EXPECT_THAT( + last_line(outs), + MatchesRegex(TIMESTAMP_PATTERN "file.cpp:42 Hello from a file/line.\n")); + + inner_log.Emit("Hello from a file/line.", "file.cpp", 42); + EXPECT_THAT(last_line(outs), + MatchesRegex(TIMESTAMP_PATTERN + "file.cpp:42 my_prefix: Hello from a file/line.\n")); + + log.WithPrefix("a").WithPrefix("b").WithPrefix("c").Emit("msg"); + EXPECT_THAT(last_line(outs), MatchesRegex(TIMESTAMP_PATTERN "a b c msg\n")); +} diff --git a/lldb/unittests/DAP/DAPSessionManagerTest.cpp b/lldb/unittests/DAP/DAPSessionManagerTest.cpp new file mode 100644 index 0000000..b840d31 --- /dev/null +++ b/lldb/unittests/DAP/DAPSessionManagerTest.cpp @@ -0,0 +1,103 @@ +//===-- DAPSessionManagerTest.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 "DAPSessionManager.h" +#include "TestBase.h" +#include "lldb/API/SBDebugger.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +using namespace lldb_dap; +using namespace lldb; +using namespace lldb_dap_tests; + +class DAPSessionManagerTest : public DAPTestBase {}; + +TEST_F(DAPSessionManagerTest, GetInstanceReturnsSameSingleton) { + DAPSessionManager &instance1 = DAPSessionManager::GetInstance(); + DAPSessionManager &instance2 = DAPSessionManager::GetInstance(); + + EXPECT_EQ(&instance1, &instance2); +} + +// UnregisterSession uses std::notify_all_at_thread_exit, so it must be called +// from a separate thread to properly release the mutex on thread exit. +TEST_F(DAPSessionManagerTest, RegisterAndUnregisterSession) { + DAPSessionManager &manager = DAPSessionManager::GetInstance(); + + // Initially not registered. + std::vector<DAP *> sessions_before = manager.GetActiveSessions(); + EXPECT_EQ( + std::count(sessions_before.begin(), sessions_before.end(), dap.get()), 0); + + manager.RegisterSession(&loop, dap.get()); + + // Should be in active sessions after registration. + std::vector<DAP *> sessions_after = manager.GetActiveSessions(); + EXPECT_EQ(std::count(sessions_after.begin(), sessions_after.end(), dap.get()), + 1); + + // Unregister. + std::thread unregister_thread([&]() { manager.UnregisterSession(&loop); }); + + unregister_thread.join(); + + // There should no longer be active sessions. + std::vector<DAP *> sessions_final = manager.GetActiveSessions(); + EXPECT_EQ(std::count(sessions_final.begin(), sessions_final.end(), dap.get()), + 0); +} + +TEST_F(DAPSessionManagerTest, DisconnectAllSessions) { + DAPSessionManager &manager = DAPSessionManager::GetInstance(); + + manager.RegisterSession(&loop, dap.get()); + + std::vector<DAP *> sessions = manager.GetActiveSessions(); + EXPECT_EQ(std::count(sessions.begin(), sessions.end(), dap.get()), 1); + + manager.DisconnectAllSessions(); + + // DisconnectAllSessions shutdown but doesn't wait for + // sessions to complete or remove them from the active sessions map. + sessions = manager.GetActiveSessions(); + EXPECT_EQ(std::count(sessions.begin(), sessions.end(), dap.get()), 1); + + std::thread unregister_thread([&]() { manager.UnregisterSession(&loop); }); + unregister_thread.join(); +} + +TEST_F(DAPSessionManagerTest, WaitForAllSessionsToDisconnect) { + DAPSessionManager &manager = DAPSessionManager::GetInstance(); + + manager.RegisterSession(&loop, dap.get()); + + std::vector<DAP *> sessions = manager.GetActiveSessions(); + EXPECT_EQ(std::count(sessions.begin(), sessions.end(), dap.get()), 1); + + // Unregister after a delay to test blocking behavior. + std::thread unregister_thread([&]() { + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + manager.UnregisterSession(&loop); + }); + + // WaitForAllSessionsToDisconnect should block until unregistered. + auto start = std::chrono::steady_clock::now(); + llvm::Error err = manager.WaitForAllSessionsToDisconnect(); + EXPECT_FALSE(err); + auto duration = std::chrono::steady_clock::now() - start; + + // Verify it waited at least 100ms. + EXPECT_GE(duration, std::chrono::milliseconds(100)); + + // Session should be unregistered now. + sessions = manager.GetActiveSessions(); + EXPECT_EQ(std::count(sessions.begin(), sessions.end(), dap.get()), 0); + + unregister_thread.join(); +} diff --git a/lldb/unittests/DAP/ProtocolRequestsTest.cpp b/lldb/unittests/DAP/ProtocolRequestsTest.cpp new file mode 100644 index 0000000..c830690 --- /dev/null +++ b/lldb/unittests/DAP/ProtocolRequestsTest.cpp @@ -0,0 +1,195 @@ +//===-- ProtocolRequestsTest.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 "Protocol/ProtocolRequests.h" +#include "Protocol/ProtocolTypes.h" +#include "TestingSupport/TestUtilities.h" +#include "llvm/Testing/Support/Error.h" +#include <gtest/gtest.h> + +using namespace llvm; +using namespace lldb_dap::protocol; +using lldb_private::PrettyPrint; +using llvm::json::parse; + +TEST(ProtocolRequestsTest, ExceptionInfoArguments) { + llvm::Expected<ExceptionInfoArguments> expected = + parse<ExceptionInfoArguments>(R"({ + "threadId": 3434 + })"); + ASSERT_THAT_EXPECTED(expected, llvm::Succeeded()); + EXPECT_EQ(expected->threadId, 3434U); + + // Check required keys; + EXPECT_THAT_EXPECTED(parse<ExceptionInfoArguments>(R"({})"), + FailedWithMessage("missing value at (root).threadId")); + + EXPECT_THAT_EXPECTED(parse<ExceptionInfoArguments>(R"({"id": 10})"), + FailedWithMessage("missing value at (root).threadId")); +} + +TEST(ProtocolRequestsTest, ExceptionInfoResponseBody) { + ExceptionInfoResponseBody body; + body.exceptionId = "signal"; + body.breakMode = eExceptionBreakModeAlways; + + // Check required keys. + Expected<json::Value> expected = parse( + R"({ + "exceptionId": "signal", + "breakMode": "always" + })"); + + ASSERT_THAT_EXPECTED(expected, llvm::Succeeded()); + EXPECT_EQ(PrettyPrint(*expected), PrettyPrint(body)); + + // Check optional keys. + body.description = "SIGNAL SIGWINCH"; + body.breakMode = eExceptionBreakModeNever; + body.details = ExceptionDetails{}; + body.details->message = "some message"; + + Expected<json::Value> expected_opt = parse( + R"({ + "exceptionId": "signal", + "description": "SIGNAL SIGWINCH", + "breakMode": "never", + "details": { + "message": "some message" + } + })"); + + ASSERT_THAT_EXPECTED(expected_opt, llvm::Succeeded()); + EXPECT_EQ(PrettyPrint(*expected_opt), PrettyPrint(body)); +} + +TEST(ProtocolRequestsTest, EvaluateArguments) { + llvm::Expected<EvaluateArguments> expected = parse<EvaluateArguments>(R"({ + "expression": "hello world", + "context": "repl" + })"); + ASSERT_THAT_EXPECTED(expected, llvm::Succeeded()); + EXPECT_EQ(expected->expression, "hello world"); + EXPECT_EQ(expected->context, eEvaluateContextRepl); + + // Check required keys. + EXPECT_THAT_EXPECTED(parse<EvaluateArguments>(R"({})"), + FailedWithMessage("missing value at (root).expression")); +} + +TEST(ProtocolRequestsTest, EvaluateResponseBody) { + EvaluateResponseBody body; + body.result = "hello world"; + body.variablesReference = 7; + + // Check required keys. + Expected<json::Value> expected = parse(R"({ + "result": "hello world", + "variablesReference": 7 + })"); + + ASSERT_THAT_EXPECTED(expected, llvm::Succeeded()); + EXPECT_EQ(PrettyPrint(*expected), PrettyPrint(body)); + + // Check optional keys. + body.result = "'abc'"; + body.type = "string"; + body.variablesReference = 42; + body.namedVariables = 1; + body.indexedVariables = 2; + body.memoryReference = "0x123"; + body.valueLocationReference = 22; + + Expected<json::Value> expected_opt = parse(R"({ + "result": "'abc'", + "type": "string", + "variablesReference": 42, + "namedVariables": 1, + "indexedVariables": 2, + "memoryReference": "0x123", + "valueLocationReference": 22 + })"); + + ASSERT_THAT_EXPECTED(expected_opt, llvm::Succeeded()); + EXPECT_EQ(PrettyPrint(*expected_opt), PrettyPrint(body)); +} + +TEST(ProtocolRequestsTest, InitializeRequestArguments) { + llvm::Expected<InitializeRequestArguments> expected = + parse<InitializeRequestArguments>(R"({"adapterID": "myid"})"); + ASSERT_THAT_EXPECTED(expected, llvm::Succeeded()); + EXPECT_EQ(expected->adapterID, "myid"); + + // Check optional keys. + expected = parse<InitializeRequestArguments>(R"({ + "adapterID": "myid", + "clientID": "myclientid", + "clientName": "lldb-dap-unit-tests", + "locale": "en-US", + "linesStartAt1": true, + "columnsStartAt1": true, + "pathFormat": "uri", + "supportsVariableType": true, + "supportsVariablePaging": true, + "supportsRunInTerminalRequest": true, + "supportsMemoryReferences": true, + "supportsProgressReporting": true, + "supportsInvalidatedEvent": true, + "supportsMemoryEvent": true, + "supportsArgsCanBeInterpretedByShell": true, + "supportsStartDebuggingRequest": true, + "supportsANSIStyling": true + })"); + ASSERT_THAT_EXPECTED(expected, llvm::Succeeded()); + EXPECT_EQ(expected->adapterID, "myid"); + EXPECT_EQ(expected->clientID, "myclientid"); + EXPECT_EQ(expected->clientName, "lldb-dap-unit-tests"); + EXPECT_EQ(expected->locale, "en-US"); + EXPECT_EQ(expected->linesStartAt1, true); + EXPECT_EQ(expected->columnsStartAt1, true); + EXPECT_EQ(expected->pathFormat, ePathFormatURI); + EXPECT_EQ(expected->supportedFeatures.contains(eClientFeatureVariableType), + true); + EXPECT_EQ( + expected->supportedFeatures.contains(eClientFeatureRunInTerminalRequest), + true); + EXPECT_EQ( + expected->supportedFeatures.contains(eClientFeatureMemoryReferences), + true); + EXPECT_EQ( + expected->supportedFeatures.contains(eClientFeatureProgressReporting), + true); + EXPECT_EQ( + expected->supportedFeatures.contains(eClientFeatureInvalidatedEvent), + true); + EXPECT_EQ(expected->supportedFeatures.contains(eClientFeatureMemoryEvent), + true); + EXPECT_EQ(expected->supportedFeatures.contains( + eClientFeatureArgsCanBeInterpretedByShell), + true); + EXPECT_EQ( + expected->supportedFeatures.contains(eClientFeatureStartDebuggingRequest), + true); + EXPECT_EQ(expected->supportedFeatures.contains(eClientFeatureANSIStyling), + true); + + // Check required keys. + EXPECT_THAT_EXPECTED(parse<InitializeRequestArguments>(R"({})"), + FailedWithMessage("missing value at (root).adapterID")); +} + +TEST(ProtocolRequestsTest, PauseRequestArguments) { + llvm::Expected<PauseArguments> expected = + parse<PauseArguments>(R"({"threadId": 123})"); + ASSERT_THAT_EXPECTED(expected, llvm::Succeeded()); + EXPECT_EQ(expected->threadId, 123U); + + // Check required keys. + EXPECT_THAT_EXPECTED(parse<PauseArguments>(R"({})"), + FailedWithMessage("missing value at (root).threadId")); +} diff --git a/lldb/unittests/DAP/ProtocolTypesTest.cpp b/lldb/unittests/DAP/ProtocolTypesTest.cpp index 8170abd..6a4620a 100644 --- a/lldb/unittests/DAP/ProtocolTypesTest.cpp +++ b/lldb/unittests/DAP/ProtocolTypesTest.cpp @@ -1129,3 +1129,50 @@ TEST(ProtocolTypesTest, DataBreakpointInfoArguments) { EXPECT_THAT_EXPECTED(parse<DataBreakpointInfoArguments>(R"({"name":"data"})"), llvm::Succeeded()); } + +TEST(ProtocolTypesTest, ExceptionBreakMode) { + const std::vector<std::pair<ExceptionBreakMode, llvm::StringRef>> test_cases = + {{ExceptionBreakMode::eExceptionBreakModeAlways, "always"}, + {ExceptionBreakMode::eExceptionBreakModeNever, "never"}, + {ExceptionBreakMode::eExceptionBreakModeUnhandled, "unhandled"}, + {ExceptionBreakMode::eExceptionBreakModeUserUnhandled, "userUnhandled"}}; + + for (const auto [value, expected] : test_cases) { + json::Value const serialized = toJSON(value); + ASSERT_EQ(serialized.kind(), llvm::json::Value::Kind::String); + EXPECT_EQ(serialized.getAsString(), expected); + } +} + +TEST(ProtocolTypesTest, ExceptionDetails) { + ExceptionDetails details; + + // Check required keys. + Expected<json::Value> expected = parse(R"({})"); + ASSERT_THAT_EXPECTED(expected, llvm::Succeeded()); + EXPECT_EQ(pp(*expected), pp(details)); + + // Check optional keys. + details.message = "SIGABRT exception"; + details.typeName = "signal"; + details.fullTypeName = "SIGABRT"; + details.evaluateName = "process handle SIGABRT"; + details.stackTrace = "some stacktrace"; + ExceptionDetails inner_details; + inner_details.message = "inner message"; + details.innerException = {std::move(inner_details)}; + + Expected<json::Value> expected_opt = parse(R"({ + "message": "SIGABRT exception", + "typeName": "signal", + "fullTypeName": "SIGABRT", + "evaluateName": "process handle SIGABRT", + "stackTrace": "some stacktrace", + "innerException": [{ + "message": "inner message" + }] + })"); + + ASSERT_THAT_EXPECTED(expected_opt, llvm::Succeeded()); + EXPECT_EQ(pp(*expected_opt), pp(details)); +} diff --git a/lldb/unittests/DAP/TestBase.cpp b/lldb/unittests/DAP/TestBase.cpp index 8cb4599..e57963e 100644 --- a/lldb/unittests/DAP/TestBase.cpp +++ b/lldb/unittests/DAP/TestBase.cpp @@ -7,7 +7,10 @@ //===----------------------------------------------------------------------===// #include "TestBase.h" +#include "DAP.h" #include "DAPLog.h" +#include "Handler/RequestHandler.h" +#include "Handler/ResponseHandler.h" #include "TestingSupport/TestUtilities.h" #include "lldb/API/SBDefines.h" #include "lldb/API/SBStructuredData.h" @@ -19,7 +22,6 @@ #include "gtest/gtest.h" #include <cstdio> #include <memory> -#include <system_error> using namespace llvm; using namespace lldb; @@ -35,10 +37,9 @@ using lldb_private::Pipe; void TransportBase::SetUp() { std::tie(to_client, to_server) = TestDAPTransport::createPair(); - std::error_code EC; - log = std::make_unique<Log>("-", EC); + log = std::make_unique<Log>(llvm::outs(), log_mutex); dap = std::make_unique<DAP>( - /*log=*/log.get(), + /*log=*/*log, /*default_repl_mode=*/ReplMode::Auto, /*pre_init_commands=*/std::vector<std::string>(), /*no_lldbinit=*/false, @@ -72,6 +73,7 @@ void DAPTestBase::TearDown() { void DAPTestBase::SetUpTestSuite() { lldb::SBError error = SBDebugger::InitializeWithErrorHandling(); + EXPECT_TRUE(error.IsValid()); EXPECT_TRUE(error.Success()); } void DAPTestBase::TeatUpTestSuite() { SBDebugger::Terminate(); } diff --git a/lldb/unittests/DAP/TestBase.h b/lldb/unittests/DAP/TestBase.h index c32f3a7..f1c7e6b 100644 --- a/lldb/unittests/DAP/TestBase.h +++ b/lldb/unittests/DAP/TestBase.h @@ -8,6 +8,8 @@ #include "DAP.h" #include "DAPLog.h" +#include "Handler/RequestHandler.h" +#include "Handler/ResponseHandler.h" #include "Protocol/ProtocolBase.h" #include "TestingSupport/Host/JSONTransportTestUtilities.h" #include "TestingSupport/SubsystemRAII.h" @@ -60,6 +62,7 @@ protected: lldb_private::MainLoop::ReadHandleUP handles[2]; std::unique_ptr<lldb_dap::Log> log; + lldb_dap::Log::Mutex log_mutex; std::unique_ptr<TestDAPTransport> to_client; MockMessageHandler<lldb_dap::ProtocolDescriptor> client; diff --git a/lldb/unittests/Editline/EditlineTest.cpp b/lldb/unittests/Editline/EditlineTest.cpp index 2875f4e..2afc336 100644 --- a/lldb/unittests/Editline/EditlineTest.cpp +++ b/lldb/unittests/Editline/EditlineTest.cpp @@ -9,6 +9,8 @@ #include "lldb/Host/Config.h" #include "lldb/Host/File.h" #include "lldb/Host/HostInfo.h" +#include "lldb/lldb-forward.h" +#include "llvm/Testing/Support/Error.h" #if LLDB_ENABLE_LIBEDIT @@ -25,7 +27,6 @@ #include "TestingSupport/SubsystemRAII.h" #include "lldb/Host/Editline.h" #include "lldb/Host/FileSystem.h" -#include "lldb/Host/Pipe.h" #include "lldb/Host/PseudoTerminal.h" #include "lldb/Host/StreamFile.h" #include "lldb/Utility/Status.h" @@ -37,27 +38,6 @@ namespace { const size_t TIMEOUT_MILLIS = 5000; } -class FilePointer { -public: - FilePointer() = delete; - - FilePointer(const FilePointer &) = delete; - - FilePointer(FILE *file_p) : _file_p(file_p) {} - - ~FilePointer() { - if (_file_p != nullptr) { - const int close_result = fclose(_file_p); - EXPECT_EQ(0, close_result); - } - } - - operator FILE *() { return _file_p; } - -private: - FILE *_file_p; -}; - /** Wraps an Editline class, providing a simple way to feed input (as if from the keyboard) and receive output from Editline. @@ -90,44 +70,39 @@ private: std::recursive_mutex output_mutex; std::unique_ptr<lldb_private::Editline> _editline_sp; - PseudoTerminal _pty; - int _pty_primary_fd = -1; - int _pty_secondary_fd = -1; - - std::unique_ptr<FilePointer> _el_secondary_file; + lldb::FileSP _el_primary_file; + lldb::FileSP _el_secondary_file; }; -EditlineAdapter::EditlineAdapter() - : _editline_sp(), _pty(), _el_secondary_file() { +EditlineAdapter::EditlineAdapter() : _editline_sp(), _el_secondary_file() { lldb_private::Status error; + PseudoTerminal pty; // Open the first primary pty available. - EXPECT_THAT_ERROR(_pty.OpenFirstAvailablePrimary(O_RDWR), llvm::Succeeded()); + EXPECT_THAT_ERROR(pty.OpenFirstAvailablePrimary(O_RDWR), llvm::Succeeded()); + // Open the corresponding secondary pty. + EXPECT_THAT_ERROR(pty.OpenSecondary(O_RDWR), llvm::Succeeded()); // Grab the primary fd. This is a file descriptor we will: // (1) write to when we want to send input to editline. // (2) read from when we want to see what editline sends back. - _pty_primary_fd = _pty.GetPrimaryFileDescriptor(); + _el_primary_file.reset( + new NativeFile(pty.ReleasePrimaryFileDescriptor(), + lldb_private::NativeFile::eOpenOptionReadWrite, true)); - // Open the corresponding secondary pty. - EXPECT_THAT_ERROR(_pty.OpenSecondary(O_RDWR), llvm::Succeeded()); - _pty_secondary_fd = _pty.GetSecondaryFileDescriptor(); - - _el_secondary_file.reset(new FilePointer(fdopen(_pty_secondary_fd, "rw"))); - EXPECT_FALSE(nullptr == *_el_secondary_file); - if (*_el_secondary_file == nullptr) - return; + _el_secondary_file.reset( + new NativeFile(pty.ReleaseSecondaryFileDescriptor(), + lldb_private::NativeFile::eOpenOptionReadWrite, true)); lldb::LockableStreamFileSP output_stream_sp = - std::make_shared<LockableStreamFile>(*_el_secondary_file, - NativeFile::Unowned, output_mutex); + std::make_shared<LockableStreamFile>(_el_secondary_file, output_mutex); lldb::LockableStreamFileSP error_stream_sp = - std::make_shared<LockableStreamFile>(*_el_secondary_file, - NativeFile::Unowned, output_mutex); + std::make_shared<LockableStreamFile>(_el_secondary_file, output_mutex); // Create an Editline instance. _editline_sp.reset(new lldb_private::Editline( - "gtest editor", *_el_secondary_file, output_stream_sp, error_stream_sp, + "gtest editor", _el_secondary_file->GetStream(), output_stream_sp, + error_stream_sp, /*color=*/false)); _editline_sp->SetPrompt("> "); @@ -140,7 +115,7 @@ EditlineAdapter::EditlineAdapter() void EditlineAdapter::CloseInput() { if (_el_secondary_file != nullptr) - _el_secondary_file.reset(nullptr); + _el_secondary_file->Close(); } bool EditlineAdapter::SendLine(const std::string &line) { @@ -148,19 +123,14 @@ bool EditlineAdapter::SendLine(const std::string &line) { if (!IsValid()) return false; + std::string out = line + "\n"; + // Write the line out to the pipe connected to editline's input. - ssize_t input_bytes_written = - ::write(_pty_primary_fd, line.c_str(), - line.length() * sizeof(std::string::value_type)); - - const char *eoln = "\n"; - const size_t eoln_length = strlen(eoln); - input_bytes_written = - ::write(_pty_primary_fd, eoln, eoln_length * sizeof(char)); - - EXPECT_NE(-1, input_bytes_written) << strerror(errno); - EXPECT_EQ(eoln_length * sizeof(char), size_t(input_bytes_written)); - return eoln_length * sizeof(char) == size_t(input_bytes_written); + size_t num_bytes = out.length() * sizeof(std::string::value_type); + EXPECT_THAT_ERROR(_el_primary_file->Write(out.c_str(), num_bytes).takeError(), + llvm::Succeeded()); + EXPECT_EQ(num_bytes, out.length() * sizeof(std::string::value_type)); + return true; } bool EditlineAdapter::SendLines(const std::vector<std::string> &lines) { @@ -215,7 +185,7 @@ bool EditlineAdapter::IsInputComplete(lldb_private::Editline *editline, } void EditlineAdapter::ConsumeAllOutput() { - FilePointer output_file(fdopen(_pty_primary_fd, "r")); + FILE *output_file = _el_primary_file->GetStream(); int ch; while ((ch = fgetc(output_file)) != EOF) { diff --git a/lldb/unittests/Expression/CMakeLists.txt b/lldb/unittests/Expression/CMakeLists.txt index 2600557..0e0b002 100644 --- a/lldb/unittests/Expression/CMakeLists.txt +++ b/lldb/unittests/Expression/CMakeLists.txt @@ -10,6 +10,7 @@ add_lldb_unittest(ExpressionTests DWARFExpressionTest.cpp CppModuleConfigurationTest.cpp ExpressionTest.cpp + ValueMatcher.cpp LINK_COMPONENTS Support diff --git a/lldb/unittests/Expression/ClangParserTest.cpp b/lldb/unittests/Expression/ClangParserTest.cpp index fab4487..c949026 100644 --- a/lldb/unittests/Expression/ClangParserTest.cpp +++ b/lldb/unittests/Expression/ClangParserTest.cpp @@ -8,7 +8,7 @@ #include "clang/Basic/Version.h" #include "clang/Config/config.h" -#include "clang/Driver/Driver.h" +#include "clang/Options/OptionUtils.h" #include "Plugins/ExpressionParser/Clang/ClangHost.h" #include "TestingSupport/SubsystemRAII.h" @@ -43,7 +43,7 @@ TEST_F(ClangHostTest, ComputeClangResourceDirectory) { std::string path_to_liblldb = "C:\\foo\\bar\\lib\\"; #endif std::string path_to_clang_dir = - clang::driver::Driver::GetResourcesPath(path_to_liblldb + "liblldb"); + clang::GetResourcesPath(path_to_liblldb + "liblldb"); llvm::SmallString<256> path_to_clang_lib_dir_real; llvm::sys::fs::real_path(path_to_clang_dir, path_to_clang_lib_dir_real); diff --git a/lldb/unittests/Expression/DWARFExpressionTest.cpp b/lldb/unittests/Expression/DWARFExpressionTest.cpp index 9d11060..f264fb3 100644 --- a/lldb/unittests/Expression/DWARFExpressionTest.cpp +++ b/lldb/unittests/Expression/DWARFExpressionTest.cpp @@ -5,8 +5,9 @@ // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// - #include "lldb/Expression/DWARFExpression.h" +#include "ValueMatcher.h" +#include <unordered_map> #ifdef ARCH_AARCH64 #include "Plugins/ABI/AArch64/ABISysV_arm64.h" #endif @@ -39,32 +40,128 @@ using namespace lldb_private; using namespace llvm::dwarf; namespace { -struct MockProcess : Process { - MockProcess(lldb::TargetSP target_sp, lldb::ListenerSP listener_sp) - : Process(target_sp, listener_sp) {} +/// A mock implementation of DWARFExpression::Delegate for testing. +/// This class provides default implementations of all delegate methods, +/// with the DWARF version being configurable via the constructor. +class MockDwarfDelegate : public DWARFExpression::Delegate { +public: + static constexpr uint16_t DEFAULT_DWARF_VERSION = 5; + static MockDwarfDelegate Dwarf5() { return MockDwarfDelegate(5); } + static MockDwarfDelegate Dwarf2() { return MockDwarfDelegate(2); } - llvm::StringRef GetPluginName() override { return "mock process"; } + MockDwarfDelegate() : MockDwarfDelegate(DEFAULT_DWARF_VERSION) {} + explicit MockDwarfDelegate(uint16_t version) : m_dwarf_version(version) {} - bool CanDebug(lldb::TargetSP target, bool plugin_specified_by_name) override { - return false; - }; + uint16_t GetVersion() const override { return m_dwarf_version; } - Status DoDestroy() override { return {}; } + dw_addr_t GetBaseAddress() const override { return 0; } - void RefreshStateAfterStop() override {} + uint8_t GetAddressByteSize() const override { return 4; } - bool DoUpdateThreadList(ThreadList &old_thread_list, - ThreadList &new_thread_list) override { + llvm::Expected<std::pair<uint64_t, bool>> + GetDIEBitSizeAndSign(uint64_t relative_die_offset) const override { + return llvm::createStringError(llvm::inconvertibleErrorCode(), + "GetDIEBitSizeAndSign not implemented"); + } + + dw_addr_t ReadAddressFromDebugAddrSection(uint32_t index) const override { + return 0; + } + + lldb::offset_t GetVendorDWARFOpcodeSize(const DataExtractor &data, + const lldb::offset_t data_offset, + const uint8_t op) const override { + return LLDB_INVALID_OFFSET; + } + + bool ParseVendorDWARFOpcode(uint8_t op, const DataExtractor &opcodes, + lldb::offset_t &offset, RegisterContext *reg_ctx, + lldb::RegisterKind reg_kind, + DWARFExpression::Stack &stack) const override { return false; + } + +private: + uint16_t m_dwarf_version; +}; + +/// Mock memory implementation for testing. +/// Stores predefined memory contents indexed by {address, size} pairs. +class MockMemory { +public: + /// Represents a memory read request with an address and size. + /// Used as a key in the memory map to look up predefined test data. + struct Request { + lldb::addr_t addr; + size_t size; + + bool operator==(const Request &other) const { + return addr == other.addr && size == other.size; + } + + /// Hash function for Request to enable its use in unordered_map. + struct Hash { + size_t operator()(const Request &req) const { + size_t h1 = std::hash<lldb::addr_t>{}(req.addr); + size_t h2 = std::hash<size_t>{}(req.size); + return h1 ^ (h2 << 1); + } + }; }; - size_t DoReadMemory(lldb::addr_t vm_addr, void *buf, size_t size, + typedef std::unordered_map<Request, std::vector<uint8_t>, Request::Hash> Map; + MockMemory() = default; + MockMemory(Map memory) : m_memory(std::move(memory)) { + // Make sure the requested memory size matches the returned value. + for ([[maybe_unused]] auto &[req, bytes] : m_memory) { + assert(bytes.size() == req.size); + } + } + + llvm::Expected<std::vector<uint8_t>> ReadMemory(lldb::addr_t addr, + size_t size) { + if (!m_memory.count({addr, size})) { + return llvm::createStringError( + llvm::inconvertibleErrorCode(), + "MockMemory::ReadMemory {address=0x%" PRIx64 ", size=%zu} not found", + addr, size); + } + return m_memory[{addr, size}]; + } + +private: + std::unordered_map<Request, std::vector<uint8_t>, Request::Hash> m_memory; +}; + +/// A Process whose `ReadMemory` override queries MockMemory. +struct MockProcess : Process { + using addr_t = lldb::addr_t; + + MockMemory m_memory; + + MockProcess(lldb::TargetSP target_sp, lldb::ListenerSP listener_sp, + MockMemory memory) + : Process(target_sp, listener_sp), m_memory(std::move(memory)) {} + size_t DoReadMemory(addr_t vm_addr, void *buf, size_t size, Status &error) override { - for (size_t i = 0; i < size; ++i) - ((char *)buf)[i] = (vm_addr + i) & 0xff; - error.Clear(); + auto expected_memory = m_memory.ReadMemory(vm_addr, size); + if (!expected_memory) { + error = Status::FromError(expected_memory.takeError()); + return 0; + } + assert(expected_memory->size() == size); + std::memcpy(buf, expected_memory->data(), expected_memory->size()); return size; } + size_t ReadMemory(addr_t addr, void *buf, size_t size, + Status &status) override { + return DoReadMemory(addr, buf, size, status); + } + bool CanDebug(lldb::TargetSP, bool) override { return true; } + Status DoDestroy() override { return Status(); } + llvm::StringRef GetPluginName() override { return ""; } + void RefreshStateAfterStop() override {} + bool DoUpdateThreadList(ThreadList &, ThreadList &) override { return false; } }; class MockThread : public Thread { @@ -135,40 +232,19 @@ private: }; } // namespace -static llvm::Expected<Scalar> Evaluate(llvm::ArrayRef<uint8_t> expr, - lldb::ModuleSP module_sp = {}, - DWARFUnit *unit = nullptr, - ExecutionContext *exe_ctx = nullptr, - RegisterContext *reg_ctx = nullptr) { - DataExtractor extractor(expr.data(), expr.size(), lldb::eByteOrderLittle, - /*addr_size*/ 4); - - llvm::Expected<Value> result = DWARFExpression::Evaluate( - exe_ctx, reg_ctx, module_sp, extractor, unit, lldb::eRegisterKindLLDB, - /*initial_value_ptr=*/nullptr, - /*object_address_ptr=*/nullptr); - if (!result) - return result.takeError(); - - switch (result->GetValueType()) { - case Value::ValueType::Scalar: - return result->GetScalar(); - case Value::ValueType::LoadAddress: - return LLDB_INVALID_ADDRESS; - case Value::ValueType::HostAddress: { - // Convert small buffers to scalars to simplify the tests. - DataBufferHeap &buf = result->GetBuffer(); - if (buf.GetByteSize() <= 8) { - uint64_t val = 0; - memcpy(&val, buf.GetBytes(), buf.GetByteSize()); - return Scalar(llvm::APInt(buf.GetByteSize() * 8, val, false)); - } - } - [[fallthrough]]; - default: - break; - } - return llvm::createStringError("unsupported value type"); +static llvm::Expected<Value> Evaluate(llvm::ArrayRef<uint8_t> expr, + lldb::ModuleSP module_sp = {}, + DWARFExpression::Delegate *unit = nullptr, + ExecutionContext *exe_ctx = nullptr, + RegisterContext *reg_ctx = nullptr) { + DataExtractor extractor( + expr.data(), expr.size(), lldb::eByteOrderLittle, + /*addr_size*/ exe_ctx ? exe_ctx->GetAddressByteSize() : 4); + + return DWARFExpression::Evaluate(exe_ctx, reg_ctx, module_sp, extractor, unit, + lldb::eRegisterKindLLDB, + /*initial_value_ptr=*/nullptr, + /*object_address_ptr=*/nullptr); } class DWARFExpressionTester : public YAMLModuleTester { @@ -177,18 +253,11 @@ public: : YAMLModuleTester(yaml_data, cu_index) {} using YAMLModuleTester::YAMLModuleTester; - llvm::Expected<Scalar> Eval(llvm::ArrayRef<uint8_t> expr) { + llvm::Expected<Value> Eval(llvm::ArrayRef<uint8_t> expr) { return ::Evaluate(expr, m_module_sp, m_dwarf_unit); } }; -/// Unfortunately Scalar's operator==() is really picky. -static Scalar GetScalar(unsigned bits, uint64_t value, bool sign) { - Scalar scalar(value); - scalar.TruncOrExtendTo(bits, sign); - return scalar; -} - /// This is needed for the tests that use a mock process. class DWARFExpressionMockProcessTest : public ::testing::Test { public: @@ -204,46 +273,23 @@ public: } }; -struct PlatformTargetDebugger { - lldb::PlatformSP platform_sp; - lldb::TargetSP target_sp; - lldb::DebuggerSP debugger_sp; -}; - -/// A helper function to create <Platform, Target, Debugger> objects with the -/// "aarch64-pc-linux" ArchSpec. -static PlatformTargetDebugger CreateTarget() { - ArchSpec arch("aarch64-pc-linux"); - Platform::SetHostPlatform( - platform_linux::PlatformLinux::CreateInstance(true, &arch)); - lldb::PlatformSP platform_sp; - lldb::TargetSP target_sp; - lldb::DebuggerSP debugger_sp = Debugger::CreateInstance(); - debugger_sp->GetTargetList().CreateTarget( - *debugger_sp, "", arch, eLoadDependentsNo, platform_sp, target_sp); - return PlatformTargetDebugger{platform_sp, target_sp, debugger_sp}; -} - -// NB: This class doesn't use the override keyword to avoid -// -Winconsistent-missing-override warnings from the compiler. The -// inconsistency comes from the overriding definitions in the MOCK_*** macros. +/// Mock target implementation for testing. +/// Provides predefined memory contents via MockMemory instead of reading from +/// a real process. class MockTarget : public Target { public: MockTarget(Debugger &debugger, const ArchSpec &target_arch, - const lldb::PlatformSP &platform_sp) - : Target(debugger, target_arch, platform_sp, true) {} - - MOCK_METHOD2(ReadMemory, - llvm::Expected<std::vector<uint8_t>>(lldb::addr_t addr, - size_t size)); + const lldb::PlatformSP &platform_sp, MockMemory memory) + : Target(debugger, target_arch, platform_sp, true), + m_memory(std::move(memory)) {} size_t ReadMemory(const Address &addr, void *dst, size_t dst_len, Status &error, bool force_live_memory = false, lldb::addr_t *load_addr_ptr = nullptr, - bool *did_read_live_memory = nullptr) /*override*/ { - auto expected_memory = this->ReadMemory(addr.GetOffset(), dst_len); + bool *did_read_live_memory = nullptr) override { + auto expected_memory = m_memory.ReadMemory(addr.GetOffset(), dst_len); if (!expected_memory) { - llvm::consumeError(expected_memory.takeError()); + error = Status::FromError(expected_memory.takeError()); return 0; } const size_t bytes_read = expected_memory->size(); @@ -251,52 +297,110 @@ public: std::memcpy(dst, expected_memory->data(), bytes_read); return bytes_read; } + +private: + MockMemory m_memory; }; +struct TestContext { + lldb::PlatformSP platform_sp; + lldb::TargetSP target_sp; + lldb::DebuggerSP debugger_sp; + lldb::ProcessSP process_sp; + lldb::ThreadSP thread_sp; + lldb::RegisterContextSP reg_ctx_sp; +}; + +/// A helper function to create TestContext objects with the +/// given triple, memory, and register contents. +static bool CreateTestContext(TestContext *ctx, llvm::StringRef triple, + std::optional<RegisterValue> reg_value = {}, + std::optional<MockMemory> process_memory = {}, + std::optional<MockMemory> target_memory = {}) { + ArchSpec arch(triple); + lldb::PlatformSP platform_sp = + platform_linux::PlatformLinux::CreateInstance(true, &arch); + Platform::SetHostPlatform(platform_sp); + lldb::TargetSP target_sp; + lldb::DebuggerSP debugger_sp = Debugger::CreateInstance(); + + Status status; + if (target_memory) + target_sp = std::make_shared<MockTarget>(*debugger_sp, arch, platform_sp, + std::move(*target_memory)); + else + status = debugger_sp->GetTargetList().CreateTarget( + *debugger_sp, "", arch, eLoadDependentsNo, platform_sp, target_sp); + + EXPECT_TRUE(status.Success()); + if (!status.Success()) + return false; + + lldb::ProcessSP process_sp; + if (!process_memory) + process_memory = MockMemory(); + process_sp = std::make_shared<MockProcess>( + target_sp, Listener::MakeListener("dummy"), std::move(*process_memory)); + + auto thread_sp = std::make_shared<MockThread>(*process_sp); + + process_sp->GetThreadList().AddThread(thread_sp); + + lldb::RegisterContextSP reg_ctx_sp; + if (reg_value) { + reg_ctx_sp = std::make_shared<MockRegisterContext>(*thread_sp, *reg_value); + thread_sp->SetRegisterContext(reg_ctx_sp); + } + + *ctx = TestContext{platform_sp, target_sp, debugger_sp, + process_sp, thread_sp, reg_ctx_sp}; + return true; +} + TEST(DWARFExpression, DW_OP_pick) { EXPECT_THAT_EXPECTED(Evaluate({DW_OP_lit1, DW_OP_lit0, DW_OP_pick, 0}), - llvm::HasValue(0)); + ExpectScalar(0)); EXPECT_THAT_EXPECTED(Evaluate({DW_OP_lit1, DW_OP_lit0, DW_OP_pick, 1}), - llvm::HasValue(1)); + ExpectScalar(1)); EXPECT_THAT_EXPECTED(Evaluate({DW_OP_lit1, DW_OP_lit0, DW_OP_pick, 2}), llvm::Failed()); } TEST(DWARFExpression, DW_OP_const) { // Extend to address size. - EXPECT_THAT_EXPECTED(Evaluate({DW_OP_const1u, 0x88}), llvm::HasValue(0x88)); + EXPECT_THAT_EXPECTED(Evaluate({DW_OP_const1u, 0x88}), ExpectScalar(0x88)); EXPECT_THAT_EXPECTED(Evaluate({DW_OP_const1s, 0x88}), - llvm::HasValue(0xffffff88)); + ExpectScalar(0xffffff88)); EXPECT_THAT_EXPECTED(Evaluate({DW_OP_const2u, 0x47, 0x88}), - llvm::HasValue(0x8847)); + ExpectScalar(0x8847)); EXPECT_THAT_EXPECTED(Evaluate({DW_OP_const2s, 0x47, 0x88}), - llvm::HasValue(0xffff8847)); + ExpectScalar(0xffff8847)); EXPECT_THAT_EXPECTED(Evaluate({DW_OP_const4u, 0x44, 0x42, 0x47, 0x88}), - llvm::HasValue(0x88474244)); + ExpectScalar(0x88474244)); EXPECT_THAT_EXPECTED(Evaluate({DW_OP_const4s, 0x44, 0x42, 0x47, 0x88}), - llvm::HasValue(0x88474244)); + ExpectScalar(0x88474244)); // Truncate to address size. EXPECT_THAT_EXPECTED( Evaluate({DW_OP_const8u, 0x00, 0x11, 0x22, 0x33, 0x44, 0x42, 0x47, 0x88}), - llvm::HasValue(0x33221100)); + ExpectScalar(0x33221100)); EXPECT_THAT_EXPECTED( Evaluate({DW_OP_const8s, 0x00, 0x11, 0x22, 0x33, 0x44, 0x42, 0x47, 0x88}), - llvm::HasValue(0x33221100)); + ExpectScalar(0x33221100)); // Don't truncate to address size for compatibility with clang (pr48087). EXPECT_THAT_EXPECTED( Evaluate({DW_OP_constu, 0x81, 0x82, 0x84, 0x88, 0x90, 0xa0, 0x40}), - llvm::HasValue(0x01010101010101)); + ExpectScalar(0x01010101010101)); EXPECT_THAT_EXPECTED( Evaluate({DW_OP_consts, 0x81, 0x82, 0x84, 0x88, 0x90, 0xa0, 0x40}), - llvm::HasValue(0xffff010101010101)); + ExpectScalar(0xffff010101010101)); } TEST(DWARFExpression, DW_OP_skip) { EXPECT_THAT_EXPECTED(Evaluate({DW_OP_const1u, 0x42, DW_OP_skip, 0x02, 0x00, DW_OP_const1u, 0xff}), - llvm::HasValue(0x42)); + ExpectScalar(0x42)); } TEST(DWARFExpression, DW_OP_bra) { @@ -309,7 +413,7 @@ TEST(DWARFExpression, DW_OP_bra) { DW_OP_const1u, 0xff, // push 0xff }), // clang-format on - llvm::HasValue(0x42)); + ExpectScalar(0x42)); EXPECT_THAT_ERROR(Evaluate({DW_OP_bra, 0x01, 0x00}).takeError(), llvm::Failed()); @@ -414,42 +518,42 @@ DWARF: EXPECT_THAT_EXPECTED( t.Eval({DW_OP_const4u, 0x11, 0x22, 0x33, 0x44, // DW_OP_convert, offs_uint32_t, DW_OP_stack_value}), - llvm::HasValue(GetScalar(64, 0x44332211, not_signed))); + ExpectScalar(64, 0x44332211, not_signed)); // Zero-extend to 64 bits. EXPECT_THAT_EXPECTED( t.Eval({DW_OP_const4u, 0x11, 0x22, 0x33, 0x44, // DW_OP_convert, offs_uint64_t, DW_OP_stack_value}), - llvm::HasValue(GetScalar(64, 0x44332211, not_signed))); + ExpectScalar(64, 0x44332211, not_signed)); // Sign-extend to 64 bits. EXPECT_THAT_EXPECTED( t.Eval({DW_OP_const4s, 0xcc, 0xdd, 0xee, 0xff, // DW_OP_convert, offs_sint64_t, DW_OP_stack_value}), - llvm::HasValue(GetScalar(64, 0xffffffffffeeddcc, is_signed))); + ExpectScalar(64, 0xffffffffffeeddcc, is_signed)); // Sign-extend, then truncate. EXPECT_THAT_EXPECTED( t.Eval({DW_OP_const4s, 0xcc, 0xdd, 0xee, 0xff, // DW_OP_convert, offs_sint64_t, // DW_OP_convert, offs_uint32_t, DW_OP_stack_value}), - llvm::HasValue(GetScalar(32, 0xffeeddcc, not_signed))); + ExpectScalar(32, 0xffeeddcc, not_signed)); // Truncate to default unspecified (pointer-sized) type. EXPECT_THAT_EXPECTED(t.Eval({DW_OP_const4s, 0xcc, 0xdd, 0xee, 0xff, // DW_OP_convert, offs_sint64_t, // DW_OP_convert, 0x00, DW_OP_stack_value}), - llvm::HasValue(GetScalar(32, 0xffeeddcc, not_signed))); + ExpectScalar(32, 0xffeeddcc, not_signed)); // Truncate to 8 bits. EXPECT_THAT_EXPECTED(t.Eval({DW_OP_const4s, 'A', 'B', 'C', 'D', DW_OP_convert, offs_uchar, DW_OP_stack_value}), - llvm::HasValue(GetScalar(8, 'A', not_signed))); + ExpectScalar(8, 'A', not_signed)); // Also truncate to 8 bits. EXPECT_THAT_EXPECTED(t.Eval({DW_OP_const4s, 'A', 'B', 'C', 'D', DW_OP_convert, offs_schar, DW_OP_stack_value}), - llvm::HasValue(GetScalar(8, 'A', is_signed))); + ExpectScalar(8, 'A', is_signed)); // // Errors. @@ -476,36 +580,41 @@ TEST(DWARFExpression, DW_OP_stack_value) { EXPECT_THAT_EXPECTED(Evaluate({DW_OP_stack_value}), llvm::Failed()); } +// This test shows that the dwarf version is used by the expression evaluation. +// Note that the different behavior tested here is not meant to imply that this +// is the correct interpretation of dwarf2 vs. dwarf5, but rather it was picked +// as an easy example that evaluates differently based on the dwarf version. +TEST(DWARFExpression, dwarf_version) { + MockDwarfDelegate dwarf2 = MockDwarfDelegate::Dwarf2(); + MockDwarfDelegate dwarf5 = MockDwarfDelegate::Dwarf5(); + + // In dwarf2 the constant on top of the stack is treated as a value. + EXPECT_THAT_EXPECTED(Evaluate({DW_OP_lit1}, {}, &dwarf2), ExpectScalar(1)); + + // In dwarf5 the constant on top of the stack is implicitly converted to an + // address. + EXPECT_THAT_EXPECTED(Evaluate({DW_OP_lit1}, {}, &dwarf5), + ExpectLoadAddress(1)); +} + TEST(DWARFExpression, DW_OP_piece) { EXPECT_THAT_EXPECTED(Evaluate({DW_OP_const2u, 0x11, 0x22, DW_OP_piece, 2, DW_OP_const2u, 0x33, 0x44, DW_OP_piece, 2}), - llvm::HasValue(GetScalar(32, 0x44332211, true))); + ExpectHostAddress({0x11, 0x22, 0x33, 0x44})); EXPECT_THAT_EXPECTED( Evaluate({DW_OP_piece, 1, DW_OP_const1u, 0xff, DW_OP_piece, 1}), // Note that the "00" should really be "undef", but we can't // represent that yet. - llvm::HasValue(GetScalar(16, 0xff00, true))); -} - -TEST(DWARFExpression, DW_OP_piece_host_address) { - static const uint8_t expr_data[] = {DW_OP_lit2, DW_OP_stack_value, - DW_OP_piece, 40}; - llvm::ArrayRef<uint8_t> expr(expr_data, sizeof(expr_data)); - DataExtractor extractor(expr.data(), expr.size(), lldb::eByteOrderLittle, 4); + ExpectHostAddress({0x00, 0xff})); // This tests if ap_int is extended to the right width. // expect 40*8 = 320 bits size. - llvm::Expected<Value> result = - DWARFExpression::Evaluate(nullptr, nullptr, nullptr, extractor, nullptr, - lldb::eRegisterKindDWARF, nullptr, nullptr); - ASSERT_THAT_EXPECTED(result, llvm::Succeeded()); - ASSERT_EQ(result->GetValueType(), Value::ValueType::HostAddress); - ASSERT_EQ(result->GetBuffer().GetByteSize(), 40ul); - const uint8_t *data = result->GetBuffer().GetBytes(); - ASSERT_EQ(data[0], 2); - for (int i = 1; i < 40; i++) { - ASSERT_EQ(data[i], 0); - } + std::vector<uint8_t> expected_host_buffer(40, 0); + expected_host_buffer[0] = 2; + + EXPECT_THAT_EXPECTED( + Evaluate({{DW_OP_lit2, DW_OP_stack_value, DW_OP_piece, 40}}), + ExpectHostAddress(expected_host_buffer)); } TEST(DWARFExpression, DW_OP_implicit_value) { @@ -513,7 +622,7 @@ TEST(DWARFExpression, DW_OP_implicit_value) { EXPECT_THAT_EXPECTED( Evaluate({DW_OP_implicit_value, bytes, 0x11, 0x22, 0x33, 0x44}), - llvm::HasValue(GetScalar(8 * bytes, 0x44332211, true))); + ExpectHostAddress({0x11, 0x22, 0x33, 0x44})); } TEST(DWARFExpression, DW_OP_unknown) { @@ -527,72 +636,36 @@ TEST_F(DWARFExpressionMockProcessTest, DW_OP_deref) { EXPECT_THAT_EXPECTED(Evaluate({DW_OP_lit0, DW_OP_deref}), llvm::Failed()); // Set up a mock process. - ArchSpec arch("i386-pc-linux"); - Platform::SetHostPlatform( - platform_linux::PlatformLinux::CreateInstance(true, &arch)); - lldb::DebuggerSP debugger_sp = Debugger::CreateInstance(); - ASSERT_TRUE(debugger_sp); - lldb::TargetSP target_sp; - lldb::PlatformSP platform_sp; - debugger_sp->GetTargetList().CreateTarget( - *debugger_sp, "", arch, eLoadDependentsNo, platform_sp, target_sp); - ASSERT_TRUE(target_sp); - ASSERT_TRUE(target_sp->GetArchitecture().IsValid()); - ASSERT_TRUE(platform_sp); - lldb::ListenerSP listener_sp(Listener::MakeListener("dummy")); - lldb::ProcessSP process_sp = - std::make_shared<MockProcess>(target_sp, listener_sp); - ASSERT_TRUE(process_sp); - - ExecutionContext exe_ctx(process_sp); + MockMemory::Map memory = { + {{0x4, 4}, {0x4, 0x5, 0x6, 0x7}}, + }; + TestContext test_ctx; + ASSERT_TRUE( + CreateTestContext(&test_ctx, "i386-pc-linux", {}, std::move(memory))); + + ExecutionContext exe_ctx(test_ctx.process_sp); // Implicit location: *0x4. EXPECT_THAT_EXPECTED( Evaluate({DW_OP_lit4, DW_OP_deref, DW_OP_stack_value}, {}, {}, &exe_ctx), - llvm::HasValue(GetScalar(32, 0x07060504, false))); + ExpectScalar(32, 0x07060504, false)); // Memory location: *(*0x4). - // Evaluate returns LLDB_INVALID_ADDRESS for all load addresses. EXPECT_THAT_EXPECTED(Evaluate({DW_OP_lit4, DW_OP_deref}, {}, {}, &exe_ctx), - llvm::HasValue(Scalar(LLDB_INVALID_ADDRESS))); + ExpectLoadAddress(0x07060504)); // Memory location: *0x4. - // Evaluate returns LLDB_INVALID_ADDRESS for all load addresses. EXPECT_THAT_EXPECTED(Evaluate({DW_OP_lit4}, {}, {}, &exe_ctx), - llvm::HasValue(Scalar(4))); - // Implicit location: *0x4. - // Evaluate returns LLDB_INVALID_ADDRESS for all load addresses. - EXPECT_THAT_EXPECTED( - Evaluate({DW_OP_lit4, DW_OP_deref, DW_OP_stack_value}, {}, {}, &exe_ctx), - llvm::HasValue(GetScalar(32, 0x07060504, false))); + ExpectScalar(Scalar(4))); } TEST_F(DWARFExpressionMockProcessTest, WASM_DW_OP_addr) { // Set up a wasm target - ArchSpec arch("wasm32-unknown-unknown-wasm"); - lldb::PlatformSP host_platform_sp = - platform_linux::PlatformLinux::CreateInstance(true, &arch); - ASSERT_TRUE(host_platform_sp); - Platform::SetHostPlatform(host_platform_sp); - lldb::DebuggerSP debugger_sp = Debugger::CreateInstance(); - ASSERT_TRUE(debugger_sp); - lldb::TargetSP target_sp; - lldb::PlatformSP platform_sp; - debugger_sp->GetTargetList().CreateTarget(*debugger_sp, "", arch, - lldb_private::eLoadDependentsNo, - platform_sp, target_sp); + TestContext test_ctx; + ASSERT_TRUE(CreateTestContext(&test_ctx, "wasm32-unknown-unknown-wasm")); - ExecutionContext exe_ctx(target_sp, false); + ExecutionContext exe_ctx(test_ctx.target_sp, false); // DW_OP_addr takes a single operand of address size width: - uint8_t expr[] = {DW_OP_addr, 0x40, 0x0, 0x0, 0x0}; - DataExtractor extractor(expr, sizeof(expr), lldb::eByteOrderLittle, - /*addr_size*/ 4); - - llvm::Expected<Value> result = DWARFExpression::Evaluate( - &exe_ctx, /*reg_ctx*/ nullptr, /*module_sp*/ {}, extractor, - /*unit*/ nullptr, lldb::eRegisterKindLLDB, - /*initial_value_ptr*/ nullptr, - /*object_address_ptr*/ nullptr); - - ASSERT_THAT_EXPECTED(result, llvm::Succeeded()); - ASSERT_EQ(result->GetValueType(), Value::ValueType::LoadAddress); + EXPECT_THAT_EXPECTED( + Evaluate({DW_OP_addr, 0x40, 0x0, 0x0, 0x0}, {}, {}, &exe_ctx), + ExpectLoadAddress(0x40)); } TEST_F(DWARFExpressionMockProcessTest, WASM_DW_OP_addr_index) { @@ -644,20 +717,9 @@ DWARF: dwarf_cu->ExtractDIEsIfNeeded(); // Set up a wasm target - ArchSpec arch("wasm32-unknown-unknown-wasm"); - lldb::PlatformSP host_platform_sp = - platform_linux::PlatformLinux::CreateInstance(true, &arch); - ASSERT_TRUE(host_platform_sp); - Platform::SetHostPlatform(host_platform_sp); - lldb::DebuggerSP debugger_sp = Debugger::CreateInstance(); - ASSERT_TRUE(debugger_sp); - lldb::TargetSP target_sp; - lldb::PlatformSP platform_sp; - debugger_sp->GetTargetList().CreateTarget(*debugger_sp, "", arch, - lldb_private::eLoadDependentsNo, - platform_sp, target_sp); - - ExecutionContext exe_ctx(target_sp, false); + TestContext test_ctx; + ASSERT_TRUE(CreateTestContext(&test_ctx, "wasm32-unknown-unknown-wasm")); + ExecutionContext exe_ctx(test_ctx.target_sp, false); auto evaluate = [&](DWARFExpression &expr) -> llvm::Expected<Value> { DataExtractor extractor; @@ -676,15 +738,11 @@ DWARF: DWARFExpression expr(extractor); llvm::Expected<Value> result = evaluate(expr); - ASSERT_THAT_EXPECTED(result, llvm::Succeeded()); - ASSERT_EQ(result->GetValueType(), Value::ValueType::LoadAddress); - ASSERT_EQ(result->GetScalar().UInt(), 0x5678u); + EXPECT_THAT_EXPECTED(result, ExpectLoadAddress(0x5678u)); ASSERT_TRUE(expr.Update_DW_OP_addr(dwarf_cu, 0xdeadbeef)); result = evaluate(expr); - ASSERT_THAT_EXPECTED(result, llvm::Succeeded()); - ASSERT_EQ(result->GetValueType(), Value::ValueType::LoadAddress); - ASSERT_EQ(result->GetScalar().UInt(), 0xdeadbeefu); + EXPECT_THAT_EXPECTED(result, ExpectLoadAddress(0xdeadbeefu)); } class CustomSymbolFileDWARF : public SymbolFileDWARF { @@ -778,11 +836,12 @@ static auto testExpressionVendorExtensions(lldb::ModuleSP module_sp, RegisterContext *reg_ctx) { // Test that expression extensions can be evaluated, for example // DW_OP_WASM_location which is not currently handled by DWARFExpression: - EXPECT_THAT_EXPECTED(Evaluate({DW_OP_WASM_location, 0x03, // WASM_GLOBAL:0x03 - 0x04, 0x00, 0x00, // index:u32 - 0x00, DW_OP_stack_value}, - module_sp, &dwarf_unit, nullptr, reg_ctx), - llvm::HasValue(GetScalar(32, 42, false))); + EXPECT_THAT_EXPECTED( + Evaluate({DW_OP_WASM_location, 0x03, // WASM_GLOBAL:0x03 + 0x04, 0x00, 0x00, // index:u32 + 0x00, DW_OP_stack_value}, + module_sp, &dwarf_unit, nullptr, reg_ctx), + ExpectScalar(32, 42, false, Value::ContextType::RegisterInfo)); // Test that searches for opcodes work in the presence of extensions: uint8_t expr[] = {DW_OP_WASM_location, 0x03, 0x04, 0x00, 0x00, 0x00, @@ -883,28 +942,10 @@ Sections: subsystems; // Set up a wasm target. - ArchSpec arch("wasm32-unknown-unknown-wasm"); - lldb::PlatformSP host_platform_sp = - platform_linux::PlatformLinux::CreateInstance(true, &arch); - ASSERT_TRUE(host_platform_sp); - Platform::SetHostPlatform(host_platform_sp); - lldb::DebuggerSP debugger_sp = Debugger::CreateInstance(); - ASSERT_TRUE(debugger_sp); - lldb::TargetSP target_sp; - lldb::PlatformSP platform_sp; - debugger_sp->GetTargetList().CreateTarget(*debugger_sp, "", arch, - lldb_private::eLoadDependentsNo, - platform_sp, target_sp); - // Set up a mock process and thread. - lldb::ListenerSP listener_sp(Listener::MakeListener("dummy")); - lldb::ProcessSP process_sp = - std::make_shared<MockProcess>(target_sp, listener_sp); - ASSERT_TRUE(process_sp); - MockThread thread(*process_sp); + TestContext test_ctx; const uint32_t kExpectedValue = 42; - lldb::RegisterContextSP reg_ctx_sp = std::make_shared<MockRegisterContext>( - thread, RegisterValue(kExpectedValue)); - thread.SetRegisterContext(reg_ctx_sp); + ASSERT_TRUE(CreateTestContext(&test_ctx, "wasm32-unknown-unknown-wasm", + RegisterValue(kExpectedValue))); llvm::Expected<TestFile> file = TestFile::fromYaml(yamldata); EXPECT_THAT_EXPECTED(file, llvm::Succeeded()); @@ -913,7 +954,8 @@ Sections: SymbolFileWasm sym_file_wasm(obj_file_sp, nullptr); auto *dwarf_unit = sym_file_wasm.DebugInfo().GetUnitAtIndex(0); - testExpressionVendorExtensions(module_sp, *dwarf_unit, reg_ctx_sp.get()); + testExpressionVendorExtensions(module_sp, *dwarf_unit, + test_ctx.reg_ctx_sp.get()); } TEST(DWARFExpression, ExtensionsSplitSymbols) { @@ -1082,28 +1124,10 @@ Sections: subsystems; // Set up a wasm target. - ArchSpec arch("wasm32-unknown-unknown-wasm"); - lldb::PlatformSP host_platform_sp = - platform_linux::PlatformLinux::CreateInstance(true, &arch); - ASSERT_TRUE(host_platform_sp); - Platform::SetHostPlatform(host_platform_sp); - lldb::DebuggerSP debugger_sp = Debugger::CreateInstance(); - ASSERT_TRUE(debugger_sp); - lldb::TargetSP target_sp; - lldb::PlatformSP platform_sp; - debugger_sp->GetTargetList().CreateTarget(*debugger_sp, "", arch, - lldb_private::eLoadDependentsNo, - platform_sp, target_sp); - // Set up a mock process and thread. - lldb::ListenerSP listener_sp(Listener::MakeListener("dummy")); - lldb::ProcessSP process_sp = - std::make_shared<MockProcess>(target_sp, listener_sp); - ASSERT_TRUE(process_sp); - MockThread thread(*process_sp); + TestContext test_ctx; const uint32_t kExpectedValue = 42; - lldb::RegisterContextSP reg_ctx_sp = std::make_shared<MockRegisterContext>( - thread, RegisterValue(kExpectedValue)); - thread.SetRegisterContext(reg_ctx_sp); + ASSERT_TRUE(CreateTestContext(&test_ctx, "wasm32-unknown-unknown-wasm", + RegisterValue(kExpectedValue))); llvm::Expected<TestFile> skeleton_file = TestFile::fromYaml(skeleton_yamldata); @@ -1119,7 +1143,8 @@ Sections: SymbolFileWasm sym_file_wasm(obj_file_sp, nullptr); auto *dwarf_unit = sym_file_wasm.DebugInfo().GetUnitAtIndex(0); - testExpressionVendorExtensions(sym_module_sp, *dwarf_unit, reg_ctx_sp.get()); + testExpressionVendorExtensions(sym_module_sp, *dwarf_unit, + test_ctx.reg_ctx_sp.get()); } TEST_F(DWARFExpressionMockProcessTest, DW_OP_piece_file_addr) { @@ -1128,66 +1153,23 @@ TEST_F(DWARFExpressionMockProcessTest, DW_OP_piece_file_addr) { using ::testing::Return; // Set up a mock process. - ArchSpec arch("i386-pc-linux"); - Platform::SetHostPlatform( - platform_linux::PlatformLinux::CreateInstance(true, &arch)); - lldb::DebuggerSP debugger_sp = Debugger::CreateInstance(); - ASSERT_TRUE(debugger_sp); - lldb::PlatformSP platform_sp; - auto target_sp = - std::make_shared<MockTarget>(*debugger_sp, arch, platform_sp); - ASSERT_TRUE(target_sp); - ASSERT_TRUE(target_sp->GetArchitecture().IsValid()); - - EXPECT_CALL(*target_sp, ReadMemory(0x40, 1)) - .WillOnce(Return(ByMove(std::vector<uint8_t>{0x11}))); - EXPECT_CALL(*target_sp, ReadMemory(0x50, 1)) - .WillOnce(Return(ByMove(std::vector<uint8_t>{0x22}))); + TestContext test_ctx; + MockMemory::Map memory = { + {{0x40, 1}, {0x11}}, + {{0x50, 1}, {0x22}}, + }; + ASSERT_TRUE( + CreateTestContext(&test_ctx, "i386-pc-linux", {}, {}, std::move(memory))); + ASSERT_TRUE(test_ctx.target_sp->GetArchitecture().IsValid()); - ExecutionContext exe_ctx(static_cast<lldb::TargetSP>(target_sp), false); + ExecutionContext exe_ctx(test_ctx.target_sp, false); uint8_t expr[] = {DW_OP_addr, 0x40, 0x0, 0x0, 0x0, DW_OP_piece, 1, DW_OP_addr, 0x50, 0x0, 0x0, 0x0, DW_OP_piece, 1}; - DataExtractor extractor(expr, sizeof(expr), lldb::eByteOrderLittle, - /*addr_size=*/4); - llvm::Expected<Value> result = DWARFExpression::Evaluate( - &exe_ctx, /*reg_ctx=*/nullptr, /*module_sp=*/{}, extractor, - /*unit=*/nullptr, lldb::eRegisterKindLLDB, - /*initial_value_ptr=*/nullptr, - /*object_address_ptr=*/nullptr); - - ASSERT_THAT_EXPECTED(result, llvm::Succeeded()); - ASSERT_EQ(result->GetValueType(), Value::ValueType::HostAddress); - ASSERT_THAT(result->GetBuffer().GetData(), ElementsAre(0x11, 0x22)); + EXPECT_THAT_EXPECTED(Evaluate(expr, {}, {}, &exe_ctx), + ExpectHostAddress({0x11, 0x22})); } -/// A Process whose `ReadMemory` override queries a DenseMap. -struct MockProcessWithMemRead : Process { - using addr_t = lldb::addr_t; - - llvm::DenseMap<addr_t, addr_t> memory_map; - - MockProcessWithMemRead(lldb::TargetSP target_sp, lldb::ListenerSP listener_sp, - llvm::DenseMap<addr_t, addr_t> &&memory_map) - : Process(target_sp, listener_sp), memory_map(memory_map) {} - size_t DoReadMemory(addr_t vm_addr, void *buf, size_t size, - Status &error) override { - assert(memory_map.contains(vm_addr)); - assert(size == sizeof(addr_t)); - *reinterpret_cast<addr_t *>(buf) = memory_map[vm_addr]; - return sizeof(addr_t); - } - size_t ReadMemory(addr_t addr, void *buf, size_t size, - Status &status) override { - return DoReadMemory(addr, buf, size, status); - } - bool CanDebug(lldb::TargetSP, bool) override { return true; } - Status DoDestroy() override { return Status(); } - llvm::StringRef GetPluginName() override { return ""; } - void RefreshStateAfterStop() override {} - bool DoUpdateThreadList(ThreadList &, ThreadList &) override { return false; } -}; - class DWARFExpressionMockProcessTestWithAArch : public DWARFExpressionMockProcessTest { public: @@ -1213,43 +1195,129 @@ public: /// The expression DW_OP_breg22, 0, DW_OP_deref should produce that same value, /// without clearing the top byte 0xff. TEST_F(DWARFExpressionMockProcessTestWithAArch, DW_op_deref_no_ptr_fixing) { - llvm::DenseMap<lldb::addr_t, lldb::addr_t> memory; constexpr lldb::addr_t expected_value = ((0xffULL) << 56) | 0xabcdefULL; constexpr lldb::addr_t addr = 42; - memory[addr] = expected_value; + MockMemory::Map memory = { + {{addr, sizeof(addr)}, {0xef, 0xcd, 0xab, 0x00, 0x00, 0x00, 0x00, 0xff}}}; - PlatformTargetDebugger test_setup = CreateTarget(); - lldb::ProcessSP process_sp = std::make_shared<MockProcessWithMemRead>( - test_setup.target_sp, Listener::MakeListener("dummy"), std::move(memory)); - auto thread = std::make_shared<MockThread>(*process_sp); - lldb::RegisterContextSP reg_ctx_sp = - std::make_shared<MockRegisterContext>(*thread, RegisterValue(addr)); - thread->SetRegisterContext(reg_ctx_sp); - process_sp->GetThreadList().AddThread(thread); + TestContext test_ctx; + ASSERT_TRUE(CreateTestContext(&test_ctx, "aarch64-pc-linux", + RegisterValue(addr), std::move(memory))); auto evaluate_expr = [&](auto &expr_data) { - DataExtractor extractor(expr_data, sizeof(expr_data), - lldb::eByteOrderLittle, - /*addr_size*/ 8); - DWARFExpression expr(extractor); - - ExecutionContext exe_ctx(process_sp); - llvm::Expected<Value> result = DWARFExpression::Evaluate( - &exe_ctx, reg_ctx_sp.get(), /*module_sp*/ nullptr, extractor, - /*unit*/ nullptr, lldb::eRegisterKindLLDB, - /*initial_value_ptr=*/nullptr, - /*object_address_ptr=*/nullptr); - return result; + ExecutionContext exe_ctx(test_ctx.process_sp); + return Evaluate(expr_data, {}, {}, &exe_ctx, test_ctx.reg_ctx_sp.get()); }; uint8_t expr_reg[] = {DW_OP_breg22, 0}; llvm::Expected<Value> result_reg = evaluate_expr(expr_reg); - ASSERT_THAT_EXPECTED(result_reg, llvm::Succeeded()); - ASSERT_EQ(result_reg->GetValueType(), Value::ValueType::LoadAddress); - ASSERT_EQ(result_reg->GetScalar().ULongLong(), addr); + EXPECT_THAT_EXPECTED(result_reg, ExpectLoadAddress(addr)); uint8_t expr_deref[] = {DW_OP_breg22, 0, DW_OP_deref}; llvm::Expected<Value> result_deref = evaluate_expr(expr_deref); - ASSERT_THAT_EXPECTED(result_deref, llvm::Succeeded()); - ASSERT_EQ(result_deref->GetScalar().ULongLong(), expected_value); + EXPECT_THAT_EXPECTED(result_deref, ExpectLoadAddress(expected_value)); +} + +TEST_F(DWARFExpressionMockProcessTest, deref_register) { + TestContext test_ctx; + constexpr uint32_t reg_r0 = 0x504; + MockMemory::Map memory = { + {{0x004, 4}, {0x1, 0x2, 0x3, 0x4}}, + {{0x504, 4}, {0xa, 0xb, 0xc, 0xd}}, + {{0x505, 4}, {0x5, 0x6, 0x7, 0x8}}, + }; + ASSERT_TRUE(CreateTestContext(&test_ctx, "i386-pc-linux", + RegisterValue(reg_r0), memory, memory)); + + ExecutionContext exe_ctx(test_ctx.process_sp); + MockDwarfDelegate delegate = MockDwarfDelegate::Dwarf5(); + auto Eval = [&](llvm::ArrayRef<uint8_t> expr_data) { + ExecutionContext exe_ctx(test_ctx.process_sp); + return Evaluate(expr_data, {}, &delegate, &exe_ctx, + test_ctx.reg_ctx_sp.get()); + }; + + // Reads from the register r0. + // Sets the context to RegisterInfo so we know this is a register location. + EXPECT_THAT_EXPECTED(Eval({DW_OP_reg0}), + ExpectScalar(reg_r0, Value::ContextType::RegisterInfo)); + + // Reads from the location(register r0). + // Clears the context so we know this is a value not a location. + EXPECT_THAT_EXPECTED(Eval({DW_OP_reg0, DW_OP_deref}), + ExpectLoadAddress(reg_r0, Value::ContextType::Invalid)); + + // Reads from the location(register r0) and adds the value to the host buffer. + // The evaluator should implicitly convert it to a memory location when + // added to a composite value and should add the contents of memory[r0] + // to the host buffer. + EXPECT_THAT_EXPECTED(Eval({DW_OP_reg0, DW_OP_deref, DW_OP_piece, 4}), + ExpectHostAddress({0xa, 0xb, 0xc, 0xd})); + + // Reads from the location(register r0) and truncates the value to one byte. + // Clears the context so we know this is a value not a location. + EXPECT_THAT_EXPECTED( + Eval({DW_OP_reg0, DW_OP_deref_size, 1}), + ExpectLoadAddress(reg_r0 & 0xff, Value::ContextType::Invalid)); + + // Reads from the location(register r0) and truncates to one byte then adds + // the value to the host buffer. The evaluator should implicitly convert it to + // a memory location when added to a composite value and should add the + // contents of memory[r0 & 0xff] to the host buffer. + EXPECT_THAT_EXPECTED(Eval({DW_OP_reg0, DW_OP_deref_size, 1, DW_OP_piece, 4}), + ExpectHostAddress({0x1, 0x2, 0x3, 0x4})); + + // Reads from the register r0 + 1. + EXPECT_THAT_EXPECTED( + Eval({DW_OP_breg0, 1}), + ExpectLoadAddress(reg_r0 + 1, Value::ContextType::Invalid)); + + // Reads from address r0 + 1, which contains the bytes [5,6,7,8]. + EXPECT_THAT_EXPECTED( + Eval({DW_OP_breg0, 1, DW_OP_deref}), + ExpectLoadAddress(0x08070605, Value::ContextType::Invalid)); +} + +TEST_F(DWARFExpressionMockProcessTest, deref_implicit_value) { + TestContext test_ctx; + MockMemory::Map memory = { + {{0x4, 1}, {0x1}}, + {{0x4, 4}, {0x1, 0x2, 0x3, 0x4}}, + }; + ASSERT_TRUE(CreateTestContext(&test_ctx, "i386-pc-linux", {}, memory)); + + ExecutionContext exe_ctx(test_ctx.process_sp); + MockDwarfDelegate delegate = MockDwarfDelegate::Dwarf5(); + auto Eval = [&](llvm::ArrayRef<uint8_t> expr_data) { + ExecutionContext exe_ctx(test_ctx.process_sp); + return Evaluate(expr_data, {}, &delegate, &exe_ctx, + test_ctx.reg_ctx_sp.get()); + }; + + // Creates an implicit location with a value of 4. + EXPECT_THAT_EXPECTED(Eval({DW_OP_lit4, DW_OP_stack_value}), + ExpectScalar(0x4)); + + // Creates an implicit location with a value of 4. The deref reads the value + // out of the location and implicitly converts it to a load address. + EXPECT_THAT_EXPECTED(Eval({DW_OP_lit4, DW_OP_stack_value, DW_OP_deref}), + ExpectLoadAddress(0x4)); + + // Creates an implicit location with a value of 0x504 (uleb128(0x504) = + // 0xa84). The deref reads the low byte out of the location and implicitly + // converts it to a load address. + EXPECT_THAT_EXPECTED( + Eval({DW_OP_constu, 0x84, 0xa, DW_OP_stack_value, DW_OP_deref_size, 1}), + ExpectLoadAddress(0x4)); + + // The tests below are similar to the ones above, but there is no implicit + // location created by a stack_value operation. They are provided here as a + // reference to contrast with the above tests. + EXPECT_THAT_EXPECTED(Eval({DW_OP_lit4}), ExpectLoadAddress(0x4)); + + EXPECT_THAT_EXPECTED(Eval({DW_OP_lit4, DW_OP_deref}), + ExpectLoadAddress(0x04030201)); + + EXPECT_THAT_EXPECTED(Eval({DW_OP_lit4, DW_OP_deref_size, 1}), + ExpectLoadAddress(0x01)); } diff --git a/lldb/unittests/Expression/ValueMatcher.cpp b/lldb/unittests/Expression/ValueMatcher.cpp new file mode 100644 index 0000000..ee7ccae --- /dev/null +++ b/lldb/unittests/Expression/ValueMatcher.cpp @@ -0,0 +1,205 @@ +//===----------------------------------------------------------------------===// +// +// 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 "ValueMatcher.h" +#include "llvm/Support/Format.h" +#include "llvm/Support/InterleavedRange.h" +#include "llvm/Support/raw_os_ostream.h" +#include "llvm/Support/raw_ostream.h" + +using namespace lldb_private; + +static void FormatValueDetails(llvm::raw_ostream &os, + Value::ValueType value_type, + Value::ContextType context_type, + const Scalar &scalar, + llvm::ArrayRef<uint8_t> buffer_data) { + os << "Value("; + os << "value_type=" << Value::GetValueTypeAsCString(value_type); + os << ", context_type=" << Value::GetContextTypeAsCString(context_type); + + if (value_type == Value::ValueType::HostAddress) { + auto bytes_to_print = buffer_data.take_front(16); + os << ", buffer=["; + llvm::interleave( + bytes_to_print, + [&](uint8_t byte) { + os << llvm::format("%02x", static_cast<unsigned>(byte)); + }, + [&]() { os << " "; }); + if (buffer_data.size() > 16) + os << " ..."; + os << "] (" << buffer_data.size() << " bytes)"; + } else { + os << ", value=" << scalar; + } + os << ")"; +} + +void lldb_private::PrintTo(const Value &val, std::ostream *os) { + if (!os) + return; + + llvm::raw_os_ostream raw_os(*os); + FormatValueDetails(raw_os, val.GetValueType(), val.GetContextType(), + val.GetScalar(), val.GetBuffer().GetData()); +} + +bool ValueMatcher::MatchAndExplain(const Value &val, + std::ostream *stream) const { + if (stream) { + llvm::raw_os_ostream os(*stream); + return MatchAndExplainImpl(val, os); + } + + llvm::raw_null_ostream os; + return MatchAndExplainImpl(val, os); +} + +// Match the provided value and explain any mismatches using +// the raw_ostream. We use the llvm::raw_ostream here to simplify the formatting +// of Scalar values which already know how to print themselves to that stream. +bool ValueMatcher::MatchAndExplainImpl(const Value &val, + llvm::raw_ostream &os) const { + if (val.GetValueType() != m_value_type) { + os << "value_type mismatch: expected " + << Value::GetValueTypeAsCString(m_value_type) << ", got " + << Value::GetValueTypeAsCString(val.GetValueType()) << " "; + return false; + } + + if (val.GetContextType() != m_context_type) { + os << "context_type mismatch: expected " + << Value::GetContextTypeAsCString(m_context_type) << ", got " + << Value::GetContextTypeAsCString(val.GetContextType()) << " "; + return false; + } + + if (m_value_type == Value::ValueType::HostAddress) { + const DataBufferHeap &buffer = val.GetBuffer(); + const size_t buffer_size = buffer.GetByteSize(); + if (buffer_size != m_expected_bytes.size()) { + os << "buffer size mismatch: expected " << m_expected_bytes.size() + << ", got " << buffer_size << " "; + return false; + } + + const uint8_t *data = buffer.GetBytes(); + for (size_t i = 0; i < buffer_size; ++i) { + if (data[i] != m_expected_bytes[i]) { + os << "byte mismatch at index " << i << ": expected " + << llvm::format("0x%02x", static_cast<unsigned>(m_expected_bytes[i])) + << ", got " << llvm::format("0x%02x", static_cast<unsigned>(data[i])) + << " "; + return false; + } + } + } else { + // For Scalar, FileAddress, and LoadAddress compare m_value. + const Scalar &actual_scalar = val.GetScalar(); + if (actual_scalar != m_expected_scalar) { + os << "scalar value mismatch: expected " << m_expected_scalar << ", got " + << actual_scalar; + return false; + } + } + + return true; +} + +void ValueMatcher::DescribeTo(std::ostream *os) const { + if (!os) + return; + llvm::raw_os_ostream raw_os(*os); + FormatValueDetails(raw_os, m_value_type, m_context_type, m_expected_scalar, + m_expected_bytes); +} + +void ValueMatcher::DescribeNegationTo(std::ostream *os) const { + if (!os) + return; + *os << "value does not match"; +} + +testing::Matcher<Value> +lldb_private::MatchScalarValue(Value::ValueType value_type, + const Scalar &expected_scalar, + Value::ContextType context_type) { + return ValueMatcher(value_type, expected_scalar, context_type); +} + +testing::Matcher<Value> +lldb_private::MatchHostValue(Value::ValueType value_type, + const std::vector<uint8_t> &expected_bytes, + Value::ContextType context_type) { + return ValueMatcher(value_type, expected_bytes, context_type); +} + +testing::Matcher<Value> +lldb_private::IsScalar(const Scalar &expected_scalar, + Value::ContextType context_type) { + return MatchScalarValue(Value::ValueType::Scalar, expected_scalar, + context_type); +} + +testing::Matcher<Value> +lldb_private::IsLoadAddress(const Scalar &expected_address, + Value::ContextType context_type) { + return MatchScalarValue(Value::ValueType::LoadAddress, expected_address, + context_type); +} + +testing::Matcher<Value> +lldb_private::IsFileAddress(const Scalar &expected_address, + Value::ContextType context_type) { + return MatchScalarValue(Value::ValueType::FileAddress, expected_address, + context_type); +} + +testing::Matcher<Value> +lldb_private::IsHostValue(const std::vector<uint8_t> &expected_bytes, + Value::ContextType context_type) { + return MatchHostValue(Value::ValueType::HostAddress, expected_bytes, + context_type); +} + +Scalar lldb_private::GetScalar(unsigned bits, uint64_t value, bool sign) { + Scalar scalar(value); + scalar.TruncOrExtendTo(bits, sign); + return scalar; +} + +llvm::detail::ValueMatchesPoly<testing::Matcher<Value>> +lldb_private::ExpectScalar(const Scalar &expected_scalar, + Value::ContextType context_type) { + return llvm::HasValue(IsScalar(expected_scalar, context_type)); +} + +llvm::detail::ValueMatchesPoly<testing::Matcher<Value>> +lldb_private::ExpectScalar(unsigned bits, uint64_t value, bool sign, + Value::ContextType context_type) { + return ExpectScalar(GetScalar(bits, value, sign), context_type); +} + +llvm::detail::ValueMatchesPoly<testing::Matcher<Value>> +lldb_private::ExpectLoadAddress(const Scalar &expected_address, + Value::ContextType context_type) { + return llvm::HasValue(IsLoadAddress(expected_address, context_type)); +} + +llvm::detail::ValueMatchesPoly<testing::Matcher<Value>> +lldb_private::ExpectFileAddress(const Scalar &expected_address, + Value::ContextType context_type) { + return llvm::HasValue(IsFileAddress(expected_address, context_type)); +} + +llvm::detail::ValueMatchesPoly<testing::Matcher<Value>> +lldb_private::ExpectHostAddress(const std::vector<uint8_t> &expected_bytes, + Value::ContextType context_type) { + return llvm::HasValue(IsHostValue(expected_bytes, context_type)); +} diff --git a/lldb/unittests/Expression/ValueMatcher.h b/lldb/unittests/Expression/ValueMatcher.h new file mode 100644 index 0000000..3ca7b15 --- /dev/null +++ b/lldb/unittests/Expression/ValueMatcher.h @@ -0,0 +1,155 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +/// \file +/// This file contains the definition of the ValueMatcher class which is a used +/// to match lldb_private::Value in gtest assert/expect macros. It also contains +/// several helper functions to create matchers for common Value types. +/// +/// The ValueMatcher class was created using the gtest guide found here: +// https://google.github.io/googletest/gmock_cook_book.html#writing-new-monomorphic-matchers +//===----------------------------------------------------------------------===// + +#ifndef LLDB_UNITTESTS_EXPRESSION_VALUEMATCHER_H +#define LLDB_UNITTESTS_EXPRESSION_VALUEMATCHER_H + +#include "lldb/Core/Value.h" +#include "lldb/Utility/Scalar.h" +#include "llvm/Support/raw_ostream.h" +#include "llvm/Testing/Support/Error.h" +#include "gtest/gtest.h" +#include <cstdint> +#include <vector> + +namespace lldb_private { + +/// Custom printer for Value objects to make test failures more readable. +void PrintTo(const Value &val, std::ostream *os); + +/// Custom matcher for Value. +/// +/// It matches against an expected value_type, and context_type. +/// For HostAddress value types it will match the expected contents of +/// the host buffer. For other value types it matches against an expected +/// scalar value. +class ValueMatcher { +public: + ValueMatcher(Value::ValueType value_type, const Scalar &expected_scalar, + Value::ContextType context_type) + : m_value_type(value_type), m_context_type(context_type), + m_expected_scalar(expected_scalar) { + assert(value_type == Value::ValueType::Scalar || + value_type == Value::ValueType::FileAddress || + value_type == Value::ValueType::LoadAddress); + } + + ValueMatcher(Value::ValueType value_type, + const std::vector<uint8_t> &expected_bytes, + Value::ContextType context_type) + : m_value_type(value_type), m_context_type(context_type), + m_expected_bytes(expected_bytes) { + assert(value_type == Value::ValueType::HostAddress); + } + + // Typedef to hook into the gtest matcher machinery. + using is_gtest_matcher = void; + + bool MatchAndExplain(const Value &val, std::ostream *os) const; + + void DescribeTo(std::ostream *os) const; + + void DescribeNegationTo(std::ostream *os) const; + +private: + Value::ValueType m_value_type = Value::ValueType::Invalid; + Value::ContextType m_context_type = Value::ContextType::Invalid; + Scalar m_expected_scalar; + std::vector<uint8_t> m_expected_bytes; + + bool MatchAndExplainImpl(const Value &val, llvm::raw_ostream &os) const; +}; + +/// Matcher for Value with Scalar, FileAddress, or LoadAddress types. +/// Use with llvm::HasValue() to match Expected<Value>: +/// EXPECT_THAT_EXPECTED(result, llvm::HasValue(MatchScalarValue(...))); +testing::Matcher<Value> MatchScalarValue(Value::ValueType value_type, + const Scalar &expected_scalar, + Value::ContextType context_type); + +/// Matcher for Value with HostAddress type. +/// Use with llvm::HasValue() to match Expected<Value>: +/// EXPECT_THAT_EXPECTED(result, llvm::HasValue(MatchHostValue(...))); +testing::Matcher<Value> +MatchHostValue(Value::ValueType value_type, + const std::vector<uint8_t> &expected_bytes, + Value::ContextType context_type); + +/// Helper to match a Scalar value and context type. +/// Use with llvm::HasValue() to match Expected<Value>: +/// EXPECT_THAT_EXPECTED(result, llvm::HasValue(IsScalar(42))); +testing::Matcher<Value> IsScalar(const Scalar &expected_scalar, + Value::ContextType context_type); + +/// Helper to match a LoadAddress value and context type. +/// Use with llvm::HasValue() to match Expected<Value>: +/// EXPECT_THAT_EXPECTED(result, llvm::HasValue(IsLoadAddress(0x1000))); +testing::Matcher<Value> IsLoadAddress(const Scalar &expected_address, + Value::ContextType context_type); + +/// Helper to match a FileAddress value and context type. +/// Use with llvm::HasValue() to match Expected<Value>: +/// EXPECT_THAT_EXPECTED(result, llvm::HasValue(IsFileAddress(Scalar(0x1000)))); +testing::Matcher<Value> IsFileAddress(const Scalar &expected_address, + Value::ContextType context_type); + +/// Helper to match a HostAddress value and context type. +/// Use with llvm::HasValue() to match Expected<Value>: +/// EXPECT_THAT_EXPECTED(result, llvm::HasValue(IsHostValue({0x11, 0x22}))); +testing::Matcher<Value> IsHostValue(const std::vector<uint8_t> &expected_bytes, + Value::ContextType context_type); + +/// Helper to create a scalar because Scalar's operator==() is really picky. +Scalar GetScalar(unsigned bits, uint64_t value, bool sign); + +/// Helper that combines IsScalar with llvm::HasValue for Expected<Value>. +/// Use it on an Expected<Value> like this: +/// EXPECT_THAT_EXPECTED(result, ExpectScalar(42)); +llvm::detail::ValueMatchesPoly<testing::Matcher<Value>> +ExpectScalar(const Scalar &expected_scalar, + Value::ContextType context_type = Value::ContextType::Invalid); + +/// Helper that combines GetScalar with ExpectScalar to get a precise scalar. +/// Use it on an Expected<Value> like this: +/// EXPECT_THAT_EXPECTED(result, ExpectScalar(8, 42, true)); +llvm::detail::ValueMatchesPoly<testing::Matcher<Value>> +ExpectScalar(unsigned bits, uint64_t value, bool sign, + Value::ContextType context_type = Value::ContextType::Invalid); + +/// Helper that combines IsLoadAddress with llvm::HasValue for Expected<Value>. +/// Use it on an Expected<Value> like this: +/// EXPECT_THAT_EXPECTED(result, ExpectLoadAddress(0x1000)); +llvm::detail::ValueMatchesPoly<testing::Matcher<Value>> ExpectLoadAddress( + const Scalar &expected_address, + Value::ContextType context_type = Value::ContextType::Invalid); + +/// Helper that combines IsFileAddress with llvm::HasValue for Expected<Value>. +/// Use it on an Expected<Value> like this: +/// EXPECT_THAT_EXPECTED(result, ExpectFileAddress(Scalar(0x2000))); +llvm::detail::ValueMatchesPoly<testing::Matcher<Value>> ExpectFileAddress( + const Scalar &expected_address, + Value::ContextType context_type = Value::ContextType::Invalid); + +/// Helper that combines IsHostValue with llvm::HasValue for Expected<Value>. +/// Use it on an Expected<Value> like this: +/// EXPECT_THAT_EXPECTED(result, ExpectHostAddress({0x11, 0x22})); +llvm::detail::ValueMatchesPoly<testing::Matcher<Value>> ExpectHostAddress( + const std::vector<uint8_t> &expected_bytes, + Value::ContextType context_type = Value::ContextType::Invalid); + +} // namespace lldb_private + +#endif // LLDB_UNITTESTS_EXPRESSION_VALUEMATCHER_H diff --git a/lldb/unittests/Host/FileTest.cpp b/lldb/unittests/Host/FileTest.cpp index d973d19..85697c4 100644 --- a/lldb/unittests/Host/FileTest.cpp +++ b/lldb/unittests/Host/FileTest.cpp @@ -8,6 +8,7 @@ #include "lldb/Host/File.h" #include "llvm/ADT/SmallString.h" +#include "llvm/ADT/StringRef.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/FileUtilities.h" #include "llvm/Support/Path.h" @@ -35,7 +36,7 @@ TEST(File, GetWaitableHandleFileno) { FILE *stream = fdopen(fd, "r"); ASSERT_TRUE(stream); - NativeFile file(stream, true); + NativeFile file(stream, File::eOpenOptionReadWrite, true); #ifdef _WIN32 EXPECT_EQ(file.GetWaitableHandle(), (HANDLE)_get_osfhandle(fd)); #else @@ -67,3 +68,22 @@ TEST(File, GetStreamFromDescriptor) { EXPECT_EQ(file.GetWaitableHandle(), (file_t)fd); #endif } + +TEST(File, ReadOnlyModeNotWritable) { + const auto *Info = testing::UnitTest::GetInstance()->current_test_info(); + llvm::SmallString<128> name; + int fd; + llvm::sys::fs::createTemporaryFile(llvm::Twine(Info->test_case_name()) + "-" + + Info->name(), + "test", fd, name); + + llvm::FileRemover remover(name); + ASSERT_GE(fd, 0); + + NativeFile file(fd, File::eOpenOptionReadOnly, true); + ASSERT_TRUE(file.IsValid()); + llvm::StringLiteral buf = "Hello World"; + size_t bytes_written = buf.size(); + Status error = file.Write(buf.data(), bytes_written); + EXPECT_EQ(error.Fail(), true); +} diff --git a/lldb/unittests/Host/common/CMakeLists.txt b/lldb/unittests/Host/common/CMakeLists.txt index 2934e6f0..8aa2dfb 100644 --- a/lldb/unittests/Host/common/CMakeLists.txt +++ b/lldb/unittests/Host/common/CMakeLists.txt @@ -1,4 +1,5 @@ set (FILES + DiagnosticsRenderingTest.cpp ZipFileResolverTest.cpp ) diff --git a/lldb/unittests/Utility/DiagnosticsRenderingTest.cpp b/lldb/unittests/Host/common/DiagnosticsRenderingTest.cpp index 4e5e0bb..851b478 100644 --- a/lldb/unittests/Utility/DiagnosticsRenderingTest.cpp +++ b/lldb/unittests/Host/common/DiagnosticsRenderingTest.cpp @@ -1,4 +1,4 @@ -#include "lldb/Utility/DiagnosticsRendering.h" +#include "lldb/Host/common/DiagnosticsRendering.h" #include "lldb/Utility/StreamString.h" #include "gtest/gtest.h" diff --git a/lldb/unittests/Interpreter/TestOptions.cpp b/lldb/unittests/Interpreter/TestOptions.cpp index 93474e3..e23dc0d 100644 --- a/lldb/unittests/Interpreter/TestOptions.cpp +++ b/lldb/unittests/Interpreter/TestOptions.cpp @@ -17,13 +17,13 @@ TEST(OptionsTest, CreateOptionParsingError) { ASSERT_THAT_ERROR( CreateOptionParsingError("yippee", 'f', "fun", "unable to convert 'yippee' to boolean"), - llvm::FailedWithMessage("Invalid value ('yippee') for -f (fun): unable " + llvm::FailedWithMessage("invalid value ('yippee') for -f (fun): unable " "to convert 'yippee' to boolean")); ASSERT_THAT_ERROR( CreateOptionParsingError("52", 'b', "bean-count"), - llvm::FailedWithMessage("Invalid value ('52') for -b (bean-count)")); + llvm::FailedWithMessage("invalid value ('52') for -b (bean-count)")); ASSERT_THAT_ERROR(CreateOptionParsingError("c", 'm'), - llvm::FailedWithMessage("Invalid value ('c') for -m")); + llvm::FailedWithMessage("invalid value ('c') for -m")); } diff --git a/lldb/unittests/Language/CPlusPlus/CPlusPlusLanguageTest.cpp b/lldb/unittests/Language/CPlusPlus/CPlusPlusLanguageTest.cpp index 8c36f54..41df35f 100644 --- a/lldb/unittests/Language/CPlusPlus/CPlusPlusLanguageTest.cpp +++ b/lldb/unittests/Language/CPlusPlus/CPlusPlusLanguageTest.cpp @@ -30,6 +30,10 @@ TEST(CPlusPlusLanguage, MethodNameParsing) { {"foo::~bar(baz)", "", "foo", "~bar", "(baz)", "", "foo::~bar"}, {"a::b::c::d(e,f)", "", "a::b::c", "d", "(e,f)", "", "a::b::c::d"}, {"void f(int)", "void", "", "f", "(int)", "", "f"}, + {"std::vector<int>foo::bar()", "std::vector<int>", "foo", "bar", "()", "", + "foo::bar"}, + {"int foo::bar::func01(int a, double b)", "int", "foo::bar", "func01", + "(int a, double b)", "", "foo::bar::func01"}, // Operators {"std::basic_ostream<char, std::char_traits<char> >& " @@ -65,6 +69,12 @@ TEST(CPlusPlusLanguage, MethodNameParsing) { "const", "std::__1::ranges::__begin::__fn::operator()[abi:v160000]<char const, " "18ul>"}, + {"bool Ball[abi:BALL]<int>::operator<<[abi:operator]<int>(int)", "bool", + "Ball[abi:BALL]<int>", "operator<<[abi:operator]<int>", "(int)", "", + "Ball[abi:BALL]<int>::operator<<[abi:operator]<int>"}, + {"bool Ball[abi:BALL]<int>::operator>>[abi:operator]<int>(int)", "bool", + "Ball[abi:BALL]<int>", "operator>>[abi:operator]<int>", "(int)", "", + "Ball[abi:BALL]<int>::operator>>[abi:operator]<int>"}, // Internal classes {"operator<<(Cls, Cls)::Subclass::function()", "", "operator<<(Cls, Cls)::Subclass", "function", "()", "", @@ -101,6 +111,8 @@ TEST(CPlusPlusLanguage, MethodNameParsing) { "std::forward<decltype(nullptr)>"}, // Templates + {"vector<int > foo::bar::func(int)", "vector<int >", "foo::bar", "func", + "(int)", "", "foo::bar::func"}, {"void llvm::PM<llvm::Module, llvm::AM<llvm::Module>>::" "addPass<llvm::VP>(llvm::VP)", "void", "llvm::PM<llvm::Module, llvm::AM<llvm::Module>>", @@ -341,7 +353,7 @@ TEST(CPlusPlusLanguage, ExtractContextAndIdentifier) { llvm::StringRef context, basename; for (const auto &test : test_cases) { EXPECT_TRUE(CPlusPlusLanguage::ExtractContextAndIdentifier( - test.input.c_str(), context, basename)); + test.input, context, basename)); EXPECT_EQ(test.context, context.str()); EXPECT_EQ(test.basename, basename.str()); } diff --git a/lldb/unittests/Language/ObjC/ObjCLanguageTest.cpp b/lldb/unittests/Language/ObjC/ObjCLanguageTest.cpp index 70baa7e..4b018a29 100644 --- a/lldb/unittests/Language/ObjC/ObjCLanguageTest.cpp +++ b/lldb/unittests/Language/ObjC/ObjCLanguageTest.cpp @@ -112,3 +112,46 @@ TEST(ObjCLanguage, InvalidMethodNameParsing) { EXPECT_FALSE(lax_method.has_value()); } } + +struct ObjCMethodTestCase { + llvm::StringRef name; + bool is_valid; +}; + +struct ObjCMethodNameTextFiture + : public testing::TestWithParam<ObjCMethodTestCase> {}; + +static ObjCMethodTestCase g_objc_method_name_test_cases[] = { + {"", false}, + {"+[Uh oh!", false}, + {"-[Definitely not...", false}, + {"[Nice try ] :)", false}, + {"+MaybeIfYouSquintYourEyes]", false}, + {"?[Tricky]", false}, + {"[]", false}, + {"-[a", false}, + {"+[a", false}, + {"-]a]", false}, + {"+]a]", false}, + + // FIXME: should these count as valid? + {"+[]", true}, + {"-[]", true}, + {"-[[]", true}, + {"+[[]", true}, + {"+[a ]", true}, + {"-[a ]", true}, + + // Valid names + {"+[a a]", true}, + {"-[a a]", true}, +}; + +TEST_P(ObjCMethodNameTextFiture, TestIsPossibleObjCMethodName) { + // Tests ObjCLanguage::IsPossibleObjCMethodName + auto [name, expect_valid] = GetParam(); + EXPECT_EQ(ObjCLanguage::IsPossibleObjCMethodName(name), expect_valid); +} + +INSTANTIATE_TEST_SUITE_P(ObjCMethodNameTests, ObjCMethodNameTextFiture, + testing::ValuesIn(g_objc_method_name_test_cases)); diff --git a/lldb/unittests/Process/gdb-remote/GDBRemoteCommunicationClientTest.cpp b/lldb/unittests/Process/gdb-remote/GDBRemoteCommunicationClientTest.cpp index 012eae0..966b37e 100644 --- a/lldb/unittests/Process/gdb-remote/GDBRemoteCommunicationClientTest.cpp +++ b/lldb/unittests/Process/gdb-remote/GDBRemoteCommunicationClientTest.cpp @@ -326,7 +326,7 @@ TEST_F(GDBRemoteCommunicationClientTest, SendSignalsToIgnore) { TEST_F(GDBRemoteCommunicationClientTest, GetMemoryRegionInfo) { const lldb::addr_t addr = 0xa000; - MemoryRegionInfo region_info; + lldb_private::MemoryRegionInfo region_info; std::future<Status> result = std::async(std::launch::async, [&] { return client.GetMemoryRegionInfo(addr, region_info); }); @@ -343,13 +343,16 @@ TEST_F(GDBRemoteCommunicationClientTest, GetMemoryRegionInfo) { EXPECT_TRUE(result.get().Success()); EXPECT_EQ(addr, region_info.GetRange().GetRangeBase()); EXPECT_EQ(0x2000u, region_info.GetRange().GetByteSize()); - EXPECT_EQ(MemoryRegionInfo::eYes, region_info.GetReadable()); - EXPECT_EQ(MemoryRegionInfo::eNo, region_info.GetWritable()); - EXPECT_EQ(MemoryRegionInfo::eYes, region_info.GetExecutable()); + EXPECT_EQ(lldb_private::MemoryRegionInfo::eYes, region_info.GetReadable()); + EXPECT_EQ(lldb_private::MemoryRegionInfo::eNo, region_info.GetWritable()); + EXPECT_EQ(lldb_private::MemoryRegionInfo::eYes, region_info.GetExecutable()); EXPECT_EQ("/foo/bar.so", region_info.GetName().GetStringRef()); - EXPECT_EQ(MemoryRegionInfo::eDontKnow, region_info.GetMemoryTagged()); - EXPECT_EQ(MemoryRegionInfo::eDontKnow, region_info.IsStackMemory()); - EXPECT_EQ(MemoryRegionInfo::eDontKnow, region_info.IsShadowStack()); + EXPECT_EQ(lldb_private::MemoryRegionInfo::eDontKnow, + region_info.GetMemoryTagged()); + EXPECT_EQ(lldb_private::MemoryRegionInfo::eDontKnow, + region_info.IsStackMemory()); + EXPECT_EQ(lldb_private::MemoryRegionInfo::eDontKnow, + region_info.IsShadowStack()); result = std::async(std::launch::async, [&] { return client.GetMemoryRegionInfo(addr, region_info); @@ -358,9 +361,9 @@ TEST_F(GDBRemoteCommunicationClientTest, GetMemoryRegionInfo) { HandlePacket(server, "qMemoryRegionInfo:a000", "start:a000;size:2000;flags:;type:stack;"); EXPECT_TRUE(result.get().Success()); - EXPECT_EQ(MemoryRegionInfo::eNo, region_info.GetMemoryTagged()); - EXPECT_EQ(MemoryRegionInfo::eYes, region_info.IsStackMemory()); - EXPECT_EQ(MemoryRegionInfo::eNo, region_info.IsShadowStack()); + EXPECT_EQ(lldb_private::MemoryRegionInfo::eNo, region_info.GetMemoryTagged()); + EXPECT_EQ(lldb_private::MemoryRegionInfo::eYes, region_info.IsStackMemory()); + EXPECT_EQ(lldb_private::MemoryRegionInfo::eNo, region_info.IsShadowStack()); result = std::async(std::launch::async, [&] { return client.GetMemoryRegionInfo(addr, region_info); @@ -369,9 +372,10 @@ TEST_F(GDBRemoteCommunicationClientTest, GetMemoryRegionInfo) { HandlePacket(server, "qMemoryRegionInfo:a000", "start:a000;size:2000;flags: mt zz mt ss ;type:ha,ha,stack;"); EXPECT_TRUE(result.get().Success()); - EXPECT_EQ(MemoryRegionInfo::eYes, region_info.GetMemoryTagged()); - EXPECT_EQ(MemoryRegionInfo::eYes, region_info.IsStackMemory()); - EXPECT_EQ(MemoryRegionInfo::eYes, region_info.IsShadowStack()); + EXPECT_EQ(lldb_private::MemoryRegionInfo::eYes, + region_info.GetMemoryTagged()); + EXPECT_EQ(lldb_private::MemoryRegionInfo::eYes, region_info.IsStackMemory()); + EXPECT_EQ(lldb_private::MemoryRegionInfo::eYes, region_info.IsShadowStack()); result = std::async(std::launch::async, [&] { return client.GetMemoryRegionInfo(addr, region_info); @@ -380,12 +384,12 @@ TEST_F(GDBRemoteCommunicationClientTest, GetMemoryRegionInfo) { HandlePacket(server, "qMemoryRegionInfo:a000", "start:a000;size:2000;type:heap;"); EXPECT_TRUE(result.get().Success()); - EXPECT_EQ(MemoryRegionInfo::eNo, region_info.IsStackMemory()); + EXPECT_EQ(lldb_private::MemoryRegionInfo::eNo, region_info.IsStackMemory()); } TEST_F(GDBRemoteCommunicationClientTest, GetMemoryRegionInfoInvalidResponse) { const lldb::addr_t addr = 0x4000; - MemoryRegionInfo region_info; + lldb_private::MemoryRegionInfo region_info; std::future<Status> result = std::async(std::launch::async, [&] { return client.GetMemoryRegionInfo(addr, region_info); }); diff --git a/lldb/unittests/ScriptInterpreter/Python/PythonTestSuite.cpp b/lldb/unittests/ScriptInterpreter/Python/PythonTestSuite.cpp index 3d0e2d8..5694aee 100644 --- a/lldb/unittests/ScriptInterpreter/Python/PythonTestSuite.cpp +++ b/lldb/unittests/ScriptInterpreter/Python/PythonTestSuite.cpp @@ -137,6 +137,11 @@ lldb_private::python::LLDBSWIGPython_CastPyObjectToSBStream(PyObject *data) { } void * +lldb_private::python::LLDBSWIGPython_CastPyObjectToSBThread(PyObject *data) { + return nullptr; +} + +void * lldb_private::python::LLDBSWIGPython_CastPyObjectToSBFrame(PyObject *data) { return nullptr; } @@ -161,6 +166,11 @@ void *lldb_private::python::LLDBSWIGPython_CastPyObjectToSBExecutionContext( return nullptr; } +void * +lldb_private::python::LLDBSWIGPython_CastPyObjectToSBFrameList(PyObject *data) { + return nullptr; +} + lldb::ValueObjectSP lldb_private::python::SWIGBridge::LLDBSWIGPython_GetValueObjectSPFromSBValue( void *data) { @@ -329,6 +339,11 @@ lldb_private::python::SWIGBridge::ToSWIGWrapper(lldb::ProcessSP) { return python::PythonObject(); } +python::PythonObject +lldb_private::python::SWIGBridge::ToSWIGWrapper(lldb::StackFrameListSP) { + return python::PythonObject(); +} + python::PythonObject lldb_private::python::SWIGBridge::ToSWIGWrapper( const lldb_private::StructuredDataImpl &) { return python::PythonObject(); diff --git a/lldb/unittests/Signals/UnixSignalsTest.cpp b/lldb/unittests/Signals/UnixSignalsTest.cpp index 582e441..3bd4aed 100644 --- a/lldb/unittests/Signals/UnixSignalsTest.cpp +++ b/lldb/unittests/Signals/UnixSignalsTest.cpp @@ -148,6 +148,18 @@ TEST(UnixSignalsTest, GetAsString) { signals.GetSignalDescription(16, 3, 0x1233, 0x1234, 0x5678)); } +TEST(UnixSignalsTest, GetNumberDescription) { + TestSignals signals; + + ASSERT_EQ("DESC2", signals.GetSignalNumberDescription(2)); + ASSERT_EQ("DESC4", signals.GetSignalNumberDescription(4)); + ASSERT_EQ("DESC8", signals.GetSignalNumberDescription(8)); + ASSERT_EQ("DESC16", signals.GetSignalNumberDescription(16)); + + // Unknown signal number. + ASSERT_EQ("", signals.GetSignalNumberDescription(100)); +} + TEST(UnixSignalsTest, VersionChange) { TestSignals signals; diff --git a/lldb/unittests/Symbol/LineTableTest.cpp b/lldb/unittests/Symbol/LineTableTest.cpp index eadab40..ca63c6f 100644 --- a/lldb/unittests/Symbol/LineTableTest.cpp +++ b/lldb/unittests/Symbol/LineTableTest.cpp @@ -176,10 +176,12 @@ Sections: if (!text_sp) return createStringError("No .text"); - auto cu_up = std::make_unique<CompileUnit>(module_sp, /*user_data=*/nullptr, - /*support_file_sp=*/nullptr, - /*uid=*/0, eLanguageTypeC, - /*is_optimized=*/eLazyBoolNo); + auto cu_up = std::make_unique<CompileUnit>( + module_sp, + /*user_data=*/nullptr, + /*support_file_nsp=*/std::make_shared<SupportFile>(), + /*uid=*/0, eLanguageTypeC, + /*is_optimized=*/eLazyBoolNo); LineTable *line_table = new LineTable(cu_up.get(), std::move(line_sequences)); cu_up->SetLineTable(line_table); cast<FakeSymbolFile>(module_sp->GetSymbolFile()) diff --git a/lldb/unittests/Symbol/TestClangASTImporter.cpp b/lldb/unittests/Symbol/TestClangASTImporter.cpp index f1b3d79..07c4208 100644 --- a/lldb/unittests/Symbol/TestClangASTImporter.cpp +++ b/lldb/unittests/Symbol/TestClangASTImporter.cpp @@ -287,7 +287,7 @@ TEST_F(TestClangASTImporter, RecordLayoutFromOrigin) { clang_utils::SourceASTWithRecord source; auto *dwarf_parser = - static_cast<DWARFASTParserClang *>(source.ast->GetDWARFParser()); + llvm::cast<DWARFASTParserClang>(source.ast->GetDWARFParser()); auto &importer = dwarf_parser->GetClangASTImporter(); // Set the layout for the origin decl in the origin ClangASTImporter. diff --git a/lldb/unittests/Symbol/TestTypeSystemClang.cpp b/lldb/unittests/Symbol/TestTypeSystemClang.cpp index 4de595f..155fc74 100644 --- a/lldb/unittests/Symbol/TestTypeSystemClang.cpp +++ b/lldb/unittests/Symbol/TestTypeSystemClang.cpp @@ -52,6 +52,12 @@ protected: return ClangUtil::GetQualType( m_ast->GetBuiltinTypeByName(ConstString(name))); } + + CompilerType GetBuiltinTypeForDWARFEncodingAndBitSize( + llvm::StringRef type_name, uint32_t encoding, uint32_t bit_size) const { + return m_ast->GetBuiltinTypeForDWARFEncodingAndBitSize(type_name, encoding, + bit_size); + } }; TEST_F(TestTypeSystemClang, TestGetBasicTypeFromEnum) { @@ -238,6 +244,91 @@ TEST_F(TestTypeSystemClang, TestBuiltinTypeForEncodingAndBitSize) { VerifyEncodingAndBitSize(*m_ast, eEncodingIEEE754, 64); } +TEST_F(TestTypeSystemClang, TestGetBuiltinTypeForDWARFEncodingAndBitSize) { + EXPECT_FALSE(GetBuiltinTypeForDWARFEncodingAndBitSize( + "_BitIn", llvm::dwarf::DW_ATE_signed, 2) + .IsValid()); + EXPECT_FALSE(GetBuiltinTypeForDWARFEncodingAndBitSize( + "BitInt", llvm::dwarf::DW_ATE_signed, 2) + .IsValid()); + EXPECT_FALSE(GetBuiltinTypeForDWARFEncodingAndBitSize( + "_BitInt(2)", llvm::dwarf::DW_ATE_signed_char, 2) + .IsValid()); + EXPECT_FALSE(GetBuiltinTypeForDWARFEncodingAndBitSize( + "_BitInt", llvm::dwarf::DW_ATE_signed_char, 2) + .IsValid()); + EXPECT_FALSE(GetBuiltinTypeForDWARFEncodingAndBitSize( + "_BitInt(2)", llvm::dwarf::DW_ATE_unsigned, 2) + .IsValid()); + EXPECT_FALSE(GetBuiltinTypeForDWARFEncodingAndBitSize( + "_BitInt", llvm::dwarf::DW_ATE_unsigned, 2) + .IsValid()); + + EXPECT_EQ(GetBuiltinTypeForDWARFEncodingAndBitSize( + "_BitInt(2)", llvm::dwarf::DW_ATE_signed, 2) + .GetTypeName(), + "_BitInt(2)"); + EXPECT_EQ(GetBuiltinTypeForDWARFEncodingAndBitSize( + "_BitInt", llvm::dwarf::DW_ATE_signed, 2) + .GetTypeName(), + "_BitInt(2)"); + EXPECT_EQ(GetBuiltinTypeForDWARFEncodingAndBitSize( + "_BitInt(129)", llvm::dwarf::DW_ATE_signed, 129) + .GetTypeName(), + "_BitInt(129)"); + EXPECT_EQ(GetBuiltinTypeForDWARFEncodingAndBitSize( + "_BitInt", llvm::dwarf::DW_ATE_signed, 129) + .GetTypeName(), + "_BitInt(129)"); + + EXPECT_FALSE(GetBuiltinTypeForDWARFEncodingAndBitSize( + "unsigned _BitIn", llvm::dwarf::DW_ATE_unsigned, 2) + .IsValid()); + EXPECT_FALSE(GetBuiltinTypeForDWARFEncodingAndBitSize( + "unsigned BitInt", llvm::dwarf::DW_ATE_unsigned, 2) + .IsValid()); + EXPECT_FALSE(GetBuiltinTypeForDWARFEncodingAndBitSize( + "unsigned _BitInt(2)", llvm::dwarf::DW_ATE_unsigned_char, 2) + .IsValid()); + EXPECT_FALSE(GetBuiltinTypeForDWARFEncodingAndBitSize( + "unsigned _BitInt", llvm::dwarf::DW_ATE_unsigned_char, 2) + .IsValid()); + EXPECT_FALSE(GetBuiltinTypeForDWARFEncodingAndBitSize( + "unsigned _BitInt(2)", llvm::dwarf::DW_ATE_signed, 2) + .IsValid()); + EXPECT_FALSE(GetBuiltinTypeForDWARFEncodingAndBitSize( + "unsigned _BitInt", llvm::dwarf::DW_ATE_signed, 2) + .IsValid()); + + EXPECT_EQ(GetBuiltinTypeForDWARFEncodingAndBitSize( + "unsigned _BitInt(2)", llvm::dwarf::DW_ATE_unsigned, 2) + .GetTypeName(), + "unsigned _BitInt(2)"); + EXPECT_EQ(GetBuiltinTypeForDWARFEncodingAndBitSize( + "unsigned _BitInt", llvm::dwarf::DW_ATE_unsigned, 2) + .GetTypeName(), + "unsigned _BitInt(2)"); + EXPECT_EQ(GetBuiltinTypeForDWARFEncodingAndBitSize( + "unsigned _BitInt(129)", llvm::dwarf::DW_ATE_unsigned, 129) + .GetTypeName(), + "unsigned _BitInt(129)"); + EXPECT_EQ(GetBuiltinTypeForDWARFEncodingAndBitSize( + "unsigned _BitInt", llvm::dwarf::DW_ATE_unsigned, 129) + .GetTypeName(), + "unsigned _BitInt(129)"); +} + +TEST_F(TestTypeSystemClang, TestBitIntTypeInfo) { + EXPECT_EQ(GetBuiltinTypeForDWARFEncodingAndBitSize( + "_BitInt", llvm::dwarf::DW_ATE_signed, 2) + .GetTypeInfo(), + eTypeIsSigned | eTypeIsScalar | eTypeHasValue | eTypeIsInteger); + EXPECT_EQ(GetBuiltinTypeForDWARFEncodingAndBitSize( + "unsigned _BitInt", llvm::dwarf::DW_ATE_unsigned, 2) + .GetTypeInfo(), + eTypeIsScalar | eTypeHasValue | eTypeIsInteger); +} + TEST_F(TestTypeSystemClang, TestBuiltinTypeForEmptyTriple) { // Test that we can access type-info of builtin Clang AST // types without crashing even when the target triple is diff --git a/lldb/unittests/SymbolFile/DWARF/DWARFASTParserClangTests.cpp b/lldb/unittests/SymbolFile/DWARF/DWARFASTParserClangTests.cpp index 064ed6d..6a753b6 100644 --- a/lldb/unittests/SymbolFile/DWARF/DWARFASTParserClangTests.cpp +++ b/lldb/unittests/SymbolFile/DWARF/DWARFASTParserClangTests.cpp @@ -42,6 +42,52 @@ public: return keys; } }; + +/// Helper structure for DWARFASTParserClang tests that want to parse DWARF +/// generated using yaml2obj. On construction parses the supplied YAML data +/// into a DWARF module and thereafter vends a DWARFASTParserClang and +/// TypeSystemClang that are guaranteed to live for the duration of this object. +class DWARFASTParserClangYAMLTester { +public: + DWARFASTParserClangYAMLTester(llvm::StringRef yaml_data) + : m_module_tester(yaml_data) {} + + DWARFDIE GetCUDIE() { + DWARFUnit *unit = m_module_tester.GetDwarfUnit(); + assert(unit); + + const DWARFDebugInfoEntry *cu_entry = unit->DIE().GetDIE(); + assert(cu_entry->Tag() == DW_TAG_compile_unit); + + return DWARFDIE(unit, cu_entry); + } + + DWARFASTParserClang &GetParser() { + auto *parser = GetTypeSystem().GetDWARFParser(); + + assert(llvm::isa_and_nonnull<DWARFASTParserClang>(parser)); + + return *llvm::cast<DWARFASTParserClang>(parser); + } + + TypeSystemClang &GetTypeSystem() { + ModuleSP module_sp = m_module_tester.GetModule(); + assert(module_sp); + + SymbolFile *symfile = module_sp->GetSymbolFile(); + assert(symfile); + + TypeSystemSP ts_sp = llvm::cantFail( + symfile->GetTypeSystemForLanguage(lldb::eLanguageTypeC_plus_plus)); + + assert(llvm::isa_and_nonnull<TypeSystemClang>(ts_sp.get())); + + return llvm::cast<TypeSystemClang>(*ts_sp); + } + +private: + YAMLModuleTester m_module_tester; +}; } // namespace // If your implementation needs to dereference the dummy pointers we are @@ -99,7 +145,6 @@ DWARF: - Value: 0x0000000000000001 - AbbrCode: 0x00000000 )"; - YAMLModuleTester t(yamldata); ASSERT_TRUE((bool)t.GetDwarfUnit()); @@ -248,17 +293,9 @@ DWARF: - AbbrCode: 0x0 ... )"; - YAMLModuleTester t(yamldata); + DWARFASTParserClangYAMLTester tester(yamldata); - DWARFUnit *unit = t.GetDwarfUnit(); - ASSERT_NE(unit, nullptr); - const DWARFDebugInfoEntry *cu_entry = unit->DIE().GetDIE(); - ASSERT_EQ(cu_entry->Tag(), DW_TAG_compile_unit); - DWARFDIE cu_die(unit, cu_entry); - - auto holder = std::make_unique<clang_utils::TypeSystemClangHolder>("ast"); - auto &ast_ctx = *holder->GetAST(); - DWARFASTParserClangStub ast_parser(ast_ctx); + DWARFDIE cu_die = tester.GetCUDIE(); std::vector<std::string> found_function_types; // The DWARF above is just a list of functions. Parse all of them to @@ -267,7 +304,8 @@ DWARF: ASSERT_EQ(func.Tag(), DW_TAG_subprogram); SymbolContext sc; bool new_type = false; - lldb::TypeSP type = ast_parser.ParseTypeFromDWARF(sc, func, &new_type); + lldb::TypeSP type = + tester.GetParser().ParseTypeFromDWARF(sc, func, &new_type); found_function_types.push_back( type->GetForwardCompilerType().GetTypeName().AsCString()); } @@ -394,18 +432,9 @@ DWARF: - AbbrCode: 0x00 # end of child tags of 0x0c ... )"; - YAMLModuleTester t(yamldata); - - DWARFUnit *unit = t.GetDwarfUnit(); - ASSERT_NE(unit, nullptr); - const DWARFDebugInfoEntry *cu_entry = unit->DIE().GetDIE(); - ASSERT_EQ(cu_entry->Tag(), DW_TAG_compile_unit); - DWARFDIE cu_die(unit, cu_entry); - - auto holder = std::make_unique<clang_utils::TypeSystemClangHolder>("ast"); - auto &ast_ctx = *holder->GetAST(); - DWARFASTParserClangStub ast_parser(ast_ctx); + DWARFASTParserClangYAMLTester tester(yamldata); + DWARFDIE cu_die = tester.GetCUDIE(); DWARFDIE ptrauth_variable = cu_die.GetFirstChild(); ASSERT_EQ(ptrauth_variable.Tag(), DW_TAG_variable); DWARFDIE ptrauth_type = @@ -415,7 +444,7 @@ DWARF: SymbolContext sc; bool new_type = false; lldb::TypeSP type_sp = - ast_parser.ParseTypeFromDWARF(sc, ptrauth_type, &new_type); + tester.GetParser().ParseTypeFromDWARF(sc, ptrauth_type, &new_type); CompilerType compiler_type = type_sp->GetForwardCompilerType(); ASSERT_EQ(compiler_type.GetPtrAuthKey(), 0U); ASSERT_EQ(compiler_type.GetPtrAuthAddressDiversity(), false); @@ -554,24 +583,17 @@ TEST_F(DWARFASTParserClangTests, TestDefaultTemplateParamParsing) { auto BufferOrError = llvm::MemoryBuffer::getFile( GetInputFilePath("DW_AT_default_value-test.yaml"), /*IsText=*/true); ASSERT_TRUE(BufferOrError); - YAMLModuleTester t(BufferOrError.get()->getBuffer()); - DWARFUnit *unit = t.GetDwarfUnit(); - ASSERT_NE(unit, nullptr); - const DWARFDebugInfoEntry *cu_entry = unit->DIE().GetDIE(); - ASSERT_EQ(cu_entry->Tag(), DW_TAG_compile_unit); - DWARFDIE cu_die(unit, cu_entry); - - auto holder = std::make_unique<clang_utils::TypeSystemClangHolder>("ast"); - auto &ast_ctx = *holder->GetAST(); - DWARFASTParserClangStub ast_parser(ast_ctx); + DWARFASTParserClangYAMLTester tester(BufferOrError.get()->getBuffer()); + DWARFDIE cu_die = tester.GetCUDIE(); llvm::SmallVector<lldb::TypeSP, 2> types; for (DWARFDIE die : cu_die.children()) { if (die.Tag() == DW_TAG_class_type) { SymbolContext sc; bool new_type = false; - types.push_back(ast_parser.ParseTypeFromDWARF(sc, die, &new_type)); + types.push_back( + tester.GetParser().ParseTypeFromDWARF(sc, die, &new_type)); } } @@ -605,23 +627,14 @@ TEST_F(DWARFASTParserClangTests, TestSpecDeclExistsError) { auto BufferOrError = llvm::MemoryBuffer::getFile( GetInputFilePath("DW_AT_spec_decl_exists-test.yaml"), /*IsText=*/true); ASSERT_TRUE(BufferOrError); - YAMLModuleTester t(BufferOrError.get()->getBuffer()); - - DWARFUnit *unit = t.GetDwarfUnit(); - ASSERT_NE(unit, nullptr); - const DWARFDebugInfoEntry *cu_entry = unit->DIE().GetDIE(); - ASSERT_EQ(cu_entry->Tag(), DW_TAG_compile_unit); - DWARFDIE cu_die(unit, cu_entry); - - auto holder = std::make_unique<clang_utils::TypeSystemClangHolder>("ast"); - auto &ast_ctx = *holder->GetAST(); - DWARFASTParserClangStub ast_parser(ast_ctx); + DWARFASTParserClangYAMLTester tester(BufferOrError.get()->getBuffer()); + DWARFDIE cu_die = tester.GetCUDIE(); llvm::SmallVector<lldb::TypeSP, 2> specializations; for (DWARFDIE die : cu_die.children()) { SymbolContext sc; bool new_type = false; - auto type = ast_parser.ParseTypeFromDWARF(sc, die, &new_type); + auto type = tester.GetParser().ParseTypeFromDWARF(sc, die, &new_type); llvm::StringRef die_name = llvm::StringRef(die.GetName()); if (die_name.starts_with("_Optional_payload")) { specializations.push_back(std::move(type)); @@ -730,18 +743,8 @@ DWARF: - AbbrCode: 0x00 # end of child tags of 0x0c ... )"; - YAMLModuleTester t(yamldata); - - DWARFUnit *unit = t.GetDwarfUnit(); - ASSERT_NE(unit, nullptr); - const DWARFDebugInfoEntry *cu_entry = unit->DIE().GetDIE(); - ASSERT_EQ(cu_entry->Tag(), DW_TAG_compile_unit); - ASSERT_EQ(unit->GetDWARFLanguageType(), DW_LANG_C_plus_plus); - DWARFDIE cu_die(unit, cu_entry); - - auto holder = std::make_unique<clang_utils::TypeSystemClangHolder>("ast"); - auto &ast_ctx = *holder->GetAST(); - DWARFASTParserClangStub ast_parser(ast_ctx); + DWARFASTParserClangYAMLTester tester(yamldata); + DWARFDIE cu_die = tester.GetCUDIE(); DWARFDIE decl_die; DWARFDIE def_die; @@ -762,6 +765,8 @@ DWARF: ParsedDWARFTypeAttributes attrs(def_die); ASSERT_TRUE(attrs.decl.IsValid()); + DWARFASTParserClang &ast_parser = tester.GetParser(); + SymbolContext sc; bool new_type = false; lldb::TypeSP type_sp = ast_parser.ParseTypeFromDWARF(sc, decl_die, &new_type); @@ -906,18 +911,8 @@ DWARF: - AbbrCode: 0x0 ... )"; - YAMLModuleTester t(yamldata); - - DWARFUnit *unit = t.GetDwarfUnit(); - ASSERT_NE(unit, nullptr); - const DWARFDebugInfoEntry *cu_entry = unit->DIE().GetDIE(); - ASSERT_EQ(cu_entry->Tag(), DW_TAG_compile_unit); - ASSERT_EQ(unit->GetDWARFLanguageType(), DW_LANG_C_plus_plus); - DWARFDIE cu_die(unit, cu_entry); - - auto holder = std::make_unique<clang_utils::TypeSystemClangHolder>("ast"); - auto &ast_ctx = *holder->GetAST(); - DWARFASTParserClangStub ast_parser(ast_ctx); + DWARFASTParserClangYAMLTester tester(yamldata); + DWARFDIE cu_die = tester.GetCUDIE(); auto context_die = cu_die.GetFirstChild(); ASSERT_TRUE(context_die.IsValid()); @@ -932,7 +927,8 @@ DWARF: auto param_die = decl_die.GetFirstChild(); ASSERT_TRUE(param_die.IsValid()); - EXPECT_EQ(param_die, ast_parser.GetObjectParameter(decl_die, context_die)); + EXPECT_EQ(param_die, + tester.GetParser().GetObjectParameter(decl_die, context_die)); } { @@ -945,8 +941,8 @@ DWARF: auto param_die = subprogram_definition.GetFirstChild(); ASSERT_TRUE(param_die.IsValid()); - EXPECT_EQ(param_die, ast_parser.GetObjectParameter(subprogram_definition, - context_die)); + EXPECT_EQ(param_die, tester.GetParser().GetObjectParameter( + subprogram_definition, context_die)); } } @@ -1076,18 +1072,8 @@ DWARF: - AbbrCode: 0x0 ... )"; - YAMLModuleTester t(yamldata); - - DWARFUnit *unit = t.GetDwarfUnit(); - ASSERT_NE(unit, nullptr); - const DWARFDebugInfoEntry *cu_entry = unit->DIE().GetDIE(); - ASSERT_EQ(cu_entry->Tag(), DW_TAG_compile_unit); - ASSERT_EQ(unit->GetDWARFLanguageType(), DW_LANG_C_plus_plus); - DWARFDIE cu_die(unit, cu_entry); - - auto holder = std::make_unique<clang_utils::TypeSystemClangHolder>("ast"); - auto &ast_ctx = *holder->GetAST(); - DWARFASTParserClangStub ast_parser(ast_ctx); + DWARFASTParserClangYAMLTester tester(yamldata); + DWARFDIE cu_die = tester.GetCUDIE(); auto context_die = cu_die.GetFirstChild(); ASSERT_TRUE(context_die.IsValid()); @@ -1105,7 +1091,7 @@ DWARF: auto param_die = subprogram_definition.GetFirstChild(); ASSERT_TRUE(param_die.IsValid()); EXPECT_EQ(param_die, - ast_parser.GetObjectParameter(subprogram_definition, {})); + tester.GetParser().GetObjectParameter(subprogram_definition, {})); } TEST_F(DWARFASTParserClangTests, TestParseSubroutine_ExplicitObjectParameter) { @@ -1243,21 +1229,15 @@ DWARF: - AbbrCode: 0x0 ... )"; - YAMLModuleTester t(yamldata); - - DWARFUnit *unit = t.GetDwarfUnit(); - ASSERT_NE(unit, nullptr); - const DWARFDebugInfoEntry *cu_entry = unit->DIE().GetDIE(); - ASSERT_EQ(cu_entry->Tag(), DW_TAG_compile_unit); - ASSERT_EQ(unit->GetDWARFLanguageType(), DW_LANG_C_plus_plus); - DWARFDIE cu_die(unit, cu_entry); + DWARFASTParserClangYAMLTester tester(yamldata); + DWARFDIE cu_die = tester.GetCUDIE(); auto ts_or_err = cu_die.GetDWARF()->GetTypeSystemForLanguage(eLanguageTypeC_plus_plus); ASSERT_TRUE(static_cast<bool>(ts_or_err)); llvm::consumeError(ts_or_err.takeError()); auto *parser = - static_cast<DWARFASTParserClang *>((*ts_or_err)->GetDWARFParser()); + llvm::cast<DWARFASTParserClang>((*ts_or_err)->GetDWARFParser()); auto context_die = cu_die.GetFirstChild(); ASSERT_TRUE(context_die.IsValid()); @@ -1419,22 +1399,8 @@ DWARF: - AbbrCode: 0x0 ... )"; - YAMLModuleTester t(yamldata); - - DWARFUnit *unit = t.GetDwarfUnit(); - ASSERT_NE(unit, nullptr); - const DWARFDebugInfoEntry *cu_entry = unit->DIE().GetDIE(); - ASSERT_EQ(cu_entry->Tag(), DW_TAG_compile_unit); - ASSERT_EQ(unit->GetDWARFLanguageType(), DW_LANG_C_plus_plus); - DWARFDIE cu_die(unit, cu_entry); - - auto ts_or_err = - cu_die.GetDWARF()->GetTypeSystemForLanguage(eLanguageTypeC_plus_plus); - ASSERT_TRUE(static_cast<bool>(ts_or_err)); - llvm::consumeError(ts_or_err.takeError()); - - auto *ts = static_cast<TypeSystemClang *>(ts_or_err->get()); - auto *parser = static_cast<DWARFASTParserClang *>(ts->GetDWARFParser()); + DWARFASTParserClangYAMLTester tester(yamldata); + DWARFDIE cu_die = tester.GetCUDIE(); auto subprogram = cu_die.GetFirstChild(); ASSERT_TRUE(subprogram.IsValid()); @@ -1442,11 +1408,13 @@ DWARF: SymbolContext sc; bool new_type; - auto type_sp = parser->ParseTypeFromDWARF(sc, subprogram, &new_type); + auto type_sp = + tester.GetParser().ParseTypeFromDWARF(sc, subprogram, &new_type); ASSERT_NE(type_sp, nullptr); - auto result = ts->GetTranslationUnitDecl()->lookup( - clang_utils::getDeclarationName(*ts, "func")); + TypeSystemClang &ts = tester.GetTypeSystem(); + auto result = ts.GetTranslationUnitDecl()->lookup( + clang_utils::getDeclarationName(ts, "func")); ASSERT_TRUE(result.isSingleResult()); auto const *func = llvm::cast<clang::FunctionDecl>(result.front()); @@ -1609,19 +1577,8 @@ DWARF: - AbbrCode: 0x0 ... )"; - - YAMLModuleTester t(yamldata); - - DWARFUnit *unit = t.GetDwarfUnit(); - ASSERT_NE(unit, nullptr); - const DWARFDebugInfoEntry *cu_entry = unit->DIE().GetDIE(); - ASSERT_EQ(cu_entry->Tag(), DW_TAG_compile_unit); - ASSERT_EQ(unit->GetDWARFLanguageType(), DW_LANG_C_plus_plus); - DWARFDIE cu_die(unit, cu_entry); - - auto holder = std::make_unique<clang_utils::TypeSystemClangHolder>("ast"); - auto &ast_ctx = *holder->GetAST(); - DWARFASTParserClangStub ast_parser(ast_ctx); + DWARFASTParserClangYAMLTester tester(yamldata); + DWARFDIE cu_die = tester.GetCUDIE(); auto context_die = cu_die.GetFirstChild(); ASSERT_TRUE(context_die.IsValid()); @@ -1640,7 +1597,8 @@ DWARF: auto param_die = sub1.GetFirstChild().GetSibling(); ASSERT_TRUE(param_die.IsValid()); - EXPECT_EQ(param_die, ast_parser.GetObjectParameter(sub1, context_die)); + EXPECT_EQ(param_die, + tester.GetParser().GetObjectParameter(sub1, context_die)); } // Object parameter is at constant index 0 @@ -1648,7 +1606,8 @@ DWARF: auto param_die = sub2.GetFirstChild(); ASSERT_TRUE(param_die.IsValid()); - EXPECT_EQ(param_die, ast_parser.GetObjectParameter(sub2, context_die)); + EXPECT_EQ(param_die, + tester.GetParser().GetObjectParameter(sub2, context_die)); } } @@ -1711,19 +1670,8 @@ DWARF: - Value: 0x02 ... )"; - - YAMLModuleTester t(yamldata); - - DWARFUnit *unit = t.GetDwarfUnit(); - ASSERT_NE(unit, nullptr); - const DWARFDebugInfoEntry *cu_entry = unit->DIE().GetDIE(); - ASSERT_EQ(cu_entry->Tag(), DW_TAG_compile_unit); - ASSERT_EQ(unit->GetDWARFLanguageType(), DW_LANG_C_plus_plus); - DWARFDIE cu_die(unit, cu_entry); - - auto holder = std::make_unique<clang_utils::TypeSystemClangHolder>("ast"); - auto &ast_ctx = *holder->GetAST(); - DWARFASTParserClangStub ast_parser(ast_ctx); + DWARFASTParserClangYAMLTester tester(yamldata); + DWARFDIE cu_die = tester.GetCUDIE(); auto type_die = cu_die.GetFirstChild(); ASSERT_TRUE(type_die.IsValid()); @@ -1734,10 +1682,439 @@ DWARF: EXPECT_EQ(attrs.data_bit_size.value_or(0), 2U); SymbolContext sc; - auto type_sp = - ast_parser.ParseTypeFromDWARF(sc, type_die, /*type_is_new_ptr=*/nullptr); + auto type_sp = tester.GetParser().ParseTypeFromDWARF( + sc, type_die, /*type_is_new_ptr=*/nullptr); ASSERT_NE(type_sp, nullptr); EXPECT_EQ(llvm::expectedToOptional(type_sp->GetByteSize(nullptr)).value_or(0), 1U); } + +TEST_F(DWARFASTParserClangTests, TestBitIntParsing) { + // Tests that we correctly parse the DW_AT_base_type for a _BitInt. + // Older versions of Clang only emit the `_BitInt` string into the + // DW_AT_name (not including the bitsize). Make sure we understand + // those too. + + const char *yamldata = R"( +--- !ELF +FileHeader: + Class: ELFCLASS64 + Data: ELFDATA2LSB + Type: ET_EXEC + Machine: EM_AARCH64 +DWARF: + debug_str: + - _BitInt(2) + - _BitInt + - unsigned _BitInt(2) + - unsigned _BitInt + debug_abbrev: + - ID: 0 + Table: + - Code: 0x1 + Tag: DW_TAG_compile_unit + Children: DW_CHILDREN_yes + Attributes: + - Attribute: DW_AT_language + Form: DW_FORM_data2 + - Code: 0x2 + Tag: DW_TAG_base_type + Children: DW_CHILDREN_no + Attributes: + - Attribute: DW_AT_name + Form: DW_FORM_strp + - Attribute: DW_AT_encoding + Form: DW_FORM_data1 + - Attribute: DW_AT_byte_size + Form: DW_FORM_data1 + - Attribute: DW_AT_bit_size + Form: DW_FORM_data1 + - Code: 0x3 + Tag: DW_TAG_base_type + Children: DW_CHILDREN_no + Attributes: + - Attribute: DW_AT_name + Form: DW_FORM_strp + - Attribute: DW_AT_encoding + Form: DW_FORM_data1 + - Attribute: DW_AT_byte_size + Form: DW_FORM_data1 + + debug_info: + - Version: 5 + UnitType: DW_UT_compile + AddrSize: 8 + Entries: + +# DW_TAG_compile_unit +# DW_AT_language [DW_FORM_data2] (DW_LANG_C_plus_plus) + + - AbbrCode: 0x1 + Values: + - Value: 0x04 + +# DW_TAG_base_type +# DW_AT_name [DW_FORM_strp] ('_BitInt(2)') + + - AbbrCode: 0x2 + Values: + - Value: 0x0 + - Value: 0x05 + - Value: 0x01 + - Value: 0x02 + +# DW_TAG_base_type +# DW_AT_name [DW_FORM_strp] ('_BitInt') + + - AbbrCode: 0x2 + Values: + - Value: 0x0b + - Value: 0x05 + - Value: 0x08 + - Value: 0x34 + +# DW_TAG_base_type +# DW_AT_name [DW_FORM_strp] ('unsigned _BitInt(2)') + + - AbbrCode: 0x2 + Values: + - Value: 0x13 + - Value: 0x07 + - Value: 0x01 + - Value: 0x02 + +# DW_TAG_base_type +# DW_AT_name [DW_FORM_strp] ('unsigned _BitInt') + + - AbbrCode: 0x2 + Values: + - Value: 0x27 + - Value: 0x07 + - Value: 0x08 + - Value: 0x34 + +# DW_TAG_base_type +# DW_AT_name [DW_FORM_strp] ('_BitInt') + + - AbbrCode: 0x3 + Values: + - Value: 0x0b + - Value: 0x05 + - Value: 0x08 +... + +)"; + DWARFASTParserClangYAMLTester tester(yamldata); + DWARFDIE cu_die = tester.GetCUDIE(); + + auto type_die = cu_die.GetFirstChild(); + ASSERT_TRUE(type_die.IsValid()); + + { + SymbolContext sc; + auto type_sp = + tester.GetParser().ParseTypeFromDWARF(sc, type_die, + /*type_is_new_ptr=*/nullptr); + ASSERT_NE(type_sp, nullptr); + + EXPECT_EQ( + llvm::expectedToOptional(type_sp->GetByteSize(nullptr)).value_or(0), + 1U); + EXPECT_EQ(type_sp->GetEncoding(), lldb::eEncodingSint); + EXPECT_EQ(type_sp->GetName(), "_BitInt(2)"); + EXPECT_EQ(type_sp->GetForwardCompilerType().GetTypeName(), "_BitInt(2)"); + } + + { + type_die = type_die.GetSibling(); + SymbolContext sc; + auto type_sp = + tester.GetParser().ParseTypeFromDWARF(sc, type_die, + /*type_is_new_ptr=*/nullptr); + ASSERT_NE(type_sp, nullptr); + + EXPECT_EQ( + llvm::expectedToOptional(type_sp->GetByteSize(nullptr)).value_or(0), + 8U); + EXPECT_EQ(type_sp->GetEncoding(), lldb::eEncodingSint); + EXPECT_EQ(type_sp->GetName(), "_BitInt"); + EXPECT_EQ(type_sp->GetForwardCompilerType().GetTypeName(), "_BitInt(52)"); + } + + { + type_die = type_die.GetSibling(); + SymbolContext sc; + auto type_sp = + tester.GetParser().ParseTypeFromDWARF(sc, type_die, + /*type_is_new_ptr=*/nullptr); + ASSERT_NE(type_sp, nullptr); + + EXPECT_EQ( + llvm::expectedToOptional(type_sp->GetByteSize(nullptr)).value_or(0), + 1U); + EXPECT_EQ(type_sp->GetEncoding(), lldb::eEncodingUint); + EXPECT_EQ(type_sp->GetName(), "unsigned _BitInt(2)"); + EXPECT_EQ(type_sp->GetForwardCompilerType().GetTypeName(), + "unsigned _BitInt(2)"); + } + + { + type_die = type_die.GetSibling(); + SymbolContext sc; + auto type_sp = + tester.GetParser().ParseTypeFromDWARF(sc, type_die, + /*type_is_new_ptr=*/nullptr); + ASSERT_NE(type_sp, nullptr); + + EXPECT_EQ( + llvm::expectedToOptional(type_sp->GetByteSize(nullptr)).value_or(0), + 8U); + EXPECT_EQ(type_sp->GetEncoding(), lldb::eEncodingUint); + EXPECT_EQ(type_sp->GetName(), "unsigned _BitInt"); + EXPECT_EQ(type_sp->GetForwardCompilerType().GetTypeName(), + "unsigned _BitInt(52)"); + } + + { + type_die = type_die.GetSibling(); + SymbolContext sc; + auto type_sp = + tester.GetParser().ParseTypeFromDWARF(sc, type_die, + /*type_is_new_ptr=*/nullptr); + ASSERT_NE(type_sp, nullptr); + + EXPECT_EQ( + llvm::expectedToOptional(type_sp->GetByteSize(nullptr)).value_or(0), + 8U); + EXPECT_EQ(type_sp->GetEncoding(), lldb::eEncodingSint); + EXPECT_EQ(type_sp->GetName(), "_BitInt"); + + // Older versions of Clang didn't emit a DW_AT_bit_size for _BitInt. In + // those cases we would format the CompilerType name using the byte-size. + EXPECT_EQ(type_sp->GetForwardCompilerType().GetTypeName(), "_BitInt(64)"); + } +} + +TEST_F(DWARFASTParserClangTests, TestTemplateAlias_NoSimpleTemplateNames) { + // Tests that we correctly parse the DW_TAG_template_alias generated by + // -gno-simple-template-names. + + const char *yamldata = R"( +--- !ELF +FileHeader: + Class: ELFCLASS64 + Data: ELFDATA2LSB + Type: ET_EXEC + Machine: EM_AARCH64 +DWARF: + debug_abbrev: + - ID: 0 + Table: + - Code: 0x1 + Tag: DW_TAG_compile_unit + Children: DW_CHILDREN_yes + Attributes: + - Attribute: DW_AT_language + Form: DW_FORM_data2 + - Code: 0x2 + Tag: DW_TAG_base_type + Children: DW_CHILDREN_no + Attributes: + - Attribute: DW_AT_name + Form: DW_FORM_string + - Code: 0x3 + Tag: DW_TAG_template_alias + Children: DW_CHILDREN_yes + Attributes: + - Attribute: DW_AT_name + Form: DW_FORM_string + - Attribute: DW_AT_type + Form: DW_FORM_ref4 + - Code: 0x4 + Tag: DW_TAG_template_type_parameter + Children: DW_CHILDREN_no + Attributes: + - Attribute: DW_AT_name + Form: DW_FORM_string + - Attribute: DW_AT_type + Form: DW_FORM_ref4 + + debug_info: + - Version: 5 + UnitType: DW_UT_compile + AddrSize: 8 + Entries: + +# DW_TAG_compile_unit +# DW_AT_language (DW_LANG_C_plus_plus) + + - AbbrCode: 0x1 + Values: + - Value: 0x04 + +# DW_TAG_base_type +# DW_AT_name ('int') + + - AbbrCode: 0x2 + Values: + - CStr: int + +# DW_TAG_template_alias +# DW_AT_name ('Foo<int>') +# DW_AT_type ('int') +# DW_TAG_template_type_parameter +# DW_AT_name ('T') +# DW_AT_type ('int') + + - AbbrCode: 0x3 + Values: + - CStr: Foo<int> + - Value: 0xf + + - AbbrCode: 0x4 + Values: + - CStr: T + - Value: 0xf + + - AbbrCode: 0x0 + - AbbrCode: 0x0 +... +)"; + DWARFASTParserClangYAMLTester tester(yamldata); + DWARFDIE cu_die = tester.GetCUDIE(); + + auto alias_die = cu_die.GetFirstChild().GetSibling(); + ASSERT_EQ(alias_die.Tag(), DW_TAG_template_alias); + + SymbolContext sc; + auto type_sp = + tester.GetParser().ParseTypeFromDWARF(sc, alias_die, + /*type_is_new_ptr=*/nullptr); + ASSERT_NE(type_sp, nullptr); + + EXPECT_TRUE(type_sp->IsTypedef()); + EXPECT_EQ(type_sp->GetName(), "Foo<int>"); + EXPECT_EQ(type_sp->GetForwardCompilerType().GetTypeName(), "Foo<int>"); +} + +TEST_F(DWARFASTParserClangTests, + TestTemplateAlias_InStruct_NoSimpleTemplateNames) { + // Tests that we correctly parse the DW_TAG_template_alias scoped inside a + // DW_TAG_structure_type *declaration* generated by + // -gno-simple-template-names. This tests the codepath the forcefully + // completes the context of the alias via PrepareContextToReceiveMembers. + + const char *yamldata = R"( +--- !ELF +FileHeader: + Class: ELFCLASS64 + Data: ELFDATA2LSB + Type: ET_EXEC + Machine: EM_AARCH64 +DWARF: + debug_abbrev: + - ID: 0 + Table: + - Code: 0x1 + Tag: DW_TAG_compile_unit + Children: DW_CHILDREN_yes + Attributes: + - Attribute: DW_AT_language + Form: DW_FORM_data2 + - Code: 0x2 + Tag: DW_TAG_base_type + Children: DW_CHILDREN_no + Attributes: + - Attribute: DW_AT_name + Form: DW_FORM_string + - Code: 0x3 + Tag: DW_TAG_structure_type + Children: DW_CHILDREN_yes + Attributes: + - Attribute: DW_AT_name + Form: DW_FORM_string + - Attribute: DW_AT_declaration + Form: DW_FORM_flag_present + - Code: 0x4 + Tag: DW_TAG_template_alias + Children: DW_CHILDREN_yes + Attributes: + - Attribute: DW_AT_name + Form: DW_FORM_string + - Attribute: DW_AT_type + Form: DW_FORM_ref4 + - Code: 0x5 + Tag: DW_TAG_template_type_parameter + Children: DW_CHILDREN_no + Attributes: + - Attribute: DW_AT_name + Form: DW_FORM_string + - Attribute: DW_AT_type + Form: DW_FORM_ref4 + + debug_info: + - Version: 5 + UnitType: DW_UT_compile + AddrSize: 8 + Entries: + +# DW_TAG_compile_unit +# DW_AT_language (DW_LANG_C_plus_plus) + + - AbbrCode: 0x1 + Values: + - Value: 0x04 + +# DW_TAG_base_type +# DW_AT_name ('int') + + - AbbrCode: 0x2 + Values: + - CStr: int + +# DW_TAG_structure_type +# DW_AT_name ('Foo') + + - AbbrCode: 0x3 + Values: + - CStr: Foo + +# DW_TAG_template_alias +# DW_AT_name ('Bar<int>') +# DW_AT_type ('int') +# DW_TAG_template_type_parameter +# DW_AT_name ('T') +# DW_AT_type ('int') + + - AbbrCode: 0x4 + Values: + - CStr: Bar<int> + - Value: 0xf + + - AbbrCode: 0x5 + Values: + - CStr: T + - Value: 0xf + + - AbbrCode: 0x0 + - AbbrCode: 0x0 + - AbbrCode: 0x0 +... +)"; + DWARFASTParserClangYAMLTester tester(yamldata); + DWARFDIE cu_die = tester.GetCUDIE(); + + auto alias_die = cu_die.GetFirstChild().GetSibling().GetFirstChild(); + ASSERT_EQ(alias_die.Tag(), DW_TAG_template_alias); + + SymbolContext sc; + auto type_sp = + tester.GetParser().ParseTypeFromDWARF(sc, alias_die, + /*type_is_new_ptr=*/nullptr); + ASSERT_NE(type_sp, nullptr); + + EXPECT_TRUE(type_sp->IsTypedef()); + EXPECT_EQ(type_sp->GetName(), "Bar<int>"); + EXPECT_EQ(type_sp->GetForwardCompilerType().GetTypeName(), "Foo::Bar<int>"); +} diff --git a/lldb/unittests/SymbolFile/NativePDB/UdtRecordCompleterTests.cpp b/lldb/unittests/SymbolFile/NativePDB/UdtRecordCompleterTests.cpp index 17284b6..cd6db5f 100644 --- a/lldb/unittests/SymbolFile/NativePDB/UdtRecordCompleterTests.cpp +++ b/lldb/unittests/SymbolFile/NativePDB/UdtRecordCompleterTests.cpp @@ -99,7 +99,7 @@ Member *AddField(Member *member, StringRef name, uint64_t byte_offset, std::make_unique<Member>(name, byte_offset * 8, byte_size * 8, clang::QualType(), lldb::eAccessPublic, 0); field->kind = kind; - field->base_offset = base_offset; + field->base_offset = base_offset * 8; member->fields.push_back(std::move(field)); return member->fields.back().get(); } @@ -111,6 +111,9 @@ TEST_F(UdtRecordCompleterRecordTests, TestAnonymousUnionInStruct) { CollectMember("m2", 0, 4); CollectMember("m3", 0, 1); CollectMember("m4", 0, 8); + CollectMember("m5", 8, 8); + CollectMember("m6", 16, 4); + CollectMember("m7", 16, 8); ConstructRecord(); // struct { @@ -120,6 +123,11 @@ TEST_F(UdtRecordCompleterRecordTests, TestAnonymousUnionInStruct) { // m3; // m4; // }; + // m5; + // union { + // m6; + // m7; + // }; // }; Record record; record.start_offset = 0; @@ -128,6 +136,10 @@ TEST_F(UdtRecordCompleterRecordTests, TestAnonymousUnionInStruct) { AddField(u, "m2", 0, 4, Member::Field); AddField(u, "m3", 0, 1, Member::Field); AddField(u, "m4", 0, 8, Member::Field); + AddField(&record.record, "m5", 8, 8, Member::Field); + Member *u2 = AddField(&record.record, "", 16, 0, Member::Union); + AddField(u2, "m6", 16, 4, Member::Field); + AddField(u2, "m7", 16, 8, Member::Field); EXPECT_EQ(WrappedRecord(this->record), WrappedRecord(record)); } @@ -243,3 +255,41 @@ TEST_F(UdtRecordCompleterRecordTests, TestNestedUnionStructInUnion) { AddField(s2, "m4", 2, 4, Member::Field); EXPECT_EQ(WrappedRecord(this->record), WrappedRecord(record)); } + +TEST_F(UdtRecordCompleterRecordTests, TestNestedStructInUnionInStructInUnion) { + SetKind(Member::Kind::Union); + CollectMember("m1", 0, 4); + CollectMember("m2", 0, 2); + CollectMember("m3", 0, 2); + CollectMember("m4", 2, 4); + CollectMember("m5", 6, 2); + CollectMember("m6", 6, 2); + CollectMember("m7", 8, 2); + ConstructRecord(); + + // union { + // m1; + // m2; + // struct { + // m3; + // m4; + // union { + // m5; + // m6; + // }; + // m7; + // }; + // }; + Record record; + record.start_offset = 0; + AddField(&record.record, "m1", 0, 4, Member::Field); + AddField(&record.record, "m2", 0, 2, Member::Field); + Member *s = AddField(&record.record, "", 0, 0, Member::Struct); + AddField(s, "m3", 0, 2, Member::Field); + AddField(s, "m4", 2, 4, Member::Field); + Member *u = AddField(s, "", 6, 0, Member::Union); + AddField(u, "m5", 6, 2, Member::Field); + AddField(u, "m6", 6, 2, Member::Field); + AddField(s, "m7", 8, 2, Member::Field); + EXPECT_EQ(WrappedRecord(this->record), WrappedRecord(record)); +} diff --git a/lldb/unittests/Target/LocateModuleCallbackTest.cpp b/lldb/unittests/Target/LocateModuleCallbackTest.cpp index 6ffa41b..d727cea 100644 --- a/lldb/unittests/Target/LocateModuleCallbackTest.cpp +++ b/lldb/unittests/Target/LocateModuleCallbackTest.cpp @@ -362,7 +362,7 @@ TEST_F(LocateModuleCallbackTest, GetOrCreateModuleCallbackFailureNoCache) { }); m_module_sp = m_target_sp->GetOrCreateModule(m_module_spec, /*notify=*/false); - ASSERT_EQ(callback_call_count, 2); + ASSERT_EQ(callback_call_count, 3); ASSERT_FALSE(m_module_sp); } @@ -383,7 +383,7 @@ TEST_F(LocateModuleCallbackTest, GetOrCreateModuleCallbackFailureCached) { }); m_module_sp = m_target_sp->GetOrCreateModule(m_module_spec, /*notify=*/false); - ASSERT_EQ(callback_call_count, 2); + ASSERT_EQ(callback_call_count, 3); CheckModule(m_module_sp); ASSERT_EQ(m_module_sp->GetFileSpec(), uuid_view); ASSERT_FALSE(m_module_sp->GetSymbolFileFileSpec()); @@ -409,7 +409,7 @@ TEST_F(LocateModuleCallbackTest, GetOrCreateModuleCallbackNoFiles) { }); m_module_sp = m_target_sp->GetOrCreateModule(m_module_spec, /*notify=*/false); - ASSERT_EQ(callback_call_count, 2); + ASSERT_EQ(callback_call_count, 3); CheckModule(m_module_sp); ASSERT_EQ(m_module_sp->GetFileSpec(), uuid_view); ASSERT_FALSE(m_module_sp->GetSymbolFileFileSpec()); @@ -435,7 +435,7 @@ TEST_F(LocateModuleCallbackTest, GetOrCreateModuleCallbackNonExistentModule) { }); m_module_sp = m_target_sp->GetOrCreateModule(m_module_spec, /*notify=*/false); - ASSERT_EQ(callback_call_count, 2); + ASSERT_EQ(callback_call_count, 3); CheckModule(m_module_sp); ASSERT_EQ(m_module_sp->GetFileSpec(), uuid_view); ASSERT_FALSE(m_module_sp->GetSymbolFileFileSpec()); @@ -464,7 +464,7 @@ TEST_F(LocateModuleCallbackTest, GetOrCreateModuleCallbackNonExistentSymbol) { }); m_module_sp = m_target_sp->GetOrCreateModule(m_module_spec, /*notify=*/false); - ASSERT_EQ(callback_call_count, 2); + ASSERT_EQ(callback_call_count, 3); CheckModule(m_module_sp); ASSERT_EQ(m_module_sp->GetFileSpec(), uuid_view); ASSERT_TRUE(m_module_sp->GetSymbolFileFileSpec().GetPath().empty()); @@ -622,7 +622,7 @@ TEST_F(LocateModuleCallbackTest, }); m_module_sp = m_target_sp->GetOrCreateModule(m_module_spec, /*notify=*/false); - ASSERT_EQ(callback_call_count, 2); + ASSERT_EQ(callback_call_count, 3); CheckModule(m_module_sp); ASSERT_EQ(m_module_sp->GetFileSpec(), uuid_view); ASSERT_EQ(m_module_sp->GetSymbolFileFileSpec(), @@ -650,7 +650,7 @@ TEST_F(LocateModuleCallbackTest, }); m_module_sp = m_target_sp->GetOrCreateModule(m_module_spec, /*notify=*/false); - ASSERT_EQ(callback_call_count, 2); + ASSERT_EQ(callback_call_count, 3); CheckModule(m_module_sp); ASSERT_EQ(m_module_sp->GetFileSpec(), uuid_view); ASSERT_EQ(m_module_sp->GetSymbolFileFileSpec(), @@ -682,7 +682,7 @@ TEST_F(LocateModuleCallbackTest, }); m_module_sp = m_target_sp->GetOrCreateModule(m_module_spec, /*notify=*/false); - ASSERT_EQ(callback_call_count, 2); + ASSERT_EQ(callback_call_count, 3); CheckModule(m_module_sp); ASSERT_EQ(m_module_sp->GetFileSpec(), uuid_view); ASSERT_EQ(m_module_sp->GetSymbolFileFileSpec(), @@ -709,7 +709,7 @@ TEST_F(LocateModuleCallbackTest, }); m_module_sp = m_target_sp->GetOrCreateModule(m_module_spec, /*notify=*/false); - ASSERT_EQ(callback_call_count, 2); + ASSERT_EQ(callback_call_count, 3); ASSERT_FALSE(m_module_sp); } @@ -731,7 +731,7 @@ TEST_F(LocateModuleCallbackTest, }); m_module_sp = m_target_sp->GetOrCreateModule(m_module_spec, /*notify=*/false); - ASSERT_EQ(callback_call_count, 2); + ASSERT_EQ(callback_call_count, 3); ASSERT_FALSE(m_module_sp); } diff --git a/lldb/unittests/Target/MemoryTest.cpp b/lldb/unittests/Target/MemoryTest.cpp index e444f68..131a3ca 100644 --- a/lldb/unittests/Target/MemoryTest.cpp +++ b/lldb/unittests/Target/MemoryTest.cpp @@ -48,6 +48,8 @@ public: } Status DoDestroy() override { return {}; } void RefreshStateAfterStop() override {} + // Required by Target::ReadMemory() to call Process::ReadMemory() + bool IsAlive() override { return true; } size_t DoReadMemory(lldb::addr_t vm_addr, void *buf, size_t size, Status &error) override { if (m_bytes_left == 0) @@ -61,7 +63,7 @@ public: m_bytes_left -= size; } - memset(buf, 'B', num_bytes_to_write); + memset(buf, m_filler, num_bytes_to_write); return num_bytes_to_write; } bool DoUpdateThreadList(ThreadList &old_thread_list, @@ -72,8 +74,10 @@ public: // Test-specific additions size_t m_bytes_left; + int m_filler = 'B'; MemoryCache &GetMemoryCache() { return m_memory_cache; } void SetMaxReadSize(size_t size) { m_bytes_left = size; } + void SetFiller(int filler) { m_filler = filler; } }; } // namespace @@ -85,6 +89,18 @@ TargetSP CreateTarget(DebuggerSP &debugger_sp, ArchSpec &arch) { return target_sp; } +static ProcessSP CreateProcess(lldb::TargetSP target_sp) { + ListenerSP listener_sp(Listener::MakeListener("dummy")); + ProcessSP process_sp = std::make_shared<DummyProcess>(target_sp, listener_sp); + + struct TargetHack : public Target { + void SetProcess(ProcessSP process) { m_process_sp = process; } + }; + static_cast<TargetHack *>(target_sp.get())->SetProcess(process_sp); + + return process_sp; +} + TEST_F(MemoryTest, TesetMemoryCacheRead) { ArchSpec arch("x86_64-apple-macosx-"); @@ -96,8 +112,7 @@ TEST_F(MemoryTest, TesetMemoryCacheRead) { TargetSP target_sp = CreateTarget(debugger_sp, arch); ASSERT_TRUE(target_sp); - ListenerSP listener_sp(Listener::MakeListener("dummy")); - ProcessSP process_sp = std::make_shared<DummyProcess>(target_sp, listener_sp); + ProcessSP process_sp = CreateProcess(target_sp); ASSERT_TRUE(process_sp); DummyProcess *process = static_cast<DummyProcess *>(process_sp.get()); @@ -227,6 +242,58 @@ TEST_F(MemoryTest, TesetMemoryCacheRead) { // old cache } +TEST_F(MemoryTest, TestReadInteger) { + ArchSpec arch("x86_64-apple-macosx-"); + + Platform::SetHostPlatform(PlatformRemoteMacOSX::CreateInstance(true, &arch)); + + DebuggerSP debugger_sp = Debugger::CreateInstance(); + ASSERT_TRUE(debugger_sp); + + TargetSP target_sp = CreateTarget(debugger_sp, arch); + ASSERT_TRUE(target_sp); + + ProcessSP process_sp = CreateProcess(target_sp); + ASSERT_TRUE(process_sp); + + DummyProcess *process = static_cast<DummyProcess *>(process_sp.get()); + Status error; + + process->SetFiller(0xff); + process->SetMaxReadSize(256); + // The ReadSignedIntegerFromMemory() methods return int64_t. Check that they + // extend the sign correctly when reading 32-bit values. + EXPECT_EQ(-1, + target_sp->ReadSignedIntegerFromMemory(Address(0), 4, 0, error)); + EXPECT_EQ(-1, process->ReadSignedIntegerFromMemory(0, 4, 0, error)); + // Check reading 64-bit values as well. + EXPECT_EQ(-1, + target_sp->ReadSignedIntegerFromMemory(Address(0), 8, 0, error)); + EXPECT_EQ(-1, process->ReadSignedIntegerFromMemory(0, 8, 0, error)); + + // ReadUnsignedIntegerFromMemory() should not extend the sign. + EXPECT_EQ(0xffffffffULL, + target_sp->ReadUnsignedIntegerFromMemory(Address(0), 4, 0, error)); + EXPECT_EQ(0xffffffffULL, + process->ReadUnsignedIntegerFromMemory(0, 4, 0, error)); + EXPECT_EQ(0xffffffffffffffffULL, + target_sp->ReadUnsignedIntegerFromMemory(Address(0), 8, 0, error)); + EXPECT_EQ(0xffffffffffffffffULL, + process->ReadUnsignedIntegerFromMemory(0, 8, 0, error)); + + // Check reading positive values. + process->GetMemoryCache().Clear(); + process->SetFiller(0x7f); + process->SetMaxReadSize(256); + EXPECT_EQ(0x7f7f7f7fLL, + target_sp->ReadSignedIntegerFromMemory(Address(0), 4, 0, error)); + EXPECT_EQ(0x7f7f7f7fLL, process->ReadSignedIntegerFromMemory(0, 4, 0, error)); + EXPECT_EQ(0x7f7f7f7f7f7f7f7fLL, + target_sp->ReadSignedIntegerFromMemory(Address(0), 8, 0, error)); + EXPECT_EQ(0x7f7f7f7f7f7f7f7fLL, + process->ReadSignedIntegerFromMemory(0, 8, 0, error)); +} + /// A process class that, when asked to read memory from some address X, returns /// the least significant byte of X. class DummyReaderProcess : public Process { diff --git a/lldb/unittests/Target/RemoteAwarePlatformTest.cpp b/lldb/unittests/Target/RemoteAwarePlatformTest.cpp index 3278674..cfcec69 100644 --- a/lldb/unittests/Target/RemoteAwarePlatformTest.cpp +++ b/lldb/unittests/Target/RemoteAwarePlatformTest.cpp @@ -32,15 +32,12 @@ public: ProcessSP(ProcessAttachInfo &, Debugger &, Target *, Status &)); MOCK_METHOD0(CalculateTrapHandlerSymbolNames, void()); - MOCK_METHOD2(ResolveExecutable, - std::pair<bool, ModuleSP>(const ModuleSpec &, - const FileSpecList *)); - Status - ResolveExecutable(const ModuleSpec &module_spec, - lldb::ModuleSP &exe_module_sp, - const FileSpecList *module_search_paths_ptr) /*override*/ + MOCK_METHOD1(ResolveExecutable, + std::pair<bool, ModuleSP>(const ModuleSpec &)); + Status ResolveExecutable(const ModuleSpec &module_spec, + lldb::ModuleSP &exe_module_sp) /*override*/ { // NOLINT(modernize-use-override) - auto pair = ResolveExecutable(module_spec, module_search_paths_ptr); + auto pair = ResolveExecutable(module_spec); exe_module_sp = pair.second; return pair.first ? Status() : Status::FromErrorString("error"); } @@ -80,14 +77,14 @@ TEST_F(RemoteAwarePlatformTest, TestResolveExecutabelOnClientByPlatform) { static const ArchSpec process_host_arch; EXPECT_CALL(platform, GetSupportedArchitectures(process_host_arch)) .WillRepeatedly(Return(std::vector<ArchSpec>())); - EXPECT_CALL(platform, ResolveExecutable(_, _)) + EXPECT_CALL(platform, ResolveExecutable(_)) .WillRepeatedly(Return(std::make_pair(true, expected_executable))); platform.SetRemotePlatform(std::make_shared<TargetPlatformTester>(false)); ModuleSP resolved_sp; lldb_private::Status status = - platform.ResolveExecutable(executable_spec, resolved_sp, nullptr); + platform.ResolveExecutable(executable_spec, resolved_sp); ASSERT_TRUE(status.Success()); EXPECT_EQ(expected_executable.get(), resolved_sp.get()); diff --git a/lldb/unittests/TestingSupport/TestUtilities.cpp b/lldb/unittests/TestingSupport/TestUtilities.cpp index b53822e..d164c22 100644 --- a/lldb/unittests/TestingSupport/TestUtilities.cpp +++ b/lldb/unittests/TestingSupport/TestUtilities.cpp @@ -20,6 +20,11 @@ using namespace lldb_private; extern const char *TestMainArgv0; std::once_flag TestUtilities::g_debugger_initialize_flag; + +std::string lldb_private::PrettyPrint(const llvm::json::Value &value) { + return llvm::formatv("{0:2}", value).str(); +} + std::string lldb_private::GetInputFilePath(const llvm::Twine &name) { llvm::SmallString<128> result = llvm::sys::path::parent_path(TestMainArgv0); llvm::sys::fs::make_absolute(result); diff --git a/lldb/unittests/TestingSupport/TestUtilities.h b/lldb/unittests/TestingSupport/TestUtilities.h index cc93a68..f05d176 100644 --- a/lldb/unittests/TestingSupport/TestUtilities.h +++ b/lldb/unittests/TestingSupport/TestUtilities.h @@ -30,6 +30,10 @@ } namespace lldb_private { + +/// Returns a pretty printed json string of a `llvm::json::Value`. +std::string PrettyPrint(const llvm::json::Value &E); + std::string GetInputFilePath(const llvm::Twine &name); class TestUtilities { diff --git a/lldb/unittests/UnwindAssembly/ARM64/TestArm64InstEmulation.cpp b/lldb/unittests/UnwindAssembly/ARM64/TestArm64InstEmulation.cpp index eaf23fd..b72abc1 100644 --- a/lldb/unittests/UnwindAssembly/ARM64/TestArm64InstEmulation.cpp +++ b/lldb/unittests/UnwindAssembly/ARM64/TestArm64InstEmulation.cpp @@ -856,3 +856,233 @@ TEST_F(TestArm64InstEmulation, TestCFAResetToSP) { EXPECT_TRUE(row->GetCFAValue().GetRegisterNumber() == gpr_sp_arm64); EXPECT_TRUE(row->GetCFAValue().IsRegisterPlusOffset() == true); } + +TEST_F(TestArm64InstEmulation, TestPrologueStartsWithStrD8) { + ArchSpec arch("aarch64"); + std::unique_ptr<UnwindAssemblyInstEmulation> engine( + static_cast<UnwindAssemblyInstEmulation *>( + UnwindAssemblyInstEmulation::CreateInstance(arch))); + ASSERT_NE(nullptr, engine); + + const UnwindPlan::Row *row; + AddressRange sample_range; + UnwindPlan unwind_plan(eRegisterKindLLDB); + UnwindPlan::Row::AbstractRegisterLocation regloc; + + // The sample function is built with 'clang --target aarch64 -O1': + // + // int bar(float x); + // int foo(float x) { + // return bar(x) + bar(x); + // } + // + // The function uses one floating point register and spills it with + // 'str d8, [sp, #-0x20]!'. + + // clang-format off + uint8_t data[] = { + // prologue + 0xe8, 0x0f, 0x1e, 0xfc, // 0: fc1e0fe8 str d8, [sp, #-0x20]! + 0xfd, 0xfb, 0x00, 0xa9, // 4: a900fbfd stp x29, x30, [sp, #0x8] + 0xf3, 0x0f, 0x00, 0xf9, // 8: f9000ff3 str x19, [sp, #0x18] + 0xfd, 0x23, 0x00, 0x91, // 12: 910023fd add x29, sp, #0x8 + + // epilogue + 0xfd, 0xfb, 0x40, 0xa9, // 16: a940fbfd ldp x29, x30, [sp, #0x8] + 0xf3, 0x0f, 0x40, 0xf9, // 20: f9400ff3 ldr x19, [sp, #0x18] + 0xe8, 0x07, 0x42, 0xfc, // 24: fc4207e8 ldr d8, [sp], #0x20 + 0xc0, 0x03, 0x5f, 0xd6, // 28: d65f03c0 ret + }; + // clang-format on + + // UnwindPlan we expect: + // 0: CFA=sp +0 => + // 4: CFA=sp+32 => d8=[CFA-32] + // 8: CFA=sp+32 => fp=[CFA-24] lr=[CFA-16] d8=[CFA-32] + // 12: CFA=sp+32 => x19=[CFA-8] fp=[CFA-24] lr=[CFA-16] d8=[CFA-32] + // 16: CFA=fp+24 => x19=[CFA-8] fp=[CFA-24] lr=[CFA-16] d8=[CFA-32] + // 20: CFA=sp+32 => x19=[CFA-8] fp=<same> lr=<same> d8=[CFA-32] + // 24: CFA=sp+32 => x19=<same> fp=<same> lr=<same> d8=[CFA-32] + // 28: CFA=sp +0 => x19=<same> fp=<same> lr=<same> d8=<same> + + sample_range = AddressRange(0x1000, sizeof(data)); + + EXPECT_TRUE(engine->GetNonCallSiteUnwindPlanFromAssembly( + sample_range, data, sizeof(data), unwind_plan)); + + // 4: CFA=sp+32 => d8=[CFA-32] + row = unwind_plan.GetRowForFunctionOffset(4); + EXPECT_EQ(4, row->GetOffset()); + EXPECT_TRUE(row->GetCFAValue().GetRegisterNumber() == gpr_sp_arm64); + EXPECT_TRUE(row->GetCFAValue().IsRegisterPlusOffset() == true); + EXPECT_EQ(32, row->GetCFAValue().GetOffset()); + + EXPECT_TRUE(row->GetRegisterInfo(fpu_d8_arm64, regloc)); + EXPECT_TRUE(regloc.IsAtCFAPlusOffset()); + EXPECT_EQ(-32, regloc.GetOffset()); + + // 16: CFA=fp+24 => x19=[CFA-8] fp=[CFA-24] lr=[CFA-16] d8=[CFA-32] + row = unwind_plan.GetRowForFunctionOffset(16); + EXPECT_EQ(16, row->GetOffset()); + EXPECT_TRUE(row->GetCFAValue().GetRegisterNumber() == gpr_fp_arm64); + EXPECT_TRUE(row->GetCFAValue().IsRegisterPlusOffset() == true); + EXPECT_EQ(24, row->GetCFAValue().GetOffset()); + + EXPECT_TRUE(row->GetRegisterInfo(gpr_x19_arm64, regloc)); + EXPECT_TRUE(regloc.IsAtCFAPlusOffset()); + EXPECT_EQ(-8, regloc.GetOffset()); + + EXPECT_TRUE(row->GetRegisterInfo(gpr_fp_arm64, regloc)); + EXPECT_TRUE(regloc.IsAtCFAPlusOffset()); + EXPECT_EQ(-24, regloc.GetOffset()); + + EXPECT_TRUE(row->GetRegisterInfo(gpr_lr_arm64, regloc)); + EXPECT_TRUE(regloc.IsAtCFAPlusOffset()); + EXPECT_EQ(-16, regloc.GetOffset()); + + EXPECT_TRUE(row->GetRegisterInfo(fpu_d8_arm64, regloc)); + EXPECT_TRUE(regloc.IsAtCFAPlusOffset()); + EXPECT_EQ(-32, regloc.GetOffset()); + + // 28: CFA=sp +0 => x19=<same> fp=<same> lr=<same> d8=<same> + row = unwind_plan.GetRowForFunctionOffset(28); + EXPECT_EQ(28, row->GetOffset()); + EXPECT_TRUE(row->GetCFAValue().GetRegisterNumber() == gpr_sp_arm64); + EXPECT_TRUE(row->GetCFAValue().IsRegisterPlusOffset() == true); + EXPECT_EQ(0, row->GetCFAValue().GetOffset()); + + if (row->GetRegisterInfo(gpr_x19_arm64, regloc)) { + EXPECT_TRUE(regloc.IsSame()); + } + if (row->GetRegisterInfo(gpr_fp_arm64, regloc)) { + EXPECT_TRUE(regloc.IsSame()); + } + if (row->GetRegisterInfo(gpr_lr_arm64, regloc)) { + EXPECT_TRUE(regloc.IsSame()); + } + if (row->GetRegisterInfo(fpu_d8_arm64, regloc)) { + EXPECT_TRUE(regloc.IsSame()); + } +} + +TEST_F(TestArm64InstEmulation, TestMidFunctionEpilogueAndBackwardsJump) { + ArchSpec arch("arm64-apple-ios15"); + std::unique_ptr<UnwindAssemblyInstEmulation> engine( + static_cast<UnwindAssemblyInstEmulation *>( + UnwindAssemblyInstEmulation::CreateInstance(arch))); + ASSERT_NE(nullptr, engine); + + const UnwindPlan::Row *row; + AddressRange sample_range; + UnwindPlan unwind_plan(eRegisterKindLLDB); + UnwindPlan::Row::AbstractRegisterLocation regloc; + + // clang-format off + uint8_t data[] = { + 0xff, 0xc3, 0x00, 0xd1, // <+0>: sub sp, sp, #0x30 + 0xfd, 0x7b, 0x02, 0xa9, // <+4>: stp x29, x30, [sp, #0x20] + 0xfd, 0x83, 0x00, 0x91, // <+8>: add x29, sp, #0x20 + 0x1f, 0x04, 0x00, 0xf1, // <+12>: cmp x0, #0x1 + 0x21, 0x01, 0x00, 0x54, // <+16>: b.ne ; <+52> DO_SOMETHING_AND_GOTO_AFTER_EPILOGUE + 0xfd, 0x7b, 0x42, 0xa9, // <+20>: ldp x29, x30, [sp, #0x20] + 0xff, 0xc3, 0x00, 0x91, // <+24>: add sp, sp, #0x30 + 0xc0, 0x03, 0x5f, 0xd6, // <+28>: ret + // AFTER_EPILOGUE + 0x37, 0x00, 0x80, 0xd2, // <+32>: mov x23, #0x1 + 0xf6, 0x5f, 0x41, 0xa9, // <+36>: ldp x22, x23, [sp, #0x10] + 0xfd, 0x7b, 0x42, 0xa9, // <+40>: ldp x29, x30, [sp, #0x20] + 0xff, 0xc3, 0x00, 0x91, // <+44>: add sp, sp, #0x30 + 0xc0, 0x03, 0x5f, 0xd6, // <+48>: ret + // DO_SOMETHING_AND_GOTO_AFTER_EPILOGUE + 0xf6, 0x5f, 0x01, 0xa9, // <+52>: stp x22, x23, [sp, #0x10] + 0x36, 0x00, 0x80, 0xd2, // <+56>: mov x22, #0x1 + 0x37, 0x00, 0x80, 0xd2, // <+60>: mov x23, #0x1 + 0xf8, 0xff, 0xff, 0x17, // <+64>: b ; <+32> AFTER_EPILOGUE + }; + + // UnwindPlan we expect: + // row[0]: 0: CFA=sp +0 => + // row[1]: 4: CFA=sp+48 => + // row[2]: 8: CFA=sp+16 => fp=[CFA-16] lr=[CFA-8] + // row[3]: 12: CFA=fp+16 => fp=[CFA-16] lr=[CFA-8] + // row[4]: 24: CFA=sp+48 => fp=<same> lr=<same> + // + // This must come from +56 + // row[5]: 32: CFA=fp+16 => fp=[CFA-16] lr=[CFA-8] x22=[CFA-32], x23=[CFA-24] + // row[6]: 40: CFA=fp+16 => fp=[CFA-16] lr=[CFA-8] x22=same, x23 = same + // row[6]: 44: CFA=sp+48 => fp=same lr=same x22=same, x23 = same + // row[6]: 48: CFA=sp0 => fp=same lr=same x22=same, x23 = same + // + // row[x]: 52: CFA=fp+16 => fp=[CFA-16] lr=[CFA-8] + // row[x]: 56: CFA=fp+16 => fp=[CFA-16] lr=[CFA-8] x22=[CFA-32], x23=[CFA-24] + // clang-format on + + sample_range = AddressRange(0x1000, sizeof(data)); + + EXPECT_TRUE(engine->GetNonCallSiteUnwindPlanFromAssembly( + sample_range, data, sizeof(data), unwind_plan)); + + // At the end of prologue (+12), CFA = fp + 16. + // <+0>: sub sp, sp, #0x30 + // <+4>: stp x29, x30, [sp, #0x20] + // <+8>: add x29, sp, #0x20 + row = unwind_plan.GetRowForFunctionOffset(12); + EXPECT_EQ(12, row->GetOffset()); + EXPECT_TRUE(row->GetCFAValue().IsRegisterPlusOffset()); + EXPECT_EQ(row->GetCFAValue().GetRegisterNumber(), gpr_fp_arm64); + EXPECT_EQ(row->GetCFAValue().GetOffset(), 16); + + // +16 and +20 are the same as +12. + // <+12>: cmp x0, #0x1 + // <+16>: b.ne ; <+52> DO_SOMETHING_AND_GOTO_AFTER_EPILOGUE + EXPECT_EQ(12, unwind_plan.GetRowForFunctionOffset(16)->GetOffset()); + EXPECT_EQ(12, unwind_plan.GetRowForFunctionOffset(20)->GetOffset()); + + // After restoring $fp to caller's value, CFA = $sp + 48 + // <+20>: ldp x29, x30, [sp, #0x20] + row = unwind_plan.GetRowForFunctionOffset(24); + EXPECT_EQ(24, row->GetOffset()); + EXPECT_TRUE(row->GetCFAValue().IsRegisterPlusOffset()); + EXPECT_TRUE(row->GetCFAValue().GetRegisterNumber() == gpr_sp_arm64); + EXPECT_EQ(row->GetCFAValue().GetOffset(), 48); + + // $sp has been restored + // <+24>: add sp, sp, #0x30 + row = unwind_plan.GetRowForFunctionOffset(28); + EXPECT_EQ(28, row->GetOffset()); + EXPECT_TRUE(row->GetCFAValue().IsRegisterPlusOffset()); + EXPECT_TRUE(row->GetCFAValue().GetRegisterNumber() == gpr_sp_arm64); + EXPECT_EQ(row->GetCFAValue().GetOffset(), 0); + + // Row for offset +32 should not inherit the state of the `ret` instruction + // in +28. Instead, it should inherit the state of the branch in +64. + // Check for register x22, which is available in row +64. + // <+28>: ret + // <+32>: mov x23, #0x1 + row = unwind_plan.GetRowForFunctionOffset(32); + EXPECT_EQ(32, row->GetOffset()); + { + UnwindPlan::Row::AbstractRegisterLocation loc; + EXPECT_TRUE(row->GetRegisterInfo(gpr_x22_arm64, loc)); + EXPECT_TRUE(loc.IsAtCFAPlusOffset()); + EXPECT_EQ(loc.GetOffset(), -32); + } + + // Check that the state of this branch + // <+16>: b.ne ; <+52> DO_SOMETHING_AND_GOTO_AFTER_EPILOGUE + // was forwarded to the branch target: + // <+52>: stp x22, x23, [sp, #0x10] + row = unwind_plan.GetRowForFunctionOffset(52); + EXPECT_EQ(52, row->GetOffset()); + EXPECT_TRUE(row->GetCFAValue().IsRegisterPlusOffset()); + EXPECT_EQ(row->GetCFAValue().GetRegisterNumber(), gpr_fp_arm64); + EXPECT_EQ(row->GetCFAValue().GetOffset(), 16); + + row = unwind_plan.GetRowForFunctionOffset(64); + { + UnwindPlan::Row::AbstractRegisterLocation loc; + EXPECT_TRUE(row->GetRegisterInfo(gpr_x22_arm64, loc)); + EXPECT_TRUE(loc.IsAtCFAPlusOffset()); + EXPECT_EQ(loc.GetOffset(), -32); + } +} diff --git a/lldb/unittests/Utility/CMakeLists.txt b/lldb/unittests/Utility/CMakeLists.txt index aed4177..4cbe15b 100644 --- a/lldb/unittests/Utility/CMakeLists.txt +++ b/lldb/unittests/Utility/CMakeLists.txt @@ -10,7 +10,6 @@ add_lldb_unittest(UtilityTests DataBufferTest.cpp DataEncoderTest.cpp DataExtractorTest.cpp - DiagnosticsRenderingTest.cpp EnvironmentTest.cpp EventTest.cpp FileSpecListTest.cpp @@ -48,6 +47,7 @@ add_lldb_unittest(UtilityTests UserIDResolverTest.cpp UUIDTest.cpp VASprintfTest.cpp + VirtualDataExtractorTest.cpp VMRangeTest.cpp XcodeSDKTest.cpp diff --git a/lldb/unittests/Utility/RegisterValueTest.cpp b/lldb/unittests/Utility/RegisterValueTest.cpp index 6239dbe..7b27e84 100644 --- a/lldb/unittests/Utility/RegisterValueTest.cpp +++ b/lldb/unittests/Utility/RegisterValueTest.cpp @@ -57,13 +57,12 @@ TEST(RegisterValueTest, GetScalarValue) { APInt(128, 0x7766554433221100))); } -static const Scalar etalon128(APInt(128, 0xffeeddccbbaa9988ull) << 64 | - APInt(128, 0x7766554433221100ull)); - -void TestSetValueFromData128(void *src, const lldb::ByteOrder endianness) { - RegisterInfo ri{"uint128_register", +void TestSetValueFromData(const Scalar &etalon, void *src, size_t src_byte_size, + const lldb::ByteOrder endianness, + const RegisterValue::Type register_value_type) { + RegisterInfo ri{"test", nullptr, - 16, + static_cast<uint32_t>(src_byte_size), 0, lldb::Encoding::eEncodingUint, lldb::Format::eFormatDefault, @@ -71,26 +70,289 @@ void TestSetValueFromData128(void *src, const lldb::ByteOrder endianness) { nullptr, nullptr, nullptr}; - DataExtractor src_extractor(src, 16, endianness, 8); + DataExtractor src_extractor(src, src_byte_size, endianness, 8); RegisterValue rv; EXPECT_TRUE(rv.SetValueFromData(ri, src_extractor, 0, false).Success()); Scalar s; EXPECT_TRUE(rv.GetScalarValue(s)); - EXPECT_EQ(s, etalon128); + EXPECT_EQ(rv.GetType(), register_value_type); + EXPECT_EQ(s, etalon); +} + +static const Scalar etalon7(APInt(32, 0x0000007F)); + +TEST(RegisterValueTest, SetValueFromData_7_le) { + uint8_t src[] = {0x7F}; + TestSetValueFromData(etalon7, src, 1, lldb::ByteOrder::eByteOrderLittle, + RegisterValue::eTypeUInt8); +} + +TEST(RegisterValueTest, SetValueFromData_7_be) { + uint8_t src[] = {0x7F}; + TestSetValueFromData(etalon7, src, 1, lldb::ByteOrder::eByteOrderBig, + RegisterValue::eTypeUInt8); +} + +static const Scalar etalon8(APInt(32, 0x000000FE)); + +TEST(RegisterValueTest, SetValueFromData_8_le) { + uint8_t src[] = {0xFE}; + TestSetValueFromData(etalon8, src, 1, lldb::ByteOrder::eByteOrderLittle, + RegisterValue::eTypeUInt8); +} + +TEST(RegisterValueTest, SetValueFromData_8_be) { + uint8_t src[] = {0xFE}; + TestSetValueFromData(etalon8, src, 1, lldb::ByteOrder::eByteOrderBig, + RegisterValue::eTypeUInt8); +} + +static const Scalar etalon9(APInt(32, 0x000001FE)); + +TEST(RegisterValueTest, SetValueFromData_9_le) { + uint8_t src[] = {0xFE, 0x01}; + TestSetValueFromData(etalon9, src, 2, lldb::ByteOrder::eByteOrderLittle, + RegisterValue::eTypeUInt16); +} + +TEST(RegisterValueTest, SetValueFromData_9_be) { + uint8_t src[] = {0x01, 0xFE}; + TestSetValueFromData(etalon9, src, 2, lldb::ByteOrder::eByteOrderBig, + RegisterValue::eTypeUInt16); +} + +static const Scalar etalon15(APInt(32, 0x00007FED)); + +TEST(RegisterValueTest, SetValueFromData_15_le) { + uint8_t src[] = {0xED, 0x7F}; + TestSetValueFromData(etalon15, src, 2, lldb::ByteOrder::eByteOrderLittle, + RegisterValue::eTypeUInt16); +} + +TEST(RegisterValueTest, SetValueFromData_15_be) { + uint8_t src[] = {0x7F, 0xED}; + TestSetValueFromData(etalon15, src, 2, lldb::ByteOrder::eByteOrderBig, + RegisterValue::eTypeUInt16); +} + +static const Scalar etalon16(APInt(32, 0x0000FEDC)); + +TEST(RegisterValueTest, SetValueFromData_16_le) { + uint8_t src[] = {0xDC, 0xFE}; + TestSetValueFromData(etalon16, src, 2, lldb::ByteOrder::eByteOrderLittle, + RegisterValue::eTypeUInt16); +} + +TEST(RegisterValueTest, SetValueFromData_16_be) { + uint8_t src[] = {0xFE, 0xDC}; + TestSetValueFromData(etalon16, src, 2, lldb::ByteOrder::eByteOrderBig, + RegisterValue::eTypeUInt16); +} + +static const Scalar etalon17(APInt(32, 0x0001FEDC)); + +TEST(RegisterValueTest, SetValueFromData_17_le) { + uint8_t src[] = {0xDC, 0xFE, 0x01}; + TestSetValueFromData(etalon17, src, 3, lldb::ByteOrder::eByteOrderLittle, + RegisterValue::eTypeUInt32); +} + +TEST(RegisterValueTest, SetValueFromData_17_be) { + uint8_t src[] = {0x01, 0xFE, 0xDC}; + TestSetValueFromData(etalon17, src, 3, lldb::ByteOrder::eByteOrderBig, + RegisterValue::eTypeUInt32); +} + +static const Scalar etalon24(APInt(32, 0x00FEDCBA)); + +TEST(RegisterValueTest, SetValueFromData_24_le) { + uint8_t src[] = {0xBA, 0xDC, 0xFE}; + TestSetValueFromData(etalon24, src, 3, lldb::ByteOrder::eByteOrderLittle, + RegisterValue::eTypeUInt32); +} + +TEST(RegisterValueTest, SetValueFromData_24_be) { + uint8_t src[] = {0xFE, 0xDC, 0xBA}; + TestSetValueFromData(etalon24, src, 3, lldb::ByteOrder::eByteOrderBig, + RegisterValue::eTypeUInt32); +} + +static const Scalar etalon31(APInt(32, 0x7EDCBA98)); + +TEST(RegisterValueTest, SetValueFromData_31_le) { + uint8_t src[] = {0x98, 0xBA, 0xDC, 0x7E}; + TestSetValueFromData(etalon31, src, 4, lldb::ByteOrder::eByteOrderLittle, + RegisterValue::eTypeUInt32); +} + +TEST(RegisterValueTest, SetValueFromData_31_be) { + uint8_t src[] = {0x7E, 0xDC, 0xBA, 0x98}; + TestSetValueFromData(etalon31, src, 4, lldb::ByteOrder::eByteOrderBig, + RegisterValue::eTypeUInt32); +} + +static const Scalar etalon32(APInt(32, 0xFEDCBA98)); + +TEST(RegisterValueTest, SetValueFromData_32_le) { + uint8_t src[] = {0x98, 0xBA, 0xDC, 0xFE}; + TestSetValueFromData(etalon32, src, 4, lldb::ByteOrder::eByteOrderLittle, + RegisterValue::eTypeUInt32); } -// Test that the "RegisterValue::SetValueFromData" method works correctly -// with 128-bit little-endian data that represents an integer. +TEST(RegisterValueTest, SetValueFromData_32_be) { + uint8_t src[] = {0xFE, 0xDC, 0xBA, 0x98}; + TestSetValueFromData(etalon32, src, 4, lldb::ByteOrder::eByteOrderBig, + RegisterValue::eTypeUInt32); +} + +static const Scalar etalon33(APInt(64, 0x00000001FEDCBA98)); + +TEST(RegisterValueTest, SetValueFromData_33_le) { + uint8_t src[] = {0x98, 0xBA, 0xDC, 0xFE, 0x01}; + TestSetValueFromData(etalon33, src, 5, lldb::ByteOrder::eByteOrderLittle, + RegisterValue::eTypeUInt64); +} + +TEST(RegisterValueTest, SetValueFromData_33_be) { + uint8_t src[] = {0x01, 0xFE, 0xDC, 0xBA, 0x98}; + TestSetValueFromData(etalon33, src, 5, lldb::ByteOrder::eByteOrderBig, + RegisterValue::eTypeUInt64); +} + +static const Scalar etalon40(APInt(64, 0x000000FEDCBA9876)); + +TEST(RegisterValueTest, SetValueFromData_40_le) { + uint8_t src[] = {0x76, 0x98, 0xBA, 0xDC, 0xFE}; + TestSetValueFromData(etalon40, src, 5, lldb::ByteOrder::eByteOrderLittle, + RegisterValue::eTypeUInt64); +} + +TEST(RegisterValueTest, SetValueFromData_40_be) { + uint8_t src[] = {0xFE, 0xDC, 0xBA, 0x98, 0x76}; + TestSetValueFromData(etalon40, src, 5, lldb::ByteOrder::eByteOrderBig, + RegisterValue::eTypeUInt64); +} + +static const Scalar etalon63(APInt(64, 0x7EDCBA9876543210)); + +TEST(RegisterValueTest, SetValueFromData_63_le) { + uint8_t src[] = {0x10, 0x32, 0x54, 0x76, 0x98, 0xBA, 0xDC, 0x7E}; + TestSetValueFromData(etalon63, src, 8, lldb::ByteOrder::eByteOrderLittle, + RegisterValue::eTypeUInt64); +} + +TEST(RegisterValueTest, SetValueFromData_63_be) { + uint8_t src[] = {0x7E, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}; + TestSetValueFromData(etalon63, src, 8, lldb::ByteOrder::eByteOrderBig, + RegisterValue::eTypeUInt64); +} + +static const Scalar etalon64(APInt(64, 0xFEDCBA9876543210)); + +TEST(RegisterValueTest, SetValueFromData_64_le) { + uint8_t src[] = {0x10, 0x32, 0x54, 0x76, 0x98, 0xBA, 0xDC, 0xFE}; + TestSetValueFromData(etalon64, src, 8, lldb::ByteOrder::eByteOrderLittle, + RegisterValue::eTypeUInt64); +} + +TEST(RegisterValueTest, SetValueFromData_64_be) { + uint8_t src[] = {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}; + TestSetValueFromData(etalon64, src, 8, lldb::ByteOrder::eByteOrderBig, + RegisterValue::eTypeUInt64); +} + +static const Scalar etalon65(APInt(72, 0x0000000000000001ull) << 1 * 64 | + APInt(72, 0x0706050403020100ull) << 0 * 64); + +TEST(RegisterValueTest, SetValueFromData_65_le) { + uint8_t src[] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01}; + TestSetValueFromData(etalon65, src, 9, lldb::ByteOrder::eByteOrderLittle, + RegisterValue::eTypeUIntN); +} + +TEST(RegisterValueTest, SetValueFromData_65_be) { + uint8_t src[] = {0x01, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00}; + TestSetValueFromData(etalon65, src, 9, lldb::ByteOrder::eByteOrderBig, + RegisterValue::eTypeUIntN); +} + +static const Scalar etalon127(APInt(128, 0x7f0e0d0c0b0a0908ull) << 1 * 64 | + APInt(128, 0x0706050403020100ull) << 0 * 64); + +TEST(RegisterValueTest, SetValueFromData_127_le) { + uint8_t src[] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x7f}; + TestSetValueFromData(etalon127, src, 16, lldb::ByteOrder::eByteOrderLittle, + RegisterValue::eTypeUIntN); +} + +TEST(RegisterValueTest, SetValueFromData_127_be) { + uint8_t src[] = {0x7f, 0x0e, 0x0d, 0x0c, 0x0b, 0x0a, 0x09, 0x08, + 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00}; + TestSetValueFromData(etalon127, src, 16, lldb::ByteOrder::eByteOrderBig, + RegisterValue::eTypeUIntN); +} + +static const Scalar etalon128(APInt(128, 0x0f0e0d0c0b0a0908ull) << 1 * 64 | + APInt(128, 0x0706050403020100ull) << 0 * 64); + TEST(RegisterValueTest, SetValueFromData_128_le) { - uint8_t src[] = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, - 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff}; - TestSetValueFromData128(src, lldb::ByteOrder::eByteOrderLittle); + uint8_t src[] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f}; + TestSetValueFromData(etalon128, src, 16, lldb::ByteOrder::eByteOrderLittle, + RegisterValue::eTypeUIntN); } -// Test that the "RegisterValue::SetValueFromData" method works correctly -// with 128-bit big-endian data that represents an integer. TEST(RegisterValueTest, SetValueFromData_128_be) { - uint8_t src[] = {0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, - 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, 0x00}; - TestSetValueFromData128(src, lldb::ByteOrder::eByteOrderBig); + uint8_t src[] = {0x0f, 0x0e, 0x0d, 0x0c, 0x0b, 0x0a, 0x09, 0x08, + 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00}; + TestSetValueFromData(etalon128, src, 16, lldb::ByteOrder::eByteOrderBig, + RegisterValue::eTypeUIntN); +} + +static const Scalar etalon256(APInt(256, 0x1f1e1d1c1b1a1918ull) << 3 * 64 | + APInt(256, 0x1716151413121110ull) << 2 * 64 | + APInt(256, 0x0f0e0d0c0b0a0908ull) << 1 * 64 | + APInt(256, 0x0706050403020100ull) << 0 * 64); + +TEST(RegisterValueTest, SetValueFromData_256_le) { + uint8_t src[] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f}; + TestSetValueFromData(etalon256, src, 32, lldb::ByteOrder::eByteOrderLittle, + RegisterValue::eTypeUIntN); +} + +TEST(RegisterValueTest, SetValueFromData_256_be) { + uint8_t src[] = {0x1f, 0x1e, 0x1d, 0x1c, 0x1b, 0x1a, 0x19, 0x18, + 0x17, 0x16, 0x15, 0x14, 0x13, 0x12, 0x11, 0x10, + 0x0f, 0x0e, 0x0d, 0x0c, 0x0b, 0x0a, 0x09, 0x08, + 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00}; + TestSetValueFromData(etalon256, src, 32, lldb::ByteOrder::eByteOrderBig, + RegisterValue::eTypeUIntN); +} + +static const Scalar etalon257(APInt(512, 0x0000000000000001ull) << 4 * 64 | + APInt(512, 0x1f1e1d1c1b1a1918ull) << 3 * 64 | + APInt(512, 0x1716151413121110ull) << 2 * 64 | + APInt(512, 0x0f0e0d0c0b0a0908ull) << 1 * 64 | + APInt(512, 0x0706050403020100ull) << 0 * 64); + +TEST(RegisterValueTest, SetValueFromData_257_le) { + uint8_t src[] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, + 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, + 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x01}; + TestSetValueFromData(etalon257, src, 33, lldb::ByteOrder::eByteOrderLittle, + RegisterValue::eTypeUIntN); +} + +TEST(RegisterValueTest, SetValueFromData_257_be) { + uint8_t src[] = {0x01, 0x1f, 0x1e, 0x1d, 0x1c, 0x1b, 0x1a, 0x19, 0x18, + 0x17, 0x16, 0x15, 0x14, 0x13, 0x12, 0x11, 0x10, 0x0f, + 0x0e, 0x0d, 0x0c, 0x0b, 0x0a, 0x09, 0x08, 0x07, 0x06, + 0x05, 0x04, 0x03, 0x02, 0x01, 0x00}; + TestSetValueFromData(etalon257, src, 33, lldb::ByteOrder::eByteOrderBig, + RegisterValue::eTypeUIntN); } diff --git a/lldb/unittests/Utility/VirtualDataExtractorTest.cpp b/lldb/unittests/Utility/VirtualDataExtractorTest.cpp new file mode 100644 index 0000000..09f3edb --- /dev/null +++ b/lldb/unittests/Utility/VirtualDataExtractorTest.cpp @@ -0,0 +1,583 @@ +//===----------------------------------------------------------------------===// +// +// 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/VirtualDataExtractor.h" +#include "lldb/Utility/DataBufferHeap.h" +#include "gtest/gtest.h" + +using namespace lldb_private; +using namespace lldb; + +using Table = VirtualDataExtractor::LookupTable; +using Entry = Table::Entry; + +TEST(VirtualDataExtractorTest, BasicConstruction) { + uint8_t buffer[] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}; + + lldb::DataExtractorSP extractor = std::make_shared<VirtualDataExtractor>( + buffer, sizeof(buffer), eByteOrderLittle, 4, Table{Entry(0x1000, 8, 0)}); + + EXPECT_EQ(extractor->GetByteSize(), 8U); +} + +TEST(VirtualDataExtractorTest, GetDataAtVirtualOffset) { + uint8_t buffer[] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}; + + lldb::DataExtractorSP extractor = std::make_shared<VirtualDataExtractor>( + buffer, sizeof(buffer), eByteOrderLittle, 4, Table{Entry(0x1000, 8, 0)}); + + offset_t virtual_offset = 0x1000; + const void *data = extractor->GetData(&virtual_offset, 4); + + ASSERT_NE(data, nullptr); + EXPECT_EQ(virtual_offset, 0x1004U); + EXPECT_EQ(memcmp(data, buffer, 4), 0); +} + +TEST(VirtualDataExtractorTest, GetDataAtVirtualOffsetInvalid) { + uint8_t buffer[] = {0x01, 0x02, 0x03, 0x04}; + + lldb::DataExtractorSP extractor = std::make_shared<VirtualDataExtractor>( + buffer, sizeof(buffer), eByteOrderLittle, 4, Table{Entry(0x1000, 4, 0)}); + + // Try to read from an invalid virtual address. + offset_t virtual_offset = 0x2000; + const void *data = extractor->GetData(&virtual_offset, 4); + + EXPECT_EQ(data, nullptr); +} + +TEST(VirtualDataExtractorTest, GetU8AtVirtualOffset) { + uint8_t buffer[] = {0x12, 0x34, 0x56, 0x78}; + + lldb::DataExtractorSP extractor = std::make_shared<VirtualDataExtractor>( + buffer, sizeof(buffer), eByteOrderLittle, 4, Table{Entry(0x1000, 4, 0)}); + + offset_t virtual_offset = 0x1000; + EXPECT_EQ(extractor->GetU8(&virtual_offset), 0x12U); + EXPECT_EQ(virtual_offset, 0x1001U); + + EXPECT_EQ(extractor->GetU8(&virtual_offset), 0x34U); + EXPECT_EQ(virtual_offset, 0x1002U); +} + +TEST(VirtualDataExtractorTest, GetU16AtVirtualOffset) { + uint8_t buffer[] = {0x12, 0x34, 0x56, 0x78}; + + lldb::DataExtractorSP extractor = std::make_shared<VirtualDataExtractor>( + buffer, sizeof(buffer), eByteOrderLittle, 4, Table{Entry(0x1000, 4, 0)}); + + offset_t virtual_offset = 0x1000; + EXPECT_EQ(extractor->GetU16(&virtual_offset), 0x3412U); + EXPECT_EQ(virtual_offset, 0x1002U); + + EXPECT_EQ(extractor->GetU16(&virtual_offset), 0x7856U); + EXPECT_EQ(virtual_offset, 0x1004U); +} + +TEST(VirtualDataExtractorTest, GetU32AtVirtualOffset) { + uint8_t buffer[] = {0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0}; + + lldb::DataExtractorSP extractor = std::make_shared<VirtualDataExtractor>( + buffer, sizeof(buffer), eByteOrderLittle, 4, Table{Entry(0x1000, 8, 0)}); + + offset_t virtual_offset = 0x1000; + EXPECT_EQ(extractor->GetU32(&virtual_offset), 0x78563412U); + EXPECT_EQ(virtual_offset, 0x1004U); + + EXPECT_EQ(extractor->GetU32(&virtual_offset), 0xF0DEBC9AU); + EXPECT_EQ(virtual_offset, 0x1008U); +} + +TEST(VirtualDataExtractorTest, GetU64AtVirtualOffset) { + uint8_t buffer[] = {0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0}; + + lldb::DataExtractorSP extractor = std::make_shared<VirtualDataExtractor>( + buffer, sizeof(buffer), eByteOrderLittle, 8, Table{Entry(0x1000, 8, 0)}); + + offset_t virtual_offset = 0x1000; + EXPECT_EQ(extractor->GetU64(&virtual_offset), 0xF0DEBC9A78563412ULL); + EXPECT_EQ(virtual_offset, 0x1008U); +} + +TEST(VirtualDataExtractorTest, GetAddressAtVirtualOffset) { + uint8_t buffer[] = {0x12, 0x34, 0x56, 0x78}; + + lldb::DataExtractorSP extractor = std::make_shared<VirtualDataExtractor>( + buffer, sizeof(buffer), eByteOrderLittle, 4, Table{Entry(0x1000, 4, 0)}); + + offset_t virtual_offset = 0x1000; + EXPECT_EQ(extractor->GetAddress(&virtual_offset), 0x78563412U); + EXPECT_EQ(virtual_offset, 0x1004U); +} + +TEST(VirtualDataExtractorTest, BigEndian) { + uint8_t buffer[] = {0x12, 0x34, 0x56, 0x78}; + + lldb::DataExtractorSP extractor = std::make_shared<VirtualDataExtractor>( + buffer, sizeof(buffer), eByteOrderBig, 4, Table{Entry(0x1000, 4, 0)}); + + offset_t virtual_offset = 0x1000; + EXPECT_EQ(extractor->GetU16(&virtual_offset), 0x1234U); + EXPECT_EQ(virtual_offset, 0x1002U); + + EXPECT_EQ(extractor->GetU16(&virtual_offset), 0x5678U); + EXPECT_EQ(virtual_offset, 0x1004U); +} + +TEST(VirtualDataExtractorTest, MultipleEntries) { + // Create a buffer with distinct patterns for each section. + uint8_t buffer[] = { + 0x01, 0x02, 0x03, 0x04, // Physical offset 0-3. + 0x11, 0x12, 0x13, 0x14, // Physical offset 4-7. + 0x21, 0x22, 0x23, 0x24 // Physical offset 8-11. + }; + + lldb::DataExtractorSP extractor = std::make_shared<VirtualDataExtractor>( + buffer, sizeof(buffer), eByteOrderLittle, 4, + Table{Entry(0x1000, 4, 0), // Virt 0x1000-0x1004 + Entry(0x2000, 4, 4), // Virt 0x2000-0x2004 + Entry(0x3000, 4, 8)}); // Virt 0x3000-0x3004 + + // Test reading from first virtual range. + offset_t virtual_offset = 0x1000; + EXPECT_EQ(extractor->GetU8(&virtual_offset), 0x01U); + + // Test reading from second virtual range. + virtual_offset = 0x2000; + EXPECT_EQ(extractor->GetU8(&virtual_offset), 0x11U); + + // Test reading from third virtual range. + virtual_offset = 0x3000; + EXPECT_EQ(extractor->GetU8(&virtual_offset), 0x21U); +} + +TEST(VirtualDataExtractorTest, NonContiguousVirtualAddresses) { + uint8_t buffer[] = {0xAA, 0xBB, 0xCC, 0xDD}; + + lldb::DataExtractorSP extractor = std::make_shared<VirtualDataExtractor>( + buffer, sizeof(buffer), eByteOrderLittle, 4, + Table{Entry(0x1000, 2, 0), // Virt 0x1000-0x1002 + Entry(0x5000, 2, 2)}); // Virt 0x5000-0x5002 + + // Test reading from first virtual range. + offset_t virtual_offset = 0x1000; + EXPECT_EQ(extractor->GetU16(&virtual_offset), 0xBBAAU); + + // Test reading from second virtual range (non-contiguous). + virtual_offset = 0x5000; + EXPECT_EQ(extractor->GetU16(&virtual_offset), 0xDDCCU); + + // Test that gap between ranges is invalid. + virtual_offset = 0x3000; + EXPECT_EQ(extractor->GetU8(&virtual_offset), 0U); +} + +TEST(VirtualDataExtractorTest, SharedDataBuffer) { + // Test with shared_ptr to DataBuffer. + uint8_t buffer[] = {0x01, 0x02, 0x03, 0x04}; + auto data_sp = std::make_shared<DataBufferHeap>(buffer, sizeof(buffer)); + + lldb::DataExtractorSP extractor = std::make_shared<VirtualDataExtractor>( + data_sp, eByteOrderLittle, 4, Table{Entry(0x1000, 4, 0)}); + + offset_t virtual_offset = 0x1000; + EXPECT_EQ(extractor->GetU32(&virtual_offset), 0x04030201U); +} + +TEST(VirtualDataExtractorTest, NullPointerHandling) { + uint8_t buffer[] = {0x01, 0x02, 0x03, 0x04}; + + lldb::DataExtractorSP extractor = std::make_shared<VirtualDataExtractor>( + buffer, sizeof(buffer), eByteOrderLittle, 4, Table{Entry(0x1000, 4, 0)}); + + // Test that passing nullptr returns default values. + EXPECT_EQ(extractor->GetU8(nullptr), 0U); + EXPECT_EQ(extractor->GetU16(nullptr), 0U); + EXPECT_EQ(extractor->GetU32(nullptr), 0U); + EXPECT_EQ(extractor->GetU64(nullptr), 0U); + EXPECT_EQ(extractor->GetAddress(nullptr), 0U); + EXPECT_EQ(extractor->GetData(nullptr, 4), nullptr); +} + +TEST(VirtualDataExtractorTest, OffsetMapping) { + // Test that virtual to physical offset mapping works correctly. + uint8_t buffer[] = {0x00, 0x00, 0x00, 0x00, 0xAA, 0xBB, 0xCC, 0xDD}; + + // Map virtual address 0x1000 to physical offset 4 (skipping first 4 bytes). + lldb::DataExtractorSP extractor = std::make_shared<VirtualDataExtractor>( + buffer, sizeof(buffer), eByteOrderLittle, 4, Table{Entry(0x1000, 4, 4)}); + + offset_t virtual_offset = 0x1000; + // Should read from physical offset 4, not 0. + EXPECT_EQ(extractor->GetU32(&virtual_offset), 0xDDCCBBAAU); +} + +TEST(VirtualDataExtractorTest, GetU8Unchecked) { + uint8_t buffer[] = {0x12, 0x34, 0x56, 0x78}; + + lldb::DataExtractorSP extractor = std::make_shared<VirtualDataExtractor>( + buffer, sizeof(buffer), eByteOrderLittle, 4, Table{Entry(0x1000, 4, 0)}); + + offset_t virtual_offset = 0x1000; + EXPECT_EQ(extractor->GetU8_unchecked(&virtual_offset), 0x12U); + EXPECT_EQ(virtual_offset, 0x1001U); + + EXPECT_EQ(extractor->GetU8_unchecked(&virtual_offset), 0x34U); + EXPECT_EQ(virtual_offset, 0x1002U); +} + +TEST(VirtualDataExtractorTest, GetU16Unchecked) { + uint8_t buffer[] = {0x12, 0x34, 0x56, 0x78}; + + lldb::DataExtractorSP extractor = std::make_shared<VirtualDataExtractor>( + buffer, sizeof(buffer), eByteOrderLittle, 4, Table{Entry(0x1000, 4, 0)}); + + offset_t virtual_offset = 0x1000; + EXPECT_EQ(extractor->GetU16_unchecked(&virtual_offset), 0x3412U); + EXPECT_EQ(virtual_offset, 0x1002U); + + EXPECT_EQ(extractor->GetU16_unchecked(&virtual_offset), 0x7856U); + EXPECT_EQ(virtual_offset, 0x1004U); +} + +TEST(VirtualDataExtractorTest, GetU32Unchecked) { + uint8_t buffer[] = {0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0}; + + lldb::DataExtractorSP extractor = std::make_shared<VirtualDataExtractor>( + buffer, sizeof(buffer), eByteOrderLittle, 4, Table{Entry(0x1000, 8, 0)}); + + offset_t virtual_offset = 0x1000; + EXPECT_EQ(extractor->GetU32_unchecked(&virtual_offset), 0x78563412U); + EXPECT_EQ(virtual_offset, 0x1004U); + + EXPECT_EQ(extractor->GetU32_unchecked(&virtual_offset), 0xF0DEBC9AU); + EXPECT_EQ(virtual_offset, 0x1008U); +} + +TEST(VirtualDataExtractorTest, GetU64Unchecked) { + uint8_t buffer[] = {0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0}; + + lldb::DataExtractorSP extractor = std::make_shared<VirtualDataExtractor>( + buffer, sizeof(buffer), eByteOrderLittle, 8, Table{Entry(0x1000, 8, 0)}); + + offset_t virtual_offset = 0x1000; + EXPECT_EQ(extractor->GetU64_unchecked(&virtual_offset), + 0xF0DEBC9A78563412ULL); + EXPECT_EQ(virtual_offset, 0x1008U); +} + +TEST(VirtualDataExtractorTest, GetMaxU64Unchecked) { + uint8_t buffer[] = {0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0}; + + lldb::DataExtractorSP extractor = std::make_shared<VirtualDataExtractor>( + buffer, sizeof(buffer), eByteOrderLittle, 4, Table{Entry(0x1000, 8, 0)}); + + // Test various byte sizes. + offset_t virtual_offset = 0x1000; + EXPECT_EQ(extractor->GetMaxU64_unchecked(&virtual_offset, 1), 0x12U); + EXPECT_EQ(virtual_offset, 0x1001U); + + virtual_offset = 0x1000; + EXPECT_EQ(extractor->GetMaxU64_unchecked(&virtual_offset, 2), 0x3412U); + EXPECT_EQ(virtual_offset, 0x1002U); + + virtual_offset = 0x1000; + EXPECT_EQ(extractor->GetMaxU64_unchecked(&virtual_offset, 4), 0x78563412U); + EXPECT_EQ(virtual_offset, 0x1004U); + + virtual_offset = 0x1000; + EXPECT_EQ(extractor->GetMaxU64_unchecked(&virtual_offset, 8), + 0xF0DEBC9A78563412ULL); + EXPECT_EQ(virtual_offset, 0x1008U); +} + +TEST(VirtualDataExtractorTest, GetAddressUnchecked) { + uint8_t buffer[] = {0x12, 0x34, 0x56, 0x78}; + + lldb::DataExtractorSP extractor = std::make_shared<VirtualDataExtractor>( + buffer, sizeof(buffer), eByteOrderLittle, 4, Table{Entry(0x1000, 4, 0)}); + + offset_t virtual_offset = 0x1000; + EXPECT_EQ(extractor->GetAddress_unchecked(&virtual_offset), 0x78563412U); + EXPECT_EQ(virtual_offset, 0x1004U); +} + +TEST(VirtualDataExtractorTest, UncheckedWithBigEndian) { + uint8_t buffer[] = {0x12, 0x34, 0x56, 0x78}; + + lldb::DataExtractorSP extractor = std::make_shared<VirtualDataExtractor>( + buffer, sizeof(buffer), eByteOrderBig, 4, Table{Entry(0x1000, 4, 0)}); + + offset_t virtual_offset = 0x1000; + EXPECT_EQ(extractor->GetU16_unchecked(&virtual_offset), 0x1234U); + EXPECT_EQ(virtual_offset, 0x1002U); + + EXPECT_EQ(extractor->GetU16_unchecked(&virtual_offset), 0x5678U); + EXPECT_EQ(virtual_offset, 0x1004U); +} + +TEST(VirtualDataExtractorTest, GetCStr) { + // Create buffer with null-terminated strings. + uint8_t buffer[] = {'H', 'e', 'l', 'l', 'o', '\0', 'W', 'o', + 'r', 'l', 'd', '\0', 'F', 'o', 'o', '\0'}; + + lldb::DataExtractorSP extractor = std::make_shared<VirtualDataExtractor>( + buffer, sizeof(buffer), eByteOrderLittle, 4, + Table{Entry(0x1000, 6, 0), Entry(0x2000, 12, 6)}); + + // Test reading first string. + offset_t virtual_offset = 0x1000; + const char *str1 = extractor->GetCStr(&virtual_offset); + ASSERT_NE(str1, nullptr); + EXPECT_STREQ(str1, "Hello"); + EXPECT_EQ(virtual_offset, 0x1006U); // After "Hello\0" + + // Test reading second string. + virtual_offset = 0x2000; + const char *str2 = extractor->GetCStr(&virtual_offset); + ASSERT_NE(str2, nullptr); + EXPECT_STREQ(str2, "World"); + EXPECT_EQ(virtual_offset, 0x2006U); // After "World\0" +} + +TEST(VirtualDataExtractorTest, GetFloat) { + // Create buffer with float value (IEEE 754 single precision). + // 3.14159f in little endian: 0xDB 0x0F 0x49 0x40 + uint8_t buffer[] = {0xDB, 0x0F, 0x49, 0x40}; + + lldb::DataExtractorSP extractor = std::make_shared<VirtualDataExtractor>( + buffer, sizeof(buffer), eByteOrderLittle, 4, Table{Entry(0x1000, 4, 0)}); + + offset_t virtual_offset = 0x1000; + float value = extractor->GetFloat(&virtual_offset); + EXPECT_NEAR(value, 3.14159f, 0.00001f); + EXPECT_EQ(virtual_offset, 0x1004U); +} + +TEST(VirtualDataExtractorTest, GetDouble) { + // Create buffer with double value (IEEE 754 double precision). + // 3.14159265358979 in little endian + uint8_t buffer[] = {0x18, 0x2D, 0x44, 0x54, 0xFB, 0x21, 0x09, 0x40}; + + lldb::DataExtractorSP extractor = std::make_shared<VirtualDataExtractor>( + buffer, sizeof(buffer), eByteOrderLittle, 8, Table{Entry(0x1000, 8, 0)}); + + offset_t virtual_offset = 0x1000; + double value = extractor->GetDouble(&virtual_offset); + EXPECT_NEAR(value, 3.14159265358979, 0.00000000000001); + EXPECT_EQ(virtual_offset, 0x1008U); +} + +TEST(VirtualDataExtractorTest, GetULEB128) { + // ULEB128 encoding: 0x624 (1572 decimal) = 0xA4 0x0C + uint8_t buffer[] = {0xA4, 0x0C, 0xFF, 0x00, 0x7F, 0x80, 0x01}; + + lldb::DataExtractorSP extractor = std::make_shared<VirtualDataExtractor>( + buffer, sizeof(buffer), eByteOrderLittle, 4, Table{Entry(0x1000, 7, 0)}); + + // Test reading first ULEB128 value (1572). + offset_t virtual_offset = 0x1000; + EXPECT_EQ(extractor->GetULEB128(&virtual_offset), 1572U); + EXPECT_EQ(virtual_offset, 0x1002U); + + // Test reading second ULEB128 value (127). + virtual_offset = 0x1004; + EXPECT_EQ(extractor->GetULEB128(&virtual_offset), 127U); + EXPECT_EQ(virtual_offset, 0x1005U); + + // Test reading third ULEB128 value (128). + EXPECT_EQ(extractor->GetULEB128(&virtual_offset), 128U); + EXPECT_EQ(virtual_offset, 0x1007U); +} + +TEST(VirtualDataExtractorTest, GetSLEB128) { + // SLEB128 encoding: -123 = 0x85 0x7F, 123 = 0xFB 0x00 + uint8_t buffer[] = {0x85, 0x7F, 0xFB, 0x00}; + + lldb::DataExtractorSP extractor = std::make_shared<VirtualDataExtractor>( + buffer, sizeof(buffer), eByteOrderLittle, 4, Table{Entry(0x1000, 4, 0)}); + + // Test reading negative SLEB128 value (-123). + offset_t virtual_offset = 0x1000; + EXPECT_EQ(extractor->GetSLEB128(&virtual_offset), -123); + EXPECT_EQ(virtual_offset, 0x1002U); + + // Test reading positive SLEB128 value (123). + EXPECT_EQ(extractor->GetSLEB128(&virtual_offset), 123); + EXPECT_EQ(virtual_offset, 0x1004U); +} + +TEST(VirtualDataExtractorTest, GetU8Array) { + uint8_t buffer[] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}; + + lldb::DataExtractorSP extractor = std::make_shared<VirtualDataExtractor>( + buffer, sizeof(buffer), eByteOrderLittle, 4, Table{Entry(0x1000, 8, 0)}); + + // Test reading array of 4 bytes. + offset_t virtual_offset = 0x1000; + uint8_t dst[4] = {0}; + void *result = extractor->GetU8(&virtual_offset, dst, 4); + ASSERT_NE(result, nullptr); + EXPECT_EQ(dst[0], 0x01U); + EXPECT_EQ(dst[1], 0x02U); + EXPECT_EQ(dst[2], 0x03U); + EXPECT_EQ(dst[3], 0x04U); + EXPECT_EQ(virtual_offset, 0x1004U); +} + +TEST(VirtualDataExtractorTest, GetU16Array) { + uint8_t buffer[] = {0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0}; + + lldb::DataExtractorSP extractor = std::make_shared<VirtualDataExtractor>( + buffer, sizeof(buffer), eByteOrderLittle, 4, Table{Entry(0x1000, 8, 0)}); + + // Test reading array of 3 uint16_t values. + offset_t virtual_offset = 0x1000; + uint16_t dst[3] = {0}; + void *result = extractor->GetU16(&virtual_offset, dst, 3); + ASSERT_NE(result, nullptr); + EXPECT_EQ(dst[0], 0x3412U); + EXPECT_EQ(dst[1], 0x7856U); + EXPECT_EQ(dst[2], 0xBC9AU); + EXPECT_EQ(virtual_offset, 0x1006U); +} + +TEST(VirtualDataExtractorTest, GetU32Array) { + uint8_t buffer[] = {0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0}; + + lldb::DataExtractorSP extractor = std::make_shared<VirtualDataExtractor>( + buffer, sizeof(buffer), eByteOrderLittle, 4, Table{Entry(0x1000, 8, 0)}); + + // Test reading array of 2 uint32_t values. + offset_t virtual_offset = 0x1000; + uint32_t dst[2] = {0}; + void *result = extractor->GetU32(&virtual_offset, dst, 2); + ASSERT_NE(result, nullptr); + EXPECT_EQ(dst[0], 0x78563412U); + EXPECT_EQ(dst[1], 0xF0DEBC9AU); + EXPECT_EQ(virtual_offset, 0x1008U); +} + +TEST(VirtualDataExtractorTest, GetU64Array) { + uint8_t buffer[] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18}; + + lldb::DataExtractorSP extractor = std::make_shared<VirtualDataExtractor>( + buffer, sizeof(buffer), eByteOrderLittle, 8, Table{Entry(0x1000, 16, 0)}); + + // Test reading array of 2 uint64_t values. + offset_t virtual_offset = 0x1000; + uint64_t dst[2] = {0}; + void *result = extractor->GetU64(&virtual_offset, dst, 2); + ASSERT_NE(result, nullptr); + EXPECT_EQ(dst[0], 0x0807060504030201ULL); + EXPECT_EQ(dst[1], 0x1817161514131211ULL); + EXPECT_EQ(virtual_offset, 0x1010U); +} + +TEST(VirtualDataExtractorTest, GetMaxU64WithVariableSizes) { + uint8_t buffer[] = {0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0}; + + lldb::DataExtractorSP extractor = std::make_shared<VirtualDataExtractor>( + buffer, sizeof(buffer), eByteOrderLittle, 4, Table{Entry(0x1000, 8, 0)}); + + // Test reading 3-byte value. + offset_t virtual_offset = 0x1000; + EXPECT_EQ(extractor->GetMaxU64(&virtual_offset, 3), 0x563412U); + EXPECT_EQ(virtual_offset, 0x1003U); + + // Test reading 5-byte value. + virtual_offset = 0x1000; + EXPECT_EQ(extractor->GetMaxU64(&virtual_offset, 5), 0x9A78563412ULL); + EXPECT_EQ(virtual_offset, 0x1005U); +} + +TEST(VirtualDataExtractorTest, GetMaxS64) { + // Test with negative number (sign extension). + uint8_t buffer[] = {0xFF, 0xFF, 0xFF, 0xFF}; + + lldb::DataExtractorSP extractor = std::make_shared<VirtualDataExtractor>( + buffer, sizeof(buffer), eByteOrderLittle, 4, Table{Entry(0x1000, 4, 0)}); + + // Test reading 1-byte signed value (-1). + offset_t virtual_offset = 0x1000; + EXPECT_EQ(extractor->GetMaxS64(&virtual_offset, 1), -1); + EXPECT_EQ(virtual_offset, 0x1001U); + + // Test reading 2-byte signed value (-1). + virtual_offset = 0x1000; + EXPECT_EQ(extractor->GetMaxS64(&virtual_offset, 2), -1); + EXPECT_EQ(virtual_offset, 0x1002U); +} + +TEST(VirtualDataExtractorTest, CannotReadAcrossEntryBoundaries) { + // Create buffer with two separate regions. + uint8_t buffer[] = {0x01, 0x02, 0x03, 0x04, 0x11, 0x12, 0x13, 0x14}; + + // First entry: virtual 0x1000-0x1004 maps to physical 0-4. + // Second entry: virtual 0x2000-0x2004 maps to physical 4-8. + // Note: there's a gap in virtual addresses (0x1004-0x2000). + lldb::DataExtractorSP extractor = std::make_shared<VirtualDataExtractor>( + buffer, sizeof(buffer), eByteOrderLittle, 4, + Table{Entry(0x1000, 4, 0), Entry(0x2000, 4, 4)}); + + // Verify we can read within the first entry. + offset_t virtual_offset = 0x1000; + EXPECT_EQ(extractor->GetU32(&virtual_offset), 0x04030201U); + EXPECT_EQ(virtual_offset, 0x1004U); + + // Verify we can read within the second entry. + virtual_offset = 0x2000; + EXPECT_EQ(extractor->GetU32(&virtual_offset), 0x14131211U); + EXPECT_EQ(virtual_offset, 0x2004U); + + // Verify we CANNOT read in the gap between entries. + // This address is not in any lookup table entry. + virtual_offset = 0x1500; + EXPECT_EQ(extractor->GetU8(&virtual_offset), 0U); + EXPECT_EQ(virtual_offset, 0x1500U); + + // Verify we CANNOT read data pointer from the gap. + virtual_offset = 0x1800; + const void *data = extractor->GetData(&virtual_offset, 1); + EXPECT_EQ(data, nullptr); + EXPECT_EQ(virtual_offset, 0x1800U); // Offset unchanged. + + // Verify we can read individual bytes within each entry. + virtual_offset = 0x1003; + EXPECT_EQ(extractor->GetU8(&virtual_offset), 0x04U); + EXPECT_EQ(virtual_offset, 0x1004U); + + // Verify we CANNOT read past the end of an entry. + virtual_offset = 0x1004; + EXPECT_EQ(extractor->GetU8(&virtual_offset), 0U); + EXPECT_EQ(virtual_offset, 0x1004U); +} + +TEST(VirtualDataExtractorTest, ReadExactlyAtEntryEnd) { + uint8_t buffer[] = {0x01, 0x02, 0x03, 0x04}; + + lldb::DataExtractorSP extractor = std::make_shared<VirtualDataExtractor>( + buffer, sizeof(buffer), eByteOrderLittle, 4, Table{Entry(0x1000, 4, 0)}); + + // Reading exactly to the end of an entry should work. + offset_t virtual_offset = 0x1000; + EXPECT_EQ(extractor->GetU32(&virtual_offset), 0x04030201U); + EXPECT_EQ(virtual_offset, 0x1004U); + + // But reading one byte past the end should fail. + virtual_offset = 0x1004; + EXPECT_EQ(extractor->GetU8(&virtual_offset), 0U); + EXPECT_EQ(virtual_offset, 0x1004U); + + // Reading from just before the end should work for smaller sizes. + virtual_offset = 0x1003; + EXPECT_EQ(extractor->GetU8(&virtual_offset), 0x04U); + EXPECT_EQ(virtual_offset, 0x1004U); +} |
