blob: dec0b1f97a59303dfcdc3fac2f42fed9bac223d9 [file] [log] [blame]
// 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());
// File and line empty.
result = SyncEvalGlobalInputLocation(eval_context, existing_location, ":");
EXPECT_TRUE(result.loc.has_error());
EXPECT_EQ(result.loc.err().msg(), "Unexpected token ':'.");
// Empty line number.
result = SyncEvalGlobalInputLocation(eval_context, existing_location, "foo/bar.cc:");
ASSERT_TRUE(result.loc.has_error());
// This message is a bit odd, it would be better if it described the missing number.
EXPECT_EQ(result.loc.err().msg(), "Unexpected input, did you forget an operator?");
// 0 line number.
result = SyncEvalGlobalInputLocation(eval_context, existing_location, "foo/bar.cc:0");
ASSERT_TRUE(result.loc.has_error());
EXPECT_EQ(result.loc.err().msg(), "Can't have a 0 line number.");
// 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());
// Empty file name with colon should be the same thing.
result = SyncEvalGlobalInputLocation(eval_context, existing_location_with_file, ":92");
ASSERT_TRUE(result.loc.ok()) << result.loc.err().msg();
EXPECT_FALSE(result.size);
EXPECT_EQ(file, result.loc.value().line.file());
EXPECT_EQ(92, 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