Skip to content

[lldb] Better matching of types in anonymous namespaces #102111

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Sep 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 21 additions & 3 deletions lldb/include/lldb/Symbol/Type.h
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,13 @@ FLAGS_ENUM(TypeQueryOptions){
/// If set, the query will ignore all Module entries in the type context,
/// even for exact matches.
e_ignore_modules = (1u << 2),
/// If set, all anonymous namespaces in the context must be matched exactly
/// by the pattern. Otherwise, superfluous namespaces are skipped.
e_strict_namespaces = (1u << 3),
/// When true, the find types call should stop the query as soon as a single
/// matching type is found. When false, the type query should find all
/// matching types.
e_find_one = (1u << 3),
e_find_one = (1u << 4),
};
LLDB_MARK_AS_BITMASK_ENUM(TypeQueryOptions)

Expand Down Expand Up @@ -264,7 +267,22 @@ class TypeQuery {
bool GetExactMatch() const { return (m_options & e_exact_match) != 0; }

bool GetIgnoreModules() const { return (m_options & e_ignore_modules) != 0; }
void SetIgnoreModules() { m_options &= ~e_ignore_modules; }
void SetIgnoreModules(bool b) {
if (b)
m_options |= e_ignore_modules;
else
m_options &= ~e_ignore_modules;
}

bool GetStrictNamespaces() const {
return (m_options & e_strict_namespaces) != 0;
}
void SetStrictNamespaces(bool b) {
if (b)
m_options |= e_strict_namespaces;
else
m_options &= ~e_strict_namespaces;
}

/// The \a m_context can be used in two ways: normal types searching with
/// the context containing a stanadard declaration context for a type, or
Expand All @@ -279,7 +297,7 @@ class TypeQuery {
if (b)
m_options |= e_find_one;
else
m_options &= (e_exact_match | e_find_one);
m_options &= ~e_find_one;
}

/// Access the internal compiler context array.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ TypeAndOrName ItaniumABILanguageRuntime::GetTypeInfo(
TypeResults results;
TypeQuery query(const_lookup_name.GetStringRef(),
TypeQueryOptions::e_exact_match |
TypeQueryOptions::e_strict_namespaces |
TypeQueryOptions::e_find_one);
if (module_sp) {
module_sp->FindTypes(query, results);
Expand Down
8 changes: 1 addition & 7 deletions lldb/source/Plugins/SymbolFile/DWARF/DWARFDIE.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -440,12 +440,6 @@ static void GetTypeLookupContextImpl(DWARFDIE die,
continue;
}

// If there is no name, then there is no need to look anything up for this
// DIE.
const char *name = die.GetName();
if (!name || !name[0])
return;

Comment on lines -443 to -448
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to only pull out this match if this is a DW_TAG_namespace? Or is it useful to have unnamed structs/unions/classes in the contenxt?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unnamed structs cannot contain named structs, so the only place these can appear is at the top (bottom?) level. We don't currently have a way to look them up from our APIs taking strings (we'd need something like an (anonymous struct)), but they could be looked up internally by code which constructs CompilerContexts directly. So yeah, I think including them here is the right choice for this function.

// Add this DIE's contribution at the end of the chain.
auto push_ctx = [&](CompilerContextKind kind, llvm::StringRef name) {
context.push_back({kind, ConstString(name)});
Expand All @@ -471,7 +465,7 @@ static void GetTypeLookupContextImpl(DWARFDIE die,
push_ctx(CompilerContextKind::Typedef, die.GetName());
break;
case DW_TAG_base_type:
push_ctx(CompilerContextKind::Builtin, name);
push_ctx(CompilerContextKind::Builtin, die.GetName());
break;
// If any of the tags below appear in the parent chain, stop the decl
// context and return. Prior to these being in here, if a type existed in a
Expand Down
36 changes: 31 additions & 5 deletions lldb/source/Symbol/Type.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,20 @@ bool TypeQuery::ContextMatches(
if (ctx == ctx_end)
return false; // Pattern too long.

if (ctx->kind == CompilerContextKind::Namespace && ctx->name.IsEmpty()) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to check for "(anonymous namespace)" here? Or do we detect this elsewhere and make sure to clear the name? Demangling names will show "(anonymous namespace)"

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The anonymous namespace is detected and nullified on line 815. I'm not aware of such code, but if anyone was constructing these contexts without going through the name parser, then they'd have to make sure they use the expected (empty) encoding for anonymous namespaces. I think that's reasonable, because ensuring this when constructing the names from some kind of a structured source is easy, and noone should be parsing name strings by hand as the process is very nontrivial. They should rather call the function in this file (which will do the nullification for them).

// We're matching an anonymous namespace. These are optional, so we check
// if the pattern expects an anonymous namespace.
if (pat->name.IsEmpty() && (pat->kind & CompilerContextKind::Namespace) ==
CompilerContextKind::Namespace) {
// Match, advance both iterators.
++pat;
}
// Otherwise, only advance the context to skip over the anonymous
// namespace, and try matching again.
++ctx;
continue;
}

// See if there is a kind mismatch; they should have 1 bit in common.
if ((ctx->kind & pat->kind) == CompilerContextKind())
return false;
Expand All @@ -145,10 +159,16 @@ bool TypeQuery::ContextMatches(
++pat;
}

// Skip over any remaining module entries if we were asked to do that.
while (GetIgnoreModules() && ctx != ctx_end &&
ctx->kind == CompilerContextKind::Module)
++ctx;
// Skip over any remaining module and anonymous namespace entries if we were
// asked to do that.
auto should_skip = [this](const CompilerContext &ctx) {
if (ctx.kind == CompilerContextKind::Module)
return GetIgnoreModules();
if (ctx.kind == CompilerContextKind::Namespace && ctx.name.IsEmpty())
return !GetStrictNamespaces();
return false;
};
ctx = std::find_if_not(ctx, ctx_end, should_skip);

// At this point, we have exhausted the pattern and we have a partial match at
// least. If that's all we're looking for, we're done.
Expand Down Expand Up @@ -788,7 +808,13 @@ Type::GetTypeScopeAndBasename(llvm::StringRef name) {
switch (pos.value()) {
case ':':
if (prev_is_colon && template_depth == 0) {
result.scope.push_back(name.slice(name_begin, pos.index() - 1));
llvm::StringRef scope_name = name.slice(name_begin, pos.index() - 1);
// The itanium demangler uses this string to represent anonymous
// namespaces. Convert it to a more language-agnostic form (which is
// also used in DWARF).
if (scope_name == "(anonymous namespace)")
scope_name = "";
Comment on lines +815 to +816
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So we can catch this here and NULL out the name, but what is the user wants to find something in an anonymous namespace with type lookup "(anonymous namespace)::A". Do we clear the name there as well?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that also goes through this piece of code. I'll add a test for that path.

result.scope.push_back(scope_name);
name_begin = pos.index() + 1;
}
break;
Expand Down
2 changes: 1 addition & 1 deletion lldb/test/API/lang/cpp/dynamic-value/Makefile
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
CXX_SOURCES := pass-to-base.cpp
CXX_SOURCES := pass-to-base.cpp anonymous-b.cpp

include Makefile.rules
15 changes: 14 additions & 1 deletion lldb/test/API/lang/cpp/dynamic-value/TestDynamicValue.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ def test_get_dynamic_vals(self):
self.assertTrue(reallyA_value)
reallyA_loc = int(reallyA_value.GetLocation(), 16)

# Finally continue to doSomething again, and make sure we get the right value for anotherA,
# Continue to doSomething again, and make sure we get the right value for anotherA,
# which this time around is just an "A".

threads = lldbutil.continue_to_breakpoint(process, do_something_bpt)
Expand All @@ -184,6 +184,19 @@ def test_get_dynamic_vals(self):
self.assertEqual(anotherA_loc, reallyA_loc)
self.assertEqual(anotherA_value.GetTypeName().find("B"), -1)

# Finally do the same with a B in an anonymous namespace.
threads = lldbutil.continue_to_breakpoint(process, do_something_bpt)
self.assertEqual(len(threads), 1)
thread = threads[0]

frame = thread.GetFrameAtIndex(0)
anotherA_value = frame.FindVariable("anotherA", use_dynamic)
self.assertTrue(anotherA_value)
self.assertIn("B", anotherA_value.GetTypeName())
anon_b_value = anotherA_value.GetChildMemberWithName("m_anon_b_value")
self.assertTrue(anon_b_value)
self.assertEqual(anon_b_value.GetValueAsSigned(), 47)

def examine_value_object_of_this_ptr(
self, this_static, this_dynamic, dynamic_location
):
Expand Down
25 changes: 25 additions & 0 deletions lldb/test/API/lang/cpp/dynamic-value/a.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#ifndef A_H
#define A_H

#include <cstdio>
#include <memory>

class A {
public:
A(int value) : m_a_value(value) {}
A(int value, A *client_A) : m_a_value(value), m_client_A(client_A) {}

virtual ~A() {}

virtual void doSomething(A &anotherA);

int Value() { return m_a_value; }

private:
int m_a_value;
std::auto_ptr<A> m_client_A;
};

A *make_anonymous_B();

#endif
13 changes: 13 additions & 0 deletions lldb/test/API/lang/cpp/dynamic-value/anonymous-b.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#include "a.h"

namespace {
class B : public A {
public:
B() : A(42) {}

private:
int m_anon_b_value = 47;
};
} // namespace

A *make_anonymous_B() { return new B(); }
38 changes: 9 additions & 29 deletions lldb/test/API/lang/cpp/dynamic-value/pass-to-base.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
#include <stdio.h>
#include <memory>
#include "a.h"

void A::doSomething(A &anotherA) {
printf("In A %p doing something with %d.\n", this, m_a_value);
int tmp_value = anotherA.Value();
printf("Also have another A at %p: %d.\n", &anotherA, tmp_value); // Break here in doSomething.
}

class Extra
{
Expand All @@ -11,33 +16,6 @@ class Extra
int m_extra_two;
};

class A
{
public:
A(int value) : m_a_value (value) {}
A(int value, A* client_A) : m_a_value (value), m_client_A (client_A) {}

virtual ~A() {}

virtual void
doSomething (A &anotherA)
{
printf ("In A %p doing something with %d.\n", this, m_a_value);
int tmp_value = anotherA.Value();
printf ("Also have another A at %p: %d.\n", &anotherA, tmp_value); // Break here in doSomething.
}

int
Value()
{
return m_a_value;
}

private:
int m_a_value;
std::auto_ptr<A> m_client_A;
};

class B : public Extra, public virtual A
{
public:
Expand Down Expand Up @@ -65,5 +43,7 @@ main (int argc, char **argv)
A reallyA (500);
myB.doSomething (reallyA); // Break here and get real address of reallyA.

myB.doSomething(*make_anonymous_B());

return 0;
}
6 changes: 6 additions & 0 deletions lldb/test/API/lang/cpp/namespace/TestNamespace.py
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,12 @@ def test_with_run_command(self):
patterns=[" = 3"],
)

# Search for a type in an anonymous namespace, both with and without the
# namespace prefix.
self.expect("type lookup -- my_uint_t", substrs=["unsigned int"])
self.expect("type lookup -- (anonymous namespace)::my_uint_t",
substrs=["unsigned int"])

# rdar://problem/8660275
# test/namespace: 'expression -- i+j' not working
# This has been fixed.
Expand Down
56 changes: 56 additions & 0 deletions lldb/unittests/Symbol/TestType.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

using namespace lldb;
using namespace lldb_private;
using testing::ElementsAre;
using testing::Not;

TEST(Type, GetTypeScopeAndBasename) {
Expand Down Expand Up @@ -59,8 +60,33 @@ MATCHER_P(MatchesIgnoringModules, pattern, "") {
TypeQuery query(pattern, TypeQueryOptions::e_ignore_modules);
return query.ContextMatches(arg);
}
MATCHER_P(MatchesWithStrictNamespaces, pattern, "") {
TypeQuery query(pattern, TypeQueryOptions::e_strict_namespaces);
return query.ContextMatches(arg);
}
} // namespace

TEST(Type, TypeQueryFlags) {
TypeQuery q("foo", e_none);
auto get = [](const TypeQuery &q) -> std::vector<bool> {
return {q.GetFindOne(), q.GetExactMatch(), q.GetModuleSearch(),
q.GetIgnoreModules(), q.GetStrictNamespaces()};
};
EXPECT_THAT(get(q), ElementsAre(false, false, false, false, false));

q.SetFindOne(true);
EXPECT_THAT(get(q), ElementsAre(true, false, false, false, false));

q.SetIgnoreModules(true);
EXPECT_THAT(get(q), ElementsAre(true, false, false, true, false));

q.SetStrictNamespaces(true);
EXPECT_THAT(get(q), ElementsAre(true, false, false, true, true));

q.SetIgnoreModules(false);
EXPECT_THAT(get(q), ElementsAre(true, false, false, false, true));
}

TEST(Type, CompilerContextPattern) {
auto make_module = [](llvm::StringRef name) {
return CompilerContext(CompilerContextKind::Module, ConstString(name));
Expand Down Expand Up @@ -103,6 +129,10 @@ TEST(Type, CompilerContextPattern) {
(std::vector{make_module("A"), make_module("B"), make_class("C")}),
Matches(
std::vector{make_module("A"), make_module("B"), make_any_type("C")}));
EXPECT_THAT((std::vector{make_module("A"), make_module("B"),
make_namespace(""), make_class("C")}),
Matches(std::vector{make_module("A"), make_module("B"),
make_any_type("C")}));
EXPECT_THAT(
(std::vector{make_module("A"), make_module("B"), make_enum("C2")}),
Not(Matches(std::vector{make_module("A"), make_module("B"),
Expand All @@ -111,4 +141,30 @@ TEST(Type, CompilerContextPattern) {
Matches(std::vector{make_class("C")}));
EXPECT_THAT((std::vector{make_namespace("NS"), make_class("C")}),
Not(Matches(std::vector{make_any_type("C")})));

EXPECT_THAT((std::vector{make_namespace(""), make_class("C")}),
Matches(std::vector{make_class("C")}));
EXPECT_THAT((std::vector{make_namespace(""), make_class("C")}),
Not(MatchesWithStrictNamespaces(std::vector{make_class("C")})));
EXPECT_THAT((std::vector{make_namespace(""), make_class("C")}),
Matches(std::vector{make_namespace(""), make_class("C")}));
EXPECT_THAT((std::vector{make_namespace(""), make_class("C")}),
MatchesWithStrictNamespaces(
std::vector{make_namespace(""), make_class("C")}));
EXPECT_THAT((std::vector{make_class("C")}),
Not(Matches(std::vector{make_namespace(""), make_class("C")})));
EXPECT_THAT((std::vector{make_class("C")}),
Not(MatchesWithStrictNamespaces(
std::vector{make_namespace(""), make_class("C")})));
EXPECT_THAT((std::vector{make_namespace(""), make_namespace("NS"),
make_namespace(""), make_class("C")}),
Matches(std::vector{make_namespace("NS"), make_class("C")}));
EXPECT_THAT(
(std::vector{make_namespace(""), make_namespace(""), make_namespace("NS"),
make_namespace(""), make_namespace(""), make_class("C")}),
Matches(std::vector{make_namespace("NS"), make_class("C")}));
EXPECT_THAT((std::vector{make_module("A"), make_namespace("NS"),
make_namespace(""), make_class("C")}),
MatchesIgnoringModules(
std::vector{make_namespace("NS"), make_class("C")}));
}
Loading
Loading