forked from OSchip/llvm-project
[lldb] Don't infinite loop in SemaSourceWithPriorities::CompleteType when trying to complete a forward decl
SemaSourceWithPriorities is a special SemaSource that wraps our normal LLDB ExternalASTSource and the ASTReader (which is used for the C++ module loading). It's only active when the `import-std-module` setting is turned on. The `CompleteType` function there in `SemaSourceWithPriorities` is looping over all ExternalASTSources and asks each to complete the type. However, that loop is in another loop that keeps doing that until the type is complete. If that function is ever called on a type that is a forward decl then that causes LLDB to go into an infinite loop. I remember I added that second loop and the comment because I thought I saw a similar pattern in some other Clang code, but after some grepping I can't find that code anywhere and it seems the rest of the code base only calls CompleteType once (It would also be kinda silly to have calling it multiple times). So it seems that's just a silly mistake. The is implicitly tested by importing `std::pair`, but I also added a simpler dedicated test that creates a dummy libc++ module with some forward declarations and then imports them into the scratch AST context. At some point the ASTImporter will check if one of the forward decls could be completed by the ExternalASTSource, which will cause the `SemaSourceWithPriorities` to go into an infinite loop once it receives the `CompleteType` call. Reviewed By: shafik Differential Revision: https://reviews.llvm.org/D87289
This commit is contained in:
parent
5106a8b8f8
commit
32c8da41dc
|
@ -359,15 +359,12 @@ public:
|
|||
}
|
||||
|
||||
void CompleteType(clang::TagDecl *Tag) override {
|
||||
while (!Tag->isCompleteDefinition())
|
||||
for (size_t i = 0; i < Sources.size(); ++i) {
|
||||
// FIXME: We are technically supposed to loop here too until
|
||||
// Tag->isCompleteDefinition() is true, but if our low quality source
|
||||
// is failing to complete the tag this code will deadlock.
|
||||
Sources[i]->CompleteType(Tag);
|
||||
if (Tag->isCompleteDefinition())
|
||||
break;
|
||||
}
|
||||
for (clang::ExternalSemaSource *S : Sources) {
|
||||
S->CompleteType(Tag);
|
||||
// Stop after the first source completed the type.
|
||||
if (Tag->isCompleteDefinition())
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void CompleteType(clang::ObjCInterfaceDecl *Class) override {
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
# We don't have any standard include directories, so we can't
|
||||
# parse the test_common.h header we usually inject as it includes
|
||||
# system headers.
|
||||
NO_TEST_COMMON_H := 1
|
||||
|
||||
CXXFLAGS_EXTRAS = -I $(SRCDIR)/root/usr/include/c++/v1/ -I $(SRCDIR)/root/usr/include/ -nostdinc -nostdinc++
|
||||
CXX_SOURCES := main.cpp
|
||||
|
||||
include Makefile.rules
|
|
@ -0,0 +1,39 @@
|
|||
"""
|
||||
Tests forward declarations coming from the `std` module.
|
||||
"""
|
||||
|
||||
from lldbsuite.test.decorators import *
|
||||
from lldbsuite.test.lldbtest import *
|
||||
from lldbsuite.test import lldbutil
|
||||
import os
|
||||
|
||||
class TestCase(TestBase):
|
||||
|
||||
mydir = TestBase.compute_mydir(__file__)
|
||||
|
||||
# We only emulate a fake libc++ in this test and don't use the real libc++,
|
||||
# but we still add the libc++ category so that this test is only run in
|
||||
# test configurations where libc++ is actually supposed to be tested.
|
||||
@add_test_categories(["libc++"])
|
||||
@skipIfRemote
|
||||
@skipIf(compiler=no_match("clang"))
|
||||
def test(self):
|
||||
self.build()
|
||||
|
||||
sysroot = os.path.join(os.getcwd(), "root")
|
||||
|
||||
# Set the sysroot where our dummy libc++ exists.
|
||||
self.runCmd("platform select --sysroot '" + sysroot + "' host", CURRENT_EXECUTABLE_SET)
|
||||
|
||||
lldbutil.run_to_source_breakpoint(self,
|
||||
"// Set break point at this line.", lldb.SBFileSpec("main.cpp"))
|
||||
|
||||
self.runCmd("settings set target.import-std-module true")
|
||||
|
||||
# Print the dummy `std::vector`. It only has the dummy member in it
|
||||
# so the standard `std::vector` formatter can't format it. Instead use
|
||||
# the raw output so LLDB has to show the member variable.
|
||||
# Both `std::vector` and the type of the member have forward
|
||||
# declarations before their definitions.
|
||||
self.expect("expr --raw -- v",
|
||||
substrs=['(std::__1::vector<int>) $0 = {', 'f = 0x', '}'])
|
|
@ -0,0 +1,8 @@
|
|||
#include <vector>
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
// Makes sure we have the mock libc headers in the debug information.
|
||||
libc_struct s;
|
||||
std::vector<int> v;
|
||||
return 0; // Set break point at this line.
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
module std {
|
||||
module "vector" { header "vector" export * }
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
#include "libc_header.h"
|
||||
|
||||
namespace std {
|
||||
inline namespace __1 {
|
||||
// A forward decl of `vector`.
|
||||
template<typename T> class vector;
|
||||
// Pretend to be a std::vector template we need to instantiate in LLDB
|
||||
// when import-std-module is enabled.
|
||||
template<typename T>
|
||||
struct vector { class F; F *f; };
|
||||
// The definition of our forward declared nested class.
|
||||
template<typename T> class vector<T>::F { int x; };
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
struct libc_struct {};
|
Loading…
Reference in New Issue