Skip to content

Commit

Permalink
[lldb] Better matching of types in anonymous namespaces (#102111)
Browse files Browse the repository at this point in the history
This patch extends TypeQuery matching to support anonymous namespaces. A
new flag is added to control the behavior. In the "strict" mode, the
query must match the type exactly -- all anonymous namespaces included.
The dynamic type resolver in the itanium abi (the motivating use case
for this) uses this flag, as it queries using the name from the
demangles, which includes anonymous namespaces.

This ensures we don't confuse a type with a same-named type in an
anonymous namespace. However, this does *not* ensure we don't confuse
two types in anonymous namespacs (in different CUs). To resolve this, we
would need to use a completely different lookup algorithm, which
probably also requires a DWARF extension.

In the "lax" mode (the default), the anonymous namespaces in the query
are optional, and this allows one search for the type using the usual
language rules (`::A` matches `::(anonymous namespace)::A`).

This patch also changes the type context computation algorithm in
DWARFDIE, so that it includes anonymous namespace information. This
causes a slight change in behavior: the algorithm previously stopped
computing the context after encountering an anonymous namespace, which
caused the outer namespaces to be ignored. This meant that a type like
`NS::(anonymous namespace)::A` would be (incorrectly) recognized as
`::A`). This can cause code depending on the old behavior to misbehave.
The fix is to specify all the enclosing namespaces in the query, or use
a non-exact match.
  • Loading branch information
labath authored Sep 2, 2024
1 parent da13754 commit dd5d730
Show file tree
Hide file tree
Showing 12 changed files with 202 additions and 48 deletions.
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;

// 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()) {
// 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 = "";
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

0 comments on commit dd5d730

Please sign in to comment.