[clang-tidy] Portability Template Virtual Member Function Check (#110099)

Introduced a new check that finds cases when an uninstantiated virtual member function in a template class causes cross-compiler incompatibility.
This commit is contained in:
isuckatcs 2024-10-10 12:32:39 +02:00 committed by GitHub
parent 003375fb2b
commit 6d8e966512
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 303 additions and 0 deletions

View File

@ -9,6 +9,7 @@ add_clang_library(clangTidyPortabilityModule STATIC
RestrictSystemIncludesCheck.cpp
SIMDIntrinsicsCheck.cpp
StdAllocatorConstCheck.cpp
TemplateVirtualMemberFunctionCheck.cpp
LINK_LIBS
clangTidy

View File

@ -12,6 +12,7 @@
#include "RestrictSystemIncludesCheck.h"
#include "SIMDIntrinsicsCheck.h"
#include "StdAllocatorConstCheck.h"
#include "TemplateVirtualMemberFunctionCheck.h"
namespace clang::tidy {
namespace portability {
@ -25,6 +26,8 @@ public:
"portability-simd-intrinsics");
CheckFactories.registerCheck<StdAllocatorConstCheck>(
"portability-std-allocator-const");
CheckFactories.registerCheck<TemplateVirtualMemberFunctionCheck>(
"portability-template-virtual-member-function");
}
};

View File

@ -0,0 +1,44 @@
//===--- TemplateVirtualMemberFunctionCheck.cpp - clang-tidy --------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "TemplateVirtualMemberFunctionCheck.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
using namespace clang::ast_matchers;
namespace clang::tidy::portability {
namespace {
AST_MATCHER(CXXMethodDecl, isUsed) { return Node.isUsed(); }
} // namespace
void TemplateVirtualMemberFunctionCheck::registerMatchers(MatchFinder *Finder) {
Finder->addMatcher(
cxxMethodDecl(ofClass(classTemplateSpecializationDecl(
unless(isExplicitTemplateSpecialization()))
.bind("specialization")),
isVirtual(), unless(isUsed()),
unless(cxxDestructorDecl(isDefaulted())))
.bind("method"),
this);
}
void TemplateVirtualMemberFunctionCheck::check(
const MatchFinder::MatchResult &Result) {
const auto *ImplicitSpecialization =
Result.Nodes.getNodeAs<ClassTemplateSpecializationDecl>("specialization");
const auto *MethodDecl = Result.Nodes.getNodeAs<CXXMethodDecl>("method");
diag(MethodDecl->getLocation(),
"unspecified virtual member function instantiation; the virtual "
"member function is not instantiated but it might be with a "
"different compiler");
diag(ImplicitSpecialization->getPointOfInstantiation(),
"template instantiated here", DiagnosticIDs::Note);
}
} // namespace clang::tidy::portability

View File

@ -0,0 +1,38 @@
//===--- TemplateVirtualMemberFunctionCheck.h - clang-tidy ------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_PORTABILITY_TEMPLATEVIRTUALMEMBERFUNCTIONCHECK_H
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_PORTABILITY_TEMPLATEVIRTUALMEMBERFUNCTIONCHECK_H
#include "../ClangTidyCheck.h"
namespace clang::tidy::portability {
/// Upon instantiating a template class, non-virtual member functions don't have
/// to be instantiated unless they are used. Virtual member function
/// instantiation on the other hand is unspecified and depends on the
/// implementation of the compiler. This check intends to find cases when a
/// virtual member function is not instantiated but it might be with a different
/// compiler.
///
/// For the user-facing documentation see:
/// http://clang.llvm.org/extra/clang-tidy/checks/portability/template-virtual-member-function.html
class TemplateVirtualMemberFunctionCheck : public ClangTidyCheck {
public:
TemplateVirtualMemberFunctionCheck(StringRef Name, ClangTidyContext *Context)
: ClangTidyCheck(Name, Context) {}
void registerMatchers(ast_matchers::MatchFinder *Finder) override;
void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
bool isLanguageVersionSupported(const LangOptions &LangOpts) const override {
return LangOpts.CPlusPlus;
}
};
} // namespace clang::tidy::portability
#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_PORTABILITY_TEMPLATEVIRTUALMEMBERFUNCTIONCHECK_H

View File

@ -121,6 +121,12 @@ New checks
Gives warnings for tagged unions, where the number of tags is
different from the number of data members inside the union.
- New :doc:`portability-template-virtual-member-function
<clang-tidy/checks/portability/template-virtual-member-function>` check.
Finds cases when an uninstantiated virtual member function in a template class
causes cross-compiler incompatibility.
New check aliases
^^^^^^^^^^^^^^^^^

View File

@ -348,6 +348,7 @@ Clang-Tidy Checks
:doc:`portability-restrict-system-includes <portability/restrict-system-includes>`, "Yes"
:doc:`portability-simd-intrinsics <portability/simd-intrinsics>`,
:doc:`portability-std-allocator-const <portability/std-allocator-const>`,
:doc:`portability-template-virtual-member-function <portability/template-virtual-member-function>`,
:doc:`readability-avoid-const-params-in-decls <readability/avoid-const-params-in-decls>`, "Yes"
:doc:`readability-avoid-nested-conditional-operator <readability/avoid-nested-conditional-operator>`,
:doc:`readability-avoid-return-with-void-value <readability/avoid-return-with-void-value>`, "Yes"

View File

@ -0,0 +1,37 @@
.. title:: clang-tidy - portability-template-virtual-member-function
portability-template-virtual-member-function
============================================
Finds cases when an uninstantiated virtual member function in a template class causes
cross-compiler incompatibility.
Upon instantiating a template class, non-virtual member functions don't have to be
instantiated unless they are used. Virtual member function instantiation on the other hand
is unspecified and depends on the implementation of the compiler.
In the following snippets the virtual member function is not instantiated by GCC and Clang,
but it is instantiated by MSVC, so while the snippet is accepted by the former compilers,
it is rejected by the latter.
.. code:: c++
template<typename T>
struct CrossPlatformError {
virtual ~CrossPlatformError() = default;
static void used() {}
virtual void unused() {
T MSVCError = this;
};
};
int main() {
CrossPlatformError<int>::used();
return 0;
}
Cross-platform projects that need to support MSVC on Windows might see compiler errors
because certain virtual member functions are instantiated, which are not instantiated
by other compilers on other platforms. This check highlights such virtual member functions.

View File

@ -0,0 +1,173 @@
// RUN: %check_clang_tidy %s portability-template-virtual-member-function %t
namespace UninstantiatedVirtualMember {
template<typename T>
struct CrossPlatformError {
virtual ~CrossPlatformError() = default;
static void used() {}
// CHECK-MESSAGES: [[#@LINE+1]]:18: warning: unspecified virtual member function instantiation
virtual void unused() {
T MSVCError = this;
};
};
int main() {
// CHECK-MESSAGES: [[#@LINE+1]]:5: note: template instantiated here
CrossPlatformError<int>::used();
return 0;
}
} // namespace UninstantiatedVirtualMember
namespace UninstantiatedVirtualMembers {
template<typename T>
struct CrossPlatformError {
virtual ~CrossPlatformError() = default;
static void used() {}
// CHECK-MESSAGES: [[#@LINE+2]]:18: warning: unspecified virtual member function instantiation
// CHECK-MESSAGES: [[#@LINE+13]]:5: note: template instantiated here
virtual void unused() {
T MSVCError = this;
};
// CHECK-MESSAGES: [[#@LINE+2]]:18: warning: unspecified virtual member function instantiation
// CHECK-MESSAGES: [[#@LINE+7]]:5: note: template instantiated here
virtual void unused2() {
T MSVCError = this;
};
};
int main() {
CrossPlatformError<int>::used();
return 0;
}
} // namespace UninstantiatedVirtualMembers
namespace UninstantiatedVirtualDestructor {
template<typename T>
struct CrossPlatformError {
// CHECK-MESSAGES: [[#@LINE+2]]:13: warning: unspecified virtual member function instantiation
// CHECK-MESSAGES: [[#@LINE+9]]:5: note: template instantiated here
virtual ~CrossPlatformError() {
T MSVCError = this;
};
static void used() {}
};
int main() {
CrossPlatformError<int>::used();
return 0;
}
} // namespace UninstantiatedVirtualDestructor
namespace MultipleImplicitInstantiations {
template<typename T>
struct CrossPlatformError {
virtual ~CrossPlatformError() = default;
static void used() {}
// CHECK-MESSAGES: [[#@LINE+2]]:18: warning: unspecified virtual member function instantiation
// CHECK-MESSAGES: [[#@LINE+7]]:5: note: template instantiated here
virtual void unused() {
T MSVCError = this;
};
};
int main() {
CrossPlatformError<int>::used();
CrossPlatformError<float>::used();
CrossPlatformError<long>::used();
return 0;
}
} // namespace MultipleImplicitInstantiations
namespace SomeImplicitInstantiationError {
template <typename T> struct CrossPlatformError {
virtual ~CrossPlatformError() = default;
static void used() {}
// CHECK-MESSAGES: [[#@LINE+2]]:18: warning: unspecified virtual member function instantiation
// CHECK-MESSAGES: [[#@LINE+5]]:5: note: template instantiated here
virtual void unused(){};
};
int main() {
CrossPlatformError<int>::used();
CrossPlatformError<float> NoError;
return 0;
}
} // namespace SomeImplicitInstantiationError
namespace InstantiatedVirtualMemberFunctions {
template<typename T>
struct NoError {
virtual ~NoError() {};
virtual void unused() {};
virtual void unused2() {};
virtual void unused3() {};
};
int main() {
NoError<int> Ne;
return 0;
}
} // namespace InstantiatedVirtualMemberFunctions
namespace UninstantiatedNonVirtualMemberFunctions {
template<typename T>
struct NoError {
static void used() {};
void unused() {};
void unused2() {};
void unused3() {};
};
int main() {
NoError<int>::used();
return 0;
}
} // namespace UninstantiatedNonVirtualMemberFunctions
namespace PartialSpecializationError {
template<typename T, typename U>
struct CrossPlatformError {};
template<typename U>
struct CrossPlatformError<int, U>{
virtual ~CrossPlatformError() = default;
static void used() {}
// CHECK-MESSAGES: [[#@LINE+2]]:18: warning: unspecified virtual member function instantiation
// CHECK-MESSAGES: [[#@LINE+7]]:5: note: template instantiated here
virtual void unused() {
U MSVCError = this;
};
};
int main() {
CrossPlatformError<int, float>::used();
return 0;
}
} // namespace PartialSpecializationError
namespace PartialSpecializationNoInstantiation {
template<typename T, typename U>
struct NoInstantiation {};
template<typename U>
struct NoInstantiation<int, U>{
virtual ~NoInstantiation() = default;
static void used() {}
virtual void unused() {
U MSVCError = this;
};
};
} // namespace PartialSpecializationNoInstantiation