|  | // 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 |