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,23 @@ 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``, subclasses must call it +in the overridden method. + +This example shows that attribute plugins combined with ``PluginASTAction`` in Clang +can do some of the same things which Java Annotations do. + +Unlike the other attribute plugin examples, this one does not attach an attribute AST +node to the declaration AST node. Instead, it keeps a separate list of attributed +declarations, which may be faster than using ``Decl::getAttr()`` in some cases. +The disadvantage of this approach is that the attribute is not part of the AST, +which means that dumping the AST will lose the attribute information, pretty printing +the AST won't write the attribute back out to source, and AST matchers will not be +able to match against the attribute on the declaration. + 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,178 @@ +//===- 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 MarkedMethods; +bool isMarkedAsCallSuper(const CXXMethodDecl *D) { + // Uses this way to avoid add an annotation attr to the AST. + return MarkedMethods.contains(D); +} + +class MethodUsageVisitor : public RecursiveASTVisitor { +public: + bool IsOverriddenUsed = false; + explicit MethodUsageVisitor( + llvm::SmallPtrSet &MustCalledMethods) + : MustCalledMethods(MustCalledMethods) {} + bool VisitCallExpr(CallExpr *CallExpr) { + const CXXMethodDecl *Callee = nullptr; + for (const auto &MustCalled : MustCalledMethods) { + if (CallExpr->getCalleeDecl() == MustCalled) { + // Super is called. + // Notice that we cannot do delete or insert in the iteration + // when using SmallPtrSet. + Callee = MustCalled; + } + } + if (Callee) + MustCalledMethods.erase(Callee); + + return true; + } + +private: + llvm::SmallPtrSet &MustCalledMethods; +}; + +class CallSuperVisitor : public RecursiveASTVisitor { +public: + CallSuperVisitor(DiagnosticsEngine &Diags) : Diags(Diags) { + WarningSuperNotCalled = Diags.getCustomDiagID( + DiagnosticsEngine::Warning, + "virtual function %q0 is marked as 'call_super' but this overriding " + "method does not call the base version"); + NotePreviousCallSuperDeclaration = Diags.getCustomDiagID( + DiagnosticsEngine::Note, "function marked 'call_super' here"); + } + bool VisitCXXMethodDecl(CXXMethodDecl *MethodDecl) { + lateDiagAppertainsToDecl(MethodDecl); + + if (MethodDecl->isThisDeclarationADefinition() && MethodDecl->hasBody()) { + // First find out which overridden methods are marked as 'call_super' + llvm::SmallPtrSet OverriddenMarkedMethods; + for (const auto *Overridden : MethodDecl->overridden_methods()) { + if (isMarkedAsCallSuper(Overridden)) { + OverriddenMarkedMethods.insert(Overridden); + } + } + + // Now find if supper is used in `MethodDecl`. + MethodUsageVisitor Visitor(OverriddenMarkedMethods); + Visitor.TraverseDecl(MethodDecl); + // After traversing, all methods left in `OverriddenMarkedMethods` + // are not get called, warning about these. + for (const auto &LeftOverriddens : OverriddenMarkedMethods) { + Diags.Report(MethodDecl->getLocation(), WarningSuperNotCalled) + << LeftOverriddens << MethodDecl; + Diags.Report(LeftOverriddens->getLocation(), + NotePreviousCallSuperDeclaration); + } + } + return true; + } + +private: + DiagnosticsEngine &Diags; + unsigned WarningSuperNotCalled; + unsigned NotePreviousCallSuperDeclaration; + // This function does checks which cannot be done in `diagAppertainsToDecl()`, + // typical example is checking Attributes (such as `FinalAttr`), on the time + // when `diagAppertainsToDecl()` is called, `FinalAttr` is not added into + // the AST yet. + void lateDiagAppertainsToDecl(const CXXMethodDecl *MethodDecl) { + if (MethodDecl->hasAttr()) { + unsigned ID = Diags.getCustomDiagID( + DiagnosticsEngine::Warning, + "'call_super' attribute marked on a final method"); + Diags.Report(MethodDecl->getLocation(), ID); + } + } +}; + +class CallSuperConsumer : public ASTConsumer { +public: + void HandleTranslationUnit(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 AddBeforeMainAction; + } +}; + +struct CallSuperAttrInfo : public ParsedAttrInfo { + CallSuperAttrInfo() { + OptArgs = 0; + static constexpr Spelling S[] = { + {ParsedAttr::AS_GNU, "call_super"}, + {ParsedAttr::AS_CXX11, "clang::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 marked on a virtual function"); + S.Diags.Report(D->getLocation(), ID); + } + MarkedMethods.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"); 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,20 @@ +// 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 { [[clang::call_super]] virtual void Test() {} }; +struct Base2 { [[clang::call_super]] virtual void Test() {} }; +struct Derive : public Base1, public Base2 { [[clang::call_super]] virtual void Test() override final; }; +// CALLSUPER: warning: 'call_super' attribute marked 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:@.+]] = dso_local constant [8 x i8] c"6Derive\00", align 1 +#ifdef BAD_CALLSUPER +struct Base1 { [[clang::call_super]] virtual void Test() {} }; +struct Base2 { [[clang::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: virtual function 'Base2::Test' is marked as 'call_super' but this overriding method does not call the base version +// BADCALLSUPER: note: function marked 'call_super' here +#endif