This patch adds the Parse and Sema support for RegularKeyword attributes, following on from a previous patch that added Attr.td support. The patch is quite large. However, nothing outside the tests is specific to the first RegularKeyword attribute (__arm_streaming). The patch should therefore be a one-off, up-front cost. Other attributes just need an entry in Attr.td and the usual Sema support. The approach taken in the patch is that the keywords can be used with any language version. If standard attributes were added in language version Y, the keyword rules for version X<Y are the same as they were for version Y (to the extent possible). Any extensions beyond Y are handled in the same way for both keywords and attributes. This ensures that existing C++11 successors like C++17 are not treated differently from versions that have yet to be defined. Some notes on the implementation: * The patch emits errors rather than warnings for diagnostics that relate to keywords. * Where possible, the patch drops “attribute” from diagnostics relating to keywords. * One exception to the previous point is that warnings about C++ extensions do still mention attributes. The use there seemed OK since the diagnostics are noting a change in the production rules. * If a diagnostic string needs to be different for keywords and attributes, the patch standardizes on passing the attribute/ name/token followed by 0 for attributes and 1 for keywords. * Although the patch updates warn_attribute_wrong_decl_type_str, warn_attribute_wrong_decl_type, and warn_attribute_wrong_decl_type, only the error forms of these strings are used for keywords. * I couldn't trigger the warnings in checkUnusedDeclAttributes, even for existing attributes. An assert on the warnings caused no failures in the testsuite. I think in practice all standard attributes would be diagnosed before this. * The patch drops a call to standardAttributesAllowed in ParseFunctionDeclarator. This is because MaybeParseCXX11Attributes checks the same thing itself, where appropriate. * The new tests are based on c2x-attributes.c and cxx0x-attributes.cpp. The C++ test also incorporates a version of cxx11-base-spec-attributes.cpp. The FIXMEs are carried across from the originals. Differential Revision: https://reviews.llvm.org/D148702
193 lines
7.3 KiB
C++
193 lines
7.3 KiB
C++
//===- 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
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
//
|
|
// 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<T>()`` 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.
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#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<const CXXMethodDecl *, 16> 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<MethodUsageVisitor> {
|
|
public:
|
|
bool IsOverriddenUsed = false;
|
|
explicit MethodUsageVisitor(
|
|
llvm::SmallPtrSet<const CXXMethodDecl *, 16> &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<const CXXMethodDecl *, 16> &MustCalledMethods;
|
|
};
|
|
|
|
class CallSuperVisitor : public RecursiveASTVisitor<CallSuperVisitor> {
|
|
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) {
|
|
if (MethodDecl->isThisDeclarationADefinition() && MethodDecl->hasBody()) {
|
|
// First find out which overridden methods are marked as 'call_super'
|
|
llvm::SmallPtrSet<const CXXMethodDecl *, 16> OverriddenMarkedMethods;
|
|
for (const auto *Overridden : MethodDecl->overridden_methods()) {
|
|
if (isMarkedAsCallSuper(Overridden)) {
|
|
OverriddenMarkedMethods.insert(Overridden);
|
|
}
|
|
}
|
|
|
|
// Now find if the superclass method is called in `MethodDecl`.
|
|
MethodUsageVisitor Visitor(OverriddenMarkedMethods);
|
|
Visitor.TraverseDecl(MethodDecl);
|
|
// After traversing, all methods left in `OverriddenMarkedMethods`
|
|
// are not called, warn 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;
|
|
};
|
|
|
|
class CallSuperConsumer : public ASTConsumer {
|
|
public:
|
|
void HandleTranslationUnit(ASTContext &Context) override {
|
|
auto &Diags = Context.getDiagnostics();
|
|
for (const auto *Method : MarkedMethods) {
|
|
lateDiagAppertainsToDecl(Diags, Method);
|
|
}
|
|
|
|
CallSuperVisitor Visitor(Context.getDiagnostics());
|
|
Visitor.TraverseDecl(Context.getTranslationUnitDecl());
|
|
}
|
|
|
|
private:
|
|
// 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(DiagnosticsEngine &Diags,
|
|
const CXXMethodDecl *MethodDecl) {
|
|
if (MethodDecl->hasAttr<FinalAttr>()) {
|
|
unsigned ID = Diags.getCustomDiagID(
|
|
DiagnosticsEngine::Warning,
|
|
"'call_super' attribute marked on a final method");
|
|
Diags.Report(MethodDecl->getLocation(), ID);
|
|
}
|
|
}
|
|
};
|
|
|
|
class CallSuperAction : public PluginASTAction {
|
|
public:
|
|
std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI,
|
|
llvm::StringRef) override {
|
|
return std::make_unique<CallSuperConsumer>();
|
|
}
|
|
|
|
bool ParseArgs(const CompilerInstance &CI,
|
|
const std::vector<std::string> &args) override {
|
|
if (!args.empty() && args[0] == "help")
|
|
llvm::errs() << "Help for the CallSuperAttr plugin goes here\n";
|
|
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<CXXMethodDecl>(D);
|
|
if (!TheMethod || !TheMethod->isVirtual()) {
|
|
S.Diag(Attr.getLoc(), diag::warn_attribute_wrong_decl_type_str)
|
|
<< Attr << Attr.isRegularKeywordAttribute() << "virtual functions";
|
|
return false;
|
|
}
|
|
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 `AnnotateAttr` is added).
|
|
// Save the address of the Decl in a set, it maybe faster than compare to
|
|
// strings.
|
|
return AttributeNotApplied;
|
|
}
|
|
};
|
|
|
|
} // namespace
|
|
static FrontendPluginRegistry::Add<CallSuperAction>
|
|
X("call_super_plugin", "clang plugin, checks every overridden virtual "
|
|
"function whether called this function or not.");
|
|
static ParsedAttrInfoRegistry::Add<CallSuperAttrInfo>
|
|
Y("call_super_attr", "Attr plugin to define 'call_super' attribute");
|