//===- RedundantVoidArgCheck.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 "RedundantVoidArgCheck.h" #include "clang/Frontend/CompilerInstance.h" #include "clang/Lex/Lexer.h" using namespace clang::ast_matchers; namespace clang::tidy::modernize { namespace { // Determine if the given QualType is a nullary function or pointer to same. bool protoTypeHasNoParms(QualType QT) { if (const auto *PT = QT->getAs()) QT = PT->getPointeeType(); if (auto *MPT = QT->getAs()) QT = MPT->getPointeeType(); if (const auto *FP = QT->getAs()) return FP->getNumParams() == 0; return false; } const char FunctionId[] = "function"; const char TypedefId[] = "typedef"; const char FieldId[] = "field"; const char VarId[] = "var"; const char NamedCastId[] = "named-cast"; const char CStyleCastId[] = "c-style-cast"; const char ExplicitCastId[] = "explicit-cast"; const char LambdaId[] = "lambda"; } // namespace void RedundantVoidArgCheck::registerMatchers(MatchFinder *Finder) { Finder->addMatcher(functionDecl(parameterCountIs(0), unless(isImplicit()), unless(isInstantiated()), unless(isExternC())) .bind(FunctionId), this); Finder->addMatcher(typedefNameDecl(unless(isImplicit())).bind(TypedefId), this); auto ParenFunctionType = parenType(innerType(functionType())); auto PointerToFunctionType = pointee(ParenFunctionType); auto FunctionOrMemberPointer = anyOf(hasType(pointerType(PointerToFunctionType)), hasType(memberPointerType(PointerToFunctionType))); Finder->addMatcher(fieldDecl(FunctionOrMemberPointer).bind(FieldId), this); Finder->addMatcher(varDecl(FunctionOrMemberPointer).bind(VarId), this); auto CastDestinationIsFunction = hasDestinationType(pointsTo(ParenFunctionType)); Finder->addMatcher( cStyleCastExpr(CastDestinationIsFunction).bind(CStyleCastId), this); Finder->addMatcher( cxxStaticCastExpr(CastDestinationIsFunction).bind(NamedCastId), this); Finder->addMatcher( cxxReinterpretCastExpr(CastDestinationIsFunction).bind(NamedCastId), this); Finder->addMatcher( cxxConstCastExpr(CastDestinationIsFunction).bind(NamedCastId), this); Finder->addMatcher(lambdaExpr().bind(LambdaId), this); } void RedundantVoidArgCheck::check(const MatchFinder::MatchResult &Result) { const BoundNodes &Nodes = Result.Nodes; if (const auto *Function = Nodes.getNodeAs(FunctionId)) processFunctionDecl(Result, Function); else if (const auto *TypedefName = Nodes.getNodeAs(TypedefId)) processTypedefNameDecl(Result, TypedefName); else if (const auto *Member = Nodes.getNodeAs(FieldId)) processFieldDecl(Result, Member); else if (const auto *Var = Nodes.getNodeAs(VarId)) processVarDecl(Result, Var); else if (const auto *NamedCast = Nodes.getNodeAs(NamedCastId)) processNamedCastExpr(Result, NamedCast); else if (const auto *CStyleCast = Nodes.getNodeAs(CStyleCastId)) processExplicitCastExpr(Result, CStyleCast); else if (const auto *ExplicitCast = Nodes.getNodeAs(ExplicitCastId)) processExplicitCastExpr(Result, ExplicitCast); else if (const auto *Lambda = Nodes.getNodeAs(LambdaId)) processLambdaExpr(Result, Lambda); } void RedundantVoidArgCheck::processFunctionDecl( const MatchFinder::MatchResult &Result, const FunctionDecl *Function) { const auto *Method = dyn_cast(Function); SourceLocation Start = Method && Method->getParent()->isLambda() ? Method->getBeginLoc() : Function->getLocation(); SourceLocation End = Function->getEndLoc(); if (Function->isThisDeclarationADefinition()) { if (const Stmt *Body = Function->getBody()) { End = Body->getBeginLoc(); if (End.isMacroID() && Result.SourceManager->isAtStartOfImmediateMacroExpansion(End)) End = Result.SourceManager->getExpansionLoc(End); End = End.getLocWithOffset(-1); } removeVoidArgumentTokens(Result, SourceRange(Start, End), "function definition"); } else removeVoidArgumentTokens(Result, SourceRange(Start, End), "function declaration"); } bool isMacroIdentifier(const IdentifierTable &Idents, const Token &ProtoToken) { if (!ProtoToken.is(tok::TokenKind::raw_identifier)) return false; IdentifierTable::iterator It = Idents.find(ProtoToken.getRawIdentifier()); if (It == Idents.end()) return false; return It->second->hadMacroDefinition(); } void RedundantVoidArgCheck::removeVoidArgumentTokens( const ast_matchers::MatchFinder::MatchResult &Result, SourceRange Range, StringRef GrammarLocation) { CharSourceRange CharRange = Lexer::makeFileCharRange(CharSourceRange::getTokenRange(Range), *Result.SourceManager, getLangOpts()); std::string DeclText = Lexer::getSourceText(CharRange, *Result.SourceManager, getLangOpts()) .str(); Lexer PrototypeLexer(CharRange.getBegin(), getLangOpts(), DeclText.data(), DeclText.data(), DeclText.data() + DeclText.size()); enum class TokenState { Start, MacroId, MacroLeftParen, MacroArguments, LeftParen, Void, }; TokenState State = TokenState::Start; Token VoidToken; Token ProtoToken; const IdentifierTable &Idents = Result.Context->Idents; int MacroLevel = 0; std::string Diagnostic = ("redundant void argument list in " + GrammarLocation).str(); while (!PrototypeLexer.LexFromRawLexer(ProtoToken)) { switch (State) { case TokenState::Start: if (ProtoToken.is(tok::TokenKind::l_paren)) State = TokenState::LeftParen; else if (isMacroIdentifier(Idents, ProtoToken)) State = TokenState::MacroId; break; case TokenState::MacroId: if (ProtoToken.is(tok::TokenKind::l_paren)) State = TokenState::MacroLeftParen; else State = TokenState::Start; break; case TokenState::MacroLeftParen: ++MacroLevel; if (ProtoToken.is(tok::TokenKind::raw_identifier)) { if (isMacroIdentifier(Idents, ProtoToken)) State = TokenState::MacroId; else State = TokenState::MacroArguments; } else if (ProtoToken.is(tok::TokenKind::r_paren)) { --MacroLevel; if (MacroLevel == 0) State = TokenState::Start; else State = TokenState::MacroId; } else State = TokenState::MacroArguments; break; case TokenState::MacroArguments: if (isMacroIdentifier(Idents, ProtoToken)) State = TokenState::MacroLeftParen; else if (ProtoToken.is(tok::TokenKind::r_paren)) { --MacroLevel; if (MacroLevel == 0) State = TokenState::Start; } break; case TokenState::LeftParen: if (ProtoToken.is(tok::TokenKind::raw_identifier)) { if (isMacroIdentifier(Idents, ProtoToken)) State = TokenState::MacroId; else if (ProtoToken.getRawIdentifier() == "void") { State = TokenState::Void; VoidToken = ProtoToken; } } else if (ProtoToken.is(tok::TokenKind::l_paren)) State = TokenState::LeftParen; else State = TokenState::Start; break; case TokenState::Void: State = TokenState::Start; if (ProtoToken.is(tok::TokenKind::r_paren)) removeVoidToken(VoidToken, Diagnostic); else if (ProtoToken.is(tok::TokenKind::l_paren)) State = TokenState::LeftParen; break; } } if (State == TokenState::Void && ProtoToken.is(tok::TokenKind::r_paren)) removeVoidToken(VoidToken, Diagnostic); } void RedundantVoidArgCheck::removeVoidToken(Token VoidToken, StringRef Diagnostic) { SourceLocation VoidLoc = VoidToken.getLocation(); diag(VoidLoc, Diagnostic) << FixItHint::CreateRemoval(VoidLoc); } void RedundantVoidArgCheck::processTypedefNameDecl( const MatchFinder::MatchResult &Result, const TypedefNameDecl *TypedefName) { if (protoTypeHasNoParms(TypedefName->getUnderlyingType())) removeVoidArgumentTokens(Result, TypedefName->getSourceRange(), isa(TypedefName) ? "typedef" : "type alias"); } void RedundantVoidArgCheck::processFieldDecl( const MatchFinder::MatchResult &Result, const FieldDecl *Member) { if (protoTypeHasNoParms(Member->getType())) removeVoidArgumentTokens(Result, Member->getSourceRange(), "field declaration"); } void RedundantVoidArgCheck::processVarDecl( const MatchFinder::MatchResult &Result, const VarDecl *Var) { if (protoTypeHasNoParms(Var->getType())) { SourceLocation Begin = Var->getBeginLoc(); if (Var->hasInit()) { SourceLocation InitStart = Result.SourceManager->getExpansionLoc(Var->getInit()->getBeginLoc()) .getLocWithOffset(-1); removeVoidArgumentTokens(Result, SourceRange(Begin, InitStart), "variable declaration with initializer"); } else removeVoidArgumentTokens(Result, Var->getSourceRange(), "variable declaration"); } } void RedundantVoidArgCheck::processNamedCastExpr( const MatchFinder::MatchResult &Result, const CXXNamedCastExpr *NamedCast) { if (protoTypeHasNoParms(NamedCast->getTypeAsWritten())) removeVoidArgumentTokens( Result, NamedCast->getTypeInfoAsWritten()->getTypeLoc().getSourceRange(), "named cast"); } void RedundantVoidArgCheck::processExplicitCastExpr( const MatchFinder::MatchResult &Result, const ExplicitCastExpr *ExplicitCast) { if (protoTypeHasNoParms(ExplicitCast->getTypeAsWritten())) removeVoidArgumentTokens(Result, ExplicitCast->getSourceRange(), "cast expression"); } void RedundantVoidArgCheck::processLambdaExpr( const MatchFinder::MatchResult &Result, const LambdaExpr *Lambda) { if (Lambda->getLambdaClass()->getLambdaCallOperator()->getNumParams() == 0 && Lambda->hasExplicitParameters()) { SourceManager *SM = Result.SourceManager; TypeLoc TL = Lambda->getLambdaClass()->getLambdaTypeInfo()->getTypeLoc(); removeVoidArgumentTokens(Result, {SM->getSpellingLoc(TL.getBeginLoc()), SM->getSpellingLoc(TL.getEndLoc())}, "lambda expression"); } } } // namespace clang::tidy::modernize