| // 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/expr/find_name.h" |
| |
| #include <lib/syslog/cpp/macros.h> |
| |
| #include <gtest/gtest.h> |
| |
| #include "llvm/BinaryFormat/Dwarf.h" |
| #include "src/developer/debug/zxdb/expr/eval_test_support.h" |
| #include "src/developer/debug/zxdb/expr/expr_parser.h" |
| #include "src/developer/debug/zxdb/expr/found_name.h" |
| #include "src/developer/debug/zxdb/symbols/base_type.h" |
| #include "src/developer/debug/zxdb/symbols/collection.h" |
| #include "src/developer/debug/zxdb/symbols/elf_symbol.h" |
| #include "src/developer/debug/zxdb/symbols/function.h" |
| #include "src/developer/debug/zxdb/symbols/identifier.h" |
| #include "src/developer/debug/zxdb/symbols/index_test_support.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_context.h" |
| #include "src/developer/debug/zxdb/symbols/symbol_test_parent_setter.h" |
| #include "src/developer/debug/zxdb/symbols/type_test_support.h" |
| #include "src/developer/debug/zxdb/symbols/variable_test_support.h" |
| |
| // NOTE: Finding variables on *this* and subclasses is EvalContextImplTest.FoundThis which tests |
| // both of our file's finding code as well as the decoding code. |
| |
| namespace zxdb { |
| |
| // This test declares the following structure. There are three levels of variables, each one has one |
| // unique variable, and one labeled "value" for testing ambiguity. |
| // |
| // namespace ns { |
| // |
| // int32_t ns_value; |
| // |
| // void Foo(int32_t value, int32_t other_param) { |
| // int32_t value; // 2nd declaration. |
| // int32_t function_local; |
| // { |
| // int32_t value; // 3rd declaration. |
| // int32_t block_local; |
| // } |
| // } |
| // |
| // } // namespace ns |
| TEST(FindName, FindLocalVariable) { |
| ProcessSymbolsTestSetup setup; |
| MockModuleSymbols* module_symbols = setup.InjectMockModule(); |
| SymbolContext symbol_context(ProcessSymbolsTestSetup::kDefaultLoadAddress); |
| auto& index_root = module_symbols->index().root(); |
| |
| auto int32_type = MakeInt32Type(); |
| |
| // Empty DWARF location expression. Since we don't evaluate any variables they can all be empty. |
| DwarfExpr var_loc; |
| |
| // Set up the module symbols. This creates "ns" and "ns_value" in the symbol index. |
| const char kNsName[] = "ns"; |
| auto ns_node = index_root.AddChild(IndexNode::Kind::kNamespace, kNsName, IndexNode::SymbolRef()); |
| const char kNsVarName[] = "ns_value"; |
| TestIndexedGlobalVariable ns_value(module_symbols, ns_node, kNsVarName); |
| |
| // Namespace. |
| auto ns = fxl::MakeRefCounted<Namespace>(); |
| ns->set_assigned_name(kNsName); |
| |
| // Function inside the namespace. |
| auto function = fxl::MakeRefCounted<Function>(DwarfTag::kSubprogram); |
| function->set_assigned_name("function"); |
| uint64_t kFunctionBeginAddr = ProcessSymbolsTestSetup::kDefaultLoadAddress + 0x1000; |
| uint64_t kFunctionEndAddr = ProcessSymbolsTestSetup::kDefaultLoadAddress + 0x2000; |
| function->set_code_ranges(AddressRanges(AddressRange(kFunctionBeginAddr, kFunctionEndAddr))); |
| SymbolTestParentSetter function_parent(function, ns); |
| |
| // Function parameters. |
| auto param_value = |
| MakeVariableForTest("value", int32_type, kFunctionBeginAddr, kFunctionEndAddr, var_loc); |
| auto param_other = |
| MakeVariableForTest("other_param", int32_type, kFunctionBeginAddr, kFunctionEndAddr, var_loc); |
| function->set_parameters({LazySymbol(param_value), LazySymbol(param_other)}); |
| |
| // Function local variables. |
| auto var_value = |
| MakeVariableForTest("value", int32_type, kFunctionBeginAddr, kFunctionEndAddr, var_loc); |
| auto var_other = MakeVariableForTest("function_local", int32_type, kFunctionBeginAddr, |
| kFunctionEndAddr, var_loc); |
| function->set_variables({LazySymbol(var_value), LazySymbol(var_other)}); |
| FindNameContext function_context(&setup.process(), symbol_context, function.get()); |
| |
| // Inner block. |
| uint64_t kBlockBeginAddr = ProcessSymbolsTestSetup::kDefaultLoadAddress + 0x1100; |
| uint64_t kBlockEndAddr = ProcessSymbolsTestSetup::kDefaultLoadAddress + 0x1200; |
| auto block = fxl::MakeRefCounted<CodeBlock>(DwarfTag::kLexicalBlock); |
| block->set_code_ranges(AddressRanges(AddressRange(kBlockBeginAddr, kBlockEndAddr))); |
| SymbolTestParentSetter block_parent(block, function); |
| function->set_inner_blocks({LazySymbol(block)}); |
| |
| // Inner block variables. |
| auto block_value = |
| MakeVariableForTest("value", int32_type, kBlockBeginAddr, kBlockEndAddr, var_loc); |
| auto block_other = |
| MakeVariableForTest("block_local", int32_type, kBlockBeginAddr, kBlockEndAddr, var_loc); |
| block->set_variables({LazySymbol(block_value), LazySymbol(block_other)}); |
| FindNameContext block_context(&setup.process(), symbol_context, block.get()); |
| |
| // ACTUAL TEST CODE ------------------------------------------------------------------------------ |
| |
| FindNameOptions all_kinds(FindNameOptions::kAllKinds); |
| |
| // Find "value" in the nested block should give the block's one. |
| ParsedIdentifier value_ident(var_value->GetAssignedName()); |
| FoundName found = FindName(block_context, all_kinds, value_ident); |
| EXPECT_TRUE(found); |
| EXPECT_EQ(block_value.get(), found.variable()); |
| |
| // Find "value" in the function block should give the function's one. |
| found = FindName(function_context, all_kinds, value_ident); |
| EXPECT_TRUE(found); |
| EXPECT_EQ(var_value.get(), found.variable()); |
| EXPECT_EQ(var_value->GetAssignedName(), found.GetName().GetFullNameNoQual()); |
| |
| // Find "::value" should match nothing. |
| ParsedIdentifier value_global_ident(IdentifierQualification::kGlobal, |
| ParsedIdentifierComponent(var_value->GetAssignedName())); |
| found = FindName(function_context, all_kinds, value_global_ident); |
| EXPECT_FALSE(found); |
| |
| // Prefix search for "va" should find all three "values". |
| std::vector<FoundName> found_vector; |
| FindNameOptions prefix_options(FindNameOptions::kAllKinds); |
| prefix_options.how = FindNameOptions::kPrefix; |
| prefix_options.max_results = 100; |
| ParsedIdentifier va_identifier("va"); |
| FindLocalVariable(prefix_options, block.get(), va_identifier, &found_vector); |
| ASSERT_EQ(3u, found_vector.size()); |
| |
| // Limiting the prefix result set to 1 should only fine one. |
| prefix_options.max_results = 1; |
| found_vector.clear(); |
| FindLocalVariable(prefix_options, block.get(), va_identifier, &found_vector); |
| ASSERT_EQ(1u, found_vector.size()); |
| |
| // Find "block_local" in the block should be found, but in the function it should not be. |
| ParsedIdentifier block_local_ident(block_other->GetAssignedName()); |
| found = FindName(block_context, all_kinds, block_local_ident); |
| EXPECT_TRUE(found); |
| EXPECT_EQ(block_other.get(), found.variable()); |
| EXPECT_EQ(block_other->GetAssignedName(), found.GetName().GetFullNameNoQual()); |
| found = FindName(function_context, all_kinds, block_local_ident); |
| EXPECT_FALSE(found); |
| |
| // Finding the other function parameter in the block should work. |
| ParsedIdentifier other_param_ident(param_other->GetAssignedName()); |
| found = FindName(block_context, all_kinds, other_param_ident); |
| EXPECT_TRUE(found); |
| EXPECT_EQ(param_other.get(), found.variable()); |
| |
| // Look up the variable "ns::ns_value" using the name "ns_value" (no namespace) from within the |
| // context of the "ns::function()" function. The namespace of the function should be implicitly |
| // picked up. |
| ParsedIdentifier ns_value_ident(kNsVarName); |
| found = FindName(block_context, all_kinds, ns_value_ident); |
| EXPECT_TRUE(found); |
| EXPECT_EQ(ns_value.var.get(), found.variable()); |
| EXPECT_EQ(kNsVarName, found.GetName().GetFullNameNoQual()); |
| |
| // Loop up the global "ns_value" var with no global symbol context. This should fail and not |
| // crash. |
| FindNameContext block_no_modules_context; |
| block_no_modules_context.block = block.get(); |
| found = FindName(block_no_modules_context, all_kinds, ns_value_ident); |
| EXPECT_FALSE(found); |
| } |
| |
| // This test only tests for finding object members. It doesn't set up the index which might find |
| // types, that's tested by FindIndexedName. |
| TEST(FindName, FindMember) { |
| DerivedClassTestSetup d; |
| |
| FindNameContext context; // Empty context = local and object vars only. |
| FindNameOptions exact_var(FindNameOptions::kAllKinds); |
| |
| // The two base classes each have a "b" member. |
| ParsedIdentifier b_ident("b"); |
| |
| // Finding one member "b" should find the first one (Base1) because the options find the first |
| // match by default. |
| std::vector<FoundName> results; |
| FindMember(context, exact_var, d.derived_type.get(), b_ident, nullptr, &results); |
| ASSERT_EQ(1u, results.size()); |
| ASSERT_EQ(FoundName::kMemberVariable, results[0].kind()); |
| EXPECT_EQ(d.kBase1Offset, results[0].member().object_path().BaseOffsetInDerived()); |
| EXPECT_EQ("b", results[0].GetName().GetFullNameNoQual()); |
| |
| // Increase the limit, it should find both in order of Base1, Base2. |
| results.clear(); |
| exact_var.max_results = 100; |
| FindMember(context, exact_var, d.derived_type.get(), b_ident, nullptr, &results); |
| ASSERT_EQ(2u, results.size()); |
| ASSERT_EQ(FoundName::kMemberVariable, results[0].kind()); |
| ASSERT_EQ(FoundName::kMemberVariable, results[1].kind()); |
| EXPECT_EQ(d.kBase1Offset, results[0].member().object_path().BaseOffsetInDerived()); |
| EXPECT_EQ(d.kBase2Offset, results[1].member().object_path().BaseOffsetInDerived()); |
| } |
| |
| // Tests that we implicitly search the object pointer ("this" in C++) for variables without |
| // qualification. |
| TEST(FindName, FindMemberOnThis) { |
| const char kMemberName[] = "member"; |
| |
| auto int_type = MakeInt32Type(); |
| auto class_type = MakeCollectionType(DwarfTag::kClassType, "MyClass", {{kMemberName, int_type}}); |
| |
| // Make lots of qualifiers to test stripping them when evaluating the class. Produce |
| // "MyClass const * const" |
| auto const_class_type = fxl::MakeRefCounted<ModifiedType>(DwarfTag::kConstType, class_type); |
| auto ptr_const_class_type = |
| fxl::MakeRefCounted<ModifiedType>(DwarfTag::kPointerType, const_class_type); |
| auto const_ptr_const_class_type = |
| fxl::MakeRefCounted<ModifiedType>(DwarfTag::kConstType, ptr_const_class_type); |
| |
| // This parameter to the function defines the "object pointer". It need not be called "this". |
| auto param = fxl::MakeRefCounted<Variable>(DwarfTag::kFormalParameter, "self", |
| const_ptr_const_class_type, VariableLocation()); |
| |
| // Make a member function. It has to have the "object pointer" set. |
| auto function = fxl::MakeRefCounted<Function>(DwarfTag::kSubprogram); |
| function->set_object_pointer(param); |
| |
| // Evaluate the unqualified name in the context of the function. |
| FindNameContext context; |
| context.block = function.get(); |
| |
| FindNameOptions options(FindNameOptions::kNoKinds); |
| options.find_vars = true; |
| |
| // Do the actual lookup. |
| FoundName found = FindName(context, options, ParsedIdentifier(kMemberName)); |
| ASSERT_TRUE(found.is_found()); |
| |
| ASSERT_EQ(FoundName::kMemberVariable, found.kind()); |
| EXPECT_EQ(const_ptr_const_class_type.get(), found.object_ptr()->type().Get()->As<Type>()); |
| EXPECT_EQ(class_type->data_members()[0].Get()->As<DataMember>(), found.member().data_member()); |
| } |
| |
| TEST(FindName, FindAnonUnion) { |
| // Makes this type: |
| // struct Outer { |
| // union Union { |
| // int inner; |
| // }; |
| // } |
| // and makes sure that we can evaluate "outer.inner", transparently going into the anonymous |
| // union. |
| |
| auto int_type = MakeInt32Type(); |
| constexpr uint32_t kInnerOffset = 4; // Offset of "inner" inside the union. |
| |
| constexpr char kInnerName[] = "inner"; |
| auto union_type = MakeCollectionTypeWithOffset(DwarfTag::kUnionType, "", kInnerOffset, |
| {{kInnerName, int_type}}); |
| |
| constexpr uint32_t kUnionOffset = 2; // Offset of the union inside "Outer". |
| auto outer_type = MakeCollectionTypeWithOffset(DwarfTag::kStructureType, "Outer", kUnionOffset, |
| {{"", union_type}}); |
| |
| constexpr uint8_t kIntValue = 42; |
| ExprValue value(outer_type, {0, 0, 0, 0, 0, 0, // Padding: kInnerOffset + kUnionOffset bytes. |
| kIntValue, 0, 0, 0}); // 32-bit integer little-endian. |
| |
| FindNameContext context; // Empty context = local and object vars only. |
| FindNameOptions exact_var(FindNameOptions::kAllKinds); |
| std::vector<FoundName> result; |
| FindMember(context, exact_var, outer_type.get(), ParsedIdentifier(kInnerName), nullptr, &result); |
| ASSERT_EQ(1u, result.size()); |
| |
| // The found value should be at the correct offset, accounting for both the union and integer |
| // offsets. |
| EXPECT_EQ(kInnerOffset + kUnionOffset, result[0].member().GetDataMemberOffset()); |
| EXPECT_EQ(kInnerName, result[0].member().data_member()->GetAssignedName()); |
| } |
| |
| // This only tests the ModuleSymbols and function naming integration, the details of the index |
| // searching are tested by FindGlobalNameInModule() |
| TEST(FindName, FindIndexedName) { |
| ProcessSymbolsTestSetup setup; |
| |
| const char kGlobalName[] = "global"; // Different variable in each. |
| const char kVar1Name[] = "var1"; // Only in module 1 |
| const char kVar2Name[] = "var2"; // Only in module 2 |
| const char kNotFoundName[] = "notfound"; |
| |
| ParsedIdentifier global_ident(kGlobalName); |
| ParsedIdentifier var1_ident(kVar1Name); |
| ParsedIdentifier var2_ident(kVar2Name); |
| ParsedIdentifier notfound_ident(kNotFoundName); |
| |
| // Module 1. |
| auto module_symbols1 = fxl::MakeRefCounted<MockModuleSymbols>("mod1.so"); |
| auto& root1 = module_symbols1->index().root(); // Root of the index for module 1. |
| TestIndexedGlobalVariable global1(module_symbols1.get(), &root1, kGlobalName); |
| TestIndexedGlobalVariable var1(module_symbols1.get(), &root1, kVar1Name); |
| constexpr uint64_t kLoadAddress1 = 0x1000; |
| SymbolContext symbol_context1(kLoadAddress1); |
| setup.InjectModule("mod1", "1234", kLoadAddress1, module_symbols1); |
| |
| // Module 2. |
| auto module_symbols2 = fxl::MakeRefCounted<MockModuleSymbols>("mod2.so"); |
| auto& root2 = module_symbols2->index().root(); // Root of the index for module 1. |
| TestIndexedGlobalVariable global2(module_symbols2.get(), &root2, kGlobalName); |
| TestIndexedGlobalVariable var2(module_symbols2.get(), &root2, kVar2Name); |
| constexpr uint64_t kLoadAddress2 = 0x2000; |
| SymbolContext symbol_context2(kLoadAddress2); |
| setup.InjectModule("mod2", "5678", kLoadAddress2, module_symbols2); |
| |
| FindNameOptions all_opts(FindNameOptions::kAllKinds); |
| std::vector<FoundName> found; |
| |
| // Searching for "global" in module1's context should give the global in that module. |
| FindNameContext mod1_context(&setup.process(), symbol_context1); |
| FindIndexedName(mod1_context, all_opts, ParsedIdentifier(), global_ident, true, &found); |
| ASSERT_EQ(1u, found.size()); |
| EXPECT_EQ(global1.var.get(), found[0].variable()); |
| |
| // Searching for "global" in module2's context should give the global in that module. |
| found.clear(); |
| FindNameContext mod2_context(&setup.process(), symbol_context2); |
| FindIndexedName(mod2_context, all_opts, ParsedIdentifier(), global_ident, true, &found); |
| ASSERT_EQ(1u, found.size()); |
| EXPECT_EQ(global2.var.get(), found[0].variable()); |
| |
| // Searching for "var1" in module2's context should still find it even though its in the other |
| // module. |
| found.clear(); |
| FindIndexedName(mod2_context, all_opts, ParsedIdentifier(), var1_ident, true, &found); |
| ASSERT_EQ(1u, found.size()); |
| EXPECT_EQ(var1.var.get(), found[0].variable()); |
| |
| // Searching for "var2" with only target-level symbols should still find it. |
| found.clear(); |
| FindIndexedName(FindNameContext(&setup.target()), all_opts, ParsedIdentifier(), var2_ident, true, |
| &found); |
| ASSERT_EQ(1u, found.size()); |
| EXPECT_EQ(var2.var.get(), found[0].variable()); |
| } |
| |
| TEST(FindName, FindIndexedNameInModule) { |
| auto module_symbols = fxl::MakeRefCounted<MockModuleSymbols>("test.so"); |
| auto& index_root = module_symbols->index().root(); // Root of the index. |
| |
| const char kVarName[] = "var"; |
| const char kNsName[] = "ns"; |
| |
| FindNameOptions all_opts(FindNameOptions::kAllKinds); |
| std::vector<FoundName> found; |
| |
| // Make a global variable in the toplevel namespace. |
| TestIndexedGlobalVariable global(module_symbols.get(), &index_root, kVarName); |
| |
| ParsedIdentifier var_ident(kVarName); |
| FindIndexedNameInModule(all_opts, module_symbols.get(), ParsedIdentifier(), var_ident, true, |
| &found); |
| ASSERT_EQ(1u, found.size()); |
| EXPECT_EQ(global.var.get(), found[0].variable()); |
| |
| // Say we're in some nested namespace and search for the same name. It should find the variable in |
| // the upper namespace. |
| ParsedIdentifier nested_ns(kNsName); |
| found.clear(); |
| FindIndexedNameInModule(all_opts, module_symbols.get(), nested_ns, var_ident, true, &found); |
| ASSERT_EQ(1u, found.size()); |
| EXPECT_EQ(global.var.get(), found[0].variable()); |
| |
| // Add a variable in the nested namespace with the same name. |
| auto ns_node = index_root.AddChild(IndexNode::Kind::kNamespace, kNsName, IndexNode::SymbolRef()); |
| TestIndexedGlobalVariable ns(module_symbols.get(), ns_node, kVarName); |
| |
| // Re-search for the same name in the nested namespace, it should get the nested one first. |
| found.clear(); |
| FindIndexedNameInModule(all_opts, module_symbols.get(), nested_ns, var_ident, true, &found); |
| ASSERT_EQ(1u, found.size()); |
| EXPECT_EQ(ns.var.get(), found[0].variable()); |
| |
| // Now do the same search but globally qualify the input "::var" which should match only the |
| // toplevel one. |
| ParsedIdentifier var_global_ident(IdentifierQualification::kGlobal, |
| ParsedIdentifierComponent(kVarName)); |
| found.clear(); |
| FindIndexedNameInModule(all_opts, module_symbols.get(), nested_ns, var_global_ident, true, |
| &found); |
| ASSERT_EQ(1u, found.size()); |
| EXPECT_EQ(global.var.get(), found[0].variable()); |
| EXPECT_EQ(kVarName, found[0].GetName().GetFullNameNoQual()); |
| } |
| |
| TEST(FindName, FindTypeName) { |
| ProcessSymbolsTestSetup setup; |
| MockModuleSymbols* module_symbols = setup.InjectMockModule(); |
| SymbolContext symbol_context(ProcessSymbolsTestSetup::kDefaultLoadAddress); |
| auto& index_root = module_symbols->index().root(); |
| |
| // Note space in "> >" which is how Clang likes to represent this. |
| const char kGlobalTypeName[] = "GlobalType<std::char_traits<char> >"; |
| const char kChildTypeName[] = "ChildType<std::char_traits<char> >"; |
| |
| // Global class name. |
| ParsedIdentifier global_type_name(kGlobalTypeName); |
| auto global_type = fxl::MakeRefCounted<Collection>(DwarfTag::kClassType); |
| global_type->set_assigned_name(kGlobalTypeName); |
| TestIndexedSymbol global_indexed(module_symbols, &index_root, kGlobalTypeName, global_type); |
| |
| // Child type definition inside the global class name. Currently types don't have child types and |
| // everything is found via the index. |
| ParsedIdentifier child_type_name(kChildTypeName); |
| ParsedIdentifier full_child_type_name; |
| Err err = ExprParser::ParseIdentifier( |
| "GlobalType<std::char_traits<char> >::ChildType<std::char_traits<char> >", |
| &full_child_type_name); |
| ASSERT_FALSE(err.has_error()); |
| auto child_type = fxl::MakeRefCounted<Collection>(DwarfTag::kClassType); |
| child_type->set_assigned_name(kChildTypeName); |
| TestIndexedSymbol child_indexed(module_symbols, global_indexed.index_node, kChildTypeName, |
| child_type); |
| |
| // Declares a variable that points to the GlobalType. It will be the "this" pointer for the |
| // function. The address range of this variable doesn't overlap the function. This means we can |
| // never compute its value, but since it's syntactically in-scope, we should still be able to use |
| // its type to resolve type names on the current class. |
| auto global_type_ptr = fxl::MakeRefCounted<ModifiedType>(DwarfTag::kPointerType, global_type); |
| auto this_var = |
| MakeVariableForTest("this", global_type_ptr, 0x9000, 0x9001, |
| DwarfExpr({llvm::dwarf::DW_OP_reg0, llvm::dwarf::DW_OP_stack_value})); |
| |
| // Function as a member of GlobalType. |
| auto function = fxl::MakeRefCounted<Function>(DwarfTag::kSubprogram); |
| function->set_assigned_name("function"); |
| uint64_t kFunctionBeginAddr = 0x1000; |
| uint64_t kFunctionEndAddr = 0x2000; |
| function->set_code_ranges(AddressRanges(AddressRange(kFunctionBeginAddr, kFunctionEndAddr))); |
| function->set_object_pointer(this_var); |
| |
| // This context declares a target and a block but no current module, which means the block and all |
| // modules should be searched with no particular preference (most other code sets a preference so |
| // this tests that less common case). |
| FindNameContext function_context; |
| function_context.target_symbols = &setup.target(); |
| function_context.block = function.get(); |
| |
| // ACTUAL TEST CODE ------------------------------------------------------------------------------ |
| |
| FindNameOptions all_kinds(FindNameOptions::kAllKinds); |
| |
| // Look up from the global function. |
| FoundName found = FindName(function_context, all_kinds, global_type_name); |
| EXPECT_TRUE(found); |
| EXPECT_EQ(FoundName::kType, found.kind()); |
| EXPECT_EQ(global_type.get(), found.type().get()); |
| // This has gone through our ParsedIdentifier template canonicalization so doesn't have the |
| // space between the ">>" like the input had. |
| EXPECT_EQ("::GlobalType<std::char_traits<char>>", found.GetName().GetFullName()); |
| |
| // Prefix search same as above. |
| FindNameOptions prefix_opts(FindNameOptions::kAllKinds); |
| prefix_opts.how = FindNameOptions::kPrefix; |
| prefix_opts.max_results = 10000; |
| std::vector<FoundName> found_vect; |
| ParsedIdentifier global_type_prefix("Gl"); |
| FindName(function_context, prefix_opts, global_type_prefix, &found_vect); |
| ASSERT_EQ(1u, found_vect.size()); |
| EXPECT_EQ(global_type.get(), found_vect[0].type().get()); |
| |
| // Look up the child function by full name. |
| found = FindName(function_context, all_kinds, full_child_type_name); |
| EXPECT_TRUE(found); |
| EXPECT_EQ(FoundName::kType, found.kind()); |
| EXPECT_EQ(child_type.get(), found.type().get()); |
| |
| // Look up the child function by just the child name. Since the function is a member of |
| // GlobalType, ChildType is a member of "this" so it should be found. |
| found = FindName(function_context, all_kinds, child_type_name); |
| EXPECT_TRUE(found); |
| EXPECT_EQ(FoundName::kType, found.kind()); |
| EXPECT_EQ(child_type.get(), found.type().get()); |
| } |
| |
| TEST(FindName, FindTemplateName) { |
| ProcessSymbolsTestSetup setup; |
| MockModuleSymbols* module_symbols = setup.InjectMockModule(); |
| SymbolContext symbol_context(ProcessSymbolsTestSetup::kDefaultLoadAddress); |
| auto& index_root = module_symbols->index().root(); |
| |
| // Declare two functions, one's a template, the other has the same prefix but isn't. |
| const char kTemplateIntName[] = "Template<int>"; |
| const char kTemplateNotName[] = "TemplateNot"; |
| |
| ParsedIdentifier template_int_name(kTemplateIntName); |
| ParsedIdentifier template_not_name(kTemplateNotName); |
| |
| auto template_int = fxl::MakeRefCounted<Collection>(DwarfTag::kClassType); |
| template_int->set_assigned_name(kTemplateIntName); |
| TestIndexedSymbol template_int_indexed(module_symbols, &index_root, kTemplateIntName, |
| template_int); |
| |
| auto template_not = fxl::MakeRefCounted<Collection>(DwarfTag::kClassType); |
| template_not->set_assigned_name(kTemplateNotName); |
| TestIndexedSymbol template_not_indexed(module_symbols, &index_root, kTemplateNotName, |
| template_not); |
| |
| // Search for names globally within the target. |
| FindNameContext context(&setup.target()); |
| |
| FindNameOptions all_types(FindNameOptions::kAllKinds); |
| |
| // The string "Template" should be identified as one. |
| ParsedIdentifier template_name("Template"); |
| auto found = FindName(context, all_types, template_name); |
| EXPECT_TRUE(found); |
| EXPECT_EQ(FoundName::kTemplate, found.kind()); |
| EXPECT_EQ("Template", found.GetName().GetFullName()); |
| |
| // The string "TemplateNot" is a type, it should be found as such. |
| std::vector<FoundName> found_vect; |
| FindName(context, all_types, template_not_name, &found_vect); |
| ASSERT_EQ(1u, found_vect.size()); |
| EXPECT_EQ(FoundName::kType, found_vect[0].kind()); |
| |
| // Now search only for templates, "TemplateNot" should not be found. |
| found_vect.clear(); |
| FindNameOptions templates_only(FindNameOptions::kNoKinds); |
| templates_only.find_templates = true; |
| FindName(context, templates_only, template_not_name, &found_vect); |
| EXPECT_TRUE(found_vect.empty()); |
| |
| // Prefix search for "Templ" should get both full types. Since prefix searching doesn't currently |
| // work for templates, we won't get a template record. These results will need to be updated if |
| // template prefix matching is added. |
| found_vect.clear(); |
| FindNameOptions all_prefixes(FindNameOptions::kAllKinds); |
| all_prefixes.how = FindNameOptions::kPrefix; |
| all_prefixes.max_results = 100; |
| ParsedIdentifier templ_name("Templ"); |
| FindName(context, all_prefixes, templ_name, &found_vect); |
| ASSERT_EQ(2u, found_vect.size()); |
| // Both results are types. |
| EXPECT_EQ(FoundName::kType, found_vect[0].kind()); |
| EXPECT_EQ(FoundName::kType, found_vect[1].kind()); |
| // Can appear in either order. |
| EXPECT_TRUE((found_vect[0].type().get() == template_int.get() && |
| found_vect[1].type().get() == template_not.get()) || |
| (found_vect[0].type().get() == template_not.get() && |
| found_vect[1].type().get() == template_int.get())); |
| } |
| |
| TEST(FindName, FindType) { |
| ProcessSymbolsTestSetup setup; |
| auto module_symbols1 = fxl::MakeRefCounted<MockModuleSymbols>("mod1.so"); |
| auto& index_root1 = module_symbols1->index().root(); |
| auto module_symbols2 = fxl::MakeRefCounted<MockModuleSymbols>("mod2.so"); |
| auto& index_root2 = module_symbols2->index().root(); |
| |
| const char kStructName[] = "Struct"; |
| |
| ParsedIdentifier struct_name(kStructName); |
| |
| // Make and index the forward declaration in module 1. |
| auto fwd_decl = fxl::MakeRefCounted<Collection>(DwarfTag::kStructureType); |
| fwd_decl->set_assigned_name(kStructName); |
| fwd_decl->set_is_declaration(true); |
| TestIndexedSymbol fwd_decl_indexed(module_symbols1.get(), &index_root1, kStructName, fwd_decl); |
| |
| // Make and index a definition in module 2. |
| auto def = fxl::MakeRefCounted<Collection>(DwarfTag::kClassType); |
| def->set_assigned_name(kStructName); |
| def->set_byte_size(12); |
| TestIndexedSymbol def_indexed(module_symbols2.get(), &index_root2, kStructName, def); |
| |
| // Set the modules as loaded. |
| constexpr uint64_t kLoadAddress1 = 0x1000; |
| SymbolContext symbol_context1(kLoadAddress1); |
| setup.InjectModule("mod1", "1234", kLoadAddress1, module_symbols1); |
| constexpr uint64_t kLoadAddress2 = 0x2000; |
| SymbolContext symbol_context2(kLoadAddress2); |
| setup.InjectModule("mod2", "5678", kLoadAddress2, module_symbols2); |
| |
| // Search for names starting from "mod1" so the output ordering is guaranteed. |
| FindNameContext context(&setup.process(), symbol_context1); |
| |
| // Finding types should return both the forward definition and the definition. |
| FindNameOptions find_types(FindNameOptions::kNoKinds); |
| find_types.find_types = true; |
| find_types.max_results = 100; |
| |
| std::vector<FoundName> results; |
| FindName(context, find_types, struct_name, &results); |
| ASSERT_EQ(2u, results.size()); |
| |
| // The forward-declaration should be found first since it's in the "current" module we passed to |
| // FindName. |
| EXPECT_EQ(fwd_decl.get(), results[0].type().get()); |
| EXPECT_EQ(def.get(), results[1].type().get()); |
| |
| // Now find only definitions. |
| FindNameOptions find_type_defs(FindNameOptions::kNoKinds); |
| find_type_defs.find_type_defs = true; |
| find_type_defs.max_results = 100; |
| |
| // Should find only the definition now. |
| results.clear(); |
| FindName(context, find_type_defs, struct_name, &results); |
| ASSERT_EQ(1u, results.size()); |
| EXPECT_EQ(def.get(), results[0].type().get()); |
| } |
| |
| TEST(FindName, FindNamespace) { |
| ProcessSymbolsTestSetup setup; |
| MockModuleSymbols* module_symbols = setup.InjectMockModule(); |
| |
| auto& index_root = module_symbols->index().root(); |
| SymbolContext symbol_context(ProcessSymbolsTestSetup::kDefaultLoadAddress); |
| FindNameContext context(&setup.process(), symbol_context); |
| |
| const char kStd[] = "std"; |
| index_root.AddChild(IndexNode::Kind::kNamespace, kStd); |
| |
| const char kStar[] = "star"; |
| auto* star_ns = index_root.AddChild(IndexNode::Kind::kNamespace, kStar); |
| |
| // star::internal |
| const char kInternal[] = "internal"; |
| star_ns->AddChild(IndexNode::Kind::kNamespace, kInternal); |
| |
| FindNameOptions find_ns(FindNameOptions::kNoKinds); |
| find_ns.find_namespaces = true; |
| find_ns.max_results = 100; |
| |
| // Find the "std" namespace. |
| std::vector<FoundName> results; |
| FindName(context, find_ns, ParsedIdentifier(kStd), &results); |
| ASSERT_EQ(1u, results.size()); |
| EXPECT_EQ(FoundName::kNamespace, results[0].kind()); |
| EXPECT_EQ(kStd, results[0].GetName().GetFullName()); |
| |
| // Find "s..." namespaces by prefix. |
| FindNameOptions find_ns_prefix = find_ns; |
| find_ns_prefix.how = FindNameOptions::kPrefix; |
| results.clear(); |
| FindName(context, find_ns_prefix, ParsedIdentifier("s"), &results); |
| ASSERT_EQ(2u, results.size()); |
| // Results can be in either order. |
| EXPECT_TRUE( |
| (results[0].GetName().GetFullName() == kStd && results[1].GetName().GetFullName() == kStar) || |
| (results[0].GetName().GetFullName() == kStar && results[1].GetName().GetFullName() == kStd)); |
| |
| // Find the "star::i" namespace by prefix. |
| ParsedIdentifier star_internal_prefix; |
| ASSERT_TRUE(ExprParser::ParseIdentifier("star::i", &star_internal_prefix).ok()); |
| results.clear(); |
| FindName(context, find_ns_prefix, star_internal_prefix, &results); |
| ASSERT_EQ(1u, results.size()); |
| EXPECT_EQ("star::internal", results[0].GetName().GetFullName()); |
| } |
| |
| // A symbol should be found in any namespace with the "all namespaces" flag set. |
| TEST(FindName, FindRecursiveNamespace) { |
| ProcessSymbolsTestSetup setup; |
| MockModuleSymbols* module_symbols = setup.InjectMockModule(); |
| |
| auto& index_root = module_symbols->index().root(); |
| SymbolContext symbol_context(ProcessSymbolsTestSetup::kDefaultLoadAddress); |
| FindNameContext context(&setup.process(), symbol_context); |
| |
| // Make several functions |
| // |
| // - ::Foo() |
| // - ::std::Foo() |
| // - ::std::bar::Foo() |
| // - ::std::$anon::Foo() |
| |
| const char kStdName[] = "std"; |
| auto std_ns_symbol = fxl::MakeRefCounted<Namespace>(kStdName); |
| auto std_ns = index_root.AddChild(IndexNode::Kind::kNamespace, kStdName); |
| |
| const char kBarName[] = "bar"; |
| auto std_bar_ns_symbol = fxl::MakeRefCounted<Namespace>(kBarName); |
| SymbolTestParentSetter std_bar_ns_symbol_parent(std_bar_ns_symbol, std_ns_symbol); |
| auto std_bar_ns = std_ns->AddChild(IndexNode::Kind::kNamespace, kBarName); |
| |
| auto std_anon_ns_symbol = fxl::MakeRefCounted<Namespace>(std::string()); |
| SymbolTestParentSetter std_anon_ns_symbol_parent(std_anon_ns_symbol, std_ns_symbol); |
| auto std_anon_ns = std_ns->AddChild(IndexNode::Kind::kNamespace, ""); |
| |
| // ::Foo(). |
| const char kFooName[] = "Foo"; |
| auto foo = fxl::MakeRefCounted<Function>(DwarfTag::kSubprogram); |
| foo->set_assigned_name(kFooName); |
| TestIndexedSymbol foo_indexed(module_symbols, &index_root, kFooName, foo); |
| |
| // ::std::Foo(). |
| auto std_foo = fxl::MakeRefCounted<Function>(DwarfTag::kSubprogram); |
| SymbolTestParentSetter std_foo_parent(std_foo, std_ns_symbol); |
| std_foo->set_assigned_name(kFooName); |
| TestIndexedSymbol std_foo_indexed(module_symbols, std_ns, kFooName, std_foo); |
| |
| // ::std::bar::Foo(). |
| auto std_bar_foo = fxl::MakeRefCounted<Function>(DwarfTag::kSubprogram); |
| SymbolTestParentSetter std_bar_foo_parent(std_bar_foo, std_bar_ns_symbol); |
| std_bar_foo->set_assigned_name(kFooName); |
| TestIndexedSymbol std_bar_foo_indexed(module_symbols, std_bar_ns, kFooName, std_bar_foo); |
| |
| // ::std::$anon::Foo(). |
| auto std_anon_foo = fxl::MakeRefCounted<Function>(DwarfTag::kSubprogram); |
| SymbolTestParentSetter std_anon_foo_parent(std_anon_foo, std_anon_ns_symbol); |
| std_anon_foo->set_assigned_name(kFooName); |
| TestIndexedSymbol std_anon_foo_indexed(module_symbols, std_anon_ns, kFooName, std_anon_foo); |
| |
| // Search for "Foo" in all namespaces. |
| ParsedIdentifier foo_ident((ParsedIdentifierComponent(kFooName))); |
| FindNameOptions opts(FindNameOptions::kAllKinds); |
| opts.max_results = 100; // Want everything. |
| opts.search_mode = FindNameOptions::kAllNamespaces; |
| std::vector<FoundName> results; |
| FindName(context, opts, foo_ident, &results); |
| |
| // It should have found all 3 Foo's in order. |
| ASSERT_EQ(4u, results.size()); |
| EXPECT_EQ(foo.get(), results[0].function().get()); |
| EXPECT_EQ(std_foo.get(), results[1].function().get()); |
| EXPECT_EQ(std_anon_foo.get(), results[2].function().get()); |
| EXPECT_EQ(std_bar_foo.get(), results[3].function().get()); |
| |
| // Now find by prefix recursively. |
| FindNameOptions prefix_opts = opts; |
| prefix_opts.how = FindNameOptions::kPrefix; |
| results.clear(); |
| FindName(context, prefix_opts, ParsedIdentifier(ParsedIdentifierComponent("F")), &results); |
| |
| // Should have found the same matches. |
| ASSERT_EQ(4u, results.size()); |
| EXPECT_EQ(foo.get(), results[0].function().get()); |
| EXPECT_EQ(std_foo.get(), results[1].function().get()); |
| EXPECT_EQ(std_anon_foo.get(), results[2].function().get()); |
| EXPECT_EQ(std_bar_foo.get(), results[3].function().get()); |
| |
| // Find "bar::Foo" should find only the one match, using the implicit toplevel namespace. |
| ParsedIdentifier bar_foo; |
| bar_foo.AppendComponent(ParsedIdentifierComponent("bar")); |
| bar_foo.AppendComponent(ParsedIdentifierComponent("Foo")); |
| results.clear(); |
| FindName(context, opts, bar_foo, &results); |
| ASSERT_EQ(1u, results.size()); |
| EXPECT_EQ(std_bar_foo.get(), results[0].function().get()); |
| |
| // Find "::Foo" should only find the toplevel one, even with implicit namespace searching. |
| ParsedIdentifier abs_foo(IdentifierQualification::kGlobal, ParsedIdentifierComponent(kFooName)); |
| results.clear(); |
| FindName(context, opts, abs_foo, &results); |
| ASSERT_EQ(1u, results.size()); |
| EXPECT_EQ(foo.get(), results[0].function().get()); |
| |
| // Find "::std::Foo" should find both ::std::Foo and the anonymous namespace one. |
| ParsedIdentifier abs_std_foo(IdentifierQualification::kGlobal, |
| ParsedIdentifierComponent(kStdName)); |
| abs_std_foo.AppendComponent(ParsedIdentifierComponent(kFooName)); |
| results.clear(); |
| FindName(context, opts, abs_std_foo, &results); |
| ASSERT_EQ(2u, results.size()); |
| EXPECT_EQ(std_foo.get(), results[0].function().get()); |
| EXPECT_EQ(std_anon_foo.get(), results[1].function().get()); |
| } |
| |
| // Tests that $elf(...) symbols look up properly. |
| TEST(FindName, ElfSymbol) { |
| ProcessSymbolsTestSetup setup; |
| MockModuleSymbols* module_symbols = setup.InjectMockModule(); |
| |
| SymbolContext symbol_context(ProcessSymbolsTestSetup::kDefaultLoadAddress); |
| FindNameContext context(&setup.process(), symbol_context); |
| |
| const char kRawName[] = "pthread_key_create"; |
| ParsedIdentifier elf_name(IdentifierQualification::kRelative, |
| ParsedIdentifierComponent(SpecialIdentifier::kElf, kRawName)); |
| |
| auto elf_symbol = fxl::MakeRefCounted<ElfSymbol>( |
| module_symbols->GetWeakPtr(), ElfSymbolRecord(ElfSymbolType::kPlt, 0x1234, 4, kRawName)); |
| Location elf_location(0x12345678, FileLine(), 0, symbol_context, elf_symbol); |
| |
| // The current implementation does not add ELF symbols to the main index although this might be a |
| // nice thing to do in the future. So for now, we just need to test that manually injected ELF |
| // symbols come out the other end. |
| module_symbols->AddSymbolLocations(ToIdentifier(elf_name), {elf_location}); |
| |
| FindNameOptions opts(FindNameOptions::kAllKinds); |
| std::vector<FoundName> results; |
| FindName(context, opts, elf_name, &results); |
| |
| ASSERT_EQ(1u, results.size()); |
| ASSERT_TRUE(results[0].other_symbol()); |
| |
| EXPECT_EQ(results[0].other_symbol().get(), elf_symbol.get()); |
| } |
| |
| } // namespace zxdb |