Skip to content

Commit d836983

Browse files
committed
[lldb][test] Add test for ASTImporter's name conflict resolution
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.).
1 parent f113a66 commit d836983

File tree

6 files changed

+98
-0
lines changed

6 files changed

+98
-0
lines changed
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#include "service.h"
2+
#include <cassert>
3+
#include <dlfcn.h>
4+
5+
#ifndef PLUGIN_PATH
6+
#error "Expected PLUGIN_PATH to be defined"
7+
#endif // !PLUGIN_PATH
8+
9+
int main() {
10+
void *handle = dlopen(PLUGIN_PATH, RTLD_NOW);
11+
assert(handle != nullptr);
12+
void (*plugin_init)(void) = (void (*)(void))dlsym(handle, "plugin_init");
13+
assert(plugin_init != nullptr);
14+
void (*plugin_entry)(void) = (void (*)(void))dlsym(handle, "plugin_entry");
15+
assert(plugin_entry != nullptr);
16+
17+
exported();
18+
plugin_init();
19+
plugin_entry();
20+
return 0;
21+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
#include "service.h"
2+
#include "plugin.h"
3+
4+
struct Proxy : public Service {
5+
State *proxyState;
6+
};
7+
8+
Proxy *gProxyThis = 0;
9+
10+
extern "C" {
11+
void plugin_init() { gProxyThis = new Proxy; }
12+
13+
void plugin_entry() {}
14+
}
15+
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#ifndef PLUGIN_H_IN
2+
#define PLUGIN_H_IN
3+
4+
extern "C" {
5+
void plugin_entry(void);
6+
void plugin_init(void);
7+
}
8+
9+
#endif // _H_IN
10+
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
#include "service.h"
2+
3+
struct ServiceAux {
4+
Service *Owner;
5+
};
6+
7+
struct Service::State {};
8+
9+
void exported() {
10+
// Make sure debug-info for definition of Service is
11+
// emitted in this CU.
12+
Service service;
13+
service.start(0);
14+
}
15+
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#ifndef SERVICE_H_IN
2+
#define SERVICE_H_IN
3+
4+
struct ServiceAux;
5+
6+
struct Service {
7+
struct State;
8+
bool start(State *) { return true; }
9+
10+
#if HIDE_FROM_PLUGIN
11+
int __resv1;
12+
#endif // !HIDE_FROM_PLUGIN
13+
14+
Service *__owner;
15+
ServiceAux *aux;
16+
};
17+
18+
void exported();
19+
20+
#endif
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# REQUIRES: system-darwin
2+
3+
# RUN: %clangxx_host %p/Inputs/name-conflict-test/plugin.cpp -shared -gdwarf -O0 -o %t.plugin.dylib
4+
#
5+
# RUN: %clangxx_host %p/Inputs/name-conflict-test/main.cpp \
6+
# RUN: %p/Inputs/name-conflict-test/service.cpp \
7+
# RUN: -DPLUGIN_PATH=\"%t.plugin.dylib\" \
8+
# RUN: -DHIDE_FROM_PLUGIN \
9+
# RUN: -gdwarf -O0 -o %t.a.out
10+
#
11+
# RUN: %lldb %t.a.out \
12+
# RUN: -o "b plugin_entry" \
13+
# RUN: -o run \
14+
# RUN: -o "expr *gProxyThis" \
15+
# RUN: -o exit | FileCheck %s
16+
17+
# CHECK: (lldb) expr *gProxyThis

0 commit comments

Comments
 (0)