diff options
author | Richard Smith <richard@metafoo.co.uk> | 2020-01-31 17:05:27 -0800 |
---|---|---|
committer | Richard Smith <richard@metafoo.co.uk> | 2020-01-31 17:06:48 -0800 |
commit | aade5fbbfef3e8555df202082bea905deebc2ca5 (patch) | |
tree | 09129207ef4be5179bc2d4502088b4fbe5fdb781 | |
parent | b074acb82f7e75a189fa7933b09627241b166121 (diff) | |
download | llvm-aade5fbbfef3e8555df202082bea905deebc2ca5.zip llvm-aade5fbbfef3e8555df202082bea905deebc2ca5.tar.gz llvm-aade5fbbfef3e8555df202082bea905deebc2ca5.tar.bz2 |
Fix wrong devirtualization when the final overrider in one base class
overrides the final overrider in a different base class.
-rw-r--r-- | clang/lib/AST/CXXInheritance.cpp | 2 | ||||
-rw-r--r-- | clang/lib/AST/DeclCXX.cpp | 32 | ||||
-rw-r--r-- | clang/test/CodeGenCXX/devirtualize-virtual-function-calls-final.cpp | 43 |
3 files changed, 73 insertions, 4 deletions
diff --git a/clang/lib/AST/CXXInheritance.cpp b/clang/lib/AST/CXXInheritance.cpp index a3a3794..0377bd32 100644 --- a/clang/lib/AST/CXXInheritance.cpp +++ b/clang/lib/AST/CXXInheritance.cpp @@ -758,6 +758,8 @@ CXXRecordDecl::getFinalOverriders(CXXFinalOverriderMap &FinalOverriders) const { return false; }; + // FIXME: IsHidden reads from Overriding from the middle of a remove_if + // over the same sequence! Is this guaranteed to work? Overriding.erase( std::remove_if(Overriding.begin(), Overriding.end(), IsHidden), Overriding.end()); diff --git a/clang/lib/AST/DeclCXX.cpp b/clang/lib/AST/DeclCXX.cpp index 48e310e..227fe80 100644 --- a/clang/lib/AST/DeclCXX.cpp +++ b/clang/lib/AST/DeclCXX.cpp @@ -2038,17 +2038,36 @@ CXXMethodDecl::getCorrespondingMethodInClass(const CXXRecordDecl *RD, if (auto *MD = getCorrespondingMethodDeclaredInClass(RD, MayBeBase)) return MD; + llvm::SmallVector<CXXMethodDecl*, 4> FinalOverriders; + auto AddFinalOverrider = [&](CXXMethodDecl *D) { + // If this function is overridden by a candidate final overrider, it is not + // a final overrider. + for (CXXMethodDecl *OtherD : FinalOverriders) { + if (declaresSameEntity(D, OtherD) || recursivelyOverrides(OtherD, D)) + return; + } + + // Other candidate final overriders might be overridden by this function. + FinalOverriders.erase( + std::remove_if(FinalOverriders.begin(), FinalOverriders.end(), + [&](CXXMethodDecl *OtherD) { + return recursivelyOverrides(D, OtherD); + }), + FinalOverriders.end()); + + FinalOverriders.push_back(D); + }; + for (const auto &I : RD->bases()) { const RecordType *RT = I.getType()->getAs<RecordType>(); if (!RT) continue; const auto *Base = cast<CXXRecordDecl>(RT->getDecl()); - CXXMethodDecl *T = this->getCorrespondingMethodInClass(Base); - if (T) - return T; + if (CXXMethodDecl *D = this->getCorrespondingMethodInClass(Base)) + AddFinalOverrider(D); } - return nullptr; + return FinalOverriders.size() == 1 ? FinalOverriders.front() : nullptr; } CXXMethodDecl *CXXMethodDecl::Create(ASTContext &C, CXXRecordDecl *RD, @@ -2105,6 +2124,11 @@ CXXMethodDecl *CXXMethodDecl::getDevirtualizedMethod(const Expr *Base, CXXMethodDecl *DevirtualizedMethod = getCorrespondingMethodInClass(BestDynamicDecl); + // If there final overrider in the dynamic type is ambiguous, we can't + // devirtualize this call. + if (!DevirtualizedMethod) + return nullptr; + // If that method is pure virtual, we can't devirtualize. If this code is // reached, the result would be UB, not a direct call to the derived class // function, and we can't assume the derived class function is defined. diff --git a/clang/test/CodeGenCXX/devirtualize-virtual-function-calls-final.cpp b/clang/test/CodeGenCXX/devirtualize-virtual-function-calls-final.cpp index 130103d..6f5e844 100644 --- a/clang/test/CodeGenCXX/devirtualize-virtual-function-calls-final.cpp +++ b/clang/test/CodeGenCXX/devirtualize-virtual-function-calls-final.cpp @@ -255,6 +255,49 @@ namespace Test10 { } } +namespace TestVBase { + struct A { virtual void f(); }; + struct B : virtual A {}; + struct C : virtual A { void f() override; }; + + extern struct BC final : B, C {} &bc; + extern struct BCusingA final : B, C { using A::f; } &bc_using_a; + extern struct BCusingB final : B, C { using B::f; } &bc_using_b; + extern struct BCusingC final : B, C { using C::f; } &bc_using_c; + + extern struct CB final : C, B {} &cb; + extern struct CBusingA final : C, B { using A::f; } &cb_using_a; + extern struct CBusingB final : C, B { using B::f; } &cb_using_b; + extern struct CBusingC final : C, B { using C::f; } &cb_using_c; + + // CHECK-LABEL: @_ZN9TestVBase4testEv( + void test() { + // FIXME: The 'using A' case can be devirtualized to call A's virtual + // adjustment thunk for C::f. + // FIXME: The 'using B' case can be devirtualized, but requires us to emit + // a derived-to-base or base-to-derived conversion as part of + // devirtualization. + + // CHECK: call void @_ZN9TestVBase1C1fEv( + bc.f(); + // CHECK: call void % + bc_using_a.f(); + // CHECK: call void % + bc_using_b.f(); + // CHECK: call void @_ZN9TestVBase1C1fEv( + bc_using_c.f(); + + // CHECK: call void @_ZN9TestVBase1C1fEv( + cb.f(); + // CHECK: call void % + cb_using_a.f(); + // CHECK: call void % + cb_using_b.f(); + // CHECK: call void @_ZN9TestVBase1C1fEv( + cb_using_c.f(); + } +} + namespace Test11 { // Check that the definitions of Derived's operators are emitted. |