diff --git a/clang/docs/ClangPlugins.rst b/clang/docs/ClangPlugins.rst --- a/clang/docs/ClangPlugins.rst +++ b/clang/docs/ClangPlugins.rst @@ -113,6 +113,11 @@ To see a working example of an attribute plugin, see `the Attribute.cpp example `_. +Defining CallSuperAttr +=================== + +Attribute plugin to mark a virtual method as call_super, sub-classes must call it in overridden the method. + Putting it all together ======================= diff --git a/clang/examples/CMakeLists.txt b/clang/examples/CMakeLists.txt --- a/clang/examples/CMakeLists.txt +++ b/clang/examples/CMakeLists.txt @@ -7,3 +7,4 @@ add_subdirectory(PrintFunctionNames) add_subdirectory(AnnotateFunctions) add_subdirectory(Attribute) +add_subdirectory(CallSuperAttribute) diff --git a/clang/examples/CallSuperAttribute/CMakeLists.txt b/clang/examples/CallSuperAttribute/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/clang/examples/CallSuperAttribute/CMakeLists.txt @@ -0,0 +1,13 @@ +add_llvm_library(CallSuperAttr MODULE CallSuperAttrInfo.cpp PLUGIN_TOOL clang) + +if(LLVM_ENABLE_PLUGINS AND (WIN32 OR CYGWIN)) + set(LLVM_LINK_COMPONENTS + Support + ) + clang_target_link_libraries(CallSuperAttr PRIVATE + clangAST + clangBasic + clangFrontend + clangLex + ) +endif() diff --git a/clang/examples/CallSuperAttribute/CallSuperAttrInfo.cpp b/clang/examples/CallSuperAttribute/CallSuperAttrInfo.cpp new file mode 100644 --- /dev/null +++ b/clang/examples/CallSuperAttribute/CallSuperAttrInfo.cpp @@ -0,0 +1,170 @@ +//===- AnnotateFunctions.cpp ----------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// +// clang plugin, checks every overridden virtual function whether called +// this function or not. +// +//===----------------------------------------------------------------------===// + +#include "clang/AST/ASTContext.h" +#include "clang/AST/Attr.h" +#include "clang/AST/RecursiveASTVisitor.h" +#include "clang/Frontend/FrontendPluginRegistry.h" +#include "clang/Sema/ParsedAttr.h" +#include "clang/Sema/Sema.h" +#include "clang/Sema/SemaDiagnostic.h" +#include "llvm/ADT/SmallPtrSet.h" +using namespace clang; + +namespace { +// cached methods which are marked as call_super +llvm::SmallPtrSet ForcingCalledMethods; +bool isMarkedAsCallSuper(const CXXMethodDecl *D) { + // use this way to avoid add an annotation attr to the AST. + return ForcingCalledMethods.contains(D); +} + +std::string fullName(const CXXMethodDecl *D) { + std::string FullName; + FullName += D->getParent()->getName(); + FullName += "::"; + FullName += D->getName(); + return FullName; +} + +class MethodUsageVisitor : public RecursiveASTVisitor { +public: + bool IsOverriddenUsed = false; + MethodUsageVisitor(const clang::CXXMethodDecl *MethodDecl) + : MethodDecl(MethodDecl) {} + bool VisitCallExpr(clang::CallExpr *CallExpr) { + if (CallExpr->getCalleeDecl() == MethodDecl) { + // super is called + IsOverriddenUsed = true; + return false; + } + return true; + } + +private: + const clang::CXXMethodDecl *MethodDecl; +}; + +class CallSuperVisitor : public RecursiveASTVisitor { +public: + CallSuperVisitor(DiagnosticsEngine &Diags) : Diags(Diags) { + WarningSuperNotCalled = Diags.getCustomDiagID( + DiagnosticsEngine::Warning, + "%0 is marked as call_super but override method %1 does not call it"); + NotePreviousCallSuperDeclaration = Diags.getCustomDiagID( + DiagnosticsEngine::Note, + "overridden method which is marked as call_super here"); + } + bool VisitCXXMethodDecl(clang::CXXMethodDecl *MethodDecl) { + // warning if this method is marked as final + if (isMarkedAsCallSuper(MethodDecl) && MethodDecl->hasAttr()) { + unsigned ID = Diags.getCustomDiagID( + DiagnosticsEngine::Warning, + "call_super attribute attached on a final method"); + Diags.Report(MethodDecl->getLocation(), ID); + } + for (const auto *Overridden : MethodDecl->overridden_methods()) { + if (isMarkedAsCallSuper(Overridden)) { + // if this method is only a declaration without definition, + // skip checking + if (!MethodDecl->isThisDeclarationADefinition()) { + continue; + } + + if (MethodDecl->hasBody()) { + MethodUsageVisitor Visitor(Overridden); + // now find if supper is used + Visitor.TraverseDecl(MethodDecl); + if (!Visitor.IsOverriddenUsed) { + Diags.Report(MethodDecl->getLocation(), WarningSuperNotCalled) + << fullName(Overridden) << fullName(MethodDecl); + Diags.Report(Overridden->getLocation(), + NotePreviousCallSuperDeclaration); + } + } + } + } + return true; + } + +private: + DiagnosticsEngine &Diags; + unsigned WarningSuperNotCalled; + unsigned NotePreviousCallSuperDeclaration; +}; + +class CallSuperConsumer : public ASTConsumer { +public: + void HandleTranslationUnit(clang::ASTContext &Context) override { + CallSuperVisitor Visitor(Context.getDiagnostics()); + Visitor.TraverseDecl(Context.getTranslationUnitDecl()); + } +}; + +class CallSuperAction : public PluginASTAction { +public: + std::unique_ptr CreateASTConsumer(CompilerInstance &CI, + llvm::StringRef) override { + return std::make_unique(); + } + + bool ParseArgs(const CompilerInstance &CI, + const std::vector &args) override { + return true; + } + + PluginASTAction::ActionType getActionType() override { + return AddAfterMainAction; + } +}; + +struct CallSuperAttrInfo : public ParsedAttrInfo { + CallSuperAttrInfo() { + OptArgs = 0; + static constexpr Spelling S[] = {{ParsedAttr::AS_GNU, "call_super"}, + {ParsedAttr::AS_CXX11, "call_super"}}; + Spellings = S; + } + + bool diagAppertainsToDecl(Sema &S, const ParsedAttr &Attr, + const Decl *D) const override { + const auto *TheMethod = dyn_cast_or_null(D); + if (!TheMethod) { + S.Diag(Attr.getLoc(), diag::warn_attribute_wrong_decl_type_str) + << Attr << "methods"; + return false; + } + if (!TheMethod->isVirtual()) { + unsigned ID = S.Diags.getCustomDiagID( + DiagnosticsEngine::Error, + "call_super attribute must attached on a virtual function"); + S.Diags.Report(D->getLocation(), ID); + } + ForcingCalledMethods.insert(TheMethod); + return true; + } + AttrHandling handleDeclAttribute(Sema &S, Decl *D, + const ParsedAttr &Attr) const override { + // no need to add an attr object (usually an annotation attr is added) + // save the address of the Decl in sets, it maybe faster than compare to + // strings. + return AttributeNotApplied; + } +}; + +} // namespace +static FrontendPluginRegistry::Add + X("call_super_plugin", "clang plugin, checks every overridden virtual " + "function whether called this function or not."); +static ParsedAttrInfoRegistry::Add + Y("call_super_attr", "Attr plugin to define call_super attribute"); \ No newline at end of file diff --git a/clang/test/CMakeLists.txt b/clang/test/CMakeLists.txt --- a/clang/test/CMakeLists.txt +++ b/clang/test/CMakeLists.txt @@ -91,6 +91,7 @@ list(APPEND CLANG_TEST_DEPS Attribute AnnotateFunctions + CallSuperAttr clang-interpreter PrintFunctionNames ) diff --git a/clang/test/Frontend/plugin-call-super.cpp b/clang/test/Frontend/plugin-call-super.cpp new file mode 100644 --- /dev/null +++ b/clang/test/Frontend/plugin-call-super.cpp @@ -0,0 +1,23 @@ +// RUN: %clang -fplugin=%llvmshlibdir/CallSuperAttr%pluginext -emit-llvm -S %s -o - 2>&1 | FileCheck %s --check-prefix=CALLSUPER +// RUN: not %clang -fplugin=%llvmshlibdir/CallSuperAttr%pluginext -emit-llvm -DBAD_CALLSUPER -S %s -o - 2>&1 | FileCheck %s --check-prefix=BADCALLSUPER +// REQUIRES: plugins, examples + +struct Base1 { [[call_super]] virtual void Test() {} }; +struct Base2 { [[call_super]] virtual void Test() {} }; +struct Derive : public Base1, public Base2 { [[call_super]] virtual void Test() override final; }; +// CALLSUPER: warning: call_super attribute attached on a final method +void Derive::Test() { Base1::Test(); Base2::Test(); } +struct Derive2 : public Base1, public Base2 { void Test() override { Base1::Test(); Base2::Test();}}; +// CALLSUPER: [[STR1_VAR:@.+]] = private unnamed_addr constant [10 x i8] c"example()\00" +// CALLSUPER: [[STR2_VAR:@.+]] = private unnamed_addr constant [20 x i8] c"example(somestring)\00" +// CALLSUPER: @llvm.global.annotations = {{.*}}@{{.*}}fn1a{{.*}}[[STR1_VAR]]{{.*}}@{{.*}}fn1b{{.*}}[[STR1_VAR]]{{.*}}@{{.*}}fn1c{{.*}}[[STR1_VAR]]{{.*}}@{{.*}}fn2{{.*}}[[STR2_VAR]] + +#ifdef BAD_CALLSUPER +struct Base1 { [[call_super]] virtual void Test() {} }; +struct Base2 { [[call_super]] virtual void Test() {} }; +struct Derive : public Base1, public Base2 { void Test() override; }; +void Derive::Test() { Base1::Test(); /*Base2::Test();*/ } +struct Derive2 : public Base1, public Base2 { void Test() override { Base1::Test(); Base2::Test();}}; +// BADCALLSUPER: warning: Base2::Test is marked as call_super but override method Derive::Test does not call it +// BADCALLSUPER: note: overridden method which is marked as call_super here +#endif