[lldb][test] Add test for ASTImporter's name conflict resolution (#112566)

This is a reduced test case from a crash we've observed in the past. The
assertion that this test triggers is:
```
Assertion failed: ((Pos == ImportedDecls.end() || Pos->second == To) && "Try to import an already imported Decl"), function MapImported, file ASTImporter.cpp, line 10494.
```

In a non-asserts build we crash later on in the ASTImporter. The root
cause is, as the assertion above points out, that we erroneously replace
an existing `From->To` decl mapping with a `To` decl that isn't
complete. Then we try to complete it but it has no definition and we
dereference a nullptr.

The reason this happens is basically what's been described in
https://reviews.llvm.org/D67803?id=220956#1676588

The dylib contains a definition of `Service` which is different to the
one in the main executable. When we start dumping the children of the
variable we're printing, we start completing it's members,
`ASTImport`ing fields in the process. When the ASTImporter realizes
there's been a name conflict (i.e., a structural mismatch on the
`Service` type) it would usually report back an error. However, LLDB
uses `ODRHandlingType::Liberal`, which means we create a new decl for
the ODR'd type instead of re-using the previously mapped decl.
Eventually this leads us to crash.

Ideally we'd be using `ODRHandlingType::Conservative` and warn/error,
though LLDB relies on this in some cases (particularly for
distinguishing template specializations, though maybe there's better a
way to deal with those).

We should really warn the user when this happens and not crash. To avoid
the crash we'd need to know to not create a decl for the ODR violation,
and instead re-use the definition we've previously seen. Though I'm not
yet sure that's viable for all of LLDB's use-cases (where ODR violations
might legimiately occur in a program, e.g., with opaque definitions,
etc.).
This commit is contained in:
Michael Buch
2024-10-18 14:19:42 +01:00
committed by GitHub
parent c89d731c5d
commit 3bc765dbbf
7 changed files with 104 additions and 0 deletions

View File

@@ -0,0 +1,6 @@
CXX_SOURCES := main.cpp service.cpp
DYLIB_CXX_SOURCES := plugin.cpp
DYLIB_NAME := plugin
include Makefile.rules

View File

@@ -0,0 +1,29 @@
import lldb
from lldbsuite.test.decorators import *
from lldbsuite.test.lldbtest import *
from lldbsuite.test import lldbutil
class OdrHandlingWithDylibTestCase(TestBase):
@skipIf(
bugnumber="https://github.com/llvm/llvm-project/issues/50375, rdar://135551810"
)
def test(self):
"""
Tests that the expression evaluator is able to deal with types
whose definitions conflict across multiple LLDB modules (in this
case the definition for 'class Service' in the main executable
has an additional field compared to the definition found in the
dylib). This causes the ASTImporter to detect a name conflict
while importing 'Service'. With LLDB's liberal ODRHandlingType
the ASTImporter happily creates a conflicting AST node for
'Service' in the scratch ASTContext, leading to a crash down
the line.
"""
self.build()
lldbutil.run_to_source_breakpoint(
self, "plugin_entry", lldb.SBFileSpec("plugin.cpp")
)
self.expect_expr("*gProxyThis")

View File

@@ -0,0 +1,11 @@
#include "plugin.h"
#define HIDE_FROM_PLUGIN 1
#include "service.h"
int main() {
exported();
plugin_init();
plugin_entry();
return 0;
}

View File

@@ -0,0 +1,14 @@
#include "plugin.h"
#include "service.h"
struct Proxy : public Service {
State *proxyState;
};
Proxy *gProxyThis = 0;
extern "C" {
void plugin_init() { gProxyThis = new Proxy; }
void plugin_entry() {}
}

View File

@@ -0,0 +1,9 @@
#ifndef PLUGIN_H_IN
#define PLUGIN_H_IN
extern "C" {
void plugin_entry(void);
void plugin_init(void);
}
#endif // PLUGIN_H_IN

View File

@@ -0,0 +1,15 @@
#define HIDE_FROM_PLUGIN 1
#include "service.h"
struct ServiceAux {
Service *Owner;
};
struct Service::State {};
void exported() {
// Make sure debug-info for definition of Service is
// emitted in this CU.
Service service;
service.start(0);
}

View File

@@ -0,0 +1,20 @@
#ifndef SERVICE_H_IN
#define SERVICE_H_IN
struct ServiceAux;
struct Service {
struct State;
bool start(State *) { return true; }
#ifdef HIDE_FROM_PLUGIN
int __resv1;
#endif // !HIDE_FROM_PLUGIN
Service *__owner;
ServiceAux *aux;
};
void exported();
#endif