| // Copyright 2018 The Fuchsia Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #include "src/developer/debug/zxdb/console/input_location_parser.h" | |
| #include <gtest/gtest.h> | |
| #include "src/developer/debug/zxdb/client/mock_frame.h" | |
| #include "src/developer/debug/zxdb/client/mock_process.h" | |
| #include "src/developer/debug/zxdb/client/mock_target.h" | |
| #include "src/developer/debug/zxdb/client/mock_thread.h" | |
| #include "src/developer/debug/zxdb/client/session.h" | |
| #include "src/developer/debug/zxdb/common/test_with_loop.h" | |
| #include "src/developer/debug/zxdb/console/command.h" | |
| #include "src/developer/debug/zxdb/expr/mock_eval_context.h" | |
| #include "src/developer/debug/zxdb/symbols/collection.h" | |
| #include "src/developer/debug/zxdb/symbols/function.h" | |
| #include "src/developer/debug/zxdb/symbols/index_test_support.h" | |
| #include "src/developer/debug/zxdb/symbols/location.h" | |
| #include "src/developer/debug/zxdb/symbols/mock_module_symbols.h" | |
| #include "src/developer/debug/zxdb/symbols/modified_type.h" | |
| #include "src/developer/debug/zxdb/symbols/namespace.h" | |
| #include "src/developer/debug/zxdb/symbols/process_symbols_test_setup.h" | |
| #include "src/developer/debug/zxdb/symbols/symbol_test_parent_setter.h" | |
| namespace zxdb { | |
| namespace { | |
| struct EvalResult { | |
| ErrOr<InputLocation> loc = ErrOr<InputLocation>(Err("<test hardness>: callback not issued>")); | |
| std::optional<uint32_t> size; | |
| }; | |
| class InputLocationParserTest : public TestWithLoop { | |
| public: | |
| void SetUp() override { mock_module_symbols_ = symbols_.InjectMockModule(); } | |
| EvalResult SyncEvalGlobalInputLocation(const fxl::RefPtr<EvalContext> eval_context, | |
| const Location& location, const std::string& input) { | |
| bool called = false; | |
| EvalResult result; | |
| EvalGlobalInputLocation( | |
| eval_context, location, input, | |
| [&called, &result](ErrOr<InputLocation> value, std::optional<uint32_t> size) { | |
| called = true; | |
| result.loc = std::move(value); | |
| result.size = size; | |
| }); | |
| loop().RunUntilNoTasks(); | |
| EXPECT_TRUE(called); | |
| return result; | |
| } | |
| protected: | |
| ProcessSymbolsTestSetup symbols_; | |
| MockModuleSymbols* mock_module_symbols_ = nullptr; | |
| SymbolContext symbol_context_ = SymbolContext(ProcessSymbolsTestSetup::kDefaultLoadAddress); | |
| }; | |
| } // namespace | |
| TEST_F(InputLocationParserTest, EvalGlobalInputLocation) { | |
| auto eval_context = fxl::MakeRefCounted<MockEvalContext>(); | |
| Location existing_location; | |
| // Valid symbol (including colons). | |
| auto result = SyncEvalGlobalInputLocation(eval_context, existing_location, "Foo::Bar"); | |
| ASSERT_TRUE(result.loc.ok()) << result.loc.err().msg(); | |
| EXPECT_FALSE(result.size); | |
| EXPECT_EQ(InputLocation::Type::kName, result.loc.value().type); | |
| EXPECT_EQ(R"("Foo"; ::"Bar")", result.loc.value().name.GetDebugName()); | |
| // Valid file/line. | |
| result = SyncEvalGlobalInputLocation(eval_context, existing_location, "foo/bar.cc:123"); | |
| ASSERT_TRUE(result.loc.ok()) << result.loc.err().msg(); | |
| EXPECT_FALSE(result.size); | |
| EXPECT_EQ(InputLocation::Type::kLine, result.loc.value().type); | |
| EXPECT_EQ("foo/bar.cc", result.loc.value().line.file()); | |
| EXPECT_EQ(123, result.loc.value().line.line()); | |
| // Invalid file/line. | |
| result = SyncEvalGlobalInputLocation(eval_context, existing_location, "foo/bar.cc:123x"); | |
| EXPECT_TRUE(result.loc.has_error()); | |
| // Valid hex address with *. | |
| result = SyncEvalGlobalInputLocation(eval_context, existing_location, "*0x12345f"); | |
| ASSERT_TRUE(result.loc.ok()) << result.loc.err().msg(); | |
| EXPECT_FALSE(result.size); | |
| EXPECT_EQ(InputLocation::Type::kAddress, result.loc.value().type); | |
| EXPECT_EQ(0x12345fu, result.loc.value().address); | |
| // Valid hex address without a *. | |
| result = SyncEvalGlobalInputLocation(eval_context, existing_location, "0x12345f"); | |
| ASSERT_TRUE(result.loc.ok()) << result.loc.err().msg(); | |
| EXPECT_FALSE(result.size); | |
| EXPECT_EQ(InputLocation::Type::kAddress, result.loc.value().type); | |
| EXPECT_EQ(0x12345fu, result.loc.value().address); | |
| // Decimal number with "*" override should be an address. | |
| result = SyncEvalGlobalInputLocation(eval_context, existing_location, "*21"); | |
| ASSERT_TRUE(result.loc.ok()) << result.loc.err().msg(); | |
| EXPECT_FALSE(result.size); | |
| EXPECT_EQ(InputLocation::Type::kAddress, result.loc.value().type); | |
| EXPECT_EQ(21u, result.loc.value().address); | |
| // Invalid address. | |
| result = SyncEvalGlobalInputLocation(eval_context, existing_location, "*2134x"); | |
| ASSERT_TRUE(result.loc.has_error()); | |
| // Line number with no Frame for context. | |
| result = SyncEvalGlobalInputLocation(eval_context, existing_location, "21"); | |
| ASSERT_TRUE(result.loc.has_error()); | |
| // Valid implicit file name. | |
| std::string file = "foo.cc"; | |
| Location existing_location_with_file(0x1234, FileLine(file, 12), 0, | |
| SymbolContext::ForRelativeAddresses(), LazySymbol()); | |
| result = SyncEvalGlobalInputLocation(eval_context, existing_location_with_file, "21"); | |
| ASSERT_TRUE(result.loc.ok()) << result.loc.err().msg(); | |
| EXPECT_FALSE(result.size); | |
| EXPECT_EQ(file, result.loc.value().line.file()); | |
| EXPECT_EQ(21, result.loc.value().line.line()); | |
| // Pointer to a double. This should give a valid expression size. | |
| result = SyncEvalGlobalInputLocation(eval_context, existing_location, "*(double*)0x123450"); | |
| ASSERT_TRUE(result.loc.ok()) << result.loc.err().msg(); | |
| ASSERT_TRUE(result.size); | |
| EXPECT_EQ(8u, *result.size); | |
| EXPECT_EQ(InputLocation::Type::kAddress, result.loc.value().type); | |
| EXPECT_EQ(0x123450u, result.loc.value().address); | |
| } | |
| TEST_F(InputLocationParserTest, ResolveInputLocation) { | |
| auto& index_root = mock_module_symbols_->index().root(); | |
| // Resolve to nothing. | |
| Location output; | |
| Err err = ResolveUniqueInputLocation(&symbols_.process(), Location(), "Foo", false, &output); | |
| EXPECT_TRUE(err.has_error()); | |
| EXPECT_EQ("Nothing matching this name was found.", err.msg()); | |
| Location expected(0x12345678, FileLine("file.cc", 12), 0, symbol_context_); | |
| // To be found, "Foo" must be in both the index and in the symbol location mock list. | |
| const char kFooName[] = "Foo"; | |
| auto foo = fxl::MakeRefCounted<Function>(DwarfTag::kSubprogram); | |
| foo->set_assigned_name(kFooName); | |
| TestIndexedSymbol foo_indexed(mock_module_symbols_, &index_root, kFooName, foo); | |
| mock_module_symbols_->AddSymbolLocations("Foo", {expected}); | |
| // Resolve to one location (success) case. | |
| err = ResolveUniqueInputLocation(&symbols_.process(), Location(), "Foo", false, &output); | |
| EXPECT_FALSE(err.has_error()) << err.msg(); | |
| EXPECT_EQ(expected.address(), output.address()); | |
| // Resolve to lots of locations, it should give suggestions. Even though we didn't request | |
| // symbols, the result should be symbolized. | |
| std::vector<Location> expected_locations; | |
| for (int i = 0; i < 15; i++) { | |
| // The address and line numbers count up for each match. | |
| expected_locations.emplace_back(0x12345000 + i, FileLine("file.cc", 100 + i), 0, | |
| symbol_context_); | |
| } | |
| mock_module_symbols_->AddSymbolLocations("Foo", expected_locations); | |
| // Resolve to all of them. | |
| std::vector<Location> output_locations; | |
| err = ResolveInputLocations(&symbols_.process(), Location(), "Foo", false, &output_locations); | |
| EXPECT_FALSE(err.has_error()); | |
| // The result should be the same as the input but not symbolized (we | |
| // requested no symbolization). | |
| ASSERT_EQ(expected_locations.size(), output_locations.size()); | |
| for (size_t i = 0; i < expected_locations.size(); i++) { | |
| EXPECT_EQ(expected_locations[i].address(), output_locations[i].address()); | |
| EXPECT_FALSE(output_locations[i].has_symbols()); | |
| } | |
| // Try to resolve one of them. Since there are many this will fail. We requested no symbolization | |
| // but the error message should still be symbolized. | |
| err = ResolveUniqueInputLocation(&symbols_.process(), Location(), "Foo", false, &output); | |
| EXPECT_TRUE(err.has_error()); | |
| EXPECT_EQ(R"(This resolves to more than one location. Could be: | |
| • file.cc:100 = 0x12345000 | |
| • file.cc:101 = 0x12345001 | |
| • file.cc:102 = 0x12345002 | |
| • file.cc:103 = 0x12345003 | |
| • file.cc:104 = 0x12345004 | |
| • file.cc:105 = 0x12345005 | |
| • file.cc:106 = 0x12345006 | |
| • file.cc:107 = 0x12345007 | |
| • file.cc:108 = 0x12345008 | |
| • file.cc:109 = 0x12345009 | |
| ...5 more omitted... | |
| )", | |
| err.msg()); | |
| } | |
| // Tests that ParseLocalInputLocation() finds matches on the local object for symbolic names. | |
| TEST_F(InputLocationParserTest, ParseLocalInputLocation) { | |
| auto& root = mock_module_symbols_->index().root(); | |
| const char kFunctionName[] = "Foo"; | |
| // The no-context case should just return the input symbol. | |
| std::vector<InputLocation> results; | |
| Err err = ParseLocalInputLocation(nullptr, kFunctionName, &results); | |
| ASSERT_TRUE(err.ok()); | |
| ASSERT_EQ(1u, results.size()); | |
| EXPECT_EQ(InputLocation::Type::kName, results[0].type); | |
| EXPECT_EQ(R"("Foo")", results[0].name.GetDebugName()); | |
| // Make a class. | |
| const char kClassName[] = "MyClass"; | |
| auto my_class = fxl::MakeRefCounted<Collection>(DwarfTag::kClassType); | |
| my_class->set_assigned_name(kClassName); | |
| TestIndexedSymbol indexed_class(mock_module_symbols_, &root, kClassName, my_class); | |
| // Function inside the class. | |
| auto foo_func = fxl::MakeRefCounted<Function>(DwarfTag::kSubprogram); | |
| SymbolTestParentSetter foo_func_parent(foo_func, my_class); | |
| foo_func->set_assigned_name(kFunctionName); | |
| constexpr uint64_t kFunctionBegin = ProcessSymbolsTestSetup::kDefaultLoadAddress + 0x1000; | |
| foo_func->set_code_ranges(AddressRanges(AddressRange(kFunctionBegin, kFunctionBegin + 0x10))); | |
| TestIndexedSymbol indexed_func(mock_module_symbols_, indexed_class.index_node, kFunctionName, | |
| foo_func); | |
| // Make a "this" pointer for the function pointing back to the class. | |
| auto my_class_ptr = fxl::MakeRefCounted<ModifiedType>(DwarfTag::kPointerType, my_class); | |
| auto this_var = | |
| fxl::MakeRefCounted<Variable>(DwarfTag::kVariable, "this", my_class_ptr, VariableLocation()); | |
| foo_func->set_object_pointer(this_var); | |
| // Process/thread setup. | |
| Session session; | |
| MockProcess process(&session); | |
| process.set_symbols(&symbols_.process()); | |
| MockThread thread(&process); | |
| // The location points to the first address of the function. | |
| Location location(kFunctionBegin, FileLine(), 0, symbol_context_, foo_func); | |
| MockFrame frame(&session, &thread, location, 0x1000); | |
| // A new search should return the more specific version in the class, plus the global one. | |
| err = ParseLocalInputLocation(&frame, kFunctionName, &results); | |
| ASSERT_TRUE(err.ok()); | |
| ASSERT_EQ(2u, results.size()); | |
| EXPECT_EQ(InputLocation::Type::kName, results[0].type); | |
| EXPECT_EQ(R"(::"MyClass"; ::"Foo")", results[0].name.GetDebugName()); | |
| EXPECT_EQ(InputLocation::Type::kName, results[1].type); | |
| EXPECT_EQ(R"("Foo")", results[1].name.GetDebugName()); | |
| // A fully qualified function name ("::Foo") should not match the current class and only the | |
| // global version should be returned. | |
| results.clear(); | |
| err = ParseLocalInputLocation(&frame, std::string("::") + kFunctionName, &results); | |
| ASSERT_TRUE(err.ok()); | |
| ASSERT_EQ(1u, results.size()); | |
| EXPECT_EQ(InputLocation::Type::kName, results[0].type); | |
| EXPECT_EQ(R"(::"Foo")", results[0].name.GetDebugName()); | |
| } | |
| // Most of the prefix searching is tested by the FindName tests. This just tests the integration of | |
| // that with the InputLocation completion. | |
| TEST_F(InputLocationParserTest, CompleteInputLocation) { | |
| auto& root = mock_module_symbols_->index().root(); | |
| // Global function. | |
| const char kGlobalName[] = "aGlobalFunction"; | |
| auto global_func = fxl::MakeRefCounted<Function>(DwarfTag::kSubprogram); | |
| global_func->set_assigned_name(kGlobalName); | |
| TestIndexedSymbol indexed_global(mock_module_symbols_, &root, kGlobalName, global_func); | |
| // Namespace | |
| const char kNsName[] = "aNamespace"; | |
| auto ns = fxl::MakeRefCounted<Namespace>(); | |
| ns->set_assigned_name(kNsName); | |
| TestIndexedSymbol indexed_ns(mock_module_symbols_, &root, kNsName, ns); | |
| // Class inside the namespace. | |
| const char kClassName[] = "Class"; | |
| auto global_type = fxl::MakeRefCounted<Collection>(DwarfTag::kClassType); | |
| SymbolTestParentSetter global_type_parent(global_type, ns); | |
| global_type->set_assigned_name(kClassName); | |
| TestIndexedSymbol indexed_type(mock_module_symbols_, indexed_ns.index_node, kClassName, | |
| global_type); | |
| // Function inside the class. | |
| const char kMemberName[] = "MemberFunction"; | |
| auto member_func = fxl::MakeRefCounted<Function>(DwarfTag::kSubprogram); | |
| member_func->set_assigned_name(kMemberName); | |
| SymbolTestParentSetter member_func_parent(member_func, global_type); | |
| TestIndexedSymbol indexed_member(mock_module_symbols_, indexed_type.index_node, kMemberName, | |
| member_func); | |
| // TODO(brettw) make a test setup helper for a whole session / target / process / thread / frame + | |
| // symbols. | |
| Session session; | |
| MockTarget target(&session); | |
| target.set_symbols(&symbols_.target()); | |
| MockProcess process(&session); | |
| process.set_symbols(&symbols_.process()); | |
| Location loc; | |
| MockFrame frame(&session, nullptr, loc, 0); | |
| target.SetRunningProcess(&process); | |
| Command command; | |
| command.set_verb(Verb::kBreak); | |
| command.set_target(&target); | |
| command.set_frame(&frame); | |
| // TEST CODE ------------------------------------------------------------------------------------- | |
| // "a" should complete to both "aNamespace" and "aGlobalFunction" (in that order). | |
| std::vector<std::string> found; | |
| CompleteInputLocation(command, "a", &found); | |
| ASSERT_EQ(2u, found.size()); | |
| EXPECT_EQ("aNamespace::", found[0]); // Namespaces get "::" appended. | |
| EXPECT_EQ(std::string("::") + kGlobalName, found[1]); | |
| // "aNamespace::" doesn't complete to anything. It might be nice to have this complete to all | |
| // functions in the namespace, but we don't implement that yet. In the meantime, at least test | |
| // this does what we currently expect. | |
| found.clear(); | |
| CompleteInputLocation(command, "aNamespace::", &found); | |
| ASSERT_TRUE(found.empty()); | |
| // Completing classes. | |
| found.clear(); | |
| CompleteInputLocation(command, "aNamespace::Cl", &found); | |
| ASSERT_EQ(1u, found.size()); | |
| EXPECT_EQ("::aNamespace::Class::", found[0]); // Classes get "::" appended. | |
| // Completing class member functions. | |
| found.clear(); | |
| CompleteInputLocation(command, "aNamespace::Class::M", &found); | |
| ASSERT_EQ(1u, found.size()); | |
| EXPECT_EQ("::aNamespace::Class::MemberFunction", found[0]); | |
| } | |
| } // namespace zxdb |